Click or drag to resize
BroccoliSharp BroConnection Class
Represents a Bro connection. PCAP functionality is enabled for this build.
Inheritance Hierarchy
SystemObject
  BroccoliSharpBroConnection

Namespace: BroccoliSharp
Assembly: BroccoliSharp (in BroccoliSharp.dll) Version: 1.0.5434.15853
Syntax
public class BroConnection : IDisposable

The BroConnection type exposes the following members.

Constructors
  NameDescription
Public methodBroConnection(Int32, BroConnectionFlags)
Creates a new BroConnection using the existing socket handle.
Public methodBroConnection(Socket, BroConnectionFlags)
Creates a new BroConnection using the existing socket.
Public methodBroConnection(TcpClient, BroConnectionFlags)
Creates a new client-based BroConnection using the existing tcpClient.
Public methodBroConnection(TcpListener, BroConnectionFlags)
Creates a new server-based BroConnection using the existing tcpListener.
Public methodBroConnection(String, BroConnectionFlags)
Creates a new BroConnection with specified connection parameters.
Top
Methods
  NameDescription
Public methodAdoptEvents(BroConnection)
Requests the same events as those in source Bro connection.
Public methodStatic memberAdoptEvents(BroConnection, BroConnection)
Requests the same events for the destination as those in source.
Public methodConnect
Attempts to establish connection to peer.
Public methodDispose
Releases all the resources used by this BroConnection object.
Protected methodDispose(Boolean)
Releases the unmanaged resources used by this BroConnection object and optionally releases the managed resources.
Public methodEquals
Determines whether the specified Object is equal to the current Object.
(Inherited from Object.)
Protected methodFinalize
Releases the unmanaged resources before this BroConnection object is reclaimed by GC.
(Overrides ObjectFinalize.)
Public methodGetHashCode
Serves as a hash function for a particular type.
(Inherited from Object.)
Public methodGetType
Gets the Type of the current instance.
(Inherited from Object.)
Protected methodMemberwiseClone
Creates a shallow copy of the current Object.
(Inherited from Object.)
Protected methodOnReceivedEvent
Raises the ReceivedEvent with the specified args.
Public methodProcessInput
Processes input sent to the sensor by Bro.
Public methodReconnect
Drops the current connection and reconnects, reusing all settings.
Public methodRegisterForEvent(String, Object)
Registers for events that arrive with the name of eventName.
Public methodRegisterForEvent(String, ActionBroEventArgs, Object)
Registers for events that arrive with the name of eventName using specified eventHandler.
Public methodRequestEvents
Notifies peering Bro to send events.
Public methodSendEvent(BroEvent)
Attempts to send a BroEvent to a Bro agent.
Public methodSendEvent(Byte, Int32)
Enqueues a serialized event directly into the send buffer for this BroConnection.
Public methodSendEvent(String, BroValue)
Attempts to send an event to a Bro agent with the specified name and parameters.
Public methodSendPacket
Sends Bro packet from this BroConnection.
Public methodToString
Returns a string that represents the current object.
(Inherited from Object.)
Public methodUnregisterForEvent
Unregisters for events that arrive with the name of eventName.
Top
Properties
  NameDescription
Public propertyClass
Gets or sets class associated with this BroConnection.
Public propertyData
Gets data storage facility that can store arbitrary data associated with this BroConnection.
Public propertyEventQueue
Gets event queue functions for this BroConnection.
Public propertyFileDescriptor
Gets file descriptor of this BroConnection.
Public propertyFlags
Gets BroConnectionFlags associated with this BroConnection.
Public propertyHostName
Gets host name, formatted as host:port, associated with this BroConnection.
Public propertyInputBufferLength
Gets connection statistic for the number of bytes to process in input buffer.
Public propertyIsAlive
Gets flag that determines whether a connection is currently alive or has died.
Public propertyOutputBufferLength
Gets connection statistic for the number of bytes to process in output buffer.
Public propertyPacketContext
Gets or sets current packet context, i.e., the libpcap DLT linklayer type, for this BroConnection.
Public propertyPeerClass
Gets connection class indicated by peer.
Top
Events
  NameDescription
Public eventReceivedEvent
Occurs when a Broccoli event call-back has been received.
Top
Remarks

Managing Connections

