##! Acld plugin for the netcontrol framework. module NetControl; @load ../main @load ../plugin @load base/frameworks/broker @ifdef ( Broker::__enable ) export { type AclRule : record { command: string; cookie: count; arg: string; comment: string &optional; }; type AcldConfig: record { ## The acld topic to send events to. acld_topic: string; ## Broker host to connect to. acld_host: addr; ## Broker port to connect to. acld_port: port; ## Do we accept rules for the monitor path? Default false. monitor: bool &default=F; ## Do we accept rules for the forward path? Default true. forward: bool &default=T; ## Predicate that is called on rule insertion or removal. ## ## p: Current plugin state. ## ## r: The rule to be inserted or removed. ## ## Returns: T if the rule can be handled by the current backend, F otherwise. check_pred: function(p: PluginState, r: Rule): bool &optional; }; ## Instantiates the acld plugin. global create_acld: function(config: AcldConfig) : PluginState; redef record PluginState += { acld_config: AcldConfig &optional; ## The ID of this acld instance - for the mapping to PluginStates. acld_id: count &optional; }; ## Hook that is called after a rule is converted to an acld rule. ## The hook may modify the rule before it is sent to acld. ## Setting the acld command to F will cause the rule to be rejected ## by the plugin. ## ## p: Current plugin state. ## ## r: The rule to be inserted or removed. ## ## ar: The acld rule to be inserted or removed. global NetControl::acld_rule_policy: hook(p: PluginState, r: Rule, ar: AclRule); ## Events that are sent from us to Broker. global acld_add_rule: event(id: count, r: Rule, ar: AclRule); global acld_remove_rule: event(id: count, r: Rule, ar: AclRule); ## Events that are sent from Broker to us. global acld_rule_added: event(id: count, r: Rule, msg: string); global acld_rule_removed: event(id: count, r: Rule, msg: string); global acld_rule_exists: event(id: count, r: Rule, msg: string); global acld_rule_error: event(id: count, r: Rule, msg: string); } global netcontrol_acld_peers: table[port, string] of PluginState; global netcontrol_acld_topics: set[string] = set(); global netcontrol_acld_id: table[count] of PluginState = table(); global netcontrol_acld_current_id: count = 0; const acld_add_to_remove: table[string] of string = { ["drop"] = "restore", ["addwhitelist"] = "remwhitelist", ["blockhosthost"] = "restorehosthost", ["droptcpport"] = "restoretcpport", ["dropudpport"] = "restoreudpport", ["droptcpdsthostport"] ="restoretcpdsthostport", ["dropudpdsthostport"] ="restoreudpdsthostport", ["permittcpdsthostport"] ="unpermittcpdsthostport", ["permitudpdsthostport"] ="unpermitudpdsthostport", ["nullzero"] ="nonullzero" }; event NetControl::acld_rule_added(id: count, r: Rule, msg: string) { if ( id !in netcontrol_acld_id ) { Reporter::error(fmt("NetControl acld plugin with id %d not found, aborting", id)); return; } local p = netcontrol_acld_id[id]; event NetControl::rule_added(r, p, msg); } event NetControl::acld_rule_exists(id: count, r: Rule, msg: string) { if ( id !in netcontrol_acld_id ) { Reporter::error(fmt("NetControl acld plugin with id %d not found, aborting", id)); return; } local p = netcontrol_acld_id[id]; event NetControl::rule_exists(r, p, msg); } event NetControl::acld_rule_removed(id: count, r: Rule, msg: string) { if ( id !in netcontrol_acld_id ) { Reporter::error(fmt("NetControl acld plugin with id %d not found, aborting", id)); return; } local p = netcontrol_acld_id[id]; event NetControl::rule_removed(r, p, msg); } event NetControl::acld_rule_error(id: count, r: Rule, msg: string) { if ( id !in netcontrol_acld_id ) { Reporter::error(fmt("NetControl acld plugin with id %d not found, aborting", id)); return; } local p = netcontrol_acld_id[id]; event NetControl::rule_error(r, p, msg); } function acld_name(p: PluginState) : string { return fmt("Acld-%s", p$acld_config$acld_topic); } # check that subnet specifies an addr function check_sn(sn: subnet) : bool { if ( is_v4_subnet(sn) && subnet_width(sn) == 32 ) return T; if ( is_v6_subnet(sn) && subnet_width(sn) == 128 ) return T; Reporter::error(fmt("Acld: rule_to_acl_rule was given a subnet that does not specify a distinct address where needed - %s", sn)); return F; } function rule_to_acl_rule(p: PluginState, r: Rule) : AclRule { local e = r$entity; local command: string = ""; local arg: string = ""; if ( e$ty == ADDRESS ) { if ( r$ty == DROP ) command = "drop"; else if ( r$ty == WHITELIST ) command = "addwhitelist"; arg = cat(e$ip); } else if ( e$ty == FLOW ) { local f = e$flow; if ( ( ! f?$src_h ) && ( ! f?$src_p ) && f?$dst_h && f?$dst_p && ( ! f?$src_m ) && ( ! f?$dst_m ) ) { if ( !check_sn(f$dst_h) ) command = ""; # invalid addr, do nothing else if ( is_tcp_port(f$dst_p) && r$ty == DROP ) command = "droptcpdsthostport"; else if ( is_tcp_port(f$dst_p) && r$ty == WHITELIST ) command = "permittcpdsthostport"; else if ( is_udp_port(f$dst_p) && r$ty == DROP) command = "dropucpdsthostport"; else if ( is_udp_port(f$dst_p) && r$ty == WHITELIST) command = "permitucpdsthostport"; arg = fmt("%s %d", subnet_to_addr(f$dst_h), f$dst_p); } else if ( f?$src_h && ( ! f?$src_p ) && f?$dst_h && ( ! f?$dst_p ) && ( ! f?$src_m ) && ( ! f?$dst_m ) ) { if ( !check_sn(f$src_h) || !check_sn(f$dst_h) ) command = ""; else if ( r$ty == DROP ) command = "blockhosthost"; arg = fmt("%s %s", subnet_to_addr(f$src_h), subnet_to_addr(f$dst_h)); } else if ( ( ! f?$src_h ) && ( ! f?$src_p ) && ( ! f?$dst_h ) && f?$dst_p && ( ! f?$src_m ) && ( ! f?$dst_m ) ) { if ( is_tcp_port(f$dst_p) && r$ty == DROP ) command = "droptcpport"; else if ( is_udp_port(f$dst_p) && r$ty == DROP ) command = "dropudpport"; arg = fmt("%d", f$dst_p); } } local ar = AclRule($command=command, $cookie=r$cid, $arg=arg); if ( r?$location ) ar$comment = r$location; hook NetControl::acld_rule_policy(p, r, ar); return ar; } function acld_check_rule(p: PluginState, r: Rule) : bool { local c = p$acld_config; if ( p$acld_config?$check_pred ) return p$acld_config$check_pred(p, r); if ( r$target == MONITOR && c$monitor ) return T; if ( r$target == FORWARD && c$forward ) return T; return F; } function acld_add_rule_fun(p: PluginState, r: Rule) : bool { if ( ! acld_check_rule(p, r) ) return F; local ar = rule_to_acl_rule(p, r); if ( ar$command == "" ) return F; Broker::send_event(p$acld_config$acld_topic, Broker::event_args(acld_add_rule, p$acld_id, r, ar)); return T; } function acld_remove_rule_fun(p: PluginState, r: Rule, reason: string) : bool { if ( ! acld_check_rule(p, r) ) return F; local ar = rule_to_acl_rule(p, r); if ( ar$command in acld_add_to_remove ) ar$command = acld_add_to_remove[ar$command]; else return F; if ( reason != "" ) { if ( ar?$comment ) ar$comment = fmt("%s (%s)", reason, ar$comment); else ar$comment = reason; } Broker::send_event(p$acld_config$acld_topic, Broker::event_args(acld_remove_rule, p$acld_id, r, ar)); return T; } function acld_init(p: PluginState) { Broker::enable(); Broker::connect(cat(p$acld_config$acld_host), p$acld_config$acld_port, 1sec); Broker::subscribe_to_events(p$acld_config$acld_topic); } event Broker::outgoing_connection_established(peer_address: string, peer_port: port, peer_name: string) { if ( [peer_port, peer_address] !in netcontrol_acld_peers ) # ok, this one was none of ours... return; local p = netcontrol_acld_peers[peer_port, peer_address]; plugin_activated(p); } global acld_plugin = Plugin( $name=acld_name, $can_expire = F, $add_rule = acld_add_rule_fun, $remove_rule = acld_remove_rule_fun, $init = acld_init ); function create_acld(config: AcldConfig) : PluginState { if ( config$acld_topic in netcontrol_acld_topics ) Reporter::warning(fmt("Topic %s was added to NetControl acld plugin twice. Possible duplication of commands", config$acld_topic)); else add netcontrol_acld_topics[config$acld_topic]; local host = cat(config$acld_host); local p: PluginState = [$acld_config=config, $plugin=acld_plugin, $acld_id=netcontrol_acld_current_id]; if ( [config$acld_port, host] in netcontrol_acld_peers ) Reporter::warning(fmt("Peer %s:%s was added to NetControl acld plugin twice.", host, config$acld_port)); else netcontrol_acld_peers[config$acld_port, host] = p; netcontrol_acld_id[netcontrol_acld_current_id] = p; ++netcontrol_acld_current_id; return p; } @endif