##! Bro's NetControl framework. ##! ##! This plugin-based framework allows to control the traffic that Bro monitors ##! as well as, if having access to the forwarding path, the traffic the network ##! forwards. By default, the framework lets everything through, to both Bro ##! itself as well as on the network. Scripts can then add rules to impose ##! restrictions on entities, such as specific connections or IP addresses. ##! ##! This framework has two APIs: a high-level and low-level. The high-level API ##! provides convenience functions for a set of common operations. The ##! low-level API provides full flexibility. module NetControl; @load ./plugin @load ./types export { ## The framework's logging stream identifier. redef enum Log::ID += { LOG }; # ### # ### Generic functions and events. # ### ## Activates a plugin. ## ## p: The plugin to activate. ## ## priority: The higher the priority, the earlier this plugin will be checked ## whether it supports an operation, relative to other plugins. global activate: function(p: PluginState, priority: int); ## Event that is used to initialize plugins. Place all plugin initialization ## related functionality in this event. global NetControl::init: event(); ## Event that is raised once all plugins activated in ``NetControl::init`` ## have finished their initialization. global NetControl::init_done: event(); # ### # ### High-level API. # ### # ### Note - other high level primitives are in catch-and-release.bro, shunt.bro and # ### drop.bro ## Allows all traffic involving a specific IP address to be forwarded. ## ## a: The address to be whitelisted. ## ## t: How long to whitelist it, with 0 being indefinitely. ## ## location: An optional string describing whitelist was triddered. ## ## Returns: The id of the inserted rule on success and zero on failure. global whitelist_address: function(a: addr, t: interval, location: string &default="") : string; ## Allows all traffic involving a specific IP subnet to be forwarded. ## ## s: The subnet to be whitelisted. ## ## t: How long to whitelist it, with 0 being indefinitely. ## ## location: An optional string describing whitelist was triddered. ## ## Returns: The id of the inserted rule on success and zero on failure. global whitelist_subnet: function(s: subnet, t: interval, location: string &default="") : string; ## Redirects a uni-directional flow to another port. ## ## f: The flow to redirect. ## ## out_port: Port to redirect the flow to. ## ## t: How long to leave the redirect in place, with 0 being indefinitely. ## ## location: An optional string describing where the redirect was triggered. ## ## Returns: The id of the inserted rule on success and zero on failure. global redirect_flow: function(f: flow_id, out_port: count, t: interval, location: string &default="") : string; ## Quarantines a host. This requires a special quarantine server, which runs a HTTP server explaining ## the quarantine and a DNS server which resolves all requests to the quarantine server. DNS queries ## from the host to the network DNS server will be rewritten and will be sent to the quarantine server ## instead. Only http communication infected to quarantinehost is allowed. All other network communication ## is blocked. ## ## infected: the host to quarantine. ## ## dns: the network dns server. ## ## quarantine: the quarantine server running a dns and a web server. ## ## t: how long to leave the quarantine in place. ## ## Returns: Vector of inserted rules on success, empty list on failure. global quarantine_host: function(infected: addr, dns: addr, quarantine: addr, t: interval, location: string &default="") : vector of string; ## Flushes all state by calling :bro:see:`NetControl::remove_rule` on all currently active rules. global clear: function(); # ### # ### Low-level API. # ### ###### Manipulation of rules. ## Installs a rule. ## ## r: The rule to install. ## ## Returns: If successful, returns an ID string unique to the rule that can ## later be used to refer to it. If unsuccessful, returns an empty ## string. The ID is also assigned to ``r$id``. Note that ## "successful" means "a plugin knew how to handle the rule", it ## doesn't necessarily mean that it was indeed successfully put in ## place, because that might happen asynchronously and thus fail ## only later. global add_rule: function(r: Rule) : string; ## Removes a rule. ## ## id: The rule to remove, specified as the ID returned by :bro:see:`NetControl::add_rule`. ## ## reason: Optional string argument giving information on why the rule was removed. ## ## Returns: True if successful, the relevant plugin indicated that it knew ## how to handle the removal. Note that again "success" means the ## plugin accepted the removal. It might still fail to put it ## into effect, as that might happen asynchronously and thus go ## wrong at that point. global remove_rule: function(id: string, reason: string &default="") : bool; ## Deletes a rule without removing it from the backends to which it has been ## added before. This means that no messages will be sent to the switches to which ## the rule has been added; if it is not removed from them by a separate mechanism, ## it will stay installed and not be removed later. ## ## id: The rule to delete, specified as the ID returned by :bro:see:`NetControl::add_rule`. ## ## reason: Optional string argument giving information on why the rule was deleted. ## ## Returns: True if removal is successful, or sent to manager. ## False if the rule could not be found. global delete_rule: function(id: string, reason: string &default="") : bool; ## Searches all rules affecting a certain IP address. ## ## This function works on both the manager and workers of a cluster. Note that on ## the worker, the internal rule variables (starting with _) will not reflect the ## current state. ## ## ip: The ip address to search for. ## ## Returns: vector of all rules affecting the IP address. global find_rules_addr: function(ip: addr) : vector of Rule; ## Searches all rules affecting a certain subnet. ## ## A rule affects a subnet, if it covers the whole subnet. Note especially that ## this function will not reveal all rules that are covered by a subnet. ## ## For example, a search for 192.168.17.0/8 will reveal a rule that exists for ## 192.168.0.0/16, since this rule affects the subnet. However, it will not reveal ## a more specific rule for 192.168.17.1/32, which does not directy affect the whole ## subnet. ## ## This function works on both the manager and workers of a cluster. Note that on ## the worker, the internal rule variables (starting with _) will not reflect the ## current state. ## ## sn: The subnet to search for. ## ## Returns: vector of all rules affecting the subnet. global find_rules_subnet: function(sn: subnet) : vector of Rule; ###### Asynchronous feedback on rules. ## Confirms that a rule was put in place by a plugin. ## ## r: The rule now in place. ## ## p: The state for the plugin that put it into place. ## ## msg: An optional informational message by the plugin. global rule_added: event(r: Rule, p: PluginState, msg: string &default=""); ## Signals that a rule that was supposed to be put in place was already ## existing at the specified plugin. Rules that already have been existing ## continue to be tracked like normal, but no timeout calls will be sent ## to the specified plugins. Removal of the rule from the hardware can ## still be forced by manually issuing a remove_rule call. ## ## r: The rule that was already in place. ## ## p: The plugin that reported that the rule already was in place. ## ## msg: An optional informational message by the plugin. global rule_exists: event(r: Rule, p: PluginState, msg: string &default=""); ## Reports that a plugin reports a rule was removed due to a ## remove_rule function call. ## ## r: The rule now removed. ## ## p: The state for the plugin that had the rule in place and now ## removed it. ## ## msg: An optional informational message by the plugin. global rule_removed: event(r: Rule, p: PluginState, msg: string &default=""); ## Reports that a rule was removed from a plugin due to a timeout. ## ## r: The rule now removed. ## ## i: Additional flow information, if supported by the protocol. ## ## p: The state for the plugin that had the rule in place and now ## removed it. ## ## msg: An optional informational message by the plugin. global rule_timeout: event(r: Rule, i: FlowInfo, p: PluginState); ## Reports an error when operating on a rule. ## ## r: The rule that encountered an error. ## ## p: The state for the plugin that reported the error. ## ## msg: An optional informational message by the plugin. global rule_error: event(r: Rule, p: PluginState, msg: string &default=""); ## This event is raised when a new rule is created by the NetControl framework ## due to a call to add_rule. From this moment, until the rule_destroyed event ## is raised, the rule is tracked internally by the NetControl framework. ## ## Note that this event does not mean that a rule was successfully added by ## any backend; it just means that the rule has been accepted and addition ## to the specified backend is queued. To get information when rules are actually ## installed by the hardware, use the rule_added, rule_exists, rule_removed, rule_timeout ## and rule_error events. global rule_new: event(r: Rule); ## This event is raised when a rule is deleted from the NetControl framework, ## because it is no longer in use. This can be caused by the fact that a rule ## was removed by all plugins to which it was added, by the fact that it timed out ## or due to rule errors. ## ## To get the cause of a rule remove, catch the rule_removed, rule_timeout and ## rule_error events. global rule_destroyed: event(r: Rule); ## Hook that allows the modification of rules passed to add_rule before they ## are passed on to the plugins. If one of the hooks uses break, the rule is ## ignored and not passed on to any plugin. ## ## r: The rule to be added. global NetControl::rule_policy: hook(r: Rule); ##### Plugin functions ## Function called by plugins once they finished their activation. After all ## plugins defined in bro_init finished to activate, rules will start to be sent ## to the plugins. Rules that scripts try to set before the backends are ready ## will be discarded. global plugin_activated: function(p: PluginState); ## Type of an entry in the NetControl log. type InfoCategory: enum { ## A log entry reflecting a framework message. MESSAGE, ## A log entry reflecting a framework message. ERROR, ## A log entry about a rule. RULE }; ## State of an entry in the NetControl log. type InfoState: enum { REQUESTED, ##< The request to add/remove a rule was sent to the respective backend. SUCCEEDED, ##< A rule was successfully added by a backend. EXISTS, ##< A backend reported that a rule was already existing. FAILED, ##< A rule addition failed. REMOVED, ##< A rule was successfully removed by a backend. TIMEOUT, ##< A rule timeout was triggered by the NetControl framework or a backend. }; ## The record type defining the column fields of the NetControl log. type Info: record { ## Time at which the recorded activity occurred. ts: time &log; ## ID of the rule; unique during each Bro run. rule_id: string &log &optional; ## Type of the log entry. category: InfoCategory &log &optional; ## The command the log entry is about. cmd: string &log &optional; ## State the log entry reflects. state: InfoState &log &optional; ## String describing an action the entry is about. action: string &log &optional; ## The target type of the action. target: TargetType &log &optional; ## Type of the entity the log entry is about. entity_type: string &log &optional; ## String describing the entity the log entry is about. entity: string &log &optional; ## String describing the optional modification of the entry (e.h. redirect) mod: string &log &optional; ## String with an additional message. msg: string &log &optional; ## Number describing the priority of the log entry. priority: int &log &optional; ## Expiry time of the log entry. expire: interval &log &optional; ## Location where the underlying action was triggered. location: string &log &optional; ## Plugin triggering the log entry. plugin: string &log &optional; }; ## Event that can be handled to access the :bro:type:`NetControl::Info` ## record as it is sent on to the logging framework. global log_netcontrol: event(rec: Info); } redef record Rule += { ## Internally set to the plugins handling the rule. _plugin_ids: set[count] &default=count_set(); ## Internally set to the plugins on which the rule is currently active. _active_plugin_ids: set[count] &default=count_set(); ## Internally set to plugins where the rule should not be removed upon timeout. _no_expire_plugins: set[count] &default=count_set(); ## Track if the rule was added successfully by all responsible plugins. _added: bool &default=F; }; # Variable tracking the state of plugin activation. Once all plugins that # have been added in bro_init are activated, this will switch to T and # the event NetControl::init_done will be raised. global plugins_active: bool = F; # Set to true at the end of bro_init (with very low priority). # Used to track when plugin activation could potentially be finished global bro_init_done: bool = F; # The counters that are used to generate the rule and plugin IDs global rule_counter: count = 1; global plugin_counter: count = 1; # List of the currently active plugins global plugins: vector of PluginState; global plugin_ids: table[count] of PluginState; # These tables hold information about rules. global rules: table[string] of Rule; # Rules indexed by id and cid # All rules that apply to a certain subnet/IP address. global rules_by_subnets: table[subnet] of set[string]; # Rules pertaining to a specific entity. # There always only can be one rule of each type for one entity. global rule_entities: table[Entity, RuleType] of Rule; event bro_init() &priority=5 { Log::create_stream(NetControl::LOG, [$columns=Info, $ev=log_netcontrol, $path="netcontrol"]); } function entity_to_info(info: Info, e: Entity) { info$entity_type = fmt("%s", e$ty); switch ( e$ty ) { case ADDRESS: info$entity = fmt("%s", e$ip); break; case CONNECTION: info$entity = fmt("%s/%d<->%s/%d", e$conn$orig_h, e$conn$orig_p, e$conn$resp_h, e$conn$resp_p); break; case FLOW: local ffrom_ip = "*"; local ffrom_port = "*"; local fto_ip = "*"; local fto_port = "*"; local ffrom_mac = "*"; local fto_mac = "*"; if ( e$flow?$src_h ) ffrom_ip = cat(e$flow$src_h); if ( e$flow?$src_p ) ffrom_port = fmt("%d", e$flow$src_p); if ( e$flow?$dst_h ) fto_ip = cat(e$flow$dst_h); if ( e$flow?$dst_p ) fto_port = fmt("%d", e$flow$dst_p); info$entity = fmt("%s/%s->%s/%s", ffrom_ip, ffrom_port, fto_ip, fto_port); if ( e$flow?$src_m || e$flow?$dst_m ) { if ( e$flow?$src_m ) ffrom_mac = e$flow$src_m; if ( e$flow?$dst_m ) fto_mac = e$flow$dst_m; info$entity = fmt("%s (%s->%s)", info$entity, ffrom_mac, fto_mac); } break; case MAC: info$entity = e$mac; break; default: info$entity = ""; break; } } function rule_to_info(info: Info, r: Rule) { info$action = fmt("%s", r$ty); info$target = r$target; info$rule_id = r$id; info$expire = r$expire; info$priority = r$priority; if ( r?$location && r$location != "" ) info$location = r$location; if ( r$ty == REDIRECT ) info$mod = fmt("-> %d", r$out_port); if ( r$ty == MODIFY ) { local mfrom_ip = "_"; local mfrom_port = "_"; local mto_ip = "_"; local mto_port = "_"; local mfrom_mac = "_"; local mto_mac = "_"; if ( r$mod?$src_h ) mfrom_ip = cat(r$mod$src_h); if ( r$mod?$src_p ) mfrom_port = fmt("%d", r$mod$src_p); if ( r$mod?$dst_h ) mto_ip = cat(r$mod$dst_h); if ( r$mod?$dst_p ) mto_port = fmt("%d", r$mod$dst_p); if ( r$mod?$src_m ) mfrom_mac = r$mod$src_m; if ( r$mod?$dst_m ) mto_mac = r$mod$dst_m; info$mod = fmt("Src: %s/%s (%s) Dst: %s/%s (%s)", mfrom_ip, mfrom_port, mfrom_mac, mto_ip, mto_port, mto_mac); if ( r$mod?$redirect_port ) info$mod = fmt("%s -> %d", info$mod, r$mod$redirect_port); } entity_to_info(info, r$entity); } function log_msg(msg: string, p: PluginState) { Log::write(LOG, [$ts=network_time(), $category=MESSAGE, $msg=msg, $plugin=p$plugin$name(p)]); } function log_error(msg: string, p: PluginState) { Log::write(LOG, [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)]); } function log_msg_no_plugin(msg: string) { Log::write(LOG, [$ts=network_time(), $category=MESSAGE, $msg=msg]); } function log_rule(r: Rule, cmd: string, state: InfoState, p: PluginState, msg: string &default="") { local info: Info = [$ts=network_time()]; info$category = RULE; info$cmd = cmd; info$state = state; info$plugin = p$plugin$name(p); if ( msg != "" ) info$msg = msg; rule_to_info(info, r); Log::write(LOG, info); } function log_rule_error(r: Rule, msg: string, p: PluginState) { local info: Info = [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)]; rule_to_info(info, r); Log::write(LOG, info); } function log_rule_no_plugin(r: Rule, state: InfoState, msg: string) { local info: Info = [$ts=network_time()]; info$category = RULE; info$state = state; info$msg = msg; rule_to_info(info, r); Log::write(LOG, info); } function whitelist_address(a: addr, t: interval, location: string &default="") : string { local e: Entity = [$ty=ADDRESS, $ip=addr_to_subnet(a)]; local r: Rule = [$ty=WHITELIST, $priority=whitelist_priority, $target=FORWARD, $entity=e, $expire=t, $location=location]; return add_rule(r); } function whitelist_subnet(s: subnet, t: interval, location: string &default="") : string { local e: Entity = [$ty=ADDRESS, $ip=s]; local r: Rule = [$ty=WHITELIST, $priority=whitelist_priority, $target=FORWARD, $entity=e, $expire=t, $location=location]; return add_rule(r); } function redirect_flow(f: flow_id, out_port: count, t: interval, location: string &default="") : string { local flow = NetControl::Flow( $src_h=addr_to_subnet(f$src_h), $src_p=f$src_p, $dst_h=addr_to_subnet(f$dst_h), $dst_p=f$dst_p ); local e: Entity = [$ty=FLOW, $flow=flow]; local r: Rule = [$ty=REDIRECT, $target=FORWARD, $entity=e, $expire=t, $location=location, $out_port=out_port]; return add_rule(r); } function quarantine_host(infected: addr, dns: addr, quarantine: addr, t: interval, location: string &default="") : vector of string { local orules: vector of string = vector(); local edrop: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected))]; local rdrop: Rule = [$ty=DROP, $target=FORWARD, $entity=edrop, $expire=t, $location=location]; orules[|orules|] = add_rule(rdrop); local todnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(dns), $dst_p=53/udp)]; local todnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=todnse, $expire=t, $location=location, $mod=FlowMod($dst_h=quarantine), $priority=+5); orules[|orules|] = add_rule(todnsr); local fromdnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(dns), $src_p=53/udp, $dst_h=addr_to_subnet(infected))]; local fromdnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=fromdnse, $expire=t, $location=location, $mod=FlowMod($src_h=dns), $priority=+5); orules[|orules|] = add_rule(fromdnsr); local wle: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(quarantine), $dst_p=80/tcp)]; local wlr = Rule($ty=WHITELIST, $target=FORWARD, $entity=wle, $expire=t, $location=location, $priority=+5); orules[|orules|] = add_rule(wlr); return orules; } function check_plugins() { if ( plugins_active ) return; local all_active = T; for ( i in plugins ) { local p = plugins[i]; if ( p$_activated == F ) all_active = F; } if ( all_active ) { plugins_active = T; # Skip log message if there are no plugins if ( |plugins| > 0 ) log_msg_no_plugin("plugin initialization done"); event NetControl::init_done(); } } function plugin_activated(p: PluginState) { local id = p$_id; if ( id !in plugin_ids ) { log_error("unknown plugin activated", p); return; } # Suppress duplicate activation if ( plugin_ids[id]$_activated == T ) return; plugin_ids[id]$_activated = T; log_msg("activation finished", p); if ( bro_init_done ) check_plugins(); } event bro_init() &priority=-5 { event NetControl::init(); } event NetControl::init() &priority=-20 { bro_init_done = T; check_plugins(); if ( plugins_active == F ) log_msg_no_plugin("waiting for plugins to initialize"); } # Low-level functions that only runs on the manager (or standalone) Bro node. function activate_impl(p: PluginState, priority: int) { p$_priority = priority; plugins[|plugins|] = p; sort(plugins, function(p1: PluginState, p2: PluginState) : int { return p2$_priority - p1$_priority; }); plugin_ids[plugin_counter] = p; p$_id = plugin_counter; ++plugin_counter; # perform one-time initialization if ( p$plugin?$init ) { log_msg(fmt("activating plugin with priority %d", priority), p); p$plugin$init(p); } else { # no initialization necessary, mark plugin as active right away plugin_activated(p); } } function add_one_subnet_entry(s: subnet, r: Rule) { if ( ! check_subnet(s, rules_by_subnets) ) rules_by_subnets[s] = set(r$id); else add rules_by_subnets[s][r$id]; } function add_subnet_entry(rule: Rule) { local e = rule$entity; if ( e$ty == ADDRESS ) { add_one_subnet_entry(e$ip, rule); } else if ( e$ty == CONNECTION ) { add_one_subnet_entry(addr_to_subnet(e$conn$orig_h), rule); add_one_subnet_entry(addr_to_subnet(e$conn$resp_h), rule); } else if ( e$ty == FLOW ) { if ( e$flow?$src_h ) add_one_subnet_entry(e$flow$src_h, rule); if ( e$flow?$dst_h ) add_one_subnet_entry(e$flow$dst_h, rule); } } function remove_one_subnet_entry(s: subnet, r: Rule) { if ( ! check_subnet(s, rules_by_subnets) ) return; if ( r$id !in rules_by_subnets[s] ) return; delete rules_by_subnets[s][r$id]; if ( |rules_by_subnets[s]| == 0 ) delete rules_by_subnets[s]; } function remove_subnet_entry(rule: Rule) { local e = rule$entity; if ( e$ty == ADDRESS ) { remove_one_subnet_entry(e$ip, rule); } else if ( e$ty == CONNECTION ) { remove_one_subnet_entry(addr_to_subnet(e$conn$orig_h), rule); remove_one_subnet_entry(addr_to_subnet(e$conn$resp_h), rule); } else if ( e$ty == FLOW ) { if ( e$flow?$src_h ) remove_one_subnet_entry(e$flow$src_h, rule); if ( e$flow?$dst_h ) remove_one_subnet_entry(e$flow$dst_h, rule); } } function find_rules_subnet(sn: subnet) : vector of Rule { local ret: vector of Rule = vector(); local matches = matching_subnets(sn, rules_by_subnets); for ( m in matches ) { local sn_entry = matches[m]; local rule_ids = rules_by_subnets[sn_entry]; for ( rule_id in rules_by_subnets[sn_entry] ) { if ( rule_id in rules ) ret[|ret|] = rules[rule_id]; else Reporter::error("find_rules_subnet - internal data structure error, missing rule"); } } return ret; } function find_rules_addr(ip: addr) : vector of Rule { return find_rules_subnet(addr_to_subnet(ip)); } function add_rule_impl(rule: Rule) : string { if ( ! plugins_active ) { log_rule_no_plugin(rule, FAILED, "plugins not initialized yet"); return ""; } rule$cid = ++rule_counter; # numeric id that can be used by plugins for their rules. if ( ! rule?$id || rule$id == "" ) rule$id = cat(rule$cid); if ( ! hook NetControl::rule_policy(rule) ) return ""; if ( [rule$entity, rule$ty] in rule_entities ) { log_rule_no_plugin(rule, FAILED, "discarded duplicate insertion"); return ""; } local accepted = F; local priority: int = +0; for ( i in plugins ) { local p = plugins[i]; if ( p$_activated == F ) next; # in this case, rule was accepted by earlier plugin and this plugin has a lower # priority. Abort and do not send there... if ( accepted == T && p$_priority != priority ) break; if ( p$plugin$add_rule(p, rule) ) { accepted = T; priority = p$_priority; log_rule(rule, "ADD", REQUESTED, p); add rule$_plugin_ids[p$_id]; } } if ( accepted ) { rules[rule$id] = rule; rule_entities[rule$entity, rule$ty] = rule; add_subnet_entry(rule); event NetControl::rule_new(rule); return rule$id; } log_rule_no_plugin(rule, FAILED, "not supported"); return ""; } function rule_cleanup(r: Rule) { if ( |r$_active_plugin_ids| > 0 ) return; remove_subnet_entry(r); delete rule_entities[r$entity, r$ty]; delete rules[r$id]; event NetControl::rule_destroyed(r); } function delete_rule_impl(id: string, reason: string): bool { if ( id !in rules ) { Reporter::error(fmt("Rule %s does not exist in NetControl::delete_rule", id)); return F; } local rule = rules[id]; rule$_active_plugin_ids = set(); rule_cleanup(rule); if ( reason != "" ) log_rule_no_plugin(rule, REMOVED, fmt("delete_rule: %s", reason)); else log_rule_no_plugin(rule, REMOVED, "delete_rule"); return T; } function remove_rule_plugin(r: Rule, p: PluginState, reason: string &default=""): bool { local success = T; if ( ! p$plugin$remove_rule(p, r, reason) ) { # still continue and send to other plugins if ( reason != "" ) log_rule_error(r, fmt("remove failed (original reason: %s)", reason), p); else log_rule_error(r, "remove failed", p); success = F; } else { log_rule(r, "REMOVE", REQUESTED, p, reason); } return success; } function remove_rule_impl(id: string, reason: string) : bool { if ( id !in rules ) { Reporter::error(fmt("Rule %s does not exist in NetControl::remove_rule", id)); return F; } local r = rules[id]; local success = T; for ( plugin_id in r$_active_plugin_ids ) { local p = plugin_ids[plugin_id]; success = remove_rule_plugin(r, p, reason); } return success; } function rule_expire_impl(r: Rule, p: PluginState) &priority=-5 { # do not emit timeout events on shutdown if ( bro_is_terminating() ) return; if ( r$id !in rules ) # Removed already. return; local rule = rules[r$id]; if ( p$_id in rule$_no_expire_plugins ) { # in this case - don't log anything, just remove the plugin from the rule # and cleaup delete rule$_active_plugin_ids[p$_id]; delete rule$_no_expire_plugins[p$_id]; rule_cleanup(rule); } else event NetControl::rule_timeout(r, FlowInfo(), p); # timeout implementation will handle the removal } function rule_added_impl(r: Rule, p: PluginState, exists: bool, msg: string &default="") { if ( r$id !in rules ) { log_rule_error(r, "Addition of unknown rule", p); return; } # use our version to prevent operating on copies. local rule = rules[r$id]; if ( p$_id !in rule$_plugin_ids ) { log_rule_error(rule, "Rule added to non-responsible plugin", p); return; } # The rule was already existing on the backend. Mark this so we don't timeout # it on this backend. if ( exists ) { add rule$_no_expire_plugins[p$_id]; log_rule(r, "ADD", EXISTS, p, msg); } else log_rule(r, "ADD", SUCCEEDED, p, msg); add rule$_active_plugin_ids[p$_id]; if ( |rule$_plugin_ids| == |rule$_active_plugin_ids| ) { # rule was completely added. rule$_added = T; } } function rule_removed_impl(r: Rule, p: PluginState, msg: string &default="") { if ( r$id !in rules ) { log_rule_error(r, "Removal of non-existing rule", p); return; } # use our version to prevent operating on copies. local rule = rules[r$id]; if ( p$_id !in rule$_plugin_ids ) { log_rule_error(r, "Removed from non-assigned plugin", p); return; } if ( p$_id in rule$_active_plugin_ids ) { delete rule$_active_plugin_ids[p$_id]; } log_rule(rule, "REMOVE", SUCCEEDED, p, msg); rule_cleanup(rule); } function rule_timeout_impl(r: Rule, i: FlowInfo, p: PluginState) { if ( r$id !in rules ) { log_rule_error(r, "Timeout of non-existing rule", p); return; } local rule = rules[r$id]; local msg = ""; if ( i?$packet_count ) msg = fmt("Packets: %d", i$packet_count); if ( i?$byte_count ) { if ( msg != "" ) msg = msg + " "; msg = fmt("%sBytes: %s", msg, i$byte_count); } log_rule(rule, "EXPIRE", TIMEOUT, p, msg); if ( ! p$plugin$can_expire ) { # in this case, we actually have to delete the rule and the timeout # call just originated locally remove_rule_plugin(rule, p); return; } if ( p$_id !in rule$_plugin_ids ) { log_rule_error(r, "Timeout from non-assigned plugin", p); return; } if ( p$_id in rule$_active_plugin_ids ) { delete rule$_active_plugin_ids[p$_id]; } rule_cleanup(rule); } function rule_error_impl(r: Rule, p: PluginState, msg: string &default="") { if ( r$id !in rules ) { log_rule_error(r, "Error of non-existing rule", p); return; } local rule = rules[r$id]; log_rule_error(rule, msg, p); # Remove the plugin both from active and all plugins of the rule. If there # are no plugins left afterwards - delete it if ( p$_id !in rule$_plugin_ids ) { log_rule_error(r, "Error from non-assigned plugin", p); return; } if ( p$_id in rule$_active_plugin_ids ) { # error during removal. Let's pretend it worked. delete rule$_plugin_ids[p$_id]; delete rule$_active_plugin_ids[p$_id]; rule_cleanup(rule); } else { # error during insertion. Meh. If we are the only plugin, remove the rule again. # Otherwhise - keep it, minus us. delete rule$_plugin_ids[p$_id]; if ( |rule$_plugin_ids| == 0 ) { rule_cleanup(rule); } } } function clear() { for ( id in rules ) remove_rule(id); }