Rename Pacf to NetControl

This commit is contained in:
Johanna Amann 2015-07-08 12:34:42 -07:00
parent eb9fbd1258
commit 0e213352d7
61 changed files with 498 additions and 498 deletions

View file

@ -0,0 +1,13 @@
@load ./types
@load ./main
@load ./plugins
@load ./catch-and-release
# The cluster framework must be loaded first.
@load base/frameworks/cluster
@if ( Cluster::is_enabled() )
@load ./cluster
@else
@load ./non-cluster
@endif

View file

@ -0,0 +1,99 @@
##! Implementation of catch-and-release functionality for NetControl
module NetControl;
export {
## Stops all packets involving an IP address from being forwarded. This function
## uses catch-and-release functionality, where the IP address is only dropped for
## a short amount of time that is incremented steadily when the IP is encountered
## again.
##
## a: The address to be dropped.
##
## t: How long to drop it, with 0 being indefinitly.
##
## location: An optional string describing where the drop was triggered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global drop_address_catch_release: function(a: addr, location: string &default="") : string;
const catch_release_intervals: vector of interval = vector(10min, 1hr, 24hrs, 7days) &redef;
}
function per_block_interval(t: table[addr] of count, idx: addr): interval
{
local ct = t[idx];
# watch for the time of the next block...
local blocktime = catch_release_intervals[ct];
if ( (ct+1) in catch_release_intervals )
blocktime = catch_release_intervals[ct+1];
return blocktime;
}
# This is the internally maintained table containing all the currently going on catch-and-release
# blocks.
global blocks: table[addr] of count = {}
&create_expire=0secs
&expire_func=per_block_interval;
function current_block_interval(s: set[addr], idx: addr): interval
{
if ( idx !in blocks )
{
Reporter::error(fmt("Address %s not in blocks while inserting into current_blocks!", idx));
return 0sec;
}
return catch_release_intervals[blocks[idx]];
}
global current_blocks: set[addr] = set()
&create_expire=0secs
&expire_func=current_block_interval;
function drop_address_catch_release(a: addr, location: string &default=""): string
{
if ( a in blocks )
{
Reporter::warning(fmt("Address %s already blocked using catch-and-release - ignoring duplicate", a));
return "";
}
local block_interval = catch_release_intervals[0];
local ret = drop_address(a, block_interval, location);
if ( ret != "" )
{
blocks[a] = 0;
add current_blocks[a];
}
return ret;
}
function check_conn(a: addr)
{
if ( a in blocks )
{
if ( a in current_blocks )
# block has not been applied yet?
return;
# ok, this one returned again while still in the backoff period.
local try = blocks[a];
if ( (try+1) in catch_release_intervals )
++try;
blocks[a] = try;
add current_blocks[a];
local block_interval = catch_release_intervals[try];
drop_address(a, block_interval, "Re-drop by catch-and-release");
}
}
event new_connection(c: connection)
{
# let's only check originating connections...
check_conn(c$id$orig_h);
}

View file

@ -0,0 +1,66 @@
@load ./main
@load base/frameworks/cluster
module NetControl;
export {
## This is the event used to transport add_rule calls to the manager.
global cluster_netcontrol_add_rule: event(r: Rule);
## This is the event used to transport remove_rule calls to the manager.
global cluster_netcontrol_remove_rule: event(id: string);
}
## Workers need ability to forward commands to manager.
redef Cluster::worker2manager_events += /NetControl::cluster_netcontrol_(add|remove)_rule/;
## Workers need to see the result events from the manager.
redef Cluster::manager2worker_events += /NetControl::rule_(added|removed|timeout|error)/;
function activate(p: PluginState, priority: int)
{
# we only run the activate function on the manager.
if ( Cluster::local_node_type() != Cluster::MANAGER )
return;
activate_impl(p, priority);
}
global local_rule_count: count = 1;
function add_rule(r: Rule) : string
{
if ( Cluster::local_node_type() == Cluster::MANAGER )
return add_rule_impl(r);
else
{
if ( r$id == "" )
r$id = cat(Cluster::node, ":", ++local_rule_count);
event NetControl::cluster_netcontrol_add_rule(r);
return r$id;
}
}
function remove_rule(id: string) : bool
{
if ( Cluster::local_node_type() == Cluster::MANAGER )
return remove_rule_impl(id);
else
{
event NetControl::cluster_netcontrol_remove_rule(id);
return T; # well, we can't know here. So - just hope...
}
}
@if ( Cluster::local_node_type() == Cluster::MANAGER )
event NetControl::cluster_netcontrol_add_rule(r: Rule)
{
add_rule_impl(r);
}
event NetControl::cluster_netcontrol_remove_rule(id: string)
{
remove_rule_impl(id);
}
@endif