You can use BroccoliSharp to establish a connection to a remote Bro, or to create a Broccoli-enabled server application that other Bros will connect to (this means that in principle, you can also use Broccoli purely as middle-ware and have multiple Broccoli applications communicate directly).

In order to establish a connection to a remote Bro, you need to create a BroConnection. You then use this connection object to request events, establish the connection with the remote Bro, send events, etc. You can construct a client based BroConnection with a host name or IP Address string; the string should be in "hostname:port" or "x.x.x.x:port" format, respectively. For more control, you can implement your own .NET TcpClient or Socket objects and use that connection to construct a new BroConnection.

To establish a Broccoli-enabled server connection, you first need to implement the usual .NET TcpListener or Socket objects. Once established, you can create a new BroConnection with a constructor that accepts these objects. The rest of the connection handling then proceeds as in the client scenario.

Note Note
BroccoliSharp has not yet been tested with SSL connections. However, this may work by simply using the .NET SslStream with an existing Socket. Note that the SSL implementation in Mono is managed, so this would not be used in conjunction with the bro_ctx structure used to initialize the Broccoli API with OpenSSL. As a result, the bro_ctx structure is not defined in the internal BroccoliSharp BroApi. See Mono SSL for more details on SSL setup.

All BroConnection constructors accept BroConnectionFlags for fine-tuning connection behavior. These flags are:

Connection FlagDescription:
None No functionality. Use when no flags are desired.
Reconnect When using this option, Broccoli will attempt to reconnect to the peer transparently after losing connectivity. Essentially whenever you try to read from or write to the peer and its connection has broke down, a full reconnect including complete handshaking is attempted. You can check whether the connection to a peer is alive at any time using IsAlive.
AlwaysQueue When using this option, Broccoli will queue any events you send for later transmission when a connection is currently down. Without using this flag, any events you attempt to send while a connection is down get dropped on the floor. Note that Broccoli maintains a maximum queue size per connection so if you attempt to send lots of events while the connection is down, the oldest events may start to get dropped nonetheless. Again, you can check whether the connection is currently okay by using IsAlive.
DontCache When using this option, Broccoli will ask the peer not to use caching on the objects it sends to us. This is the default and the flag need not normally be used. It is kept to maintain backward compatibility.
Yield When using this option, ProcessInput processes at most one event at a time and then returns.
Cache When using this option, Broccoli will ask the peer to use caching on the objects it sends to us. Caching is normally disabled.

Just by creating a BroConnection instance you do not also establish a connection right away, this is done using Connect. The main reason for this is to allow you to subscribe to events (using RegisterForEvent(), see example below) before establishing the connection. Upon successfully returning from Connect you are guaranteed to receive all instances of the event types you have requested, while later on during the connection some time may elapse between the issuing of a request for events and the processing of that request at the remote end. Connections are established via TCP. The port numbers Bro agents and Broccoli applications listen on can vary from peer to peer.

C#
BroConnection connection = new BroConnection("bro.yourorg.com:1234"))

// Register for events before connecting to Bro...
connection.RegisterForEvent("foo");

// Connect to remote Bro
connection.Connect();

Finally, Dispose will terminate a connection and release all resources associated with the connection. You can create as many connections as you like, to one or more peers.

Connections Classes

When you want to establish connections from multiple BroccoliSharp applications with different purposes, the peer needs a means to understand what kind of application each connection belongs to. The real meaning of "kind of application" here is "sets of event types to request", because depending on the class of an application, the peer will likely want to receive different types of events.

BroccoliSharp lets you set the class of a connection by assigning a class name to the connection object's Class property. When using this feature, you need set that value before issuing a Connect since the class of a connection is determined at connection startup.

If your peer is a Bro node, you need to match the chosen connection class in the remote Bro's Communication::nodes configuration. See "Configuring event reception in Bro scripts", for how to do this. Finally, in order to obtain the class of a connection as indicated by the remote side, check the value of the PeerClass property:

C#
Console.WriteLine("Peer class = " + connection.PeerClass);

Composing and Sending Events

In order to send an event to the remote Bro agent, you first create an empty BroEvent with the name of the event, then add parameters to pass to the event handler at the remote agent and then send off the event.

