Merge remote-tracking branch 'origin/topic/robin/pacf' into topic/johanna/openflow

This commit is contained in:
Johanna Amann 2015-04-07 17:28:38 -07:00
commit 94c67dc030
9 changed files with 839 additions and 0 deletions

View file

@ -0,0 +1,3 @@
@load ./types
@load ./main
@load ./plugins

View file

@ -0,0 +1,477 @@
##! Bro's packet aquisition and control framework.
##!
##! This plugin-based framework allows to control the traffic that Bro monitors
##! as well as, if having access to the forwarding path, the traffic the network
##! forwards. By default, the framework lets evyerthing through, to both Bro
##! itself as well as on the network. Scripts can then add rules to impose
##! restrictions on entities, such as specific connections or IP addresses.
##!
##! This framework has two API: a high-level and low-level. The high-levem API
##! provides convinience functions for a set of common operations. The
##! low-level API provides full flexibility.
module Pacf;
@load ./plugin
@load ./types
export {
## The framework's logging stream identifier.
redef enum Log::ID += { LOG };
# ###
# ### Generic functions.
# ###
# Activates a plugin.
#
# plugin: The plugin to acticate.
#
# priority: The higher the priority, the earlier this plugin will be checked
# whether it supports an operation, relative to other plugins.
global activate: function(p: PluginState, priority: int);
# ###
# ### High-level API.
# ###
## Stops all packets involving an IP address from being forwarded.
##
## a: The address to be dropped.
##
## t: How long to drop it, with 0 being indefinitly.
##
## location: An optional string describing where the drop was triggered.
##
## Returns: True if a plugin accepted the rule for carrying it out.
global drop_address: function(a: addr, t: interval, location: string &default="") : bool;
## 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: True if a plugin accepted the rule for carrying it out.
global shunt_flow: function(f: flow_id, t: interval, location: string &default="") : bool;
## Removes all rules and notifications for an entity.
##
## e: The entity. Note that this will be directly to entities of existing
## notifications and notifications, which must match exactly field by field.
global reset: function(e: Entity);
## Flushes all state.
global clear: function();
# ###
# ### Low-level API.
# ###
###### Manipulation of rules.
## Installs a rule.
##
## r: The rule to install.
##
## Returns: If succesful, returns an ID string unique to the rule that can later
## be used to refer to it. If unsuccessful, returns an empty string. The ID is also
## assigned to ``r$id``. Note that "successful" means "a plugin knew how to handle
## the rule", it doesn't necessarily mean that it was indeed successfully put in
## place, because that might happen asynchronously and thus fail only later.
global add_rule: function(r: Rule) : string;
## Removes a rule.
##
## id: The rule to remove, specified as the ID returned by :bro:id:`add_rule` .
##
## Returns: True if succesful, the relevant plugin indicated that ity knew how
## to handle the removal. Note that again "success" means the plugin accepted the
## removal. They might still fail to put it into effect, as that might happen
## asynchronously and thus go wrong at that point.
global remove_rule: function(id: string) : bool;
###### Asynchronous feedback on rules.
## Confirms that a rule was put in place.
##
## r: The rule now in place.
##
## plugin: The name of the plugin that put it into place.
##
## msg: An optional informational message by the plugin.
global rule_added: event(r: Rule, p: PluginState, msg: string &default="");
## Reports that a rule was removed due to a remove: function() call.
##
## r: The rule now removed.
##
## plugin: The name of the plugin that had the rule in place and now
## removed it.
##
## msg: An optional informational message by the plugin.
global rule_removed: event(r: Rule, p: PluginState, msg: string &default="");
## Reports that a rule was removed internally due to a timeout.
##
## r: The rule now removed.
##
## plugin: The name of the plugin that had the rule in place and now
## removed it.
##
## msg: An optional informational message by the plugin.
global rule_timeout: event(r: Rule, p: PluginState);
## Reports an error when operating on a rule.
##
## r: The rule that encountered an error.
##
## plugin: The name of the plugin that reported the error.
##
## msg: An optional informational message by the plugin.
global rule_error: event(r: Rule, p: PluginState, msg: string &default="");
## Installs a notification.
##
## n: The notification to install.
##
## Returns: If succesful, returns an ID string unique to the notification 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 notification", 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_notification: function(n: Notification) : string;
## Removes a notification.
##
## id: The notification to remove, specified as the ID returned by :bro:id:`add_notification` .
##
## Returns: True if succesful, the relevant plugin indicated that ity knew how
## to handle the removal. Note that again "success" means the plugin accepted the
## removal. They might still fail to put it into effect, as that might happen
## asynchronously and thus go wrong at that point.
global remove_notification: function(id: string) : bool;
###### Asynchronous feedback on notifications.
## Confirms that a notification was put in place.
##
## n: The notification now in place.
##
## plugin: The name of the plugin that put it into place.
##
## msg: An optional informational message by the plugin.
global notification_added: event(n: Notification, p: PluginState, msg: string &default="");
## Reports that a notification was removed due to a remove: function() call.
##
## n: The notification now removed.
##
## plugin: The name of the plugin that had the notification in place and now
## removed it.
##
## msg: An optional informational message by the plugin.
global notification_removed: event(n: Notification, p: PluginState, msg: string &default="");
## Reports that a notification was removed internally due to a timeout.
##
## n: The notification now removed.
##
## plugin: The name of the plugin that had the notification in place and now
## removed it.
##
## msg: An optional informational message by the plugin.
global notification_timeout: event(n: Notification, p: PluginState);
## Reports an error when operating on a notification.
##
## n: The notification that encountered an error.
##
## plugin: The name of the plugin that reported the error.
##
## msg: An optional informational message by the plugin.
global notification_error: event(n: Notification, p: PluginState, msg: string &default="");
## Type of an entry in the PACF log.
type InfoCategory: enum {
## A log entry reflecting a framework message.
MESSAGE,
## A log entry reflecting a framework message.
ERROR,
## A log entry about about a rule.
RULE,
## A log entry about about a notification.
NOTIFICATION
};
## State of an entry in the PACF log.
type InfoState: enum {
REQUESTED,
SUCCEEDED,
FAILED,
REMOVED,
TIMEOUT,
};
## The record type which contains column fields of the PACF log.
type Info: record {
## Time at which the recorded activity occurred.
ts: time &log;
## Type of the log entry.
category: InfoCategory &log &optional;
## The command the log entry is about.
cmd: string &log &optional;
## State the log entry reflects.
state: InfoState &log &optional;
## String describing an action the entry is about.
action: string &log &optional;
## The target type of the action.
target: TargetType &log &optional;
## Type of the entity the log entry is about.
entity_type: string &log &optional;
## String describing the entity the log entry is about.
entity: string &log &optional;
## String with an additional message.
msg: string &log &optional;
## Logcation where the underlying action was triggered.
location: string &log &optional;
## Plugin triggering the log entry.
plugin: string &log &optional;
};
}
redef record Rule += {
##< Internally set to the plugin handling the rule.
_plugin: PluginState &optional;
};
global plugins: vector of PluginState;
global rule_counter: count = 0;
global rules: table[string] of Rule;
event bro_init() &priority=5
{
Log::create_stream(Pacf::LOG, [$columns=Info]);
}
function entity_to_info(info: Info, e: Entity)
{
info$entity_type = fmt("%s", e$ty);
switch ( e$ty ) {
case ADDRESS, ORIGINATOR, RESPONDER:
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:
info$entity = fmt("%s/%d->%s/%d",
e$flow$src_h, e$flow$src_p,
e$flow$dst_h, e$flow$dst_p);
break;
case MAC:
info$entity = e$mac;
break;
default:
info$entity = "<unknown entity type>";
break;
}
}
function rule_to_info(info: Info, r: Rule)
{
info$action = fmt("%s", r$ty);
info$target = r$target;
if ( r?$location )
info$location = r$location;
entity_to_info(info, r$entity);
}
function log_msg(msg: string, p: PluginState)
{
Log::write(LOG, [$ts=network_time(), $category=MESSAGE, $msg=msg, $plugin=p$plugin$name(p)]);
}
function log_error(msg: string, p: PluginState)
{
Log::write(LOG, [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)]);
}
function log_rule(r: Rule, cmd: string, state: InfoState, p: PluginState)
{
local info: Info = [$ts=network_time()];
info$category = RULE;
info$cmd = cmd;
info$state = state;
info$plugin = p$plugin$name(p);
rule_to_info(info, r);
Log::write(LOG, info);
}
function log_rule_error(r: Rule, msg: string, p: PluginState)
{
local info: Info = [$ts=network_time(), $category=ERROR, $msg=msg, $plugin=p$plugin$name(p)];
rule_to_info(info, r);
Log::write(LOG, info);
}
function log_rule_no_plugin(r: Rule, state: InfoState, msg: string)
{
local info: Info = [$ts=network_time()];
info$category = RULE;
info$state = state;
info$msg = msg;
rule_to_info(info, r);
Log::write(LOG, info);
}
function activate(p: PluginState, priority: int)
{
p$_priority = priority;
plugins[|plugins|] = p;
sort(plugins, function(p1: PluginState, p2: PluginState) : int { return p2$_priority - p1$_priority; });
log_msg(fmt("activated plugin with priority %d", priority), p);
}
function drop_address(a: addr, t: interval, location: string &default="") : bool
{
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);
return |id| > 0;
}
function shunt_flow(f: flow_id, t: interval, location: string &default="") : bool
{
local e: Entity = [$ty=FLOW, $flow=f];
local r: Rule = [$ty=DROP, $target=MONITOR, $entity=e, $expire=t, $location=location];
local id = add_rule(r);
return |id| > 0;
}
function reset(e: Entity)
{
print "Pacf::reset not implemented yet";
}
function clear()
{
print "Pacf::clear not implemented yet";
}
function add_rule(r: Rule) : string
{
r$id = fmt("%d", ++rule_counter);
for ( i in plugins )
{
local p = plugins[i];
if ( p$plugin$add_rule(p, r) )
{
r$_plugin = p;
log_rule(r, "ADD", REQUESTED, p);
return r$id;
}
}
log_rule_no_plugin(r, FAILED, "not supported");
}
function remove_rule(id: string) : bool
{
local r = rules[id];
local p = r$_plugin;
if ( ! p$plugin$remove_rule(r$_plugin, r) )
{
log_rule_error(r, "remove failed", p);
return F;
}
log_rule(r, "REMOVE", REQUESTED, p);
return T;
}
event rule_expire(r: Rule, p: PluginState)
{
if ( r$id !in rules )
# Remove already.
return;
event rule_timeout(r, p);
remove_rule(r$id);
}
event rule_added(r: Rule, p: PluginState, msg: string &default="")
{
log_rule(r, "ADD", SUCCEEDED, p);
rules[r$id] = r;
if ( r?$expire && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };
}
event rule_removed(r: Rule, p: PluginState, msg: string &default="")
{
delete rules[r$id];
log_rule(r, "REMOVE", SUCCEEDED, p);
}
event rule_timeout(r: Rule, p: PluginState)
{
delete rules[r$id];
log_rule(r, "EXPIRE", TIMEOUT, p);
}
event rule_error(r: Rule, p: PluginState, msg: string &default="")
{
log_rule_error(r, msg, p);
}
function add_notification(n: Notification) : string
{
print "Pacf::add_notification not implemented yet";
}
function remove_notification(id: string) : bool
{
print "Pacf::remove_notification not implemented yet";
}
event notification_added(n: Notification, p: PluginState, msg: string &default="")
{
}
event notification_removed(n: Notification, p: PluginState, msg: string &default="")
{
}
event notification_timeout(n: Notification, p: PluginState)
{
}
event notification_error(n: Notification, p: PluginState, msg: string &default="")
{
}

