Chapter 3. Using Broccoli

Table of Contents
3.1. The build environment
3.2. Suggestions for instrumenting applications
3.3. The Broccoli API
3.4. Configuring encrypted communication
3.5. Configuring event reception in Bro policies
3.6. Configuring debugging output
3.7. Test programs


3.1. The build environment

To make it easier to add Broccoli support to your build environment, the distribution comes with a shell script that provides the necessary compiler and linker flags (similarly to many other packages): broccoli-config. Use the --cflags option to obtain the compiler options and --libs to obtain the linker options.

If you use the autoconf/automake tools, we recommend something along the following lines for your configure script:

dnl ##################################################
dnl # Check for Broccoli
dnl ##################################################
AC_ARG_WITH(broccoli-config,
    AC_HELP_STRING([--with-broccoli-config=FILE], [Use given broccoli-config]),
    [ brocfg="$withval" ],
    [ AC_PATH_GENERIC(broccoli,,
          brocfg="broccoli-config",
          AC_MSG_ERROR(Cannot find Broccoli: Is broccoli-config in path? Use more fertilizer?)) ])

broccoli_libs=`$brocfg --libs`
broccoli_cflags=`$brocfg --cflags`
AC_SUBST(broccoli_libs)
AC_SUBST(broccoli_cflags)
      

3.2. Suggestions for instrumenting applications

Often you will want to make existing applications Bro-aware, that is, instrument them so that they can send and receive Bro events at appropriate moments in the execution flow. This will involve modifying an existing code tree, so care needs to be taken to avoid unwanted side effects. By protecting the instrumented code with #ifdef/#endif statements you can still build the original application, using the instrumented source tree. The broccoli-config script helps you in doing so because it already adds -DBROCCOLI to the compiler flags reported when run with the --cflags option:

cpk25@localhost:/home/cpk25 > broccoli-config --cflags
-I/usr/local/include -I/usr/local/include -DBROCCOLI
      

So simply surround all inserted code with a preprocessor check for BROCCOLI and you will be able to build the original application as soon as BROCCOLI is not defined.


3.3. The Broccoli API

Time for some code. In the code snippets below we will introduce variables whenever context requires them and not necessarily when C requires them. The library does not require calling a global initialization function. In order to make the API known, include broccoli.h:

#ifdef BROCCOLI
#include <broccoli.h>
#endif
      

Note

A note on Broccoli's memory management philosophy:

Broccoli generally does not release objects you allocate. The approach taken is "you clean up what you allocate."


3.3.1. Data types in Broccoli

Broccoli declares a number of data types in broccoli.h that you should know about. The more complex ones are kept opaque, while you do get access to the fields in the simpler ones. The full list is as follows:

  • Simple unsigned types: uint, uint32, uint16 and uchar.

  • Connection handles: BroConn, kept opaque.

  • Bro events: BroEvent, kept opaque.

  • Buffer objects: BroBuf, kept opaque. See the separate section on buffer management for details.

  • Records: BroRecord, kept opaque. See the separate section on record handling for details.

  • Strings (character and binary): BroString, defined as follows:

    typedef struct bro_string {
      int          str_len;
      char        *str_val;
    } BroString;
                  

    BroStrings are mostly kept transparent for convenience; please have a look at the string API: bro_string_init(), bro_string_set(), bro_string_set_data(), bro_string_copy(), bro_string_cleanup(), and bro_string_free().

  • Ports: BroPort for network ports, defined as follows:

    typedef struct bro_port {
      uint16       port_num;   /* port number in host byte order */
      int          port_proto; /* IPPROTO_xxx */
    } BroPort;
                  
  • Subnets: BroSubnet, defined as follows:

    typedef struct bro_subnet
    {
      uint32       sn_net;     /* IP address in network byte order */
      uint32       sn_width;   /* Length of prefix to consider. */
    } BroSubnet;
                  


3.3.2. Managing connections

You need to obtain a connection handle before you can receive or send events. You then use this connection handle to identify the connection you want to use. Connection handles are pointers to BroConn structures, and kept opaque. Use bro_connect() or bro_connect_str() to obtain such a handle, depending on what parameters are more convenient for you: the former accepts the IP address and port number as separate numerical arguments, the latter uses a single string to encode both, in "hostname:port" format.

