The PACF provides a transparent interface for dynamically changing both the input traffic that Zeek sees (packet aquisition) and the traffic that the network forwards (packet control). Scripts cann functions provided by the framework when they want to, e.g., block a host or start sampling a flow’s traffic. The framework than triggers that action in whatever way the user’s environment supports.
The user configure that support at startup time by registering instances of equipment-specific plugins that implement the corresponding functionality. That could be, e.g., an OpenFlow plugin that knows how to talk to the forwarding router; a load-balancer plugin that adapts the hardware frontend feeding traffic to a cluster; or even a BPF plugin that implements actions by adapting Zeek’s BPF filter. A user can instantiate multiple plugins simultaneously; for every requested action, they’ll be asked successively if they supported the desired operation; the first which does, gets to execute it. That gives us the flexibility to push individual actions out into the network as far as possible, while potentially falling back to less efficient schemes where no other support is available.
Note
This proposal currently focuses on controlling external entities to change what Zeek sees and what the network does. Another aspect of the aquistion part is adding more packet sources to Zeek than just libpcap, including better control over low-level features such as on-NIC load-balancing. This could in principle go in here as well eventually, but might be best planned independently for now at least.
The following types and functions options are exposed to other scripts to transparently manipulate packet aquisition and control:
module PACF; # Type of Entity we're defining an action for. type EntityType: enum { CONNECTION, ADDRESS, }; # An entity instance we're defining an action for. type Entity: record { ty: EntityType: conn: conn_id &optional; # Used with CONNECTION. ip: addr &optional; # Used with ADDRESS. }; # A type of trigger we define for an entity. type TriggerType: record { BYTES, PACKETS, }; # An instance of a trigger we're defining for an entity. type Trigger: record { ty: TriggerType; num: count &optional; # Used with BYTES and PACKETS. }; # Remove entity from traffic Zeek is analyzing. global monitorDrop(e: Entity, timeout: interval = 0 secs) : bool; # Begin sampling traffic from entity in Zeek's input. global monitorSample(e: Entity, factor: double, timeout: interval = 0 secs) : bool; # Trigger callback when entity exceeds trigger condition. global monitorTrigger(e: Entity, trigger: Trigger, timeout: interval = 0 secs) : bool; # Remove all monitoring constraints on entity. global monitorReset(e: Entity); # Drop entity on forwarding path. global enforceDrop(e: Entity, timeout: interval = 0 secs) : bool; # Enforce a rate limit in entity on forwarding path. global enforceLimit(e: Entity, rate: double, timeout: interval = 0 secs) : bool; # Redirect entity to elsewhere on forwarding path. global enforceRedirect(e: rate: double, target: string, timeout: interval = 0 secs) : bool; # Remove all forwarding path changes installed for entityt global encorceReset(e: Entity); # Flush all state. global clear();
A plugin is defined by specifying a subset of functions implementing any of the actions that the public API offers:
module PACF: # Definitioan of a plugin. A plugin implements just the functions it supports. # Each function returns true if it could execute the corresponding action (and # hence no other plugin will be asked to do so); and false if it couldn't # support it (and hence we'll check further plugins down the chain). Not # defining a function is equivalent to always returning false. type Plugin: record { # Returns a descriptive name of the plugin instance, suitable for use in # logging messages. Note that this function is not optional. name: function() : string; # One-time initialization functionl called when plugin gets registered, and # befire any ther methods are called. init: function() &optional; # One-time finalization function called when a plugin is shutdown; no # further functions will be called afterwords. done: function() &optional; # A transaction groups a number of operations. The plugin can queue them # internally and postpone putting them into effect until committed. Most # importantly, this allows to flush all state and then rebuild it # incrementally without intermittently incosistent state. beginTransaction: function() &optional; commitTransaction: function() &optional; # Operations. monitorDropInstall: function(e: Entity) : bool &optional; monitorDropRemove: function(e: Entity) : bool &optional; monitorSampleInstall: function(e: Entity, factor: double) : bool &optional; monitorSampleRemove: function(e: Entity) : bool &optional; monitorTriggerInstall: function(e: Entity, trigger: Trigger) : bool &optional; monitorTriggerRemove: function(e: Entity, trigger: Trigger) : bool &optional; monitorClearAll() : bool &optional; controlDropInstall: function(e: Entity) : bool &optional; controlDropRemove: function(e: Entity) : bool &optional; controlLimitInstall: function(e: Entity, limit: double) : bool &optional; controlLimitRemove: function(e: Entity) : bool &optional; controlRedirectInstall: function(e: Entity, target: string) : bool &optional; controlRedirectRemove: function(e: Entity) : bool &optional; controlClearAll() : bool &optional; # Table for the plugin to store instance-specific configuration information. config: table[string] of string; } # Register a plugin. The higher the priority, the earlier it will be checked # whether it supports an operation, relative to other plugins. global registerPlugin(p: Plugin, priority: int);
Say we have plugins for OpenFlow and BPF. Both can control the input Zeek sees; the former by talking to a frontend switch, the latter by installing BPF filters on the fly. They are defined like this:
module PACF; global OpenFlow: Plugin = [ name = [....] monitorDropInstall = ... monitorDropRemove = ... monitorSampleInstall = ... monitorSampleRemove = ... ... }; global BPF: Plugin = [ name = [....] monitorDropInstall = ... monitorDropRemove = ... ... };
Both plugins also provide instantiation functions:
PACF::openflow_create(switch: addr); PACF::bpf_create(iface: string);
These create an instance of the corresponding plugin class and use the configuration table to store their arguments and whatever else they need.
Now say in our network we want to use OpenFlow whenever it support an operation, and fall-back to BPF otherwise:
event bro_init() { local openflow = PACF::openflow_create(192.168.1.1); local bpf = PACF::bpf_create("eth1); PACF::registerPlugin(openflow, 2); # High priority. PACF::registerPlugin(bpf, 1); # Low priority. }
Now other scripts can adjust what Zeek sees:
# Stop analyzing 10.0.0.1 for the next 5 minutes. local e = Entity($ty=PACF::ADDRESS, $ip=10.0.0.1); PACF::monitorDrop(e, 5mins);
- Zeek does all the state management, like watching the timeout and invoking the corresponding Remove functions once expired. It keeps the current state persistently on disk. At startup, it replays it to the plugins (that’s what the "transactions" are for). That way one also savely change the plugin instances; the current state will be transparently played back to the new setup (though one might have to remove elements no longer available manually).
- Ideally a plugin instance should not keep any state at all, however I’m not sure that always realistic.
- Not quite sure what should happen at Zeek termination with the current control rules. Leave it place? Remove? Provide an option?
- The arguments to the various actions need some more fleshing out. For example, it’s not clear whast redirect should take.
- We probably need a more sophisticated structure for the plugin instances that a sorted list. We could do a tree where an operation is always passed on to all childs. For example, we could have multiple BPF plugin instances, one for each load-balanced interface; they’d all get a drop operation so that all can suppress the corresponding traffic.
- Can we get control for load-balancing into the API?
© 2014 The Bro Project.