View file

@ -0,0 +1,91 @@
module Pacf;
@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();
## Set internally.
_priority: int &default=+0;
};
# Definitioan of a plugin.
#
# Generally a plugin needs to implement only what it can support. By
# returning failure, it indicates that it can't support something and the
# the framework will then try another plugin, if available; or informn the
# that the operation failed. If a function isn't implemented by a plugin,
# that's considered an implicit failure to support the operation.
#
# If plugin accepts a rule operation, it *must* generate one of the reporting
# events ``rule_{added,remove,error}`` to signal if it indeed worked out;
# this is separate from accepting the operation because often a plugin
# will only know later (i.e., asynchrously) if that was an error for
# something it thought it could handle. The same applies to notifications,
# with the corresponding ``notification_*`` events.
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/notifications itself. If false,
## framework will manage rule expiration.
can_expire: bool;
# One-time initialization functionl called when plugin gets registered, and
# befire any ther methods are called.
init: function(state: PluginState) &optional;
# One-time finalization function called when a plugin is shutdown; no further
# functions will be called afterwords.
done: function(state: PluginState) &optional;
# Implements the add_rule() operation. If the plugin accepts the rule,
# it returns true, false otherwise. The rule will already have its
# ``id`` field set, which the plugin may use for identification
# purposes.
add_rule: function(state: PluginState, r: Rule) : bool &optional;
# Implements the remove_rule() operation. This will only be called for
# rules that the plugins has previously accepted with add_rule(). The
# ``id`` field will match that of the add_rule() call. Generally,
# a plugin that accepts an add_rule() should also accept the
# remove_rule().
remove_rule: function(state: PluginState, r: Rule) : bool &optional;
# Implements the add_notification() operation. If the plugin accepts the notification,
# it returns true, false otherwise. The notification will already have its
# ``id`` field set, which the plugin may use for identification
# purposes.
add_notification: function(state: PluginState, r: Notification) : bool &optional;
# Implements the remove_notification() operation. This will only be called for
# notifications that the plugins has previously accepted with add_notification().
# The ``id`` field will match that of the add_notification() call. Generally,
# a plugin that accepts an add_notification() should also accept the
# remove_notification().
remove_notification: function(state: PluginState, r: Notification) : 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;
};
}