Both calls accept additional flags for fine-tuning connection behaviour. These flags are:

  • BRO_CFLAG_NONE: no functionality. Use when no flags are desired.

  • BRO_CFLAG_COMPLETE_HANDSHAKE: The Bro communication protocol performs a fairly complex handshaking procedure during connection setup that exchanges various amounts of information before the connection is in a consistent state. By default, bro_connect() and bro_connect_str() return immediately after the connection is established and the handshake is performed implicitly with information sent and received in the future. You can request the handshake be completed before the functions return by using BRO_CFLAG_COMPLETE_HANDSHAKE. This is normally recommended except for one situation: if you know from the outset that you want to request certain events from the peer, then it is more efficient not to use the flag when connecting, then request the events, and complete the handshake using bro_conn_await_handshake(). See the section on requesting event delivery below for more information.

  • BRO_CFLAG_RECONNECT: When using this option, Broccoli will attempt to reconnect to the peer after lost connectivity transparently. Essentially whenever you try to read from or write to the peer and its connection 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 bro_conn_alive().

  • BRO_CFLAG_ALWAYS_QUEUE: 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 bro_conn_alive().

  • BRO_CFLAG_SHAREABLE: When using this option, Broccoli will synchronize parallel read/write requests to a connection handle. Note that without this option, this is not a safe operation, particularly with SSL-enabled connections, because the encryption contexts will get desynchronized and connections will abort or hang. The BRO_CFLAG_SHAREABLE flag ensures that all I/O is handled from a single process, using shared memory and semaphores for synchronization. Once you have obtained a connection handle in the usual way, you can fork new processes or create new threads and keep on using the connection handle.

    Caution

    BRO_CFLAG_SHAREABLE is new IN 0.7 and experimental at this point.

bro_disconnect() terminates a connection and releases all resources associated with it. When using BRO_CFLAG_SHAREABLE, you can call bro_disconnect() in each process using the connection, but you must make sure that the process that originally created the connection handle calls bro_disconnect() after all other execution contexts using the handle are done[1]. You can create as many connections as you like, to one or more peers. You can obtain the file descriptor of a connection using bro_conn_get_fd().

Connections are established via TCP, optionally using SSL encryption. See "Configuring encrypted communication" below for more information on setting up enncryption. The port numbers Bro agents and Broccoli applications listen on can vary from peer to peer.

char host_str = "bro.yourcompany.com";
int port      = 1234;
struct hostent *host;
BroConn *bc;

if (! (host = gethostbyname(host_str)) ||
    ! (host->h_addr_list[0])) {
	/* Error handling -- could not resolve host */
}

/* In this example, we connect, then request events from the peer, and
 * then ask for the handshake to be completed.
 *
 * First connect to the peer:
 */
if (! (bc = bro_connect((struct in_addr*) host->h_addr_list[0], htons(port), BRO_CFLAG_NONE))) {
	/* Error handling  - could not connect to peer */
}

/* Request events ... */

if (! bro_conn_await_handshake(bc, 10)) {
	/* Error handling - handshake could not be completed in 10s. */
}

/* Send and receive events ... */

/* Disconnect from Bro */
bro_disconnect(bc);
        

Or simply use the string-based version:

char host_str = "bro.yourcompany.com:1234";
BroConn *bc;

/* In this example we don't request any events from the peer and complete
 * the handshake right away.
 *
 * Again, first connect to the peer:
 */
if (! (bc = bro_connect_str(host_str, BRO_CFLAG_COMPLETE_HANDSHAKE))) {
	/* Error handling  - could not connect to peer */
}

/* ... */
        

3.3.3. Composing and sending events

In order to send an event to the remote Bro agent, you first create an empty event structure 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.

Note

Bro peers ignore unrequested events.

You need to make sure that the remote Bro agent is interested in receiving the events you send. This interest is expressed in policy configuration. We'll explain this in more detail below and for now assume that our remote peer is configured to receive the events we send.

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_conn events containing the information we asked for. The definition of our requesting event could look as follows in the Bro policy:

event report_conns(req_id: int, dest_host: string, dest_port: port, min_size: count);
        

First, create a new event:

BroEvent *ev;

if (! (ev = bro_event_new("report_conns"))) {
	/* Error handling - could not allocate new event. */
}
        

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 bro_event_add_val() All values are passed as pointer arguments and are copied internally, so the object you're pointing to stays unmodified at all times. You clean up what you allocate. In order to indicate the type of the value passed into the function, you need to pass a numerical type identifier along as well. Table 1 lists the value types that Broccoli supports along with the type identifier and data structures to point to.

