Event Groups

Zeek supports enabling and disabling event and hook handlers at runtime through event groups. While named event groups, hook handlers are covered due to their structural similarity to event handlers as well.

Event and hook handlers can be part of multiple event groups. An event or hook handler is disabled if any of the groups it’s part of is disabled. Conversely, event and hook handlers are enabled when all groups they are part of are enabled. When Zeek starts, all event groups are implicitly enabled. An event or hook handler that is not part of any event group is always enabled.

Currently, two types of event groups exist: Attribute and module based.

Attribute Based Event Group

Attribute based event groups come into existence when an event or hook handler has a &group attribute. The value of the group attribute is a string identifying the group. There’s a single global namespace for attribute based event groups. Two event handlers in different files or modules, but with the same group attribute value, are part of the same group. Event and hook handlers can have more than one group attributes.

event_groups_attr_01.zeek
 1event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string) &group="http-print-debugging"
 2    {
 3        print fmt("HTTP request: %s %s (%s->%s)", method, original_URI, c$id$orig_h, c$id$resp_h);
 4    }
 5
 6event http_header(c: connection, is_orig: bool, original_name: string, name: string, value: string) &group="http-print-debugging"
 7    {
 8    if ( name != "USER-AGENT" && name != "SERVER" )
 9        return;
10
11    local snd = is_orig ? c$id$orig_h : c$id$resp_h;
12    local rcv = is_orig ? c$id$resp_h : c$id$orig_h;
13    print fmt("HTTP header : %s=%s (%s->%s)", original_name, value, snd, rcv);
14    }
15
16event http_reply(c: connection, version: string, code: count, reason: string) &group="http-print-debugging"
17    {
18        print fmt("HTTP reply: %s/%s version %s (%s->%s)", code, reason, version, c$id$resp_h, c$id$orig_h);
19    }

This example shows http_request, http_header and http_reply event handlers, all with a group attribute of http-print-debugging. When running Zeek against a pcap containing a single HTTP transaction, the output is as follows.

$ zeek -r traces/get.trace  ./event_groups_attr_01.zeek
HTTP request: GET /download/CHANGES.bro-aux.txt (141.142.228.5->192.150.187.43)
HTTP header : User-Agent=Wget/1.14 (darwin12.2.0) (141.142.228.5->192.150.187.43)
HTTP reply: 200/OK version 1.1 (192.150.187.43->141.142.228.5)
HTTP header : Server=Apache/2.4.3 (Fedora) (192.150.187.43->141.142.228.5)

Such debugging functionality would generally only be enabled on demand. Extending the above script, we introduce an option and a change handler function from the configuration framework` to enable and disable the http-print-debugging event group at runtime.

event_groups_attr_02.zeek
 1@load base/frameworks/config
 2
 3redef Config::config_files += { "./myconfig.dat" };
 4
 5module Debug;
 6
 7export {
 8    option http_print_debugging = F;
 9}
10
11event http_request(c: connection, method: string, original_URI: string, unescaped_URI: string, version: string) &group="http-print-debugging"
12    {
13        print fmt("HTTP request: %s %s (%s->%s)", method, original_URI, c$id$orig_h, c$id$resp_h);
14    }
15
16event http_header(c: connection, is_orig: bool, original_name: string, name: string, value: string) &group="http-print-debugging"
17    {
18    if ( name != "USER-AGENT" && name != "SERVER" )
19        return;
20
21    local snd = is_orig ? c$id$orig_h : c$id$resp_h;
22    local rcv = is_orig ? c$id$resp_h : c$id$orig_h;
23    print fmt("HTTP header : %s=%s (%s->%s)", original_name, value, snd, rcv);
24    }
25
26event http_reply(c: connection, version: string, code: count, reason: string) &group="http-print-debugging"
27    {
28        print fmt("HTTP reply  : %s/%s version %s (%s->%s)", code, reason, version, c$id$resp_h, c$id$orig_h);
29    }
30
31event zeek_init()
32    {
33
34    Option::set_change_handler("Debug::http_print_debugging", function(id: string, new_value: bool): bool {
35        print id, new_value;
36        if ( new_value )
37            enable_event_group("http-print-debugging");
38        else
39            disable_event_group("http-print-debugging");
40
41        return new_value;
42    });
43
44    # Trigger the change handler, once.
45    Config::set_value("Debug::http_print_debugging", F);
46    }

Whenever the option Debug::http_print_debugging is set to T, enable_event_group is invoked to ensure the http-print-debugging group is enabled. Conversely, when the option is set to F, disable_event_group disables all event handlers in the group http-print-debugging.

The very same behavior can be achieved by testing the Debug::http_print_debugging option within the respective event handlers using and if statement and early return. In contrast, event groups work in a more declarative way. Further, when disabling event handlers via event groups, their implementation is never invoked and is therefore a more performant way to short-circuit execution.

Module Based Event Group

Besides attribute based event groups, Zeek supports implicit module based event groups. Event and hook handlers are part of an event group that represents the module in which they were implemented. The builtin functions disable_module_events and enable_module_events can be used to disable and enable all event and hook handlers within modules.

An interesting idea here is to implement enabling and disabling of Zeek packages at runtime. For example, the CommunityID package implements its functionality in the CommunityID and CommunityID::Notice modules. The JA3 package implements its event handlers in the JA3 and JA3_Server modules.

event_groups_module_01.zeek
 1@load base/frameworks/config
 2
 3@load ja3
 4@load zeek-community-id
 5@load zeek-community-id/notice
 6
 7redef Config::config_files += { "./myconfig.dat" };
 8
 9module Packages;
10
11export {
12    # All packages off by default.
13    option community_id_enabled = F;
14    option ja3_enabled = F;
15}
16
17event zeek_init()
18    {
19    local package_change_handler = function(id: string, new_value: bool): bool {
20        local modules: set[string];
21
22        if ( id == "Packages::community_id_enabled" )
23            modules = ["CommunityID", "CommunityID::Notice"];
24        else if ( id == "Packages::ja3_enabled" )
25            modules = ["JA3", "JA3_Server"];
26        else
27            {
28            Reporter::error(fmt("Unknown option: %s", id));
29            return new_value;
30            }
31
32        # Toggle the modules.
33        for ( m in modules )
34            if ( new_value )
35                enable_module_events(m);
36            else
37                disable_module_events(m);
38
39        return new_value;
40    };
41
42    Option::set_change_handler("Packages::community_id_enabled", package_change_handler);
43    Option::set_change_handler("Packages::ja3_enabled", package_change_handler);
44
45    Config::set_value("Packages::community_id_enabled", F);
46    Config::set_value("Packages::ja3_enabled", F);
47    }

The above script implements toggling of Zeek package functionality at runtime via the options Packages::ja3_enabled and Packages::community_id_enabled. While for most packages and deployments a Zeek restart is an acceptable way to disable or enable a package - generally this isn’t a regular operation - module based event groups provide a powerful primitive to support runtime toggling of scripting functionality.

Note

A caveat around the above example: The JA3 package builds up state based on the ssl_extension events from SSL ClientHello and ServerHello messages. When the JA3 event handlers are enabled right during processing of these events, the resulting JA3 hash might be based on a partial list of extensions only.

While all ssl_extension handlers are processed jointly for each instance of the event, generally state build up and dyanamic enabling and disabling may need careful consideration.