Let's assume we want to request a report of all connections a remote Bro currently keeps state for that match a given destination port and host name and that have amassed more than a certain number of bytes. The idea is to send an event to the remote Bro that contains the query, identifiable through a request ID, and have the remote Bro answer us with remote_conns events containing the information we asked for. The definition of our requesting event could like the following in the Bro policy:

Bro Script
event report_conns(req_id: int, dest_host: string, dest_port: port, min_size: count);
First, create a new event:
C#
BroEvent ev = new BroEvent("report_conns");
Now we need to add parameters to the event. The sequence and types must match the event handler declaration – check the Bro policy to make sure they match. The function to use for adding parameter values is BroEvent.AddParameter. All values are eventually passed as BroValue instances and are copied internally, so the source object remains unmodified. However, this is often transparent to the developer since all BroType implementations are implicitly castable to a BroValue, i.e., you do not have to explicitly cast existing BroType implementation instances to a BroValue. As an example, you can pass a literal String value to the AddParameter(BroValue) function and this will be implicitly converted to a BroString and then automatically cast to a BroValue. The BroValue class manages the type of the value for you. See the BroType enumeration for all the value types that BroccoliSharp supports along with data structures associated with each item.

With this information, we can now compose a Bro request_connections event:

C#
ev.AddParameter(new BroValue(0, BroType.Int));      // req_id
ev.AddParameter("desthost.destdomain.com");         // dest_host
ev.AddParameter(new BroPort(80, "tcp");             // dest_port
ev.AddParameter(new BroValue(1000, BroType.Count)); // min_size
BroValue also defines a constructor parameter called typeName that will allow you to specify a specialization of any of the BroType values. This is generally not necessary except for one situation: when using BroType.Enum. You currently cannot define a Bro-level enum type in the Broccoli API, and thus when sending an enum value, you have to specify the type of the enum along with the value. For example, in order to add an instance of enum transport_type defined in Bro's bro.init, you would use:
C#
ev.AddParameter(new BroValue(2, BroType.Enum, "transport_proto"));
to get the equivalent of "udp" on the remote side. In BroccoliSharp, however, most all methods dealing with a BroValue have overloads that will take a value, the BroType and an optional type name; you will rarely ever have to explicitly create a new BroValue to specify a desired type name, for example:
C#
ev.AddParameter(2, BroType.Enum, "transport_proto");
The same method is used to specify type names when calling other BroccoliSharp functions, such as: BroEvent.ReplaceParameter(), BroRecord.Add(), BroRecord.this[int] and BroRecord.this[string].

All that's left to do now is to send off the event. For this, use the Bro connection method SendEvent(BroEvent) providing the event instance as the parameter. This function returns true when the event could be sent right away or was queued for later delivery; otherwise, false is returned on error. If the event gets queued, this does not indicate an error – it is likely that the connection was just not ready to send the event at this point. Whenever you call SendEvent(BroEvent), Broccoli attempts to send as much of an existing event queue as possible. Again, the event is copied internally to make it easier for you to send the same event repeatedly:

C#
connection.SendEvent(ev);
Note Note
You don't have to create a BroEvent object if you don't intend on reusing it. The BroConnection overloads for the SendEvent method include one that takes an args style parameter array:
C#
connection.SendEvent("report_conns",
    new BroValue(0, BroType.Int),
    "desthost.destdomain.com",
    new BroPort(80, "tcp"),
    new BroValue(1000, BroType.Count));
Two other functions may be useful to you: BroEventQueue.Length tells you how many events are currently queued, and BroEventQueue.Flush() attempts to flush the current event queue and returns the number of events that do remain in the queue after the flush. However, you do not normally need to call BroEventQueue.Flush(), queue flushing is attempted every time you send an event.

The BroEventQueue object is always referenced from the source BroConnection using the read-only BroConnection.EventQueue property, for example:

C#
connection.EventQueue.Length;

Receiving Events

Receiving events is a little more work because you need to:

  1. Attach to the ReceivedEvent to handle requested events when they arrive.
  2. Let the remote Bro agent know that you would like to receive those events.
  3. Write code in your event handler for extracting and processing arriving events.
Each of these steps is explained in the following sections.

Implementing an Event Handler

The BroccoliSharp library internally handles creating a callback for the Broccoli API to use for any events to be received. Under the covers, BroccoliSharp will use the Broccoli Compact Argument Passing mechanism and expose the received events through normal .NET event handling mechanisms so as to simplify receiving Bro events for the developer. As a result, all a developer needs to do is to create a handler for the BroConnection.ReceivedEvent and then attach to this event. The function signature for these events looks like delegate void ReceivedEventSignature(object sender, BroEventArgs e):

C#
// Establish event handler for Bro events
connection.ReceivedEvent += connection_ReceivedEvent;

// Bro event handler - called when a new Bro event is received
static void connection_ReceivedEvent(object sender, BroEventArgs e)
{
    if (e.EventName == "foo")
    {
        // Code to handle foo event...
    }
}

Register to Receive Event

In order to register to receive an event, use BroConnection.RegisterForEvent()and provide it the name of the Bro event you want to receive. You can optionally also provide user definable data that will be provided to the event handler any time an event with this name is received.

Note Note
Any user data associated with the event is maintained in managed memory and does not cross into the Broccoli API.

Continuing our example, we will now want to process the connection reports that contain the responses to our prior defined remote_conns event. Let's assume those responses look as follows:

Bro Script
event report_conn(req_id: int, conn: connection);
The reply events contain the request ID so we can associate requests with replies, and a connection record (defined in bro.init in Bro). Now just register to receive the event using the connection object's RegisterForEvent(String, Object) function:
C#
connection.RegisterForEvent("report_conn");
In this case we have no additional data to be passed into the callback, so we did not specify a value for the optional userData argument. If you have multiple events you are interested in, register each one in this fashion.

Handling Received Events

Events are received in a BroEventArgs object that is passed to the event handler that is attached to the connection object's ReceivedEvent. All received events can be distinguished by keying off the BroEventArgs.EventName value, for example:

C#
// Bro event handler
static void connection_ReceivedEvent(object sender, BroEventArgs e)
{
    // Raised when a new Bro event is received
    switch (e.EventName)
    {
        case "foo":
            // Handle foo event
            Console.WriteLine("Received event \"foo\" with {0} parameters at {1}", e.Parameters.Length, e.EventTime);

            for (int i = 0; i < e.Parameters.Length; i++)
                Console.WriteLine("    Event \"foo\" parameter[{0}] = {1}", i, e.Parameters[i]);

            break;
        default:
            Console.WriteLine("Received unexpected event \"{0}\".", e.EventName);
            break;
    }
}
Note Note
In addition to attaching to the ReceivedEvent, you can also use the use the RegisterForEvent(String, ActionBroEventArgs, Object) overload that accepts a Delegate as the desired event handler. You can use this in-lieu of creating a single common event handler for all registered events:
C#
// Register to receive the pong event
connection.RegisterForEvent("pong", e =>
{
    BroRecord pongData = e.Parameters[0];
    DateTime dst_time = pongData["dst_time"];
    DateTime src_time = pongData["src_time"];

    Console.WriteLine("pong event from {0}: seq={1}, time={2}/{3} s",
        s_hostName,
        pongData["seq"],
        (dst_time - src_time).TotalSeconds,
        (BroTime.Now - src_time).TotalSeconds);
});

Requesting Event Delivery

BroccoliSharp will now know what to do with the requested events when they arrive. What's left to do is to let the remote Bro know that you would like to receive the events for which you registered. If you haven't yet called Connect, then there is nothing to do, since calling the connect function will request the registered events anyway. If you are already connected, you can still request events. To do so, simply call the BroConnection.RequestEvents() method.

This mechanism also implies that no unrequested events will be delivered to us (and if that happened for whatever reason, the event would simply be dropped on the floor).

Note Note
Currently you cannot un-request events, nor can you request events based on predicates on the values of the events' arguments.

Reading Events from the Connection Handle

At this point the remote Bro will start sending you the requested events once they are triggered - however, this does not automatically raise the connection's ReceivedEvent. What is left to do is to trigger processing of the queued events in the connection which will start dispatching them to the event handlers.

To start raising the events and firing event handlers with received event data, you can choose among two approaches: explicit polling or using events on the source Socket to know when data has arrived for processing. Explicit polling is particularly straightforward: all you need to do is call the BroConnection.ProcessInput() function from a timer or loop, which will then go off and check if any events have arrived and if so, dispatch them accordingly. Note that the ProcessInput function does not block – if no events have arrived, then the call will return immediately. For more fine-grained control over your I/O handling, you can use a source Socket to determine when data has arrived and then invoke ProcessInput - one way to do this would be to use asynchronous sockets.

As a side note, if you don't process arriving events frequently enough, then TCP's flow control will start to slow down the sender until eventually events will queue up and be dropped at the sending end.

Note Note
When using your own Socket, it is recommended to use the BroConnection constructors that accept sockets. It is also possible to get a handle to the socket that the Broccoli API will create when using the Bro constructor that accepts a host-name. You can do this by accessing the BroConnection.FileDescriptor property, but using the descriptor from within a .NET application is outside the scope of this documentation.
Examples

This example shows how to create and use a BroConnection:

using System;
using System.Net.Sockets;
using BroccoliSharp;

namespace Example
{
    class Program
    {
        static void Main(string[] args)
        {
            using (BroConnection connection = new BroConnection("bro.yourorg.com:1234"))
            {
                // Establish event handler for received Bro events
                connection.ReceivedEvent += connection_ReceivedEvent;

                // Register for event "foo"
                connection.RegisterForEvent("foo");

                // Connect to remote Bro
                connection.Connect();

                Console.WriteLine("Peer class = " + connection.PeerClass);

                // Create a new event
                BroEvent bar = new BroEvent("bar");

                bar.AddParameter("Text parameter");
                bar.AddParameter(true);
                bar.AddParameter("192.168.1.1", BroType.IpAddr);
                bar.AddParameter(new BroPort(80, ProtocolType.Tcp));
                bar.AddParameter(2, BroType.Enum, "transport_proto");
                bar.AddParameter(BroTime.Now);

                // Send the event
                bool result = connection.SendEvent(bar);
                Console.WriteLine("Event \"bar\" {0}", result ? "was sent or queued for later delivery" : "failed to send or queue");

                // Process any incoming events...
                connection.ProcessInput();

                Console.ReadLine();

                // Unregister from event "foo"
                connection.UnregisterForEvent("foo");
            }
        }

        // Bro event handler
        static void connection_ReceivedEvent(object sender, BroEventArgs e)
        {
            // Raised when a new Bro event is received
            switch (e.EventName)
            {
                case "foo":
                    // Handle foo event
                    Console.WriteLine("Received event \"foo\" with {0} parameters at {1}", e.Parameters.Length, e.EventTime);

                    for (int i = 0; i < e.Parameters.Length; i++)
                        Console.WriteLine("    Event \"foo\" parameter[{0}] = {1}", i, e.Parameters[i]);

                    break;
                default:
                    Console.WriteLine("Received unexpected event \"{0}\".", e.EventName);
                    break;
            }
        }
    }
}

This example shows a "ping" working with the broping.bro script:

C#
using System;
using System.Threading;
using BroccoliSharp;

namespace BroPing
{
    class Program
    {
        static int Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("Usage:");
                Console.WriteLine("    BroPing host:port");
                return 1;
            }

            try
            {
                string hostName = args[0];

                Console.WriteLine("Attempting to establish Bro connection to \"{0}\"...", hostName);

                // Create the connection object
                using (BroConnection connection = new BroConnection(hostName))
                {
                    // Register to receive the pong event
                    connection.RegisterForEvent("pong", e =>
                    {
                        BroValue[] values = e.Parameters;
                        DateTime src_time = values[0];
                        DateTime dst_time = values[1];

                        Console.WriteLine("pong event from {0}: seq={1}, time={2}/{3} s",
                            hostName,
                            values[2],
                            (dst_time - src_time).TotalSeconds,
                            (BroTime.Now - src_time).TotalSeconds);
                    });

                    connection.Connect();

                    Console.WriteLine("Bro connection established. Starting ping cycle, press any key to cancel...");

                    int seq = 0;

                    while (!Console.KeyAvailable)
                    {
                        // Send ping
                        connection.SendEvent("ping", BroTime.Now, new BroValue(seq++, BroType.Count));

                        // Process any received responses
                        connection.ProcessInput();

                        // Wait one second between pings
                        Thread.Sleep(1000);
                    }
                }

                return 0;
            }
            catch (Exception ex)
            {
                Console.Write("Exception: {0}{1}", ex.Message, Environment.NewLine);
                return 1;
            }
        }
    }
}

See Also