View file

@ -0,0 +1,621 @@
##! Bro's packet aquisition and control 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 evyerthing 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 API: a high-level and low-level. The high-levem API
##! provides convinience 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.
# ###
# Activates a plugin.
#
# plugin: The plugin to acticate.
#
# 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);
# ###
# ### High-level API.
# ###
## Stops all packets involving an IP address from being forwarded.
##
## a: The address to be dropped.
##
## t: How long to drop it, with 0 being indefinitly.
##
## location: An optional string describing where the drop was triggered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global drop_address: function(a: addr, t: interval, location: string &default="") : string;
## Stops all packets involving an connection address from being forwarded.
##
## c: The connection to be dropped.
##
## t: How long to drop it, with 0 being indefinitly.
##
## location: An optional string describing where the drop was triggered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global drop_connection: function(c: conn_id, t: interval, location: string &default="") : string;
## Stops forwarding a uni-directional flow's packets to Bro.
##
## f: The flow to shunt.
##
## t: How long to leave the shunt in place, with 0 being indefinitly.
##
## location: An optional string describing where the shunt was triggered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global shunt_flow: function(f: flow_id, t: interval, location: string &default="") : string;
## Allows all traffic involving a specific IP address to be forwarded.
##
## a: The address to be whitelistet.
##
## t: How long to whitelist it, with 0 being indefinitly.
##
## location: An optional string describing whitelist was triddered.
##
## Returns: The id of the inserted rule on succes 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 whitelistet.
##
## t: How long to whitelist it, with 0 being indefinitly.
##
## location: An optional string describing whitelist was triddered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global whitelist_subnet: function(s: subnet, t: interval, location: string &default="") : string;
## Redirects an 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 indefinitly.
##
## location: An optional string describing where the redirect was triggered.
##
## Returns: The id of the inserted rule on succes and zero on failure.
global redirect_flow: function(f: flow_id, out_port: count, t: interval, location: string &default="") : string;
## Quarantines a host by redirecting rewriting DNS queries to the network dns server dns
## to the host. Host has to answer to all queries with its own address. Only http communication
## from infected to quarantinehost is allowed.
##
## 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) : vector of string;
## Flushes all state.
global clear: function();
# ###
# ### Low-level API.
# ###
###### Manipulation of rules.
## Installs a rule.
##
## r: The rule to install.
##
## Returns: If succesful, 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:id:`add_rule` .
##
## Returns: True if succesful, the relevant plugin indicated that ity knew how
## to handle the removal. Note that again "success" means the plugin accepted the
## removal. They 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) : bool;
###### Asynchronous feedback on rules.
## Confirms that a rule was put in place.
##
## r: The rule now in place.
##
## plugin: The name of 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="");
## Reports that a rule was removed due to a remove: function() call.
##
## r: The rule now removed.
##
## plugin: The name of 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 internally due to a timeout.
##
## r: The rule now removed.
##
## i: Additional flow information, if supported by the protocol.
##
## plugin: The name of 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.
##
## plugin: The name of 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="");
## 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);
## Type of an entry in the PACF log.
type InfoCategory: enum {
## A log entry reflecting a framework message.
MESSAGE,
## A log entry reflecting a framework message.
ERROR,
## A log entry about about a rule.
RULE
};
## State of an entry in the PACF log.
type InfoState: enum {
REQUESTED,
SUCCEEDED,
FAILED,
REMOVED,
TIMEOUT,
};
## The record type which contains column fields of the PACF log.
type Info: record {
## Time at which the recorded activity occurred.
ts: time &log;
## 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 with an additional message.
msg: string &log &optional;
## Logcation 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 plugin handling the rule.
_plugin_id: count &optional;
};
global plugins: vector of PluginState;
global plugin_ids: table[count] of PluginState;
global rule_counter: count = 1;
global plugin_counter: count = 1;
global rules: table[string,count] of Rule; # Rules indexed by id and cid
global id_to_cids: table[string] of set[count]; # id to cid
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 = "*";
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);
break;
case MAC:
info$entity = e$mac;
break;
default:
info$entity = "<unknown entity type>";
break;
}
}
function rule_to_info(info: Info, r: Rule)
{
info$action = fmt("%s", r$ty);
info$target = r$target;
if ( r?$location )
info$location = r$location;
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_rule(r: Rule, cmd: string, state: InfoState, p: PluginState)
{
local info: Info = [$ts=network_time()];
info$category = RULE;
info$cmd = cmd;
info$state = state;
info$plugin = p$plugin$name(p);
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 drop_connection(c: conn_id, t: interval, location: string &default="") : string
{
local e: Entity = [$ty=CONNECTION, $conn=c];
local r: Rule = [$ty=DROP, $target=FORWARD, $entity=e, $expire=t, $location=location];
return add_rule(r);
}
function drop_address(a: addr, t: interval, location: string &default="") : string
{
local e: Entity = [$ty=ADDRESS, $ip=addr_to_subnet(a)];
local r: Rule = [$ty=DROP, $target=FORWARD, $entity=e, $expire=t, $location=location];
return add_rule(r);
}
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 shunt_flow(f: flow_id, 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=DROP, $target=MONITOR, $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, $c=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;
}
# 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-timi initialization
if ( p$plugin?$init )
p$plugin$init(p);
log_msg(fmt("activated plugin with priority %d", priority), p);
}
function add_rule_impl(rule: Rule) : string
{
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 "";
local accepted = F;
local priority: int = +0;
local r = rule;
for ( i in plugins )
{
local p = plugins[i];
# in this case, rule was accepted by earlier plugin and thus plugin has same
# priority. accept, but give out new rule id.
if ( accepted == T && p$_priority == priority )
{
r = copy(rule);
r$cid = ++rule_counter;
}
else if ( accepted == T )
# in this case, rule was accepted by earlier plugin and this plugin has a lower
# priority. Abort and do not send there...
break;
# set before, in case the plugins sends and regenerates the plugin record later.
r$_plugin_id = p$_id;
if ( p$plugin$add_rule(p, r) )
{
accepted = T;
priority = p$_priority;
log_rule(r, "ADD", REQUESTED, p);
}
}
if ( accepted )
return rule$id;
log_rule_no_plugin(r, FAILED, "not supported");
return "";
}
function remove_single_rule(id: string, cid: count) : bool
{
if ( [id,cid] !in rules )
{
Reporter::error(fmt("Rule %s -- %d does not exist in NetControl::remove_single_rule", id, cid));
return F;
}
local r = rules[id,cid];
local p = plugin_ids[r$_plugin_id];
# remove the respective rules from its plugins..
if ( ! p$plugin$remove_rule(p, r) )
{
log_rule_error(r, "remove failed", p);
return F;
}
log_rule(r, "REMOVE", REQUESTED, p);
return T;
}
function remove_rule_impl(id: string) : bool
{
if ( id !in id_to_cids )
{
Reporter::error(fmt("Rule %s does not exist in NetControl::remove_rule", id));
return F;
}
local cids = id_to_cids[id];
local success = T;
for ( cid in cids )
{
if ( [id,cid] !in rules )
{
Reporter::error(fmt("Internal error in netcontrol::remove_rule - cid %d does not belong to rule %s", cid, id));
delete cids[cid];
next;
}
if ( ! remove_single_rule(id, cid) )
success = F;
}
return success;
}
event rule_expire(r: Rule, p: PluginState)
{
if ( [r$id,r$cid] !in rules )
# Removed already.
return;
event rule_timeout(r, FlowInfo(), p);
remove_single_rule(r$id, r$cid);
}
event rule_added(r: Rule, p: PluginState, msg: string &default="")
{
log_rule(r, "ADD", SUCCEEDED, p);
rules[r$id,r$cid] = r;
if ( r$id !in id_to_cids )
id_to_cids[r$id] = set();
add id_to_cids[r$id][r$cid];
if ( r?$expire && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };
}
event rule_removed(r: Rule, p: PluginState, msg: string &default="")
{
delete rules[r$id,r$cid];
delete id_to_cids[r$id][r$cid];
if ( |id_to_cids[r$id]| == 0 )
delete id_to_cids[r$id];
log_rule(r, "REMOVE", SUCCEEDED, p);
}
event rule_timeout(r: Rule, i: FlowInfo, p: PluginState)
{
delete rules[r$id,r$cid];
delete id_to_cids[r$id][r$cid];
if ( |id_to_cids[r$id]| == 0 )
delete id_to_cids[r$id];
log_rule(r, "EXPIRE", TIMEOUT, p);
}
event rule_error(r: Rule, p: PluginState, msg: string &default="")
{
log_rule_error(r, msg, p);
# errors can occur during deletion. Since this probably means we wo't hear
# from it again, let's just remove it if it exists...
delete rules[r$id,r$cid];
delete id_to_cids[r$id][r$cid];
if ( |id_to_cids[r$id]| == 0 )
delete id_to_cids[r$id];
}
function clear()
{
for ( [id,cid] in rules )
remove_single_rule(id, cid);
}

View file

@ -0,0 +1,18 @@
module NetControl;
@load ./main
function activate(p: PluginState, priority: int)
{
activate_impl(p, priority);
}
function add_rule(r: Rule) : string
{
return add_rule_impl(r);
}
function remove_rule(id: string) : bool
{
return remove_rule_impl(id);
}

View file

@ -0,0 +1,80 @@
module NetControl;
@load ./types
export {
## State for a plugin instance.
type PluginState: record {
## Table for a plugin to store custom, instance-specfific state.
config: table[string] of string &default=table();
## Unique plugin identifier -- used for backlookup of plugins from Rules. Set internally.
_id: count &optional;
## Set internally.
_priority: int &default=+0;
};
# Definition of a plugin.
#
# Generally a plugin needs to implement only what it can support. By
# returning failure, it indicates that it can't support something and the
# the framework will then try another plugin, if available; or informn the
# that the operation failed. If a function isn't implemented by a plugin,
# that's considered an implicit failure to support the operation.
#
# If plugin accepts a rule operation, it *must* generate one of the reporting
# events ``rule_{added,remove,error}`` to signal if it indeed worked out;
# this is separate from accepting the operation because often a plugin
# will only know later (i.e., asynchrously) if that was an error for
# something it thought it could handle.
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(state: PluginState) : string;
## If true, plugin can expire rules itself. If false,
## framework will manage rule expiration.
can_expire: bool;
# One-time initialization function called when plugin gets registered, and
# before any other methods are called.
init: function(state: PluginState) &optional;
# One-time finalization function called when a plugin is shutdown; no further
# functions will be called afterwords.
done: function(state: PluginState) &optional;
# Implements the add_rule() operation. If the plugin accepts the rule,
# it returns true, false otherwise. The rule will already have its
# ``id`` field set, which the plugin may use for identification
# purposes.
add_rule: function(state: PluginState, r: Rule) : bool &optional;
# Implements the remove_rule() operation. This will only be called for
# rules that the plugins has previously accepted with add_rule(). The
# ``id`` field will match that of the add_rule() call. Generally,
# a plugin that accepts an add_rule() should also accept the
# remove_rule().
remove_rule: function(state: PluginState, r: Rule) : bool &optional;
# A transaction groups a number of operations. The plugin can add them internally
# and postpone putting them into effect until committed. This allows to build a
# configuration of multiple rules at once, including replaying a previous state.
transaction_begin: function(state: PluginState) &optional;
transaction_end: function(state: PluginState) &optional;
};
# Table for a plugin to store instance-specific configuration information.
#
# Note, it would be nicer to pass the Plugin instance to all the below, instead
# of this state table. However Bro's type resolver has trouble with refering to a
# record type from inside itself.
redef record PluginState += {
## The plugin that the state belongs to. (Defined separately
## because of cyclic type dependency.)
plugin: Plugin &optional &weaken;
};
}

