mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
Merge branch 'master' into topic/jgras/intel-update
This commit is contained in:
commit
06faee2cc8
141 changed files with 6503 additions and 108 deletions
15
scripts/base/frameworks/netcontrol/__load__.bro
Normal file
15
scripts/base/frameworks/netcontrol/__load__.bro
Normal file
|
@ -0,0 +1,15 @@
|
|||
@load ./types
|
||||
@load ./main
|
||||
@load ./plugins
|
||||
@load ./drop
|
||||
@load ./shunt
|
||||
@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
|
104
scripts/base/frameworks/netcontrol/catch-and-release.bro
Normal file
104
scripts/base/frameworks/netcontrol/catch-and-release.bro
Normal file
|
@ -0,0 +1,104 @@
|
|||
##! Implementation of catch-and-release functionality for NetControl.
|
||||
|
||||
module NetControl;
|
||||
|
||||
@load ./main
|
||||
@load ./drop
|
||||
|
||||
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;
|
||||
|
||||
## Time intervals for which a subsequent drops of the same IP take
|
||||
## effect.
|
||||
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);
|
||||
}
|
99
scripts/base/frameworks/netcontrol/cluster.bro
Normal file
99
scripts/base/frameworks/netcontrol/cluster.bro
Normal file
|
@ -0,0 +1,99 @@
|
|||
##! Cluster support for the NetControl framework.
|
||||
|
||||
@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
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
event rule_expire(r: Rule, p: PluginState) &priority=-5
|
||||
{
|
||||
rule_expire_impl(r, p);
|
||||
}
|
||||
|
||||
event rule_added(r: Rule, p: PluginState, msg: string &default="") &priority=5
|
||||
{
|
||||
rule_added_impl(r, p, msg);
|
||||
|
||||
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
|
||||
schedule r$expire { rule_expire(r, p) };
|
||||
}
|
||||
|
||||
event rule_removed(r: Rule, p: PluginState, msg: string &default="") &priority=-5
|
||||
{
|
||||
rule_removed_impl(r, p, msg);
|
||||
}
|
||||
|
||||
event rule_timeout(r: Rule, i: FlowInfo, p: PluginState) &priority=-5
|
||||
{
|
||||
rule_timeout_impl(r, i, p);
|
||||
}
|
||||
|
||||
event rule_error(r: Rule, p: PluginState, msg: string &default="") &priority=-5
|
||||
{
|
||||
rule_error_impl(r, p, msg);
|
||||
}
|
||||
@endif
|
||||
|
98
scripts/base/frameworks/netcontrol/drop.bro
Normal file
98
scripts/base/frameworks/netcontrol/drop.bro
Normal file
|
@ -0,0 +1,98 @@
|
|||
##! Implementation of the drop functionality for NetControl.
|
||||
|
||||
module NetControl;
|
||||
|
||||
@load ./main
|
||||
|
||||
export {
|
||||
redef enum Log::ID += { DROP };
|
||||
|
||||
## 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;
|
||||
|
||||
type DropInfo: record {
|
||||
## Time at which the recorded activity occurred.
|
||||
ts: time &log;
|
||||
## ID of the rule; unique during each Bro run
|
||||
rule_id: string &log;
|
||||
orig_h: addr &log; ##< The originator's IP address.
|
||||
orig_p: port &log &optional; ##< The originator's port number.
|
||||
resp_h: addr &log &optional; ##< The responder's IP address.
|
||||
resp_p: port &log &optional; ##< The responder's port number.
|
||||
## Expiry time of the shunt
|
||||
expire: interval &log;
|
||||
## Location where the underlying action was triggered.
|
||||
location: string &log &optional;
|
||||
};
|
||||
|
||||
## Event that can be handled to access the :bro:type:`NetControl::ShuntInfo`
|
||||
## record as it is sent on to the logging framework.
|
||||
global log_netcontrol_drop: event(rec: DropInfo);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(NetControl::DROP, [$columns=DropInfo, $ev=log_netcontrol_drop, $path="netcontrol_drop"]);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
local id = add_rule(r);
|
||||
|
||||
# Error should already be logged
|
||||
if ( id == "" )
|
||||
return id;
|
||||
|
||||
local log = DropInfo($ts=network_time(), $rule_id=id, $orig_h=c$orig_h, $orig_p=c$orig_p, $resp_h=c$resp_h, $resp_p=c$resp_p, $expire=t);
|
||||
|
||||
if ( location != "" )
|
||||
log$location=location;
|
||||
|
||||
Log::write(DROP, log);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
local id = add_rule(r);
|
||||
|
||||
# Error should already be logged
|
||||
if ( id == "" )
|
||||
return id;
|
||||
|
||||
local log = DropInfo($ts=network_time(), $rule_id=id, $orig_h=a, $expire=t);
|
||||
|
||||
if ( location != "" )
|
||||
log$location=location;
|
||||
|
||||
Log::write(DROP, log);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
935
scripts/base/frameworks/netcontrol/main.bro
Normal file
935
scripts/base/frameworks/netcontrol/main.bro
Normal file
|
@ -0,0 +1,935 @@
|
|||
##! 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 everything through, to both Bro
|
||||
##! itself as well as on the network. Scripts can then add rules to impose
|
||||
##! restrictions on entities, such as specific connections or IP addresses.
|
||||
##!
|
||||
##! This framework has two APIs: a high-level and low-level. The high-level API
|
||||
##! provides 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 and events.
|
||||
# ###
|
||||
|
||||
# Activates a plugin.
|
||||
#
|
||||
# p: 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);
|
||||
|
||||
# Event that is used to initialize plugins. Place all plugin initialization
|
||||
# related functionality in this event.
|
||||
global NetControl::init: event();
|
||||
|
||||
# Event that is raised once all plugins activated in ``NetControl::init`` have finished
|
||||
# their initialization.
|
||||
global NetControl::init_done: event();
|
||||
|
||||
# ###
|
||||
# ### High-level API.
|
||||
# ###
|
||||
|
||||
# ### Note - other high level primitives are in catch-and-release.bro, shunt.bro and
|
||||
# ### drop.bro
|
||||
|
||||
## Allows all traffic involving a specific IP address to be forwarded.
|
||||
##
|
||||
## a: The address to be 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 &default="") : 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 it 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;
|
||||
|
||||
## Searches all rules affecting a certain IP address.
|
||||
##
|
||||
## ip: The ip address to search for
|
||||
##
|
||||
## Returns: vector of all rules affecting the IP address
|
||||
global find_rules_addr: function(ip: addr) : vector of Rule;
|
||||
|
||||
## Searches all rules affecting a certain subnet.
|
||||
##
|
||||
## sn: The subnet to search for
|
||||
##
|
||||
## Returns: vector of all rules affecting the subnet
|
||||
global find_rules_subnet: function(sn: subnet) : vector of Rule;
|
||||
|
||||
###### Asynchronous feedback on rules.
|
||||
|
||||
## Confirms that a rule was put in place.
|
||||
##
|
||||
## r: The rule now in place.
|
||||
##
|
||||
## p: The state for the plugin that put it into place.
|
||||
##
|
||||
## msg: An optional informational message by the plugin.
|
||||
global rule_added: event(r: Rule, p: PluginState, msg: string &default="");
|
||||
|
||||
## Reports that a rule was removed due to a remove: function() call.
|
||||
##
|
||||
## r: The rule now removed.
|
||||
##
|
||||
## p: The state for the plugin that had the rule in place and now
|
||||
## removed it.
|
||||
##
|
||||
## msg: An optional informational message by the plugin.
|
||||
global rule_removed: event(r: Rule, p: PluginState, msg: string &default="");
|
||||
|
||||
## Reports that a rule was removed internally due to a timeout.
|
||||
##
|
||||
## r: The rule now removed.
|
||||
##
|
||||
## i: Additional flow information, if supported by the protocol.
|
||||
##
|
||||
## p: The state for the plugin that had the rule in place and now
|
||||
## removed it.
|
||||
##
|
||||
## msg: An optional informational message by the plugin.
|
||||
global rule_timeout: event(r: Rule, i: FlowInfo, p: PluginState);
|
||||
|
||||
## Reports an error when operating on a rule.
|
||||
##
|
||||
## r: The rule that encountered an error.
|
||||
##
|
||||
## p: The state for the plugin that reported the error.
|
||||
##
|
||||
## msg: An optional informational message by the plugin.
|
||||
global rule_error: event(r: Rule, p: PluginState, msg: string &default="");
|
||||
|
||||
## Hook that allows the modification of rules passed to add_rule before they
|
||||
## are passed on to the plugins. If one of the hooks uses break, the rule is
|
||||
## ignored and not passed on to any plugin.
|
||||
##
|
||||
## r: The rule to be added
|
||||
global NetControl::rule_policy: hook(r: Rule);
|
||||
|
||||
##### Plugin functions
|
||||
|
||||
## Function called by plugins once they finished their activation. After all
|
||||
## plugins defined in bro_init finished to activate, rules will start to be sent
|
||||
## to the plugins. Rules that scripts try to set before the backends are ready
|
||||
## will be discarded.
|
||||
global plugin_activated: function(p: PluginState);
|
||||
|
||||
## Type of an entry in the NetControl log.
|
||||
type InfoCategory: enum {
|
||||
## A log entry reflecting a framework message.
|
||||
MESSAGE,
|
||||
## A log entry reflecting a framework message.
|
||||
ERROR,
|
||||
## A log entry about about a rule.
|
||||
RULE
|
||||
};
|
||||
|
||||
## State of an entry in the NetControl log.
|
||||
type InfoState: enum {
|
||||
REQUESTED,
|
||||
SUCCEEDED,
|
||||
FAILED,
|
||||
REMOVED,
|
||||
TIMEOUT,
|
||||
};
|
||||
|
||||
## The record type defining the column fields of the NetControl log.
|
||||
type Info: record {
|
||||
## Time at which the recorded activity occurred.
|
||||
ts: time &log;
|
||||
## ID of the rule; unique during each Bro run
|
||||
rule_id: string &log &optional;
|
||||
## Type of the log entry.
|
||||
category: InfoCategory &log &optional;
|
||||
## The command the log entry is about.
|
||||
cmd: string &log &optional;
|
||||
## State the log entry reflects.
|
||||
state: InfoState &log &optional;
|
||||
## String describing an action the entry is about.
|
||||
action: string &log &optional;
|
||||
## The target type of the action.
|
||||
target: TargetType &log &optional;
|
||||
## Type of the entity the log entry is about.
|
||||
entity_type: string &log &optional;
|
||||
## String describing the entity the log entry is about.
|
||||
entity: string &log &optional;
|
||||
## String describing the optional modification of the entry (e.h. redirect)
|
||||
mod: string &log &optional;
|
||||
## String with an additional message.
|
||||
msg: string &log &optional;
|
||||
## Number describing the priority of the log entry
|
||||
priority: int &log &optional;
|
||||
## Expiry time of the log entry
|
||||
expire: interval &log &optional;
|
||||
## Location where the underlying action was triggered.
|
||||
location: string &log &optional;
|
||||
## Plugin triggering the log entry.
|
||||
plugin: string &log &optional;
|
||||
};
|
||||
|
||||
## Event that can be handled to access the :bro:type:`NetControl::Info`
|
||||
## record as it is sent on to the logging framework.
|
||||
global log_netcontrol: event(rec: Info);
|
||||
}
|
||||
|
||||
redef record Rule += {
|
||||
##< Internally set to the plugins handling the rule.
|
||||
_plugin_ids: set[count] &default=count_set();
|
||||
##< Internally set to the plugins on which the rule is currently active.
|
||||
_active_plugin_ids: set[count] &default=count_set();
|
||||
##< Track if the rule was added succesfully by all responsible plugins.
|
||||
_added: bool &default=F;
|
||||
};
|
||||
|
||||
# Variable tracking the state of plugin activation. Once all plugins that
|
||||
# have been added in bro_init are activated, this will switch to T and
|
||||
# the event NetControl::init_done will be raised.
|
||||
global plugins_active: bool = F;
|
||||
|
||||
# Set to true at the end of bro_init (with very low priority).
|
||||
# Used to track when plugin activation could potentially be finished
|
||||
global bro_init_done: bool = F;
|
||||
|
||||
# The counters that are used to generate the rule and plugin IDs
|
||||
global rule_counter: count = 1;
|
||||
global plugin_counter: count = 1;
|
||||
|
||||
# List of the currently active plugins
|
||||
global plugins: vector of PluginState;
|
||||
global plugin_ids: table[count] of PluginState;
|
||||
|
||||
# These tables hold information about rules.
|
||||
global rules: table[string] of Rule; # Rules indexed by id and cid
|
||||
|
||||
# All rules that apply to a certain subnet/IP address.
|
||||
global rules_by_subnets: table[subnet] of set[string];
|
||||
|
||||
# Rules pertaining to a specific entity.
|
||||
# There always only can be one rule of each type for one entity.
|
||||
global rule_entities: table[Entity, RuleType] of Rule;
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(NetControl::LOG, [$columns=Info, $ev=log_netcontrol, $path="netcontrol"]);
|
||||
}
|
||||
|
||||
function entity_to_info(info: Info, e: Entity)
|
||||
{
|
||||
info$entity_type = fmt("%s", e$ty);
|
||||
|
||||
switch ( e$ty ) {
|
||||
case ADDRESS:
|
||||
info$entity = fmt("%s", e$ip);
|
||||
break;
|
||||
|
||||
case CONNECTION:
|
||||
info$entity = fmt("%s/%d<->%s/%d",
|
||||
e$conn$orig_h, e$conn$orig_p,
|
||||
e$conn$resp_h, e$conn$resp_p);
|
||||
break;
|
||||
|
||||
case FLOW:
|
||||
local ffrom_ip = "*";
|
||||
local ffrom_port = "*";
|
||||
local fto_ip = "*";
|
||||
local fto_port = "*";
|
||||
local ffrom_mac = "*";
|
||||
local fto_mac = "*";
|
||||
if ( e$flow?$src_h )
|
||||
ffrom_ip = cat(e$flow$src_h);
|
||||
if ( e$flow?$src_p )
|
||||
ffrom_port = fmt("%d", e$flow$src_p);
|
||||
if ( e$flow?$dst_h )
|
||||
fto_ip = cat(e$flow$dst_h);
|
||||
if ( e$flow?$dst_p )
|
||||
fto_port = fmt("%d", e$flow$dst_p);
|
||||
info$entity = fmt("%s/%s->%s/%s",
|
||||
ffrom_ip, ffrom_port,
|
||||
fto_ip, fto_port);
|
||||
if ( e$flow?$src_m || e$flow?$dst_m )
|
||||
{
|
||||
if ( e$flow?$src_m )
|
||||
ffrom_mac = e$flow$src_m;
|
||||
if ( e$flow?$dst_m )
|
||||
fto_mac = e$flow$dst_m;
|
||||
|
||||
info$entity = fmt("%s (%s->%s)", info$entity, ffrom_mac, fto_mac);
|
||||
}
|
||||
break;
|
||||
|
||||
case MAC:
|
||||
info$entity = e$mac;
|
||||
break;
|
||||
|
||||
default:
|
||||
info$entity = "<unknown entity type>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function rule_to_info(info: Info, r: Rule)
|
||||
{
|
||||
info$action = fmt("%s", r$ty);
|
||||
info$target = r$target;
|
||||
info$rule_id = r$id;
|
||||
info$expire = r$expire;
|
||||
info$priority = r$priority;
|
||||
|
||||
if ( r?$location && r$location != "" )
|
||||
info$location = r$location;
|
||||
|
||||
if ( r$ty == REDIRECT )
|
||||
info$mod = fmt("-> %d", r$out_port);
|
||||
|
||||
if ( r$ty == MODIFY )
|
||||
{
|
||||
local mfrom_ip = "_";
|
||||
local mfrom_port = "_";
|
||||
local mto_ip = "_";
|
||||
local mto_port = "_";
|
||||
local mfrom_mac = "_";
|
||||
local mto_mac = "_";
|
||||
if ( r$mod?$src_h )
|
||||
mfrom_ip = cat(r$mod$src_h);
|
||||
if ( r$mod?$src_p )
|
||||
mfrom_port = fmt("%d", r$mod$src_p);
|
||||
if ( r$mod?$dst_h )
|
||||
mto_ip = cat(r$mod$dst_h);
|
||||
if ( r$mod?$dst_p )
|
||||
mto_port = fmt("%d", r$mod$dst_p);
|
||||
|
||||
if ( r$mod?$src_m )
|
||||
mfrom_mac = r$mod$src_m;
|
||||
if ( r$mod?$dst_m )
|
||||
mto_mac = r$mod$dst_m;
|
||||
|
||||
info$mod = fmt("Src: %s/%s (%s) Dst: %s/%s (%s)",
|
||||
mfrom_ip, mfrom_port, mfrom_mac, mto_ip, mto_port, mto_mac);
|
||||
|
||||
if ( r$mod?$redirect_port )
|
||||
info$mod = fmt("%s -> %d", info$mod, r$mod$redirect_port);
|
||||
|
||||
}
|
||||
|
||||
entity_to_info(info, r$entity);
|
||||
}
|
||||
|
||||
function log_msg(msg: string, p: PluginState)
|
||||
{
|
||||
Log::write(LOG, [$ts=network_time(), $category=MESSAGE, $msg=msg, $plugin=p$plugin$name(p)]);
|
||||
}
|
||||
|
||||
function log_error(msg: string, p: PluginState)
|
||||
{
|
||||
Log::write(LOG, [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)]);
|
||||
}
|
||||
|
||||
function log_msg_no_plugin(msg: string)
|
||||
{
|
||||
Log::write(LOG, [$ts=network_time(), $category=MESSAGE, $msg=msg]);
|
||||
}
|
||||
|
||||
function log_rule(r: Rule, cmd: string, state: InfoState, p: PluginState, msg: string &default="")
|
||||
{
|
||||
local info: Info = [$ts=network_time()];
|
||||
info$category = RULE;
|
||||
info$cmd = cmd;
|
||||
info$state = state;
|
||||
info$plugin = p$plugin$name(p);
|
||||
if ( msg != "" )
|
||||
info$msg = msg;
|
||||
|
||||
rule_to_info(info, r);
|
||||
|
||||
Log::write(LOG, info);
|
||||
}
|
||||
|
||||
function log_rule_error(r: Rule, msg: string, p: PluginState)
|
||||
{
|
||||
local info: Info = [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)];
|
||||
rule_to_info(info, r);
|
||||
Log::write(LOG, info);
|
||||
}
|
||||
|
||||
function log_rule_no_plugin(r: Rule, state: InfoState, msg: string)
|
||||
{
|
||||
local info: Info = [$ts=network_time()];
|
||||
info$category = RULE;
|
||||
info$state = state;
|
||||
info$msg = msg;
|
||||
|
||||
rule_to_info(info, r);
|
||||
|
||||
Log::write(LOG, info);
|
||||
}
|
||||
|
||||
function whitelist_address(a: addr, t: interval, location: string &default="") : string
|
||||
{
|
||||
local e: Entity = [$ty=ADDRESS, $ip=addr_to_subnet(a)];
|
||||
local r: Rule = [$ty=WHITELIST, $priority=whitelist_priority, $target=FORWARD, $entity=e, $expire=t, $location=location];
|
||||
|
||||
return add_rule(r);
|
||||
}
|
||||
|
||||
function whitelist_subnet(s: subnet, t: interval, location: string &default="") : string
|
||||
{
|
||||
local e: Entity = [$ty=ADDRESS, $ip=s];
|
||||
local r: Rule = [$ty=WHITELIST, $priority=whitelist_priority, $target=FORWARD, $entity=e, $expire=t, $location=location];
|
||||
|
||||
return add_rule(r);
|
||||
}
|
||||
|
||||
|
||||
function redirect_flow(f: flow_id, out_port: count, t: interval, location: string &default="") : string
|
||||
{
|
||||
local flow = NetControl::Flow(
|
||||
$src_h=addr_to_subnet(f$src_h),
|
||||
$src_p=f$src_p,
|
||||
$dst_h=addr_to_subnet(f$dst_h),
|
||||
$dst_p=f$dst_p
|
||||
);
|
||||
local e: Entity = [$ty=FLOW, $flow=flow];
|
||||
local r: Rule = [$ty=REDIRECT, $target=FORWARD, $entity=e, $expire=t, $location=location, $out_port=out_port];
|
||||
|
||||
return add_rule(r);
|
||||
}
|
||||
|
||||
function quarantine_host(infected: addr, dns: addr, quarantine: addr, t: interval, location: string &default="") : vector of string
|
||||
{
|
||||
local orules: vector of string = vector();
|
||||
local edrop: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected))];
|
||||
local rdrop: Rule = [$ty=DROP, $target=FORWARD, $entity=edrop, $expire=t, $location=location];
|
||||
orules[|orules|] = add_rule(rdrop);
|
||||
|
||||
local todnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(dns), $dst_p=53/udp)];
|
||||
local todnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=todnse, $expire=t, $location=location, $mod=FlowMod($dst_h=quarantine), $priority=+5);
|
||||
orules[|orules|] = add_rule(todnsr);
|
||||
|
||||
local fromdnse: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(dns), $src_p=53/udp, $dst_h=addr_to_subnet(infected))];
|
||||
local fromdnsr = Rule($ty=MODIFY, $target=FORWARD, $entity=fromdnse, $expire=t, $location=location, $mod=FlowMod($src_h=dns), $priority=+5);
|
||||
orules[|orules|] = add_rule(fromdnsr);
|
||||
|
||||
local wle: Entity = [$ty=FLOW, $flow=Flow($src_h=addr_to_subnet(infected), $dst_h=addr_to_subnet(quarantine), $dst_p=80/tcp)];
|
||||
local wlr = Rule($ty=WHITELIST, $target=FORWARD, $entity=wle, $expire=t, $location=location, $priority=+5);
|
||||
orules[|orules|] = add_rule(wlr);
|
||||
|
||||
return orules;
|
||||
}
|
||||
|
||||
function check_plugins()
|
||||
{
|
||||
if ( plugins_active )
|
||||
return;
|
||||
|
||||
local all_active = T;
|
||||
for ( i in plugins )
|
||||
{
|
||||
local p = plugins[i];
|
||||
if ( p$_activated == F )
|
||||
all_active = F;
|
||||
}
|
||||
|
||||
if ( all_active )
|
||||
{
|
||||
plugins_active = T;
|
||||
|
||||
# Skip log message if there are no plugins
|
||||
if ( |plugins| > 0 )
|
||||
log_msg_no_plugin("plugin initialization done");
|
||||
|
||||
event NetControl::init_done();
|
||||
}
|
||||
}
|
||||
|
||||
function plugin_activated(p: PluginState)
|
||||
{
|
||||
local id = p$_id;
|
||||
if ( id !in plugin_ids )
|
||||
{
|
||||
log_error("unknown plugin activated", p);
|
||||
return;
|
||||
}
|
||||
plugin_ids[id]$_activated = T;
|
||||
log_msg("activation finished", p);
|
||||
|
||||
if ( bro_init_done )
|
||||
check_plugins();
|
||||
}
|
||||
|
||||
event bro_init() &priority=-5
|
||||
{
|
||||
event NetControl::init();
|
||||
}
|
||||
|
||||
event NetControl::init() &priority=-20
|
||||
{
|
||||
bro_init_done = T;
|
||||
|
||||
check_plugins();
|
||||
|
||||
if ( plugins_active == F )
|
||||
log_msg_no_plugin("waiting for plugins to initialize");
|
||||
}
|
||||
|
||||
# Low-level functions that only runs on the manager (or standalone) Bro node.
|
||||
|
||||
function activate_impl(p: PluginState, priority: int)
|
||||
{
|
||||
p$_priority = priority;
|
||||
plugins[|plugins|] = p;
|
||||
sort(plugins, function(p1: PluginState, p2: PluginState) : int { return p2$_priority - p1$_priority; });
|
||||
|
||||
plugin_ids[plugin_counter] = p;
|
||||
p$_id = plugin_counter;
|
||||
++plugin_counter;
|
||||
|
||||
# perform one-time initialization
|
||||
if ( p$plugin?$init )
|
||||
{
|
||||
log_msg(fmt("activating plugin with priority %d", priority), p);
|
||||
p$plugin$init(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
# no initialization necessary, mark plugin as active right away
|
||||
plugin_activated(p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function add_one_subnet_entry(s: subnet, r: Rule)
|
||||
{
|
||||
if ( ! check_subnet(s, rules_by_subnets) )
|
||||
rules_by_subnets[s] = set(r$id);
|
||||
else
|
||||
add rules_by_subnets[s][r$id];
|
||||
}
|
||||
|
||||
function add_subnet_entry(rule: Rule)
|
||||
{
|
||||
local e = rule$entity;
|
||||
if ( e$ty == ADDRESS )
|
||||
{
|
||||
add_one_subnet_entry(e$ip, rule);
|
||||
}
|
||||
else if ( e$ty == CONNECTION )
|
||||
{
|
||||
add_one_subnet_entry(addr_to_subnet(e$conn$orig_h), rule);
|
||||
add_one_subnet_entry(addr_to_subnet(e$conn$resp_h), rule);
|
||||
}
|
||||
else if ( e$ty == FLOW )
|
||||
{
|
||||
if ( e$flow?$src_h )
|
||||
add_one_subnet_entry(e$flow$src_h, rule);
|
||||
if ( e$flow?$dst_h )
|
||||
add_one_subnet_entry(e$flow$dst_h, rule);
|
||||
}
|
||||
}
|
||||
|
||||
function remove_one_subnet_entry(s: subnet, r: Rule)
|
||||
{
|
||||
if ( ! check_subnet(s, rules_by_subnets) )
|
||||
return;
|
||||
|
||||
if ( r$id !in rules_by_subnets[s] )
|
||||
return;
|
||||
|
||||
delete rules_by_subnets[s][r$id];
|
||||
if ( |rules_by_subnets[s]| == 0 )
|
||||
delete rules_by_subnets[s];
|
||||
}
|
||||
|
||||
function remove_subnet_entry(rule: Rule)
|
||||
{
|
||||
local e = rule$entity;
|
||||
if ( e$ty == ADDRESS )
|
||||
{
|
||||
remove_one_subnet_entry(e$ip, rule);
|
||||
}
|
||||
else if ( e$ty == CONNECTION )
|
||||
{
|
||||
remove_one_subnet_entry(addr_to_subnet(e$conn$orig_h), rule);
|
||||
remove_one_subnet_entry(addr_to_subnet(e$conn$resp_h), rule);
|
||||
}
|
||||
else if ( e$ty == FLOW )
|
||||
{
|
||||
if ( e$flow?$src_h )
|
||||
remove_one_subnet_entry(e$flow$src_h, rule);
|
||||
if ( e$flow?$dst_h )
|
||||
remove_one_subnet_entry(e$flow$dst_h, rule);
|
||||
}
|
||||
}
|
||||
|
||||
function find_rules_subnet(sn: subnet) : vector of Rule
|
||||
{
|
||||
local ret: vector of Rule = vector();
|
||||
|
||||
local matches = matching_subnets(sn, rules_by_subnets);
|
||||
|
||||
for ( m in matches )
|
||||
{
|
||||
local sn_entry = matches[m];
|
||||
local rule_ids = rules_by_subnets[sn_entry];
|
||||
for ( rule_id in rules_by_subnets[sn_entry] )
|
||||
{
|
||||
if ( rule_id in rules )
|
||||
ret[|ret|] = rules[rule_id];
|
||||
else
|
||||
Reporter::error("find_rules_subnet - internal data structure error, missing rule");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function find_rules_addr(ip: addr) : vector of Rule
|
||||
{
|
||||
return find_rules_subnet(addr_to_subnet(ip));
|
||||
}
|
||||
|
||||
function add_rule_impl(rule: Rule) : string
|
||||
{
|
||||
if ( ! plugins_active )
|
||||
{
|
||||
log_rule_no_plugin(rule, FAILED, "plugins not initialized yet");
|
||||
return "";
|
||||
}
|
||||
|
||||
rule$cid = ++rule_counter; # numeric id that can be used by plugins for their rules.
|
||||
|
||||
if ( ! rule?$id || rule$id == "" )
|
||||
rule$id = cat(rule$cid);
|
||||
|
||||
if ( ! hook NetControl::rule_policy(rule) )
|
||||
return "";
|
||||
|
||||
if ( [rule$entity, rule$ty] in rule_entities )
|
||||
{
|
||||
log_rule_no_plugin(rule, FAILED, "discarded duplicate insertion");
|
||||
return "";
|
||||
}
|
||||
|
||||
local accepted = F;
|
||||
local priority: int = +0;
|
||||
|
||||
for ( i in plugins )
|
||||
{
|
||||
local p = plugins[i];
|
||||
|
||||
if ( p$_activated == F )
|
||||
next;
|
||||
|
||||
# in this case, rule was accepted by earlier plugin and this plugin has a lower
|
||||
# priority. Abort and do not send there...
|
||||
if ( accepted == T && p$_priority != priority )
|
||||
break;
|
||||
|
||||
if ( p$plugin$add_rule(p, rule) )
|
||||
{
|
||||
accepted = T;
|
||||
priority = p$_priority;
|
||||
log_rule(rule, "ADD", REQUESTED, p);
|
||||
|
||||
add rule$_plugin_ids[p$_id];
|
||||
}
|
||||
}
|
||||
|
||||
if ( accepted )
|
||||
{
|
||||
rules[rule$id] = rule;
|
||||
rule_entities[rule$entity, rule$ty] = rule;
|
||||
|
||||
add_subnet_entry(rule);
|
||||
|
||||
return rule$id;
|
||||
}
|
||||
|
||||
log_rule_no_plugin(rule, FAILED, "not supported");
|
||||
return "";
|
||||
}
|
||||
|
||||
function remove_rule_plugin(r: Rule, p: PluginState): bool
|
||||
{
|
||||
local success = T;
|
||||
|
||||
if ( ! p$plugin$remove_rule(p, r) )
|
||||
{
|
||||
# still continue and send to other plugins
|
||||
log_rule_error(r, "remove failed", p);
|
||||
success = F;
|
||||
}
|
||||
else
|
||||
{
|
||||
log_rule(r, "REMOVE", REQUESTED, p);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
function remove_rule_impl(id: string) : bool
|
||||
{
|
||||
if ( id !in rules )
|
||||
{
|
||||
Reporter::error(fmt("Rule %s does not exist in NetControl::remove_rule", id));
|
||||
return F;
|
||||
}
|
||||
|
||||
local r = rules[id];
|
||||
|
||||
local success = T;
|
||||
for ( plugin_id in r$_active_plugin_ids )
|
||||
{
|
||||
local p = plugin_ids[plugin_id];
|
||||
success = remove_rule_plugin(r, p);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
function rule_expire_impl(r: Rule, p: PluginState) &priority=-5
|
||||
{
|
||||
# do not emit timeout events on shutdown
|
||||
if ( bro_is_terminating() )
|
||||
return;
|
||||
|
||||
if ( r$id !in rules )
|
||||
# Removed already.
|
||||
return;
|
||||
|
||||
event NetControl::rule_timeout(r, FlowInfo(), p); # timeout implementation will handle the removal
|
||||
}
|
||||
|
||||
function rule_added_impl(r: Rule, p: PluginState, msg: string &default="")
|
||||
{
|
||||
if ( r$id !in rules )
|
||||
{
|
||||
log_rule_error(r, "Addition of unknown rule", p);
|
||||
return;
|
||||
}
|
||||
|
||||
# use our version to prevent operating on copies.
|
||||
local rule = rules[r$id];
|
||||
if ( p$_id !in rule$_plugin_ids )
|
||||
{
|
||||
log_rule_error(rule, "Rule added to non-responsible plugin", p);
|
||||
return;
|
||||
}
|
||||
|
||||
log_rule(r, "ADD", SUCCEEDED, p, msg);
|
||||
|
||||
add rule$_active_plugin_ids[p$_id];
|
||||
if ( |rule$_plugin_ids| == |rule$_active_plugin_ids| )
|
||||
{
|
||||
# rule was completely added.
|
||||
rule$_added = T;
|
||||
}
|
||||
}
|
||||
|
||||
function rule_cleanup(r: Rule)
|
||||
{
|
||||
if ( |r$_active_plugin_ids| > 0 )
|
||||
return;
|
||||
|
||||
remove_subnet_entry(r);
|
||||
|
||||
delete rule_entities[r$entity, r$ty];
|
||||
delete rules[r$id];
|
||||
}
|
||||
|
||||
function rule_removed_impl(r: Rule, p: PluginState, msg: string &default="")
|
||||
{
|
||||
if ( r$id !in rules )
|
||||
{
|
||||
log_rule_error(r, "Removal of non-existing rule", p);
|
||||
return;
|
||||
}
|
||||
|
||||
# use our version to prevent operating on copies.
|
||||
local rule = rules[r$id];
|
||||
|
||||
if ( p$_id !in rule$_plugin_ids )
|
||||
{
|
||||
log_rule_error(r, "Removed from non-assigned plugin", p);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( p$_id in rule$_active_plugin_ids )
|
||||
{
|
||||
delete rule$_active_plugin_ids[p$_id];
|
||||
}
|
||||
|
||||
log_rule(rule, "REMOVE", SUCCEEDED, p, msg);
|
||||
rule_cleanup(rule);
|
||||
}
|
||||
|
||||
function rule_timeout_impl(r: Rule, i: FlowInfo, p: PluginState)
|
||||
{
|
||||
if ( r$id !in rules )
|
||||
{
|
||||
log_rule_error(r, "Timeout of non-existing rule", p);
|
||||
return;
|
||||
}
|
||||
|
||||
local rule = rules[r$id];
|
||||
|
||||
local msg = "";
|
||||
if ( i?$packet_count )
|
||||
msg = fmt("Packets: %d", i$packet_count);
|
||||
if ( i?$byte_count )
|
||||
{
|
||||
if ( msg != "" )
|
||||
msg = msg + " ";
|
||||
msg = fmt("%sBytes: %s", msg, i$byte_count);
|
||||
}
|
||||
|
||||
log_rule(rule, "EXPIRE", TIMEOUT, p, msg);
|
||||
|
||||
if ( ! p$plugin$can_expire )
|
||||
{
|
||||
# in this case, we actually have to delete the rule and the timeout
|
||||
# call just originated locally
|
||||
remove_rule_plugin(rule, p);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( p$_id !in rule$_plugin_ids )
|
||||
{
|
||||
log_rule_error(r, "Timeout from non-assigned plugin", p);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( p$_id in rule$_active_plugin_ids )
|
||||
{
|
||||
delete rule$_active_plugin_ids[p$_id];
|
||||
}
|
||||
|
||||
rule_cleanup(rule);
|
||||
}
|
||||
|
||||
function rule_error_impl(r: Rule, p: PluginState, msg: string &default="")
|
||||
{
|
||||
if ( r$id !in rules )
|
||||
{
|
||||
log_rule_error(r, "Error of non-existing rule", p);
|
||||
return;
|
||||
}
|
||||
|
||||
local rule = rules[r$id];
|
||||
|
||||
log_rule_error(rule, msg, p);
|
||||
|
||||
# Remove the plugin both from active and all plugins of the rule. If there
|
||||
# are no plugins left afterwards - delete it
|
||||
if ( p$_id !in rule$_plugin_ids )
|
||||
{
|
||||
log_rule_error(r, "Error from non-assigned plugin", p);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( p$_id in rule$_active_plugin_ids )
|
||||
{
|
||||
# error during removal. Let's pretend it worked.
|
||||
delete rule$_plugin_ids[p$_id];
|
||||
delete rule$_active_plugin_ids[p$_id];
|
||||
rule_cleanup(rule);
|
||||
}
|
||||
else
|
||||
{
|
||||
# error during insertion. Meh. If we are the only plugin, remove the rule again.
|
||||
# Otherwhise - keep it, minus us.
|
||||
delete rule$_plugin_ids[p$_id];
|
||||
if ( |rule$_plugin_ids| == 0 )
|
||||
{
|
||||
rule_cleanup(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
for ( id in rules )
|
||||
remove_rule(id);
|
||||
}
|
47
scripts/base/frameworks/netcontrol/non-cluster.bro
Normal file
47
scripts/base/frameworks/netcontrol/non-cluster.bro
Normal file
|
@ -0,0 +1,47 @@
|
|||
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);
|
||||
}
|
||||
|
||||
event rule_expire(r: Rule, p: PluginState) &priority=-5
|
||||
{
|
||||
rule_expire_impl(r, p);
|
||||
}
|
||||
|
||||
event rule_added(r: Rule, p: PluginState, msg: string &default="") &priority=5
|
||||
{
|
||||
rule_added_impl(r, p, msg);
|
||||
|
||||
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
|
||||
schedule r$expire { rule_expire(r, p) };
|
||||
}
|
||||
|
||||
event rule_removed(r: Rule, p: PluginState, msg: string &default="") &priority=-5
|
||||
{
|
||||
rule_removed_impl(r, p, msg);
|
||||
}
|
||||
|
||||
event rule_timeout(r: Rule, i: FlowInfo, p: PluginState) &priority=-5
|
||||
{
|
||||
rule_timeout_impl(r, i, p);
|
||||
}
|
||||
|
||||
event rule_error(r: Rule, p: PluginState, msg: string &default="") &priority=-5
|
||||
{
|
||||
rule_error_impl(r, p, msg);
|
||||
}
|
||||
|
89
scripts/base/frameworks/netcontrol/plugin.bro
Normal file
89
scripts/base/frameworks/netcontrol/plugin.bro
Normal file
|
@ -0,0 +1,89 @@
|
|||
##! Plugin interface for NetControl backends.
|
||||
|
||||
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;
|
||||
|
||||
## Set internally. Signifies if the plugin has returned that it has activated succesfully
|
||||
_activated: bool &default=F;
|
||||
};
|
||||
|
||||
# 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 inform 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.
|
||||
#
|
||||
# If this function is provided, NetControl assumes that the plugin has to
|
||||
# perform, potentially lengthy, initialization before the plugin will become
|
||||
# active. In this case, the plugin has to call ``NetControl::plugin_activated``,
|
||||
# once initialization finishes.
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
5
scripts/base/frameworks/netcontrol/plugins/__load__.bro
Normal file
5
scripts/base/frameworks/netcontrol/plugins/__load__.bro
Normal file
|
@ -0,0 +1,5 @@
|
|||
@load ./debug
|
||||
@load ./openflow
|
||||
@load ./packetfilter
|
||||
@load ./broker
|
||||
@load ./acld
|
294
scripts/base/frameworks/netcontrol/plugins/acld.bro
Normal file
294
scripts/base/frameworks/netcontrol/plugins/acld.bro
Normal 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;
|
||||
}
|
||||
|
163
scripts/base/frameworks/netcontrol/plugins/broker.bro
Normal file
163
scripts/base/frameworks/netcontrol/plugins/broker.bro
Normal 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;
|
||||
}
|
99
scripts/base/frameworks/netcontrol/plugins/debug.bro
Normal file
99
scripts/base/frameworks/netcontrol/plugins/debug.bro
Normal 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;
|
||||
}
|
||||
|
||||
|
432
scripts/base/frameworks/netcontrol/plugins/openflow.bro
Normal file
432
scripts/base/frameworks/netcontrol/plugins/openflow.bro
Normal 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;
|
||||
}
|
113
scripts/base/frameworks/netcontrol/plugins/packetfilter.bro
Normal file
113
scripts/base/frameworks/netcontrol/plugins/packetfilter.bro
Normal 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;
|
||||
}
|
||||
|
69
scripts/base/frameworks/netcontrol/shunt.bro
Normal file
69
scripts/base/frameworks/netcontrol/shunt.bro
Normal file
|
@ -0,0 +1,69 @@
|
|||
##! Implementation of the shunt functionality for NetControl.
|
||||
|
||||
module NetControl;
|
||||
|
||||
@load ./main
|
||||
|
||||
export {
|
||||
redef enum Log::ID += { SHUNT };
|
||||
|
||||
## 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;
|
||||
|
||||
type ShuntInfo: record {
|
||||
## Time at which the recorded activity occurred.
|
||||
ts: time &log;
|
||||
## ID of the rule; unique during each Bro run
|
||||
rule_id: string &log;
|
||||
## Flow ID of the shunted flow
|
||||
f: flow_id &log;
|
||||
## Expiry time of the shunt
|
||||
expire: interval &log;
|
||||
## Location where the underlying action was triggered.
|
||||
location: string &log &optional;
|
||||
};
|
||||
|
||||
## Event that can be handled to access the :bro:type:`NetControl::ShuntInfo`
|
||||
## record as it is sent on to the logging framework.
|
||||
global log_netcontrol_shunt: event(rec: ShuntInfo);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(NetControl::SHUNT, [$columns=ShuntInfo, $ev=log_netcontrol_shunt, $path="netcontrol_shunt"]);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
local id = add_rule(r);
|
||||
|
||||
# Error should already be logged
|
||||
if ( id == "" )
|
||||
return id;
|
||||
|
||||
local log = ShuntInfo($ts=network_time(), $rule_id=id, $f=f, $expire=t);
|
||||
if ( location != "" )
|
||||
log$location=location;
|
||||
|
||||
Log::write(SHUNT, log);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
109
scripts/base/frameworks/netcontrol/types.bro
Normal file
109
scripts/base/frameworks/netcontrol/types.bro
Normal file
|
@ -0,0 +1,109 @@
|
|||
##! Types used by the NetControl framework.
|
||||
|
||||
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.
|
||||
|
||||
out_port: count &optional; ##< Argument for bro:id:`REDIRECT` rules.
|
||||
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
|
||||
};
|
||||
}
|
13
scripts/base/frameworks/openflow/__load__.bro
Normal file
13
scripts/base/frameworks/openflow/__load__.bro
Normal file
|
@ -0,0 +1,13 @@
|
|||
@load ./consts
|
||||
@load ./types
|
||||
@load ./main
|
||||
@load ./plugins
|
||||
|
||||
# The cluster framework must be loaded first.
|
||||
@load base/frameworks/cluster
|
||||
|
||||
@if ( Cluster::is_enabled() )
|
||||
@load ./cluster
|
||||
@else
|
||||
@load ./non-cluster
|
||||
@endif
|
120
scripts/base/frameworks/openflow/cluster.bro
Normal file
120
scripts/base/frameworks/openflow/cluster.bro
Normal file
|
@ -0,0 +1,120 @@
|
|||
##! Cluster support for the OpenFlow framework.
|
||||
|
||||
@load ./main
|
||||
@load base/frameworks/cluster
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
export {
|
||||
## This is the event used to transport flow_mod messages to the manager.
|
||||
global cluster_flow_mod: event(name: string, match: ofp_match, flow_mod: ofp_flow_mod);
|
||||
|
||||
## This is the event used to transport flow_clear messages to the manager.
|
||||
global cluster_flow_clear: event(name: string);
|
||||
}
|
||||
|
||||
## Workers need ability to forward commands to manager.
|
||||
redef Cluster::worker2manager_events += /OpenFlow::cluster_flow_(mod|clear)/;
|
||||
|
||||
# the flow_mod function wrapper
|
||||
function flow_mod(controller: Controller, match: ofp_match, flow_mod: ofp_flow_mod): bool
|
||||
{
|
||||
if ( ! controller?$flow_mod )
|
||||
return F;
|
||||
|
||||
if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
return controller$flow_mod(controller$state, match, flow_mod);
|
||||
else
|
||||
event OpenFlow::cluster_flow_mod(controller$state$_name, match, flow_mod);
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function flow_clear(controller: Controller): bool
|
||||
{
|
||||
if ( ! controller?$flow_clear )
|
||||
return F;
|
||||
|
||||
if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
return controller$flow_clear(controller$state);
|
||||
else
|
||||
event OpenFlow::cluster_flow_clear(controller$state$_name);
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
@if ( Cluster::local_node_type() == Cluster::MANAGER )
|
||||
event OpenFlow::cluster_flow_mod(name: string, match: ofp_match, flow_mod: ofp_flow_mod)
|
||||
{
|
||||
if ( name !in name_to_controller )
|
||||
{
|
||||
Reporter::error(fmt("OpenFlow controller %s not found in mapping on master", name));
|
||||
return;
|
||||
}
|
||||
|
||||
local c = name_to_controller[name];
|
||||
|
||||
if ( ! c$state$_activated )
|
||||
return;
|
||||
|
||||
if ( c?$flow_mod )
|
||||
c$flow_mod(c$state, match, flow_mod);
|
||||
}
|
||||
|
||||
event OpenFlow::cluster_flow_clear(name: string)
|
||||
{
|
||||
if ( name !in name_to_controller )
|
||||
{
|
||||
Reporter::error(fmt("OpenFlow controller %s not found in mapping on master", name));
|
||||
return;
|
||||
}
|
||||
|
||||
local c = name_to_controller[name];
|
||||
|
||||
if ( ! c$state$_activated )
|
||||
return;
|
||||
|
||||
if ( c?$flow_clear )
|
||||
c$flow_clear(c$state);
|
||||
}
|
||||
@endif
|
||||
|
||||
function register_controller(tpe: OpenFlow::Plugin, name: string, controller: Controller)
|
||||
{
|
||||
controller$state$_name = cat(tpe, name);
|
||||
controller$state$_plugin = tpe;
|
||||
|
||||
# we only run the init functions on the manager.
|
||||
if ( Cluster::local_node_type() != Cluster::MANAGER )
|
||||
return;
|
||||
|
||||
register_controller_impl(tpe, name, controller);
|
||||
}
|
||||
|
||||
function unregister_controller(controller: Controller)
|
||||
{
|
||||
# we only run the on the manager.
|
||||
if ( Cluster::local_node_type() != Cluster::MANAGER )
|
||||
return;
|
||||
|
||||
unregister_controller_impl(controller);
|
||||
}
|
||||
|
||||
function lookup_controller(name: string): vector of Controller
|
||||
{
|
||||
# we only run the on the manager. Otherwhise we don't have a mapping or state -> return empty
|
||||
if ( Cluster::local_node_type() != Cluster::MANAGER )
|
||||
return vector();
|
||||
|
||||
# I am not quite sure if we can actually get away with this - in the
|
||||
# current state, this means that the individual nodes cannot lookup
|
||||
# a controller by name.
|
||||
#
|
||||
# This means that there can be no reactions to things on the actual
|
||||
# worker nodes - because they cannot look up a name. On the other hand -
|
||||
# currently we also do not even send the events to the worker nodes (at least
|
||||
# not if we are using broker). Because of that I am not really feeling that
|
||||
# badly about it...
|
||||
|
||||
return lookup_controller_impl(name);
|
||||
}
|
229
scripts/base/frameworks/openflow/consts.bro
Normal file
229
scripts/base/frameworks/openflow/consts.bro
Normal file
|
@ -0,0 +1,229 @@
|
|||
##! Constants used by the OpenFlow framework.
|
||||
|
||||
# All types/constants not specific to OpenFlow will be defined here
|
||||
# unitl they somehow get into Bro.
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
# Some cookie specific constants.
|
||||
# first 24 bits
|
||||
const COOKIE_BID_SIZE = 16777216;
|
||||
# start at bit 40 (1 << 40)
|
||||
const COOKIE_BID_START = 1099511627776;
|
||||
# bro specific cookie ID shall have the 42 bit set (1 << 42)
|
||||
const BRO_COOKIE_ID = 4;
|
||||
# 8 bits group identifier
|
||||
const COOKIE_GID_SIZE = 256;
|
||||
# start at bit 32 (1 << 32)
|
||||
const COOKIE_GID_START = 4294967296;
|
||||
# 32 bits unique identifier
|
||||
const COOKIE_UID_SIZE = 4294967296;
|
||||
# start at bit 0 (1 << 0)
|
||||
const COOKIE_UID_START = 0;
|
||||
|
||||
export {
|
||||
# All ethertypes can be found at
|
||||
# http://standards.ieee.org/develop/regauth/ethertype/eth.txt
|
||||
# but are not interesting for us at this point
|
||||
#type ethertype: enum {
|
||||
# Internet protocol version 4
|
||||
const ETH_IPv4 = 0x0800;
|
||||
# Address resolution protocol
|
||||
const ETH_ARP = 0x0806;
|
||||
# Wake on LAN
|
||||
const ETH_WOL = 0x0842;
|
||||
# Reverse address resolution protocol
|
||||
const ETH_RARP = 0x8035;
|
||||
# Appletalk
|
||||
const ETH_APPLETALK = 0x809B;
|
||||
# Appletalk address resolution protocol
|
||||
const ETH_APPLETALK_ARP = 0x80F3;
|
||||
# IEEE 802.1q & IEEE 802.1aq
|
||||
const ETH_VLAN = 0x8100;
|
||||
# Novell IPX old
|
||||
const ETH_IPX_OLD = 0x8137;
|
||||
# Novell IPX
|
||||
const ETH_IPX = 0x8138;
|
||||
# Internet protocol version 6
|
||||
const ETH_IPv6 = 0x86DD;
|
||||
# IEEE 802.3x
|
||||
const ETH_ETHER_FLOW_CONTROL = 0x8808;
|
||||
# Multiprotocol Label Switching unicast
|
||||
const ETH_MPLS_UNICAST = 0x8847;
|
||||
# Multiprotocol Label Switching multicast
|
||||
const ETH_MPLS_MULTICAST = 0x8848;
|
||||
# Point-to-point protocol over Ethernet discovery phase (rfc2516)
|
||||
const ETH_PPPOE_DISCOVERY = 0x8863;
|
||||
# Point-to-point protocol over Ethernet session phase (rfc2516)
|
||||
const ETH_PPPOE_SESSION = 0x8864;
|
||||
# Jumbo frames
|
||||
const ETH_JUMBO_FRAMES = 0x8870;
|
||||
# IEEE 802.1X
|
||||
const ETH_EAP_OVER_LAN = 0x888E;
|
||||
# IEEE 802.1ad & IEEE 802.1aq
|
||||
const ETH_PROVIDER_BRIDING = 0x88A8;
|
||||
# IEEE 802.1ae
|
||||
const ETH_MAC_SECURITY = 0x88E5;
|
||||
# IEEE 802.1ad (QinQ)
|
||||
const ETH_QINQ = 0x9100;
|
||||
#};
|
||||
|
||||
# A list of ip protocol numbers can be found at
|
||||
# http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers
|
||||
#type iptype: enum {
|
||||
# IPv6 Hop-by-Hop Option (RFC2460)
|
||||
const IP_HOPOPT = 0x00;
|
||||
# Internet Control Message Protocol (RFC792)
|
||||
const IP_ICMP = 0x01;
|
||||
# Internet Group Management Protocol (RFC1112)
|
||||
const IP_IGMP = 0x02;
|
||||
# Gateway-to-Gateway Protocol (RFC823)
|
||||
const IP_GGP = 0x03;
|
||||
# IP-Within-IP (encapsulation) (RFC2003)
|
||||
const IP_IPIP = 0x04;
|
||||
# Internet Stream Protocol (RFC1190;RFC1819)
|
||||
const IP_ST = 0x05;
|
||||
# Tansmission Control Protocol (RFC793)
|
||||
const IP_TCP = 0x06;
|
||||
# Core-based trees (RFC2189)
|
||||
const IP_CBT = 0x07;
|
||||
# Exterior Gateway Protocol (RFC888)
|
||||
const IP_EGP = 0x08;
|
||||
# Interior Gateway Protocol (any private interior
|
||||
# gateway (used by Cisco for their IGRP))
|
||||
const IP_IGP = 0x09;
|
||||
# User Datagram Protocol (RFC768)
|
||||
const IP_UDP = 0x11;
|
||||
# Reliable Datagram Protocol (RFC908)
|
||||
const IP_RDP = 0x1B;
|
||||
# IPv6 Encapsulation (RFC2473)
|
||||
const IP_IPv6 = 0x29;
|
||||
# Resource Reservation Protocol (RFC2205)
|
||||
const IP_RSVP = 0x2E;
|
||||
# Generic Routing Encapsulation (RFC2784;RFC2890)
|
||||
const IP_GRE = 0x2F;
|
||||
# Open Shortest Path First (RFC1583)
|
||||
const IP_OSPF = 0x59;
|
||||
# Multicast Transport Protocol
|
||||
const IP_MTP = 0x5C;
|
||||
# IP-within-IP Encapsulation Protocol (RFC2003)
|
||||
### error 0x5E;
|
||||
# Ethernet-within-IP Encapsulation Protocol (RFC3378)
|
||||
const IP_ETHERIP = 0x61;
|
||||
# Layer Two Tunneling Protocol Version 3 (RFC3931)
|
||||
const IP_L2TP = 0x73;
|
||||
# Intermediate System to Intermediate System (IS-IS) Protocol over IPv4 (RFC1142;RFC1195)
|
||||
const IP_ISIS = 0x7C;
|
||||
# Fibre Channel
|
||||
const IP_FC = 0x85;
|
||||
# Multiprotocol Label Switching Encapsulated in IP (RFC4023)
|
||||
const IP_MPLS = 0x89;
|
||||
#};
|
||||
|
||||
## Return value for a cookie from a flow
|
||||
## which is not added, modified or deleted
|
||||
## from the bro openflow framework
|
||||
const INVALID_COOKIE = 0xffffffffffffffff;
|
||||
# Openflow pysical port definitions
|
||||
## Send the packet out the input port. This
|
||||
## virual port must be explicitly used in
|
||||
## order to send back out of the input port.
|
||||
const OFPP_IN_PORT = 0xfffffff8;
|
||||
## Perform actions in flow table.
|
||||
## NB: This can only be the destination port
|
||||
## for packet-out messages.
|
||||
const OFPP_TABLE = 0xfffffff9;
|
||||
## Process with normal L2/L3 switching.
|
||||
const OFPP_NORMAL = 0xfffffffa;
|
||||
## All pysical ports except input port and
|
||||
## those disabled by STP.
|
||||
const OFPP_FLOOD = 0xfffffffb;
|
||||
## All pysical ports except input port.
|
||||
const OFPP_ALL = 0xfffffffc;
|
||||
## Send to controller.
|
||||
const OFPP_CONTROLLER = 0xfffffffd;
|
||||
## Local openflow "port".
|
||||
const OFPP_LOCAL = 0xfffffffe;
|
||||
## Wildcard port used only for flow mod (delete) and flow stats requests.
|
||||
const OFPP_ANY = 0xffffffff;
|
||||
# Openflow no buffer constant.
|
||||
const OFP_NO_BUFFER = 0xffffffff;
|
||||
## Send flow removed message when flow
|
||||
## expires or is deleted.
|
||||
const OFPFF_SEND_FLOW_REM = 0x1;
|
||||
## Check for overlapping entries first.
|
||||
const OFPFF_CHECK_OVERLAP = 0x2;
|
||||
## Remark this is for emergency.
|
||||
## Flows added with this are only used
|
||||
## when the controller is disconnected.
|
||||
const OFPFF_EMERG = 0x4;
|
||||
|
||||
# Wildcard table used for table config,
|
||||
# flow stats and flow deletes.
|
||||
const OFPTT_ALL = 0xff;
|
||||
|
||||
## Openflow action_type definitions
|
||||
##
|
||||
## The openflow action type defines
|
||||
## what actions openflow can take
|
||||
## to modify a packet
|
||||
type ofp_action_type: enum {
|
||||
## Output to switch port.
|
||||
OFPAT_OUTPUT = 0x0000,
|
||||
## Set the 802.1q VLAN id.
|
||||
OFPAT_SET_VLAN_VID = 0x0001,
|
||||
## Set the 802.1q priority.
|
||||
OFPAT_SET_VLAN_PCP = 0x0002,
|
||||
## Strip the 802.1q header.
|
||||
OFPAT_STRIP_VLAN = 0x0003,
|
||||
## Ethernet source address.
|
||||
OFPAT_SET_DL_SRC = 0x0004,
|
||||
## Ethernet destination address.
|
||||
OFPAT_SET_DL_DST = 0x0005,
|
||||
## IP source address
|
||||
OFPAT_SET_NW_SRC = 0x0006,
|
||||
## IP destination address.
|
||||
OFPAT_SET_NW_DST = 0x0007,
|
||||
## IP ToS (DSCP field, 6 bits).
|
||||
OFPAT_SET_NW_TOS = 0x0008,
|
||||
## TCP/UDP source port.
|
||||
OFPAT_SET_TP_SRC = 0x0009,
|
||||
## TCP/UDP destination port.
|
||||
OFPAT_SET_TP_DST = 0x000a,
|
||||
## Output to queue.
|
||||
OFPAT_ENQUEUE = 0x000b,
|
||||
## Vendor specific
|
||||
OFPAT_VENDOR = 0xffff,
|
||||
};
|
||||
|
||||
## Openflow flow_mod_command definitions
|
||||
##
|
||||
## The openflow flow_mod_command describes
|
||||
## of what kind an action is.
|
||||
type ofp_flow_mod_command: enum {
|
||||
## New flow.
|
||||
OFPFC_ADD = 0x0,
|
||||
## Modify all matching flows.
|
||||
OFPFC_MODIFY = 0x1,
|
||||
## Modify entry strictly matching wildcards.
|
||||
OFPFC_MODIFY_STRICT = 0x2,
|
||||
## Delete all matching flows.
|
||||
OFPFC_DELETE = 0x3,
|
||||
## Strictly matching wildcards and priority.
|
||||
OFPFC_DELETE_STRICT = 0x4,
|
||||
};
|
||||
|
||||
## Openflow config flag definitions
|
||||
##
|
||||
## TODO: describe
|
||||
type ofp_config_flags: enum {
|
||||
## No special handling for fragments.
|
||||
OFPC_FRAG_NORMAL = 0,
|
||||
## Drop fragments.
|
||||
OFPC_FRAG_DROP = 1,
|
||||
## Reassemble (only if OFPC_IP_REASM set).
|
||||
OFPC_FRAG_REASM = 2,
|
||||
OFPC_FRAG_MASK = 3,
|
||||
};
|
||||
|
||||
}
|
289
scripts/base/frameworks/openflow/main.bro
Normal file
289
scripts/base/frameworks/openflow/main.bro
Normal file
|
@ -0,0 +1,289 @@
|
|||
##! Bro's OpenFlow control framework
|
||||
##!
|
||||
##! This plugin-based framework allows to control OpenFlow capable
|
||||
##! switches by implementing communication to an OpenFlow controller
|
||||
##! via plugins. The framework has to be instantiated via the new function
|
||||
##! in one of the plugins. This framework only offers very low-level
|
||||
##! functionality; if you want to use OpenFlow capable switches, e.g.,
|
||||
##! for shunting, please look at the PACF framework, which provides higher
|
||||
##! level functions and can use the OpenFlow framework as a backend.
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
@load ./consts
|
||||
@load ./types
|
||||
|
||||
export {
|
||||
## Global flow_mod function.
|
||||
##
|
||||
## controller: The controller which should execute the flow modification
|
||||
##
|
||||
## match: The ofp_match record which describes the flow to match.
|
||||
##
|
||||
## flow_mod: The openflow flow_mod record which describes the action to take.
|
||||
##
|
||||
## Returns: F on error or if the plugin does not support the operation, T when the operation was queued.
|
||||
global flow_mod: function(controller: Controller, match: ofp_match, flow_mod: ofp_flow_mod): bool;
|
||||
|
||||
## Clear the current flow table of the controller.
|
||||
##
|
||||
## controller: The controller which should execute the flow modification
|
||||
##
|
||||
## Returns: F on error or if the plugin does not support the operation, T when the operation was queued.
|
||||
global flow_clear: function(controller: Controller): bool;
|
||||
|
||||
## Event confirming successful modification of a flow rule.
|
||||
##
|
||||
## name: The unique name of the OpenFlow controller from which this event originated.
|
||||
##
|
||||
## match: The ofp_match record which describes the flow to match.
|
||||
##
|
||||
## flow_mod: The openflow flow_mod record which describes the action to take.
|
||||
##
|
||||
## msg: An optional informational message by the plugin.
|
||||
global flow_mod_success: event(name: string, match: ofp_match, flow_mod: ofp_flow_mod, msg: string &default="");
|
||||
|
||||
## Reports an error while installing a flow Rule.
|
||||
##
|
||||
## name: The unique name of the OpenFlow controller from which this event originated.
|
||||
##
|
||||
## match: The ofp_match record which describes the flow to match.
|
||||
##
|
||||
## flow_mod: The openflow flow_mod record which describes the action to take.
|
||||
##
|
||||
## msg: Message to describe the event.
|
||||
global flow_mod_failure: event(name: string, match: ofp_match, flow_mod: ofp_flow_mod, msg: string &default="");
|
||||
|
||||
## Reports that a flow was removed by the switch because of either the hard or the idle timeout.
|
||||
## This message is only generated by controllers that indicate that they support flow removal
|
||||
## in supports_flow_removed.
|
||||
##
|
||||
## name: The unique name of the OpenFlow controller from which this event originated.
|
||||
##
|
||||
## match: The ofp_match record which was used to create the flow.
|
||||
##
|
||||
## cookie: The cookie that was specified when creating the flow.
|
||||
##
|
||||
## priority: The priority that was specified when creating the flow.
|
||||
##
|
||||
## reason: The reason for flow removal (OFPRR_*)
|
||||
##
|
||||
## duration_sec: duration of the flow in seconds
|
||||
##
|
||||
## packet_count: packet count of the flow
|
||||
##
|
||||
## byte_count: byte count of the flow
|
||||
global flow_removed: event(name: string, match: ofp_match, cookie: count, priority: count, reason: count, duration_sec: count, idle_timeout: count, packet_count: count, byte_count: count);
|
||||
|
||||
## Convert a conn_id record into an ofp_match record that can be used to
|
||||
## create match objects for OpenFlow.
|
||||
##
|
||||
## id: the conn_id record that describes the record.
|
||||
##
|
||||
## reverse: reverse the sources and destinations when creating the match record (default F)
|
||||
##
|
||||
## Returns: ofp_match object for the conn_id record.
|
||||
global match_conn: function(id: conn_id, reverse: bool &default=F): ofp_match;
|
||||
|
||||
# ###
|
||||
# ### Low-level functions for cookie handling and plugin registration.
|
||||
# ###
|
||||
|
||||
## Function to get the unique id out of a given cookie.
|
||||
##
|
||||
## cookie: The openflow match cookie.
|
||||
##
|
||||
## Returns: The cookie unique id.
|
||||
global get_cookie_uid: function(cookie: count): count;
|
||||
|
||||
## Function to get the group id out of a given cookie.
|
||||
##
|
||||
## cookie: The openflow match cookie.
|
||||
##
|
||||
## Returns: The cookie group id.
|
||||
global get_cookie_gid: function(cookie: count): count;
|
||||
|
||||
## Function to generate a new cookie using our group id.
|
||||
##
|
||||
## cookie: The openflow match cookie.
|
||||
##
|
||||
## Returns: The cookie group id.
|
||||
global generate_cookie: function(cookie: count &default=0): count;
|
||||
|
||||
## Function to register a controller instance. This function
|
||||
## is called automatically by the plugin _new functions.
|
||||
##
|
||||
## tpe: type of this plugin
|
||||
##
|
||||
## name: unique name of this controller instance.
|
||||
##
|
||||
## controller: The controller to register
|
||||
global register_controller: function(tpe: OpenFlow::Plugin, name: string, controller: Controller);
|
||||
|
||||
## Function to unregister a controller instance. This function
|
||||
## should be called when a specific controller should no longer
|
||||
## be used.
|
||||
##
|
||||
## controller: The controller to unregister
|
||||
global unregister_controller: function(controller: Controller);
|
||||
|
||||
## Function to signal that a controller finished activation and is
|
||||
## ready to use. Will throw the ``OpenFlow::controller_activated``
|
||||
## event.
|
||||
global controller_init_done: function(controller: Controller);
|
||||
|
||||
## Event that is raised once a controller finishes initialization
|
||||
## and is completely activated.
|
||||
## name: unique name of this controller instance.
|
||||
##
|
||||
## controller: The controller that finished activation.
|
||||
global OpenFlow::controller_activated: event(name: string, controller: Controller);
|
||||
|
||||
## Function to lookup a controller instance by name
|
||||
##
|
||||
## name: unique name of the controller to look up
|
||||
##
|
||||
## Returns: one element vector with controller, if found. Empty vector otherwhise.
|
||||
global lookup_controller: function(name: string): vector of Controller;
|
||||
}
|
||||
|
||||
global name_to_controller: table[string] of Controller;
|
||||
|
||||
|
||||
function match_conn(id: conn_id, reverse: bool &default=F): ofp_match
|
||||
{
|
||||
local dl_type = ETH_IPv4;
|
||||
local proto = IP_TCP;
|
||||
|
||||
local orig_h: addr;
|
||||
local orig_p: port;
|
||||
local resp_h: addr;
|
||||
local resp_p: port;
|
||||
|
||||
if ( reverse == F )
|
||||
{
|
||||
orig_h = id$orig_h;
|
||||
orig_p = id$orig_p;
|
||||
resp_h = id$resp_h;
|
||||
resp_p = id$resp_p;
|
||||
}
|
||||
else
|
||||
{
|
||||
orig_h = id$resp_h;
|
||||
orig_p = id$resp_p;
|
||||
resp_h = id$orig_h;
|
||||
resp_p = id$orig_p;
|
||||
}
|
||||
|
||||
if ( is_v6_addr(orig_h) )
|
||||
dl_type = ETH_IPv6;
|
||||
|
||||
if ( is_udp_port(orig_p) )
|
||||
proto = IP_UDP;
|
||||
else if ( is_icmp_port(orig_p) )
|
||||
proto = IP_ICMP;
|
||||
|
||||
return ofp_match(
|
||||
$dl_type=dl_type,
|
||||
$nw_proto=proto,
|
||||
$nw_src=addr_to_subnet(orig_h),
|
||||
$tp_src=port_to_count(orig_p),
|
||||
$nw_dst=addr_to_subnet(resp_h),
|
||||
$tp_dst=port_to_count(resp_p)
|
||||
);
|
||||
}
|
||||
|
||||
# local function to forge a flow_mod cookie for this framework.
|
||||
# all flow entries from the openflow framework should have the
|
||||
# 42 bit of the cookie set.
|
||||
function generate_cookie(cookie: count &default=0): count
|
||||
{
|
||||
local c = BRO_COOKIE_ID * COOKIE_BID_START;
|
||||
|
||||
if ( cookie >= COOKIE_UID_SIZE )
|
||||
Reporter::warning(fmt("The given cookie uid '%d' is > 32bit and will be discarded", cookie));
|
||||
else
|
||||
c += cookie;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
# local function to check if a given flow_mod cookie is forged from this framework.
|
||||
function is_valid_cookie(cookie: count): bool
|
||||
{
|
||||
if ( cookie / COOKIE_BID_START == BRO_COOKIE_ID )
|
||||
return T;
|
||||
|
||||
Reporter::warning(fmt("The given Openflow cookie '%d' is not valid", cookie));
|
||||
|
||||
return F;
|
||||
}
|
||||
|
||||
function get_cookie_uid(cookie: count): count
|
||||
{
|
||||
if( is_valid_cookie(cookie) )
|
||||
return (cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START));
|
||||
|
||||
return INVALID_COOKIE;
|
||||
}
|
||||
|
||||
function get_cookie_gid(cookie: count): count
|
||||
{
|
||||
if( is_valid_cookie(cookie) )
|
||||
return (
|
||||
(cookie - (COOKIE_BID_START * BRO_COOKIE_ID) -
|
||||
(cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START))) /
|
||||
COOKIE_GID_START
|
||||
);
|
||||
|
||||
return INVALID_COOKIE;
|
||||
}
|
||||
|
||||
function controller_init_done(controller: Controller)
|
||||
{
|
||||
if ( controller$state$_name !in name_to_controller )
|
||||
{
|
||||
Reporter::error(fmt("Openflow initialized unknown plugin %s successfully?", controller$state$_name));
|
||||
return;
|
||||
}
|
||||
|
||||
controller$state$_activated = T;
|
||||
event OpenFlow::controller_activated(controller$state$_name, controller);
|
||||
}
|
||||
|
||||
# Functions that are called from cluster.bro and non-cluster.bro
|
||||
|
||||
function register_controller_impl(tpe: OpenFlow::Plugin, name: string, controller: Controller)
|
||||
{
|
||||
if ( controller$state$_name in name_to_controller )
|
||||
{
|
||||
Reporter::error(fmt("OpenFlow Controller %s was already registered. Ignored duplicate registration", controller$state$_name));
|
||||
return;
|
||||
}
|
||||
|
||||
name_to_controller[controller$state$_name] = controller;
|
||||
|
||||
if ( controller?$init )
|
||||
controller$init(controller$state);
|
||||
else
|
||||
controller_init_done(controller);
|
||||
}
|
||||
|
||||
function unregister_controller_impl(controller: Controller)
|
||||
{
|
||||
if ( controller$state$_name in name_to_controller )
|
||||
delete name_to_controller[controller$state$_name];
|
||||
else
|
||||
Reporter::error("OpenFlow Controller %s was not registered in unregister.");
|
||||
|
||||
if ( controller?$destroy )
|
||||
controller$destroy(controller$state);
|
||||
}
|
||||
|
||||
function lookup_controller_impl(name: string): vector of Controller
|
||||
{
|
||||
if ( name in name_to_controller )
|
||||
return vector(name_to_controller[name]);
|
||||
else
|
||||
return vector();
|
||||
}
|
44
scripts/base/frameworks/openflow/non-cluster.bro
Normal file
44
scripts/base/frameworks/openflow/non-cluster.bro
Normal file
|
@ -0,0 +1,44 @@
|
|||
@load ./main
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
# the flow_mod function wrapper
|
||||
function flow_mod(controller: Controller, match: ofp_match, flow_mod: ofp_flow_mod): bool
|
||||
{
|
||||
if ( ! controller$state$_activated )
|
||||
return F;
|
||||
|
||||
if ( controller?$flow_mod )
|
||||
return controller$flow_mod(controller$state, match, flow_mod);
|
||||
else
|
||||
return F;
|
||||
}
|
||||
|
||||
function flow_clear(controller: Controller): bool
|
||||
{
|
||||
if ( ! controller$state$_activated )
|
||||
return F;
|
||||
|
||||
if ( controller?$flow_clear )
|
||||
return controller$flow_clear(controller$state);
|
||||
else
|
||||
return F;
|
||||
}
|
||||
|
||||
function register_controller(tpe: OpenFlow::Plugin, name: string, controller: Controller)
|
||||
{
|
||||
controller$state$_name = cat(tpe, name);
|
||||
controller$state$_plugin = tpe;
|
||||
|
||||
register_controller_impl(tpe, name, controller);
|
||||
}
|
||||
|
||||
function unregister_controller(controller: Controller)
|
||||
{
|
||||
unregister_controller_impl(controller);
|
||||
}
|
||||
|
||||
function lookup_controller(name: string): vector of Controller
|
||||
{
|
||||
return lookup_controller_impl(name);
|
||||
}
|
3
scripts/base/frameworks/openflow/plugins/__load__.bro
Normal file
3
scripts/base/frameworks/openflow/plugins/__load__.bro
Normal file
|
@ -0,0 +1,3 @@
|
|||
@load ./ryu
|
||||
@load ./log
|
||||
@load ./broker
|
95
scripts/base/frameworks/openflow/plugins/broker.bro
Normal file
95
scripts/base/frameworks/openflow/plugins/broker.bro
Normal file
|
@ -0,0 +1,95 @@
|
|||
##! OpenFlow plugin for interfacing to controllers via Broker.
|
||||
|
||||
@load base/frameworks/openflow
|
||||
@load base/frameworks/broker
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
export {
|
||||
redef enum Plugin += {
|
||||
BROKER,
|
||||
};
|
||||
|
||||
## Broker controller constructor.
|
||||
##
|
||||
## host: Controller ip.
|
||||
##
|
||||
## host_port: Controller listen port.
|
||||
##
|
||||
## topic: broker topic to send messages to.
|
||||
##
|
||||
## dpid: OpenFlow switch datapath id.
|
||||
##
|
||||
## Returns: OpenFlow::Controller record
|
||||
global broker_new: function(name: string, host: addr, host_port: port, topic: string, dpid: count): OpenFlow::Controller;
|
||||
|
||||
redef record ControllerState += {
|
||||
## Controller ip.
|
||||
broker_host: addr &optional;
|
||||
## Controller listen port.
|
||||
broker_port: port &optional;
|
||||
## OpenFlow switch datapath id.
|
||||
broker_dpid: count &optional;
|
||||
## Topic to sent events for this controller to
|
||||
broker_topic: string &optional;
|
||||
};
|
||||
|
||||
global broker_flow_mod: event(name: string, dpid: count, match: ofp_match, flow_mod: ofp_flow_mod);
|
||||
global broker_flow_clear: event(name: string, dpid: count);
|
||||
}
|
||||
|
||||
global broker_peers: table[port, string] of Controller;
|
||||
|
||||
function broker_describe(state: ControllerState): string
|
||||
{
|
||||
return fmt("Broker-%s:%d-%d", state$broker_host, state$broker_port, state$broker_dpid);
|
||||
}
|
||||
|
||||
function broker_flow_mod_fun(state: ControllerState, match: ofp_match, flow_mod: OpenFlow::ofp_flow_mod): bool
|
||||
{
|
||||
BrokerComm::event(state$broker_topic, BrokerComm::event_args(broker_flow_mod, state$_name, state$broker_dpid, match, flow_mod));
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function broker_flow_clear_fun(state: OpenFlow::ControllerState): bool
|
||||
{
|
||||
BrokerComm::event(state$broker_topic, BrokerComm::event_args(broker_flow_clear, state$_name, state$broker_dpid));
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function broker_init(state: OpenFlow::ControllerState)
|
||||
{
|
||||
BrokerComm::enable();
|
||||
BrokerComm::connect(cat(state$broker_host), state$broker_port, 1sec);
|
||||
BrokerComm::subscribe_to_events(state$broker_topic); # openflow success and failure events are directly sent back via the other plugin via broker.
|
||||
}
|
||||
|
||||
event BrokerComm::outgoing_connection_established(peer_address: string, peer_port: port, peer_name: string)
|
||||
{
|
||||
if ( [peer_port, peer_address] !in broker_peers )
|
||||
# ok, this one was none of ours...
|
||||
return;
|
||||
|
||||
local p = broker_peers[peer_port, peer_address];
|
||||
controller_init_done(p);
|
||||
delete broker_peers[peer_port, peer_address];
|
||||
}
|
||||
|
||||
# broker controller constructor
|
||||
function broker_new(name: string, host: addr, host_port: port, topic: string, dpid: count): OpenFlow::Controller
|
||||
{
|
||||
local c = OpenFlow::Controller($state=OpenFlow::ControllerState($broker_host=host, $broker_port=host_port, $broker_dpid=dpid, $broker_topic=topic),
|
||||
$flow_mod=broker_flow_mod_fun, $flow_clear=broker_flow_clear_fun, $describe=broker_describe, $supports_flow_removed=T, $init=broker_init);
|
||||
|
||||
register_controller(OpenFlow::BROKER, name, c);
|
||||
|
||||
if ( [host_port, cat(host)] in broker_peers )
|
||||
Reporter::warning(fmt("Peer %s:%s was added to NetControl acld plugin twice.", host, host_port));
|
||||
else
|
||||
broker_peers[host_port, cat(host)] = c;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
76
scripts/base/frameworks/openflow/plugins/log.bro
Normal file
76
scripts/base/frameworks/openflow/plugins/log.bro
Normal file
|
@ -0,0 +1,76 @@
|
|||
##! OpenFlow plugin that outputs flow-modification commands
|
||||
##! to a Bro log file.
|
||||
|
||||
@load base/frameworks/openflow
|
||||
@load base/frameworks/logging
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
export {
|
||||
redef enum Plugin += {
|
||||
OFLOG,
|
||||
};
|
||||
|
||||
redef enum Log::ID += { LOG };
|
||||
|
||||
## Log controller constructor.
|
||||
##
|
||||
## dpid: OpenFlow switch datapath id.
|
||||
##
|
||||
## success_event: If true, flow_mod_success is raised for each logged line.
|
||||
##
|
||||
## Returns: OpenFlow::Controller record
|
||||
global log_new: function(dpid: count, success_event: bool &default=T): OpenFlow::Controller;
|
||||
|
||||
redef record ControllerState += {
|
||||
## OpenFlow switch datapath id.
|
||||
log_dpid: count &optional;
|
||||
## Raise or do not raise success event
|
||||
log_success_event: bool &optional;
|
||||
};
|
||||
|
||||
## The record type which contains column fields of the OpenFlow log.
|
||||
type Info: record {
|
||||
## Network time
|
||||
ts: time &log;
|
||||
## OpenFlow switch datapath id
|
||||
dpid: count &log;
|
||||
## OpenFlow match fields
|
||||
match: ofp_match &log;
|
||||
## OpenFlow modify flow entry message
|
||||
flow_mod: ofp_flow_mod &log;
|
||||
};
|
||||
|
||||
## Event that can be handled to access the :bro:type:`OpenFlow::Info`
|
||||
## record as it is sent on to the logging framework.
|
||||
global log_openflow: event(rec: Info);
|
||||
}
|
||||
|
||||
event bro_init() &priority=5
|
||||
{
|
||||
Log::create_stream(OpenFlow::LOG, [$columns=Info, $ev=log_openflow, $path="openflow"]);
|
||||
}
|
||||
|
||||
function log_flow_mod(state: ControllerState, match: ofp_match, flow_mod: OpenFlow::ofp_flow_mod): bool
|
||||
{
|
||||
Log::write(OpenFlow::LOG, [$ts=network_time(), $dpid=state$log_dpid, $match=match, $flow_mod=flow_mod]);
|
||||
if ( state$log_success_event )
|
||||
event OpenFlow::flow_mod_success(state$_name, match, flow_mod);
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function log_describe(state: ControllerState): string
|
||||
{
|
||||
return fmt("Log-%d", state$log_dpid);
|
||||
}
|
||||
|
||||
function log_new(dpid: count, success_event: bool &default=T): OpenFlow::Controller
|
||||
{
|
||||
local c = OpenFlow::Controller($state=OpenFlow::ControllerState($log_dpid=dpid, $log_success_event=success_event),
|
||||
$flow_mod=log_flow_mod, $describe=log_describe, $supports_flow_removed=F);
|
||||
|
||||
register_controller(OpenFlow::OFLOG, cat(dpid), c);
|
||||
|
||||
return c;
|
||||
}
|
190
scripts/base/frameworks/openflow/plugins/ryu.bro
Normal file
190
scripts/base/frameworks/openflow/plugins/ryu.bro
Normal file
|
@ -0,0 +1,190 @@
|
|||
##! OpenFlow plugin for the Ryu controller.
|
||||
|
||||
@load base/frameworks/openflow
|
||||
@load base/utils/active-http
|
||||
@load base/utils/exec
|
||||
@load base/utils/json
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
export {
|
||||
redef enum Plugin += {
|
||||
RYU,
|
||||
};
|
||||
|
||||
## Ryu controller constructor.
|
||||
##
|
||||
## host: Controller ip.
|
||||
##
|
||||
## host_port: Controller listen port.
|
||||
##
|
||||
## dpid: OpenFlow switch datapath id.
|
||||
##
|
||||
## Returns: OpenFlow::Controller record
|
||||
global ryu_new: function(host: addr, host_port: count, dpid: count): OpenFlow::Controller;
|
||||
|
||||
redef record ControllerState += {
|
||||
## Controller ip.
|
||||
ryu_host: addr &optional;
|
||||
## Controller listen port.
|
||||
ryu_port: count &optional;
|
||||
## OpenFlow switch datapath id.
|
||||
ryu_dpid: count &optional;
|
||||
## Enable debug mode - output JSON to stdout; do not perform actions
|
||||
ryu_debug: bool &default=F;
|
||||
};
|
||||
}
|
||||
|
||||
# Ryu ReST API flow_mod URL-path
|
||||
const RYU_FLOWENTRY_PATH = "/stats/flowentry/";
|
||||
# Ryu ReST API flow_stats URL-path
|
||||
#const RYU_FLOWSTATS_PATH = "/stats/flow/";
|
||||
|
||||
# Ryu ReST API action_output type.
|
||||
type ryu_flow_action: record {
|
||||
# Ryu uses strings as its ReST API output action.
|
||||
_type: string;
|
||||
# The output port for type OUTPUT
|
||||
_port: count &optional;
|
||||
};
|
||||
|
||||
# The ReST API documentation can be found at
|
||||
# https://media.readthedocs.org/pdf/ryu/latest/ryu.pdf
|
||||
# Ryu ReST API flow_mod type.
|
||||
type ryu_ofp_flow_mod: record {
|
||||
dpid: count;
|
||||
cookie: count &optional;
|
||||
cookie_mask: count &optional;
|
||||
table_id: count &optional;
|
||||
idle_timeout: count &optional;
|
||||
hard_timeout: count &optional;
|
||||
priority: count &optional;
|
||||
flags: count &optional;
|
||||
match: OpenFlow::ofp_match;
|
||||
actions: vector of ryu_flow_action;
|
||||
out_port: count &optional;
|
||||
out_group: count &optional;
|
||||
};
|
||||
|
||||
# Mapping between ofp flow mod commands and ryu urls
|
||||
const ryu_url: table[ofp_flow_mod_command] of string = {
|
||||
[OFPFC_ADD] = "add",
|
||||
[OFPFC_MODIFY] = "modify",
|
||||
[OFPFC_MODIFY_STRICT] = "modify_strict",
|
||||
[OFPFC_DELETE] = "delete",
|
||||
[OFPFC_DELETE_STRICT] = "delete_strict",
|
||||
};
|
||||
|
||||
# Ryu flow_mod function
|
||||
function ryu_flow_mod(state: OpenFlow::ControllerState, match: ofp_match, flow_mod: OpenFlow::ofp_flow_mod): bool
|
||||
{
|
||||
if ( state$_plugin != RYU )
|
||||
{
|
||||
Reporter::error("Ryu openflow plugin was called with state of non-ryu plugin");
|
||||
return F;
|
||||
}
|
||||
|
||||
# Generate ryu_flow_actions because their type differs (using strings as type).
|
||||
local flow_actions: vector of ryu_flow_action = vector();
|
||||
|
||||
for ( i in flow_mod$actions$out_ports )
|
||||
flow_actions[|flow_actions|] = ryu_flow_action($_type="OUTPUT", $_port=flow_mod$actions$out_ports[i]);
|
||||
|
||||
# Generate our ryu_flow_mod record for the ReST API call.
|
||||
local mod: ryu_ofp_flow_mod = ryu_ofp_flow_mod(
|
||||
$dpid=state$ryu_dpid,
|
||||
$cookie=flow_mod$cookie,
|
||||
$idle_timeout=flow_mod$idle_timeout,
|
||||
$hard_timeout=flow_mod$hard_timeout,
|
||||
$priority=flow_mod$priority,
|
||||
$flags=flow_mod$flags,
|
||||
$match=match,
|
||||
$actions=flow_actions
|
||||
);
|
||||
|
||||
if ( flow_mod?$out_port )
|
||||
mod$out_port = flow_mod$out_port;
|
||||
if ( flow_mod?$out_group )
|
||||
mod$out_group = flow_mod$out_group;
|
||||
|
||||
# Type of the command
|
||||
local command_type: string;
|
||||
|
||||
if ( flow_mod$command in ryu_url )
|
||||
command_type = ryu_url[flow_mod$command];
|
||||
else
|
||||
{
|
||||
Reporter::warning(fmt("The given OpenFlow command type '%s' is not available", cat(flow_mod$command)));
|
||||
return F;
|
||||
}
|
||||
|
||||
local url=cat("http://", cat(state$ryu_host), ":", cat(state$ryu_port), RYU_FLOWENTRY_PATH, command_type);
|
||||
|
||||
if ( state$ryu_debug )
|
||||
{
|
||||
print url;
|
||||
print to_json(mod);
|
||||
event OpenFlow::flow_mod_success(state$_name, match, flow_mod);
|
||||
return T;
|
||||
}
|
||||
|
||||
# Create the ActiveHTTP request and convert the record to a Ryu ReST API JSON string
|
||||
local request: ActiveHTTP::Request = ActiveHTTP::Request(
|
||||
$url=url,
|
||||
$method="POST",
|
||||
$client_data=to_json(mod)
|
||||
);
|
||||
|
||||
# Execute call to Ryu's ReST API
|
||||
when ( local result = ActiveHTTP::request(request) )
|
||||
{
|
||||
if(result$code == 200)
|
||||
event OpenFlow::flow_mod_success(state$_name, match, flow_mod, result$body);
|
||||
else
|
||||
{
|
||||
Reporter::warning(fmt("Flow modification failed with error: %s", result$body));
|
||||
event OpenFlow::flow_mod_failure(state$_name, match, flow_mod, result$body);
|
||||
return F;
|
||||
}
|
||||
}
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function ryu_flow_clear(state: OpenFlow::ControllerState): bool
|
||||
{
|
||||
local url=cat("http://", cat(state$ryu_host), ":", cat(state$ryu_port), RYU_FLOWENTRY_PATH, "clear", "/", state$ryu_dpid);
|
||||
|
||||
if ( state$ryu_debug )
|
||||
{
|
||||
print url;
|
||||
return T;
|
||||
}
|
||||
|
||||
local request: ActiveHTTP::Request = ActiveHTTP::Request(
|
||||
$url=url,
|
||||
$method="DELETE"
|
||||
);
|
||||
|
||||
when ( local result = ActiveHTTP::request(request) )
|
||||
{
|
||||
}
|
||||
|
||||
return T;
|
||||
}
|
||||
|
||||
function ryu_describe(state: ControllerState): string
|
||||
{
|
||||
return fmt("Ryu-%d-http://%s:%d", state$ryu_dpid, state$ryu_host, state$ryu_port);
|
||||
}
|
||||
|
||||
# Ryu controller constructor
|
||||
function ryu_new(host: addr, host_port: count, dpid: count): OpenFlow::Controller
|
||||
{
|
||||
local c = OpenFlow::Controller($state=OpenFlow::ControllerState($ryu_host=host, $ryu_port=host_port, $ryu_dpid=dpid),
|
||||
$flow_mod=ryu_flow_mod, $flow_clear=ryu_flow_clear, $describe=ryu_describe, $supports_flow_removed=F);
|
||||
|
||||
register_controller(OpenFlow::RYU, cat(host,host_port,dpid), c);
|
||||
|
||||
return c;
|
||||
}
|
132
scripts/base/frameworks/openflow/types.bro
Normal file
132
scripts/base/frameworks/openflow/types.bro
Normal file
|
@ -0,0 +1,132 @@
|
|||
##! Types used by the OpenFlow framework.
|
||||
|
||||
module OpenFlow;
|
||||
|
||||
@load ./consts
|
||||
|
||||
export {
|
||||
## Available openflow plugins
|
||||
type Plugin: enum {
|
||||
## Internal placeholder plugin
|
||||
INVALID,
|
||||
};
|
||||
|
||||
## Controller related state.
|
||||
## Can be redefined by plugins to
|
||||
## add state.
|
||||
type ControllerState: record {
|
||||
## Internally set to the type of plugin used.
|
||||
_plugin: Plugin &optional;
|
||||
## Internally set to the unique name of the controller.
|
||||
_name: string &optional;
|
||||
## Internally set to true once the controller is activated
|
||||
_activated: bool &default=F;
|
||||
} &redef;
|
||||
|
||||
## Openflow match definition.
|
||||
##
|
||||
## The openflow match record describes
|
||||
## which packets match to a specific
|
||||
## rule in a flow table.
|
||||
type ofp_match: record {
|
||||
# Input switch port.
|
||||
in_port: count &optional;
|
||||
# Ethernet source address.
|
||||
dl_src: string &optional;
|
||||
# Ethernet destination address.
|
||||
dl_dst: string &optional;
|
||||
# Input VLAN id.
|
||||
dl_vlan: count &optional;
|
||||
# Input VLAN priority.
|
||||
dl_vlan_pcp: count &optional;
|
||||
# Ethernet frame type.
|
||||
dl_type: count &optional;
|
||||
# IP ToS (actually DSCP field, 6bits).
|
||||
nw_tos: count &optional;
|
||||
# IP protocol or lower 8 bits of ARP opcode.
|
||||
nw_proto: count &optional;
|
||||
# At the moment, we store both v4 and v6 in the same fields.
|
||||
# This is not how OpenFlow does it, we might want to change that...
|
||||
# IP source address.
|
||||
nw_src: subnet &optional;
|
||||
# IP destination address.
|
||||
nw_dst: subnet &optional;
|
||||
# TCP/UDP source port.
|
||||
tp_src: count &optional;
|
||||
# TCP/UDP destination port.
|
||||
tp_dst: count &optional;
|
||||
} &log;
|
||||
|
||||
## The actions that can be taken in a flow.
|
||||
## (Sepearate record to make ofp_flow_mod less crowded)
|
||||
type ofp_flow_action: record {
|
||||
## Output ports to send data to.
|
||||
out_ports: vector of count &default=vector();
|
||||
## set vlan vid to this value
|
||||
vlan_vid: count &optional;
|
||||
## set vlan priority to this value
|
||||
vlan_pcp: count &optional;
|
||||
## strip vlan tag
|
||||
vlan_strip: bool &default=F;
|
||||
## set ethernet source address
|
||||
dl_src: string &optional;
|
||||
## set ethernet destination address
|
||||
dl_dst: string &optional;
|
||||
## set ip tos to this value
|
||||
nw_tos: count &optional;
|
||||
## set source to this ip
|
||||
nw_src: addr &optional;
|
||||
## set destination to this ip
|
||||
nw_dst: addr &optional;
|
||||
## set tcp/udp source port
|
||||
tp_src: count &optional;
|
||||
## set tcp/udp destination port
|
||||
tp_dst: count &optional;
|
||||
} &log;
|
||||
|
||||
## Openflow flow_mod definition, describing the action to perform.
|
||||
type ofp_flow_mod: record {
|
||||
## Opaque controller-issued identifier.
|
||||
# This is optional in the specification - but let's force
|
||||
# it so we always can identify our flows...
|
||||
cookie: count; # &default=BRO_COOKIE_ID * COOKIE_BID_START;
|
||||
# Flow actions
|
||||
## Table to put the flow in. OFPTT_ALL can be used for delete,
|
||||
## to delete flows from all matching tables.
|
||||
table_id: count &optional;
|
||||
## One of OFPFC_*.
|
||||
command: ofp_flow_mod_command; # &default=OFPFC_ADD;
|
||||
## Idle time before discarding (seconds).
|
||||
idle_timeout: count &default=0;
|
||||
## Max time before discarding (seconds).
|
||||
hard_timeout: count &default=0;
|
||||
## Priority level of flow entry.
|
||||
priority: count &default=0;
|
||||
## For OFPFC_DELETE* commands, require matching entried to include
|
||||
## this as an output port/group. OFPP_ANY/OFPG_ANY means no restrictions.
|
||||
out_port: count &optional;
|
||||
out_group: count &optional;
|
||||
## Bitmap of the OFPFF_* flags
|
||||
flags: count &default=0;
|
||||
## Actions to take on match
|
||||
actions: ofp_flow_action &default=ofp_flow_action();
|
||||
} &log;
|
||||
|
||||
## Controller record representing an openflow controller
|
||||
type Controller: record {
|
||||
## Controller related state.
|
||||
state: ControllerState;
|
||||
## Does the controller support the flow_removed event?
|
||||
supports_flow_removed: bool;
|
||||
## function that describes the controller. Has to be implemented.
|
||||
describe: function(state: ControllerState): string;
|
||||
## one-time initialization function. If defined, controller_init_done has to be called once initialization finishes.
|
||||
init: function (state: ControllerState) &optional;
|
||||
## one-time destruction function
|
||||
destroy: function (state: ControllerState) &optional;
|
||||
## flow_mod function
|
||||
flow_mod: function(state: ControllerState, match: ofp_match, flow_mod: ofp_flow_mod): bool &optional;
|
||||
## flow_clear function
|
||||
flow_clear: function(state: ControllerState): bool &optional;
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue