Merge remote-tracking branch 'origin/topic/johanna/netcontrol'

BIT-1550 #merged

* origin/topic/johanna/netcontrol: (72 commits)
  Update baselines and news
  Move prefixtable back to all IPv6 internal handling.
  NetControl: Add functions to search for rules affecting IPs/subnets
  Add check_subnet bif that allows exact membership test for subnet tables.
  Rewrite internal handling of rules.
  Add bif that allows searching for all matching subnets in table.
  Add signaling of succesful initialization of plugins to NetControl.
  Add rule hooks to the acld plugin.
  Add new logfiles for shunting and drops to netcontrol
  Extend NetControl logging and fix bugs.
  Update OpenFlow API and events.
  small acld plugin fix
  Revert "introduce &weaken attribute"
  Fix crash when printing type of recursive structures.
  Testcase for crash when a record contains a function referencing a record.
  Rename Pacf to NetControl
  fix acld plugin to use address instead of subnet (and add functions for conversion)
  implement quarantine
  miscelaneous missing bits and pieces
  Acld implementation for Pacf - Bro side.
  ...
This commit is contained in:
Robin Sommer 2016-03-11 14:27:47 -08:00
commit 2233521de7
107 changed files with 6071 additions and 19 deletions

View file

@ -0,0 +1,5 @@
@load ./debug
@load ./openflow
@load ./packetfilter
@load ./broker
@load ./acld

View file

@ -0,0 +1,294 @@
##! Acld plugin for the netcontrol framework.
module NetControl;
@load ../main
@load ../plugin
@load base/frameworks/broker
export {
type AclRule : record {
command: string;
cookie: count;
arg: string;
comment: string &optional;
};
type AcldConfig: record {
## The acld topic used 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 otherwhise
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_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",
["whitelist"] = "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_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 = "whitelist";
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;
BrokerComm::event(p$acld_config$acld_topic, BrokerComm::event_args(acld_add_rule, p$acld_id, r, ar));
return T;
}
function acld_remove_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 in acld_add_to_remove )
ar$command = acld_add_to_remove[ar$command];
else
return F;
BrokerComm::event(p$acld_config$acld_topic, BrokerComm::event_args(acld_remove_rule, p$acld_id, r, ar));
return T;
}
function acld_init(p: PluginState)
{
BrokerComm::enable();
BrokerComm::connect(cat(p$acld_config$acld_host), p$acld_config$acld_port, 1sec);
BrokerComm::subscribe_to_events(p$acld_config$acld_topic);
}
event BrokerComm::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;
}

View file