View file

@ -0,0 +1 @@
@load ./debug

View file

@ -0,0 +1,119 @@
@load ../plugin
module Pacf;
export {
## Instantiates a debug plugin for the PACF framework. The debug
## plugin simply logs the operations it receives.
##
## do_something: If true, the plugin will claim it supports all operations; if
## false, it will indicate it doesn't support any.
global create_debug: function(do_something: bool) : PluginState;
}
function do_something(p: PluginState) : bool
{
return p$config["all"] == "1";
}
function debug_name(p: PluginState) : string
{
return fmt("Debug-%s", (do_something(p) ? "All" : "None"));
}
function debug_log(p: PluginState, msg: string)
{
print fmt("pacf debug (%s): %s", debug_name(p), msg);
}
function debug_init(p: PluginState)
{
debug_log(p, "init");
}
function debug_done(p: PluginState)
{
debug_log(p, "init");
}
function debug_add_rule(p: PluginState, r: Rule) : bool
{
local s = fmt("add_rule: %s", r);
debug_log(p, s);
if ( do_something(p) )
{
event Pacf::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 Pacf::rule_removed(r, p);
return T;
}
function debug_add_notification(p: PluginState, r: Notification) : bool
{
local s = fmt("add_notification: %s", r);
debug_log(p, s);
if ( do_something(p) )
{
event Pacf::notification_added(r, p);
return T;
}
return F;
}
function debug_remove_notification(p: PluginState, r: Notification) : bool
{
local s = fmt("remove_notification: %s", r);
debug_log(p, s);
return do_something(p);
}
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,
$add_notification = debug_add_notification,
$remove_notification = debug_remove_notification,
$transaction_begin = debug_transaction_begin,
$transaction_end = debug_transaction_end
);
function create_debug(do_something: bool) : PluginState
{
local p: PluginState = [$plugin=debug_plugin];
# FIXME: Why's the default not working?
p$config = table();
p$config["all"] = (do_something ? "1" : "0");
return p;
}