Table 3-1. Types, type tags, and data structures for event parameters in Broccoli

TypeType tagData type pointed to
BooleanBRO_TYPE_BOOLint
Integer valueBRO_TYPE_INTint
Counter (nonnegative integers)BRO_TYPE_COUNTuint32
Floating-point numberBRO_TYPE_DOUBLEdouble
TimestampBRO_TYPE_TIMEdouble (see also bro_util_timeval_to_double() and bro_util_current_time())
Time intervalBRO_TYPE_INTERVALdouble
Strings (text and binary)BRO_TYPE_STRINGBroString (see also the family of bro_string_xxx() functions)
Network portsBRO_TYPE_PORTBroPort, with the port number in host byte order
IPv4 addressBRO_TYPE_IPADDRuint32, in network byte order
IPv4 networkBRO_TYPE_NETuint32, in network byte order
IPv4 subnetBRO_TYPE_SUBNETBroSubnet, with the sn_net member in network byte order
RecordBRO_TYPE_RECORDBroRecord (see also the family of bro_record_xxx() functions and the explanation below)

Knowing these, we can now compose a request_connections event:

BroString dest_host;
BroPort dest_port;
uint32 min_size;
int req_id = 0;

bro_event_add_val(ev, BRO_TYPE_INT, &req_id);
req_id++;

bro_string_set(&dest_host, "desthost.destdomain.com");
bro_event_add_val(ev, BRO_TYPE_STRING, &dest_host);
bro_string_cleanup(&dest_host);

dest_port.dst_port = 80;
dest_port.dst_proto = IPPROTO_TCP;
bro_event_add_val(ev, BRO_TYPE_PORT, &dest_port);

min_size = 1000;
bro_event_add_val(ev, BRO_TYPE_COUNT, &min_size);
        

All that's left to do now is to send off the event. For this, use bro_event_send() and pass it the connection handle and the event. The function returns TRUE when the event could be sent right away or if it was queued for later delivery. FALSE is returned on error. If the event get queued, this does not indicate an error — likely the connection was just not ready to send the event at this point. Whenever you call bro_event_send(), 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. You clean up what you allocate.

bro_event_send(bc, ev);
bro_event_free(ev);
        

Two other functions may be useful to you: bro_event_queue_length() tells you how many events are currently queued, and bro_event_queue_flush() attempts to flush the current event queue and returns the number of events that do remain in the queue after the flush. Note: you do not normally need to call this function, queue flushing is attempted every time you send an event.


3.3.4. Handling record objects