@ -0,0 +1,163 @@
##! Broker plugin for the netcontrol framework. Sends the raw data structures
##! used in NetControl on to Broker to allow for easy handling, e.g., of
##! command-line scripts.
module NetControl;
@load ../main
@load ../plugin
@load base/frameworks/broker
export {
## Instantiates the broker plugin.
global create_broker: function(host: addr, host_port: port, topic: string, can_expire: bool &default=F) : PluginState;
redef record PluginState += {
## The broker topic used to send events to
broker_topic: string &optional;
## The ID of this broker instance - for the mapping to PluginStates
broker_id: count &optional;
## Broker host to connect to
broker_host: addr &optional;
## Broker port to connect to
broker_port: port &optional;
};
global broker_add_rule: event(id: count, r: Rule);
global broker_remove_rule: event(id: count, r: Rule);
global broker_rule_added: event(id: count, r: Rule, msg: string);
global broker_rule_removed: event(id: count, r: Rule, msg: string);
global broker_rule_error: event(id: count, r: Rule, msg: string);
global broker_rule_timeout: event(id: count, r: Rule, i: FlowInfo);
}
global netcontrol_broker_peers: table[port, string] of PluginState;
global netcontrol_broker_topics: set[string] = set();
global netcontrol_broker_id: table[count] of PluginState = table();
global netcontrol_broker_current_id: count = 0;
event NetControl::broker_rule_added(id: count, r: Rule, msg: string)
{
if ( id !in netcontrol_broker_id )
{
Reporter::error(fmt("NetControl broker plugin with id %d not found, aborting", id));
return;
}
local p = netcontrol_broker_id[id];
event NetControl::rule_added(r, p, msg);
}
event NetControl::broker_rule_removed(id: count, r: Rule, msg: string)
{
if ( id !in netcontrol_broker_id )
{
Reporter::error(fmt("NetControl broker plugin with id %d not found, aborting", id));
return;
}
local p = netcontrol_broker_id[id];
event NetControl::rule_removed(r, p, msg);
}
event NetControl::broker_rule_error(id: count, r: Rule, msg: string)
{
if ( id !in netcontrol_broker_id )
{
Reporter::error(fmt("NetControl broker plugin with id %d not found, aborting", id));
return;
}
local p = netcontrol_broker_id[id];
event NetControl::rule_error(r, p, msg);
}
event NetControl::broker_rule_timeout(id: count, r: Rule, i: FlowInfo)
{
if ( id !in netcontrol_broker_id )
{
Reporter::error(fmt("NetControl broker plugin with id %d not found, aborting", id));
return;
}
local p = netcontrol_broker_id[id];
event NetControl::rule_timeout(r, i, p);
}
function broker_name(p: PluginState) : string
{
return fmt("Broker-%s", p$broker_topic);
}
function broker_add_rule_fun(p: PluginState, r: Rule) : bool
{
BrokerComm::event(p$broker_topic, BrokerComm::event_args(broker_add_rule, p$broker_id, r));
return T;
}
function broker_remove_rule_fun(p: PluginState, r: Rule) : bool
{
BrokerComm::event(p$broker_topic, BrokerComm::event_args(broker_remove_rule, p$broker_id, r));
return T;
}
function broker_init(p: PluginState)
{
BrokerComm::enable();
BrokerComm::connect(cat(p$broker_host), p$broker_port, 1sec);
BrokerComm::subscribe_to_events(p$broker_topic);
}
event BrokerComm::outgoing_connection_established(peer_address: string, peer_port: port, peer_name: string)
{
if ( [peer_port, peer_address] !in netcontrol_broker_peers )
return;
local p = netcontrol_broker_peers[peer_port, peer_address];
plugin_activated(p);
}
global broker_plugin = Plugin(
$name=broker_name,
$can_expire = F,
$add_rule = broker_add_rule_fun,
$remove_rule = broker_remove_rule_fun,
$init = broker_init
);
global broker_plugin_can_expire = Plugin(
$name=broker_name,
$can_expire = T,
$add_rule = broker_add_rule_fun,
$remove_rule = broker_remove_rule_fun,
$init = broker_init
);
function create_broker(host: addr, host_port: port, topic: string, can_expire: bool &default=F) : PluginState
{
if ( topic in netcontrol_broker_topics )
Reporter::warning(fmt("Topic %s was added to NetControl broker plugin twice. Possible duplication of commands", topic));
else
add netcontrol_broker_topics[topic];
local plugin = broker_plugin;
if ( can_expire )
plugin = broker_plugin_can_expire;
local p: PluginState = [$broker_host=host, $broker_port=host_port, $plugin=plugin, $broker_topic=topic, $broker_id=netcontrol_broker_current_id];
if ( [host_port, cat(host)] in netcontrol_broker_peers )
Reporter::warning(fmt("Peer %s:%s was added to NetControl broker plugin twice.", host, host_port));
else
netcontrol_broker_peers[host_port, cat(host)] = p;
netcontrol_broker_id[netcontrol_broker_current_id] = p;
++netcontrol_broker_current_id;
return p;
}

View file

@ -0,0 +1,99 @@
##! Debugging plugin for the NetControl framework, providing insight into
##! executed operations.
@load ../plugin
@load ../main
module NetControl;
export {
## Instantiates a debug plugin for the NetControl framework. The debug
## plugin simply logs the operations it receives.
##
## do_something: If true, the plugin will claim it supports all operations; if
## false, it will indicate it doesn't support any.
global create_debug: function(do_something: bool) : PluginState;
}
function do_something(p: PluginState) : bool
{
return p$config["all"] == "1";
}
function debug_name(p: PluginState) : string
{
return fmt("Debug-%s", (do_something(p) ? "All" : "None"));
}
function debug_log(p: PluginState, msg: string)
{
print fmt("netcontrol debug (%s): %s", debug_name(p), msg);
}
function debug_init(p: PluginState)
{
debug_log(p, "init");
plugin_activated(p);
}
function debug_done(p: PluginState)
{
debug_log(p, "init");
}
function debug_add_rule(p: PluginState, r: Rule) : bool
{
local s = fmt("add_rule: %s", r);
debug_log(p, s);
if ( do_something(p) )
{
event NetControl::rule_added(r, p);
return T;
}
return F;
}
function debug_remove_rule(p: PluginState, r: Rule) : bool
{
local s = fmt("remove_rule: %s", r);
debug_log(p, s);
event NetControl::rule_removed(r, p);
return T;
}
function debug_transaction_begin(p: PluginState)
{
debug_log(p, "transaction_begin");
}
function debug_transaction_end(p: PluginState)
{
debug_log(p, "transaction_end");
}
global debug_plugin = Plugin(
$name=debug_name,
$can_expire = F,
$init = debug_init,
$done = debug_done,
$add_rule = debug_add_rule,
$remove_rule = debug_remove_rule,
$transaction_begin = debug_transaction_begin,
$transaction_end = debug_transaction_end
);
function create_debug(do_something: bool) : PluginState
{
local p: PluginState = [$plugin=debug_plugin];
# FIXME: Why's the default not working?
p$config = table();
p$config["all"] = (do_something ? "1" : "0");
return p;
}