View file

@ -0,0 +1,122 @@
module Pacf;
export {
## Type of a :bro:id:`Entity` for defining an action.
type EntityType: enum {
ADDRESS, ##< Activity involving a specific IP address.
ORIGINATOR, ##< Activity *from* a source IP address.
RESPONDER, ##< Activity *to* a destination IP address.
CONNECTION, ##< All of a bi-directional connection's activity.
FLOW, ##< All of a uni-directional flow's activity.
MAC, ##< Activity involving a 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_id &optional; ##< Used with :bro:id:`FLOW` .
ip: subnet &optional; ##< Used with :bro:id:`ORIGINATOR`/:bro:id:`RESPONDER`/: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 rate-limiting flows matching entity.
##
## d: Percent of available bandwidth.
LIMIT,
## Begin modifying all packets matching entity.
##
## .. todo::
## Define arguments.
MODIFY,
## Begin redirecting all packets matching entity.
##
## .. todo::
## Define arguments.
REDIRECT,
## Begin sampling all flows matching entity.
##
## d: Probability to include a flow between 0 and 1.
SAMPLE,
## 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,
};
## 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 riles 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=+0; ##< Priority if multiple rules match an entity (larger value is higher priority).
location: string &optional; ##< Optional string describing where/what installed the rule.
i: int &optional; ##< Argument for rule types requiring an integer argument.
d: double &optional; ##< Argument for rule types requiring a double argument.
s: string &optional; ##< Argument for rule types requiring a string argument.
id: string &default=""; ##< Internally determined unique ID for this rule. Will be set when added.
};
## Type of notifications that the framework supports. Each type lists the
## :bro:id:`Notification` argument(s) it uses, if any.
##
## Plugins may extend this type to define their own.
type NotificationType: enum {
## Notify if threshold of packets has been reached by entity.
##
## i: Number of packets.
NUM_PACKETS,
## Notify if threshold of bytes has been reached by entity.
##
## i: Number of bytes.
NUM_BYTES,
};
## A notification for the framework to raise when a condition has been reached.
## Different than with rules, all matching conditions will be reported, not only
## the first match.
type Notification: record {
ty: NotificationType; ##< Type of notification.
entity: Entity; ##< Entity to apply notification to.
expire: interval &optional; ##< Timeout after which to expire the notification.
src: string &optional; ##< Optional string describing where/what installed the notification.
i: int; ##< Argument for notification types requiring an integer argument.
d: double; ##< Argument for notification types requiring a double argument.
s: string; ##< Argument for notification types requiring a string argument.
id: string &default=""; ##< Internally determined unique ID for this notification. Will be set when added.
};
}