Broccoli also supports records, i.e., values that pack more than one value together. In Broccoli, the way you handle records is somewhat similar to events: after creating an empty record (of opaque type BroRecord, you can iteratively add vals to it. The main difference here is that you must specify a field name with the val; each val in a record can be identified both by position (a numerical index starting from zero), and by field name. You can retrieve vals in a record by field index or field name. You can also reassign values.

There is currently no separate definition of record types. You defined the type of a record implicitly by the sequence of field names and the sequence of the types of the vals you put into the record.

All fields in a record must be assigned before it can be shipped.

The API you want to look at consists of bro_record_new(), bro_record_free(), bro_record_add_val(), bro_record_get_nth_val(), bro_record_get_named_val(), bro_record_get_nth_val(), and bro_record_get_named_val(). Whenever type identifiers are required, please refer to Table 1 again for the valid type tag – data structure combinations.


3.3.5. Receiving events

Receiving events is a little more work because you need to

  1. tell Broccoli what to do with the arriving events,

  2. let the remote Bro agent know that you would like to receive those events,

  3. find a spot in the code path suitable for extracting and processing arriving events.

Each is handled in the following sections.


3.3.5.1. Implementing event callbacks

When Broccoli receives an event, it tries to dispatch the event through a callback registry. Any callbacks registered for the arriving event's name are invoked with the parameters shipped with the event. In order to register a callback, use bro_event_registry_add() and pass it the connection handle, the name of the event for which you register the callback, and the callback itself that matches the signature of the BroEventFunc type. The callback's type is defined rather generically as follows:

typedef void (*BroEventFunc) (BroConn *bc, ...);
          

As you can see, all it requires is a connection handle as its first argument. Broccoli will pass the connection handle of the connection on which the event arrived through to the callback. BroEventFuncs are variadic, because each callback you provide is directly invoked with pointers to the parameters of the event, in a useful format. All you need to know is what type to point to in order to receive the parameters in the right layout. Refer to Table 1 again for a summary of those types. Continuing our example, we will want to process the connection reports that contain the responses to our report_conns event. Let's assume those look as follows:

event remote_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. (It'd be nicer to report all replies in a single event but we'll ignore that for now.) For this event, our callback would look like this:

void remote_conn_cb(BroConn *bc, int *req_id, BroRecord *conn);
          

Once more, you clean up what you allocate, and since you never allocated the space these arguments point to, you also don't clean them up. Finally, we register the callback using bro_event_registry_add():

bro_event_registry_add(bc, "remote_conn", remote_conn_cb);
          

If you have multiple events you are interested in, register each one in this fashion.


3.3.5.2. Requesting event delivery

At this point, Broccoli knows what to do with the requested events upon arrival. What's left to do is to let the remote Bro agent know that you would like to receive the events for which you registered. To do so, call bro_event_registry_request():

bro_event_registry_request(bc);
          

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 that at the moment you cannot unrequest events.

Support for this feature will be added shortly.


3.3.5.3. Reading events from the connection handle

At this point the remote Bro will start sending you the requested events once they are triggered. What is left to do is to read the arriving events from the connection and trigger dispatching them to the registered callbacks.

If you are writing a new Bro-enabled application, this is easy, and you can choose among two approaches: polling explicitly via Broccoli's API, or using select() on the file handle associated with a BroConn. The former case is particularly straightforward; all you need to do is call bro_conn_process_input(), which will go off and check if any events have arrived and if so, dispatch them accordingly. This 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 will probably want to use bro_conn_get_fd() to obtain the file descriptor of your connection and then incorporate that in your standard FD_SET/select() code. Once you have determined that data in fact are ready to be read from the obtained file descriptor, you can then come back to using bro_conn_get_fd(), however this time knowing that it'll find something to dispatch.

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.


3.3.6. Associating data with connections

You will often find that you would like to connect data with a BroConn. Broccoli provides an API that lets you associate data items with a connection handle through a string-based key–value registry. The functions of interest are bro_conn_data_set(), bro_conn_data_get(), and bro_conn_data_del(). You need to provide a string identifier for a data item and can then use that string to register, look up, and remove the associated data item. Note that there is currently no mechanism to trigger a destructor function for registered data items when the Bro connection is terminated. You therefore need to make sure that all data items that you do not have pointers to via some other means are properly released before calling bro_disconnect().


3.3.7. Configuration files

Imagine you have instrumented the mother of all server applications. Building it takes forever, and every now and then you need to change some of the parameters that your Broccoli code uses, such as the host names of the Bro agents to talk to. To allow you to do this quickly, Broccoli comes with support for configuration files. All you need to do is change the settings in the file and restart the application (we're considering adding support for volatile configuration items that are read from the file every time they are requested).

A configuration is read from a single configuration file. This file can be read from two different locations:

  • The system-wide configuration file. You can obtain the location of this config file by running broccoli-config --config.

  • Alternatively, a per-user configuration file stored in ~/.broccoli.conf can be used.

If a user has a configuration file in ~/.broccoli.conf, it is used exclusively, otherwise the global one is used.

Caution

~/.broccoli.conf will only be used if it is a regular file, not executable, and neither group nor others have any permissions on the file. That is, the file's permissions must look like -rw------- or -r--------.

In the configuration file, a "#" anywhere starts a comment that runs to the end of the line. Configuration items are specified as key-value pairs:

# This is the Broccoli system-wide configuration file.
#
# Entries are of the form <identifier> <value>, where the identifier
# is a sequence of letters, and value can be a string (including
# whitespace), and floating point or integer numbers. Comments start
# with a "#" and go to the end of the line. For boolean values, you
# may also use "yes", "on", "true", "no", "off", or "false".
# Strings may contain whitespace, but need to be surrounded by
# double quotes '"'.
#
# Examples:
#
Foo/PeerName          mybro.securesite.com
Foo/PortNum           123
Bar/SomeFloat         1.23443543
Bar/SomeLongStr       "Hello World"
        

You can also have multiple sections in your configuration. Your application can select a section as the current one, and queries for configuration settings will then only be answered with values specified in that section. A section is started by putting its name (no whitespace please) between square brackets. Configuration items positioned before the first section title are in the default domain and will be used by default.

# This section contains all settings for myapp.
[ myapp ]
        

You can name identifiers any way you like, but to keep things organized it is recommended to keep a namespace hierarchy similar to the file system. In the code, you can query configuration items using bro_conf_get_str(), bro_conf_get_int(), and bro_conf_get_dbl(). You can switch between sections using bro_conf_set_domain().


3.3.8. Using dynamic buffers

Broccoli provides an API for dynamically allocatable, growable, shrinkable, and consumable buffers with BroBufs. You may or may not find this useful — Broccoli mainly provides this feature in broccoli.h because these buffers are used internally anyway and because they are typical case of something that people implement themselves over and over again, for example to collect a set of data before sending it through a file descriptor, etc.

The buffers work as follows. The structure implementing a buffer is called BroBuf. BroBufs are initialized to a default size when created via bro_buf_new(), and released using bro_buf_free(). Each BroBuf has a content pointer that points to an arbitrary location between the start of the buffer and the first byte after the last byte currently used in the buffer (see buf_off in the illustration below). The content pointer can seek to arbitrary locations, and data can be copied from and into the buffer, adjusting the content pointer accordingly. You can repeatedly append data to end of the buffer's used contents using bro_buf_append().


   <---------------- allocated buffer space ------------>
   <======== used buffer space ========>
   ^              ^                    ^               ^                 
   |              |                    |               |
   `buf           `buf_ptr             `buf_off        `buf_len
        

Have a look at the following functions for the details: bro_buf_new(), bro_buf_free(), bro_buf_append(), bro_buf_get(), bro_buf_get_end(), bro_buf_get_size(), bro_buf_get_used_size(), bro_buf_ptr_get(), bro_buf_ptr_tell(), bro_buf_ptr_seek(), bro_buf_ptr_check(), and bro_buf_ptr_read().


3.4. Configuring encrypted communication

Encrypted communication between Bro peers takes place over an SSL connection in which both endpoints of the connection are authenticated. This requires at least some PKI in the form of a certificate authority (CA) which you use to issue and sign certificates for your Bro peers. To facilitate the SSL setup, each peer requires three documents: a certificate signed by the CA and containing the public key, the corresponding private key, and a copy of the CA's certificate.

The OpenSSL command line tool openssl can be used to create all files neccessary, but its unstructured arguments and poor documentation make it a pain to use and waste lots of people a lot of time[2]. Therefore, the Bro distribution comes with two scripts, ca-create and ca-issue. You use the former once to set up your CA, and the latter to create a certificate for each of the Bro peers in your infrastructure.

In order to enable encrypted communication for your Broccoli application, you need to put the CA certificate and the peer certificate in the /broccoli/ca_cert and /broccoli/host_cert keys, respectively, in the configuration file.

Caution

This is where you configure whether to use encrypted or unencrypted connections.

Not pointing to the certificates means unencrypted communication will be used, otherwise encrypted communication will be attempted. If you point to the certificates but there is a problem in the connection handshake, the connection is aborted. There is no fallback to unencrypted communication.

/broccoli/ca_cert          <path>/ca_cert.pem
/broccoli/host_cert        <path>/bro_cert.pem
      

In a Bro policy, you need to load the listen-ssl.bro policy and redef ssl_ca_certificate and ssl_private_key, defined in bro.init:

@load listen-ssl

redef ssl_ca_certificate   = "<path>/ca_cert.pem";
redef ssl_private_key      = "<path>/bro.pem";
      

By default, you will be prompted for the passphrase for the private key matching the public key in your agent's certificate. Depending on your application's user interface and deployment, this may be inappropriate. You can store the passphrase in the config file as well, using the following identifier:

/broccoli/host_pass        foobar
      

Caution

Make sure that access to your configuration is restricted.

If you provide the passphrase this way, it is obviously essential to have restrictive permissions on the configuration file. Broccoli partially enforces this. Please refer to the section on configuration files for details.


3.5. Configuring event reception in Bro policies

Before a remote Bro will accept your connection and your events, it needs to have its policy configured accordingly:

  1. Load either listen-ssl or listen-clear, depending on whether you want to have encrypted or cleartext communication. Obviously, encrypting the event exchange is recommended and cleartext should only be used for early experimental setups. See below for details on how to set up encrypted communication via SSL.

  2. You need to find a port to use for the Bros and Broccoli applications that will listen for connections. Every such agent can use a different port, though default ports are provided in the Bro policies. To change the port the Bro agent will be listening on from its default redefine the listen_port_ssl or listen_port_clear variables from listen-clear.bro or listen-ssl.bro, respectively. Have a look at these policies as well as remote.bro for the default values. Here is the policy for the unencrypted case:

    @load listen-clear
    
    redef listen_port_clear = 12345/tcp;
                

    Including the settings for the cryptographic files introduced in the previous section, here is the encrypted one:

    @load listen-ssl
    
    redef listen_port_ssl = 12345/tcp;
    redef ssl_ca_certificate   = "<path>/ca_cert.pem";
    redef ssl_private_key      = "<path>/bro.pem";
                
  3. The policy controlling which peers a Bro agent will communicate with and how this communication will happen are defined in the destinations table defined in remote.bro. This table contains entries of type Destination, whose members mostly provide default values so you do not need to define everything. You need to come up with a tag for the connection under which it can be found in the table (a creative one would be "broccoli"), the IP address of the peer, the pattern of names of the events the Bro will accept from you, whether you want Bro to connect to your machine on startup or not, if so, a port to connect to (defaults are default_port_ssl and default_port_clear, also defined in remote.bro), a retry timeout, and whether to use SSL. An example could look as follows:

    
redef Remote::destinations += {
    	["broping"] = [$host = 127.0.0.1, $events = /ping/, $connect=F, $ssl=F]
    };
                

    This example is taken from broping.bro, the policy the remote Bro must run when you want to use the broping tool explained in the section on testing below. It will allow an agent on the local host to connect and send "ping" events. Our Bro will not attempt to connect, and incoming connections will be expected in cleartext.


3.6. Configuring debugging output

If your Broccoli installation was configured with --enable-debug, Broccoli will report two kinds of debugging information: (i) function call traces and (ii) individual debugging messages. Both are enabled by default, but can be adjusted in two ways.


3.7. Test programs

The Broccoli distribution comes with a few small test programs, located in the test/ directory of the tree. The most notable one is broping [3], a mini-version of ping. It sends "ping" events to a remote Bro agent, expecting "pong" events in return. It operates in two flavours: one uses atomic types for sending information across, and the other one uses records. The Bro agent you want to ping needs to run either the broping.bro or broping-record.bro policies. You can find these in the test/ directory of the source tree, and in <prefix>/share/broccoli in the installed version. broping.bro is shown below. By default, pinging a Bro on the same machine is configured. If you want your Bro to be pinged from another machine, you need to update the destinations variable accordingly.

@load listen-clear;

global ping_log = open_log_file("ping");

redef Remote::destinations += {
	["broping"] = [$host = 127.0.0.1, $events = /ping/, $connect=F, $retry = 60 secs, $ssl=F]
};

event ping(src_time: time, seq: count)
{
        event pong(src_time, current_time(), seq);
}

event pong(src_time: time, dst_time: time, seq: count)
{
        print ping_log, fmt("ping received, seq %d, %f at src, %f at dest, one-way: %f",
                            seq, src_time, dst_time, dst_time-src_time);
}
      

broping sends ping events to Bro. Bro accepts those because they are configured accordingly in the destinations table. As shown in the policy, ping events trigger pong events, and broccoli requests delivery of all pong events back to it. When running broping, you'll see something like this:

cpk25@localhost:/home/cpk25/devel/broccoli > ./test/broping
pong event from 127.0.0.1: seq=1, time=0.004700/1.010303 s
pong event from 127.0.0.1: seq=2, time=0.053777/1.010266 s
pong event from 127.0.0.1: seq=3, time=0.006435/1.010284 s
pong event from 127.0.0.1: seq=4, time=0.020278/1.010319 s
pong event from 127.0.0.1: seq=5, time=0.004563/1.010187 s
pong event from 127.0.0.1: seq=6, time=0.005685/1.010393 s
      

Notes

[1]

This is because calling bro_disconnect() from the original process releases all the synchronization primitives associated with the connection handle.

[2]

In other documents and books on OpenSSL you will find this expressed more politely, using terms such as "daunting to the uninitiated", "challenging", "complex", "intimidating".

[3]

Pronunciation is said to be somewhere on the continuum between "brooping" and "burping".