View file

@ -0,0 +1,432 @@
##! OpenFlow plugin for the NetControl framework.
@load ../main
@load ../plugin
@load base/frameworks/openflow
module NetControl;
export {
type OfConfig: record {
monitor: bool &default=T;
forward: bool &default=T;
idle_timeout: count &default=0;
table_id: count &optional;
priority_offset: int &default=+0; ##< add this to all rule priorities. Can be useful if you want the openflow priorities be offset from the netcontrol priorities without having to write a filter function.
## 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 otherwhise
check_pred: function(p: PluginState, r: Rule): bool &optional;
match_pred: function(p: PluginState, e: Entity, m: vector of OpenFlow::ofp_match): vector of OpenFlow::ofp_match &optional;
flow_mod_pred: function(p: PluginState, r: Rule, m: OpenFlow::ofp_flow_mod): OpenFlow::ofp_flow_mod &optional;
};
redef record PluginState += {
## OpenFlow controller for NetControl OpenFlow plugin
of_controller: OpenFlow::Controller &optional;
## OpenFlow configuration record that is passed on initialization
of_config: OfConfig &optional;
};
type OfTable: record {
p: PluginState;
r: Rule;
c: count &default=0; # how many replies did we see so far? needed for ids where we have multiple rules...
packet_count: count &default=0;
byte_count: count &default=0;
duration_sec: double &default=0.0;
};
## the time interval after which an openflow message is considered to be timed out
## and we delete it from our internal tracking.
const openflow_message_timeout = 20secs &redef;
## the time interval after we consider a flow timed out. This should be fairly high (or
## even disabled) if you expect a lot of long flows. However, one also will have state
## buildup for quite a while if keeping this around...
const openflow_flow_timeout = 24hrs &redef;
## Instantiates an openflow plugin for the NetControl framework.
global create_openflow: function(controller: OpenFlow::Controller, config: OfConfig &default=[]) : PluginState;
}
global of_messages: table[count, OpenFlow::ofp_flow_mod_command] of OfTable &create_expire=openflow_message_timeout
&expire_func=function(t: table[count, OpenFlow::ofp_flow_mod_command] of OfTable, idx: any): interval
{
local rid: count;
local command: OpenFlow::ofp_flow_mod_command;
[rid, command] = idx;
local p = t[rid, command]$p;
local r = t[rid, command]$r;
event NetControl::rule_error(r, p, "Timeout during rule insertion/removal");
return 0secs;
};
global of_flows: table[count] of OfTable &create_expire=openflow_flow_timeout;
global of_instances: table[string] of PluginState;
function openflow_name(p: PluginState) : string
{
return fmt("Openflow-%s", p$of_controller$describe(p$of_controller$state));
}
function openflow_check_rule(p: PluginState, r: Rule) : bool
{
local c = p$of_config;
if ( p$of_config?$check_pred )
return p$of_config$check_pred(p, r);
if ( r$target == MONITOR && c$monitor )
return T;
if ( r$target == FORWARD && c$forward )
return T;
return F;
}
function openflow_match_pred(p: PluginState, e: Entity, m: vector of OpenFlow::ofp_match) : vector of OpenFlow::ofp_match
{
if ( p$of_config?$match_pred )
return p$of_config$match_pred(p, e, m);
return m;
}
function openflow_flow_mod_pred(p: PluginState, r: Rule, m: OpenFlow::ofp_flow_mod): OpenFlow::ofp_flow_mod
{
if ( p$of_config?$flow_mod_pred )
return p$of_config$flow_mod_pred(p, r, m);
return m;
}
function determine_dl_type(s: subnet): count
{
local pdl = OpenFlow::ETH_IPv4;
if ( is_v6_subnet(s) )
pdl = OpenFlow::ETH_IPv6;
return pdl;
}
function determine_proto(p: port): count
{
local proto = OpenFlow::IP_TCP;
if ( is_udp_port(p) )
proto = OpenFlow::IP_UDP;
else if ( is_icmp_port(p) )
proto = OpenFlow::IP_ICMP;
return proto;
}
function entity_to_match(p: PluginState, e: Entity): vector of OpenFlow::ofp_match
{
local v : vector of OpenFlow::ofp_match = vector();
if ( e$ty == CONNECTION )
{
v[|v|] = OpenFlow::match_conn(e$conn); # forward and...
v[|v|] = OpenFlow::match_conn(e$conn, T); # reverse
return openflow_match_pred(p, e, v);
}
if ( e$ty == MAC )
{
v[|v|] = OpenFlow::ofp_match(
$dl_src=e$mac
);
v[|v|] = OpenFlow::ofp_match(
$dl_dst=e$mac
);
return openflow_match_pred(p, e, v);
}
local dl_type = OpenFlow::ETH_IPv4;
if ( e$ty == ADDRESS )
{
if ( is_v6_subnet(e$ip) )
dl_type = OpenFlow::ETH_IPv6;
v[|v|] = OpenFlow::ofp_match(
$dl_type=dl_type,
$nw_src=e$ip
);
v[|v|] = OpenFlow::ofp_match(
$dl_type=dl_type,
$nw_dst=e$ip
);
return openflow_match_pred(p, e, v);
}
local proto = OpenFlow::IP_TCP;
if ( e$ty == FLOW )
{
local m = OpenFlow::ofp_match();
local f = e$flow;
if ( f?$src_m )
m$dl_src=f$src_m;
if ( f?$dst_m )
m$dl_dst=f$dst_m;
if ( f?$src_h )
{
m$dl_type = determine_dl_type(f$src_h);
m$nw_src = f$src_h;
}
if ( f?$dst_h )
{
m$dl_type = determine_dl_type(f$dst_h);
m$nw_dst = f$dst_h;
}
if ( f?$src_p )
{
m$nw_proto = determine_proto(f$src_p);
m$tp_src = port_to_count(f$src_p);
}
if ( f?$dst_p )
{
m$nw_proto = determine_proto(f$dst_p);
m$tp_dst = port_to_count(f$dst_p);
}
v[|v|] = m;
return openflow_match_pred(p, e, v);
}
Reporter::error(fmt("Entity type %s not supported for openflow yet", cat(e$ty)));
return openflow_match_pred(p, e, v);
}
function openflow_rule_to_flow_mod(p: PluginState, r: Rule) : OpenFlow::ofp_flow_mod
{
local c = p$of_config;
local flow_mod = OpenFlow::ofp_flow_mod(
$cookie=OpenFlow::generate_cookie(r$cid*2), # leave one space for the cases in which we need two rules.
$command=OpenFlow::OFPFC_ADD,
$idle_timeout=c$idle_timeout,
$priority=int_to_count(r$priority + c$priority_offset),
$flags=OpenFlow::OFPFF_SEND_FLOW_REM # please notify us when flows are removed
);
if ( r?$expire )
flow_mod$hard_timeout = double_to_count(interval_to_double(r$expire));
if ( c?$table_id )
flow_mod$table_id = c$table_id;
if ( r$ty == DROP )
{
# default, nothing to do. We simply do not add an output port to the rule...
}
else if ( r$ty == WHITELIST )
{
# at the moment our interpretation of whitelist is to hand this off to the switches L2/L3 routing.
flow_mod$actions$out_ports = vector(OpenFlow::OFPP_NORMAL);
}
else if ( r$ty == MODIFY )
{
# if no ports are given, just assume normal pipeline...
flow_mod$actions$out_ports = vector(OpenFlow::OFPP_NORMAL);
local mod = r$mod;
if ( mod?$redirect_port )
flow_mod$actions$out_ports = vector(mod$redirect_port);
if ( mod?$src_h )
flow_mod$actions$nw_src = mod$src_h;
if ( mod?$dst_h )
flow_mod$actions$nw_dst = mod$dst_h;
if ( mod?$src_m )
flow_mod$actions$dl_src = mod$src_m;
if ( mod?$dst_m )
flow_mod$actions$dl_dst = mod$dst_m;
if ( mod?$src_p )
flow_mod$actions$tp_src = mod$src_p;
if ( mod?$dst_p )
flow_mod$actions$tp_dst = mod$dst_p;
}
else if ( r$ty == REDIRECT )
{
# redirect to port c
flow_mod$actions$out_ports = vector(r$out_port);
}
else
{
Reporter::error(fmt("Rule type %s not supported for openflow yet", cat(r$ty)));
}
return openflow_flow_mod_pred(p, r, flow_mod);
}
function openflow_add_rule(p: PluginState, r: Rule) : bool
{
if ( ! openflow_check_rule(p, r) )
return F;
local flow_mod = openflow_rule_to_flow_mod(p, r);
local matches = entity_to_match(p, r$entity);
for ( i in matches )
{
if ( OpenFlow::flow_mod(p$of_controller, matches[i], flow_mod) )
{
of_messages[r$cid, flow_mod$command] = OfTable($p=p, $r=r);
flow_mod = copy(flow_mod);
++flow_mod$cookie;
}
else
event rule_error(r, p, "Error while executing OpenFlow::flow_mod");
}
return T;
}
function openflow_remove_rule(p: PluginState, r: Rule) : bool
{
if ( ! openflow_check_rule(p, r) )
return F;
local flow_mod: OpenFlow::ofp_flow_mod = [
$cookie=OpenFlow::generate_cookie(r$cid*2),
$command=OpenFlow::OFPFC_DELETE
];
if ( OpenFlow::flow_mod(p$of_controller, [], flow_mod) )
of_messages[r$cid, flow_mod$command] = OfTable($p=p, $r=r);
else
{
event rule_error(r, p, "Error while executing OpenFlow::flow_mod");
return F;
}
# if this was an address or mac match, we also need to remove the reverse
if ( r$entity$ty == ADDRESS || r$entity$ty == MAC )
{
local flow_mod_2 = copy(flow_mod);
++flow_mod_2$cookie;
OpenFlow::flow_mod(p$of_controller, [], flow_mod_2);
}
return T;
}
event OpenFlow::flow_mod_success(name: string, match: OpenFlow::ofp_match, flow_mod: OpenFlow::ofp_flow_mod, msg: string) &priority=3
{
local id = OpenFlow::get_cookie_uid(flow_mod$cookie)/2;
if ( [id, flow_mod$command] !in of_messages )
return;
local r = of_messages[id,flow_mod$command]$r;
local p = of_messages[id,flow_mod$command]$p;
local c = of_messages[id,flow_mod$command]$c;
if ( r$entity$ty == ADDRESS || r$entity$ty == MAC )
{
++of_messages[id,flow_mod$command]$c;
if ( of_messages[id,flow_mod$command]$c < 2 )
return; # will do stuff once the second part arrives...
}
delete of_messages[id,flow_mod$command];
if ( p$of_controller$supports_flow_removed )
of_flows[id] = OfTable($p=p, $r=r);
if ( flow_mod$command == OpenFlow::OFPFC_ADD )
event NetControl::rule_added(r, p, msg);
else if ( flow_mod$command == OpenFlow::OFPFC_DELETE || flow_mod$command == OpenFlow::OFPFC_DELETE_STRICT )
event NetControl::rule_removed(r, p, msg);
}
event OpenFlow::flow_mod_failure(name: string, match: OpenFlow::ofp_match, flow_mod: OpenFlow::ofp_flow_mod, msg: string) &priority=3
{
local id = OpenFlow::get_cookie_uid(flow_mod$cookie)/2;
if ( [id, flow_mod$command] !in of_messages )
return;
local r = of_messages[id,flow_mod$command]$r;
local p = of_messages[id,flow_mod$command]$p;
delete of_messages[id,flow_mod$command];
event NetControl::rule_error(r, p, msg);
}
event OpenFlow::flow_removed(name: string, match: OpenFlow::ofp_match, cookie: count, priority: count, reason: count, duration_sec: count, idle_timeout: count, packet_count: count, byte_count: count)
{
local id = OpenFlow::get_cookie_uid(cookie)/2;
if ( id !in of_flows )
return;
local rec = of_flows[id];
local r = rec$r;
local p = rec$p;
if ( r$entity$ty == ADDRESS || r$entity$ty == MAC )
{
++of_flows[id]$c;
if ( of_flows[id]$c < 2 )
return; # will do stuff once the second part arrives...
else
event NetControl::rule_timeout(r, FlowInfo($duration=double_to_interval((rec$duration_sec+duration_sec)/2), $packet_count=packet_count+rec$packet_count, $byte_count=byte_count+rec$byte_count), p);
return;
}
event NetControl::rule_timeout(r, FlowInfo($duration=double_to_interval(duration_sec+0.0), $packet_count=packet_count, $byte_count=byte_count), p);
}
function openflow_init(p: PluginState)
{
local name = p$of_controller$state$_name;
if ( name in of_instances )
Reporter::error(fmt("OpenFlow instance %s added to NetControl twice.", name));
of_instances[name] = p;
# let's check, if our OpenFlow controller is already active. If not, we have to wait for it to become active.
if ( p$of_controller$state$_activated )
plugin_activated(p);
}
event OpenFlow::controller_activated(name: string, controller: OpenFlow::Controller)
{
if ( name in of_instances )
plugin_activated(of_instances[name]);
}
global openflow_plugin = Plugin(
$name=openflow_name,
$can_expire = T,
$init = openflow_init,
# $done = openflow_done,
$add_rule = openflow_add_rule,
$remove_rule = openflow_remove_rule
# $transaction_begin = openflow_transaction_begin,
# $transaction_end = openflow_transaction_end
);
function create_openflow(controller: OpenFlow::Controller, config: OfConfig &default=[]) : PluginState
{
local p: PluginState = [$plugin=openflow_plugin, $of_controller=controller, $of_config=config];
return p;
}