View file

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

View file

@ -0,0 +1,230 @@
# Acld plugin for the netcontrol framework.
module NetControl;
@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;
## Function that can decide weather to accept add request
add_pred: function(p: PluginState, r: Rule, ar: AclRule): bool &optional &weaken;
};
## 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;
};
global acld_add_rule: event(id: count, r: Rule, ar: AclRule);
global acld_remove_rule: event(id: count, r: Rule, ar: AclRule);
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_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("PACF acld plugin - using broker topic %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(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;
return ar;
}
function acld_add_rule_fun(p: PluginState, r: Rule) : bool
{
local ar = rule_to_acl_rule(r);
if ( p$acld_config?$add_pred )
if ( ! p$acld_config$add_pred(p, r, ar) )
return F;
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
{
local ar = rule_to_acl_rule(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);
}
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 p: PluginState = [$acld_config=config, $plugin=acld_plugin, $acld_id=netcontrol_acld_current_id];
netcontrol_acld_id[netcontrol_acld_current_id] = p;
++netcontrol_acld_current_id;
return p;
}

View file

@ -0,0 +1,147 @@
# 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 ../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_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("PACF Broker plugin - topic %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);
}
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];
netcontrol_broker_id[netcontrol_broker_current_id] = p;
++netcontrol_broker_current_id;
return p;
}