View file

@ -120,6 +120,18 @@ type conn_id: record {
resp_p: port; ##< The responder's port number.
} &log;
## The identifying 4-tuple of a uni-directional flow.
##
## .. note:: It's actually a 5-tuple: the transport-layer protocol is stored as
## part of the port values, `src_p` and `dst_p`, and can be extracted from
## them with :bro:id:`get_port_transport_proto`.
type flow_id : record {
src_h: addr; ##< The source IP address.
src_p: port; ##< The source port number.
dst_h: addr; ##< The destination IP address.
dst_p: port; ##< The desintation port number.
};
## Specifics about an ICMP conversation. ICMP events typically pass this in
## addition to :bro:type:`conn_id`.
##

View file

@ -37,6 +37,7 @@
@load base/frameworks/reporter
@load base/frameworks/sumstats
@load base/frameworks/tunnels
@load base/frameworks/pacf
@load base/protocols/conn
@load base/protocols/dhcp

View file

@ -2366,6 +2366,19 @@ function to_subnet%(sn: string%): subnet
return ret;
%}
## Converts a :bro:type:`addr` to a :bro:type:`subnet`.
##
## a: The address to convert.
##
## Returns: The *sn* string as a :bro:type:`subnet`.
##
## .. bro:see:: to_subset
function addr_to_subnet%(a: addr%): subnet
%{
int width = (a->AsAddr().GetFamily() == IPv4 ? 32 : 128);
return new SubNetVal(a->AsAddr(), width);
%}
## Converts a :bro:type:`string` to a :bro:type:`double`.
##
## str: The :bro:type:`string` to convert.