View file

@ -0,0 +1,113 @@
##! NetControl plugin for the process-level PacketFilter that comes with
##! Bro. Since the PacketFilter in Bro is quite limited in scope
##! and can only add/remove filters for addresses, this is quite
##! limited in scope at the moment.
module NetControl;
@load ../plugin
export {
## Instantiates the packetfilter plugin.
global create_packetfilter: function() : PluginState;
}
# Check if we can handle this rule. If it specifies ports or
# anything Bro cannot handle, simply ignore it for now.
function packetfilter_check_rule(r: Rule) : bool
{
if ( r$ty != DROP )
return F;
if ( r$target != MONITOR )
return F;
local e = r$entity;
if ( e$ty == ADDRESS )
return T;
if ( e$ty != FLOW ) # everything else requires ports or MAC stuff
return F;
if ( e$flow?$src_p || e$flow?$dst_p || e$flow?$src_m || e$flow?$dst_m )
return F;
return T;
}
function packetfilter_add_rule(p: PluginState, r: Rule) : bool
{
if ( ! packetfilter_check_rule(r) )
return F;
local e = r$entity;
if ( e$ty == ADDRESS )
{
install_src_net_filter(e$ip, 0, 1.0);
install_dst_net_filter(e$ip, 0, 1.0);
return T;
}
if ( e$ty == FLOW )
{
local f = e$flow;
if ( f?$src_h )
install_src_net_filter(f$src_h, 0, 1.0);
if ( f?$dst_h )
install_dst_net_filter(f$dst_h, 0, 1.0);
return T;
}
return F;
}
function packetfilter_remove_rule(p: PluginState, r: Rule) : bool
{
if ( ! packetfilter_check_rule(r) )
return F;
local e = r$entity;
if ( e$ty == ADDRESS )
{
uninstall_src_net_filter(e$ip);
uninstall_dst_net_filter(e$ip);
return T;
}
if ( e$ty == FLOW )
{
local f = e$flow;
if ( f?$src_h )
uninstall_src_net_filter(f$src_h);
if ( f?$dst_h )
uninstall_dst_net_filter(f$dst_h);
return T;
}
return F;
}
function packetfilter_name(p: PluginState) : string
{
return "Packetfilter";
}
global packetfilter_plugin = Plugin(
$name=packetfilter_name,
$can_expire = F,
# $init = packetfilter_init,
# $done = packetfilter_done,
$add_rule = packetfilter_add_rule,
$remove_rule = packetfilter_remove_rule
);
function create_packetfilter() : PluginState
{
local p: PluginState = [$plugin=packetfilter_plugin];
return p;
}