View file

@ -0,0 +1,95 @@
@load ../plugin
module NetControl;
export {
## Instantiates a debug plugin for the PACF 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");
}
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,402 @@
@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.
check_pred: function(p: PluginState, r: Rule): bool &optional &weaken;
match_pred: function(p: PluginState, e: Entity, m: vector of OpenFlow::ofp_match): vector of OpenFlow::ofp_match &optional &weaken;
flow_mod_pred: function(p: PluginState, r: Rule, m: OpenFlow::ofp_flow_mod): OpenFlow::ofp_flow_mod &optional &weaken;
};
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 PACF 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;
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$c);
}
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),
$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(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(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(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);
}
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 @@
# PACF plugin for the PacketFilter handling 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 "PACF plugin for the Bro 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;
}

View file

@ -0,0 +1,111 @@
module NetControl;
export {
const default_priority: int = +0 &redef;
const whitelist_priority: int = +5 &redef;
## Type of a :bro:id:`Entity` for defining an action.
type EntityType: enum {
ADDRESS, ##< Activity involving a specific IP address.
CONNECTION, ##< All of a bi-directional connection's activity.
FLOW, ##< All of a uni-directional flow's activity. Can contain wildcards.
MAC, ##< Activity involving a MAC address.
};
## Type of a :bro:id:`Flow` for defining a flow.
type Flow: record {
src_h: subnet &optional; ##< The source IP address/subnet.
src_p: port &optional; ##< The source port number.
dst_h: subnet &optional; ##< The destination IP address/subnet.
dst_p: port &optional; ##< The desintation port number.
src_m: string &optional; ##< The source MAC address.
dst_m: string &optional; ##< The destination MAC address.
};
## Type defining the enity an :bro:id:`Rule` is operating on.
type Entity: record {
ty: EntityType; ##< Type of entity.
conn: conn_id &optional; ##< Used with :bro:id:`CONNECTION` .
flow: Flow &optional; ##< Used with :bro:id:`FLOW` .
ip: subnet &optional; ##< Used with bro:id:`ADDRESS`; can specifiy a CIDR subnet.
mac: string &optional; ##< Used with :bro:id:`MAC`.
};
## Target of :bro:id:`Rule` action.
type TargetType: enum {
FORWARD, #< Apply rule actively to traffic on forwarding path.
MONITOR, #< Apply rule passively to traffic sent to Bro for monitoring.
};
## Type of rules that the framework supports. Each type lists the
## :bro:id:`Rule` argument(s) it uses, if any.
##
## Plugins may extend this type to define their own.
type RuleType: enum {
## Stop forwarding all packets matching entity.
##
## No arguments.
DROP,
## Begin modifying all packets matching entity.
##
## .. todo::
## Define arguments.
MODIFY,
## Begin redirecting all packets matching entity.
##
## .. todo::
## c: output port to redirect traffic to.
REDIRECT,
## Whitelists all packets of an entity, meaning no restrictions will be applied.
## While whitelisting is the default if no rule matches an this can type can be
## used to override lower-priority rules that would otherwise take effect for the
## entity.
WHITELIST,
};
## Type of a :bro:id:`FlowMod` for defining a flow modification action.
type FlowMod: record {
src_h: addr &optional; ##< The source IP address.
src_p: count &optional; ##< The source port number.
dst_h: addr &optional; ##< The destination IP address.
dst_p: count &optional; ##< The desintation port number.
src_m: string &optional; ##< The source MAC address.
dst_m: string &optional; ##< The destination MAC address.
redirect_port: count &optional;
};
## A rule for the framework to put in place. Of all rules currently in
## place, the first match will be taken, sorted by priority. All
## further rules will be ignored.
type Rule: record {
ty: RuleType; ##< Type of rule.
target: TargetType; ##< Where to apply rule.
entity: Entity; ##< Entity to apply rule to.
expire: interval &optional; ##< Timeout after which to expire the rule.
priority: int &default=default_priority; ##< Priority if multiple rules match an entity (larger value is higher priority).
location: string &optional; ##< Optional string describing where/what installed the rule.
c: count &optional; ##< Argument for rule types requiring an count argument.
i: int &optional; ##< Argument for rule types requiring an integer argument.
d: double &optional; ##< Argument for rule types requiring a double argument.
s: string &optional; ##< Argument for rule types requiring a string argument.
mod: FlowMod &optional; ##< Argument for :bro:id:`MODIFY` rules.
id: string &default=""; ##< Internally determined unique ID for this rule. Will be set when added.
cid: count &default=0; ##< Internally determined unique numeric ID for this rule. Set when added.
};
## Information of a flow that can be provided by switches when the flow times out.
## Currently this is heavily influenced by the data that OpenFlow returns by default.
## That being said - their design makes sense and this is probably the data one
## can expect to be available.
type FlowInfo: record {
duration: interval &optional; ##< total duration of the rule
packet_count: count &optional; ##< number of packets exchanged over connections matched by the rule
byte_count: count &optional; ##< total bytes exchanged over connections matched by the rule
};
}