Merge remote-tracking branch 'origin/master' into topic/johanna/imap-starttls

This commit is contained in:
Johanna Amann 2016-04-26 10:52:16 -07:00
commit 3669b6aa9c
626 changed files with 13981 additions and 3994 deletions

View file

@ -1 +1,2 @@
@load ./main
@load ./store

View file

@ -1,11 +1,11 @@
##! Various data structure definitions for use with Bro's communication system.
module BrokerComm;
module Broker;
export {
## A name used to identify this endpoint to peers.
## .. bro:see:: BrokerComm::connect BrokerComm::listen
## .. bro:see:: Broker::connect Broker::listen
const endpoint_name = "" &redef;
## Change communication behavior.
@ -32,11 +32,11 @@ export {
## Opaque communication data.
type Data: record {
d: opaque of BrokerComm::Data &optional;
d: opaque of Broker::Data &optional;
};
## Opaque communication data.
type DataVector: vector of BrokerComm::Data;
type DataVector: vector of Broker::Data;
## Opaque event communication data.
type EventArgs: record {
@ -49,55 +49,7 @@ export {
## Opaque communication data used as a convenient way to wrap key-value
## pairs that comprise table entries.
type TableItem : record {
key: BrokerComm::Data;
val: BrokerComm::Data;
};
}
module BrokerStore;
export {
## Whether a data store query could be completed or not.
type QueryStatus: enum {
SUCCESS,
FAILURE,
};
## An expiry time for a key-value pair inserted in to a data store.
type ExpiryTime: record {
## Absolute point in time at which to expire the entry.
absolute: time &optional;
## A point in time relative to the last modification time at which
## to expire the entry. New modifications will delay the expiration.
since_last_modification: interval &optional;
};
## The result of a data store query.
type QueryResult: record {
## Whether the query completed or not.
status: BrokerStore::QueryStatus;
## The result of the query. Certain queries may use a particular
## data type (e.g. querying store size always returns a count, but
## a lookup may return various data types).
result: BrokerComm::Data;
};
## Options to tune the SQLite storage backend.
type SQLiteOptions: record {
## File system path of the database.
path: string &default = "store.sqlite";
};
## Options to tune the RocksDB storage backend.
type RocksDBOptions: record {
## File system path of the database.
path: string &default = "store.rocksdb";
};
## Options to tune the particular storage backends.
type BackendOptions: record {
sqlite: SQLiteOptions &default = SQLiteOptions();
rocksdb: RocksDBOptions &default = RocksDBOptions();
key: Broker::Data;
val: Broker::Data;
};
}

View file

@ -0,0 +1,51 @@
##! Various data structure definitions for use with Bro's communication system.
@load ./main
module Broker;
export {
## Whether a data store query could be completed or not.
type QueryStatus: enum {
SUCCESS,
FAILURE,
};
## An expiry time for a key-value pair inserted in to a data store.
type ExpiryTime: record {
## Absolute point in time at which to expire the entry.
absolute: time &optional;
## A point in time relative to the last modification time at which
## to expire the entry. New modifications will delay the expiration.
since_last_modification: interval &optional;
};
## The result of a data store query.
type QueryResult: record {
## Whether the query completed or not.
status: Broker::QueryStatus;
## The result of the query. Certain queries may use a particular
## data type (e.g. querying store size always returns a count, but
## a lookup may return various data types).
result: Broker::Data;
};
## Options to tune the SQLite storage backend.
type SQLiteOptions: record {
## File system path of the database.
path: string &default = "store.sqlite";
};
## Options to tune the RocksDB storage backend.
type RocksDBOptions: record {
## File system path of the database.
path: string &default = "store.rocksdb";
};
## Options to tune the particular storage backends.
type BackendOptions: record {
sqlite: SQLiteOptions &default = SQLiteOptions();
rocksdb: RocksDBOptions &default = RocksDBOptions();
};
}

View file

@ -43,35 +43,35 @@ export {
## software.
TIME_MACHINE,
};
## Events raised by a manager and handled by the workers.
const manager2worker_events = /Drop::.*/ &redef;
## Events raised by a manager and handled by proxies.
const manager2proxy_events = /EMPTY/ &redef;
## Events raised by proxies and handled by a manager.
const proxy2manager_events = /EMPTY/ &redef;
## Events raised by proxies and handled by workers.
const proxy2worker_events = /EMPTY/ &redef;
## Events raised by workers and handled by a manager.
const worker2manager_events = /(TimeMachine::command|Drop::.*)/ &redef;
## Events raised by workers and handled by proxies.
const worker2proxy_events = /EMPTY/ &redef;
## Events raised by TimeMachine instances and handled by a manager.
const tm2manager_events = /EMPTY/ &redef;
## Events raised by TimeMachine instances and handled by workers.
const tm2worker_events = /EMPTY/ &redef;
## Events sent by the control host (i.e. BroControl) when dynamically
## Events sent by the control host (i.e. BroControl) when dynamically
## connecting to a running instance to update settings or request data.
const control_events = Control::controller_events &redef;
## Record type to indicate a node in a cluster.
type Node: record {
## Identifies the type of cluster node in this node's configuration.
@ -96,13 +96,13 @@ export {
## Name of a time machine node with which this node connects.
time_machine: string &optional;
};
## This function can be called at any time to determine if the cluster
## framework is being enabled for this run.
##
## Returns: True if :bro:id:`Cluster::node` has been set.
global is_enabled: function(): bool;
## This function can be called at any time to determine what type of
## cluster node the current Bro instance is going to be acting as.
## If :bro:id:`Cluster::is_enabled` returns false, then
@ -110,22 +110,25 @@ export {
##
## Returns: The :bro:type:`Cluster::NodeType` the calling node acts as.
global local_node_type: function(): NodeType;
## This gives the value for the number of workers currently connected to,
## and it's maintained internally by the cluster framework. It's
## primarily intended for use by managers to find out how many workers
## and it's maintained internally by the cluster framework. It's
## primarily intended for use by managers to find out how many workers
## should be responding to requests.
global worker_count: count = 0;
## The cluster layout definition. This should be placed into a filter
## named cluster-layout.bro somewhere in the BROPATH. It will be
## named cluster-layout.bro somewhere in the BROPATH. It will be
## automatically loaded if the CLUSTER_NODE environment variable is set.
## Note that BroControl handles all of this automatically.
const nodes: table[string] of Node = {} &redef;
## This is usually supplied on the command line for each instance
## of the cluster that is started up.
const node = getenv("CLUSTER_NODE") &redef;
## Interval for retrying failed connections between cluster nodes.
const retry_interval = 1min &redef;
}
function is_enabled(): bool
@ -158,6 +161,6 @@ event bro_init() &priority=5
Reporter::error(fmt("'%s' is not a valid node in the Cluster::nodes configuration", node));
terminate();
}
Log::create_stream(Cluster::LOG, [$columns=Info, $path="cluster"]);
}

View file

@ -11,7 +11,7 @@ module Cluster;
event bro_init() &priority=9
{
local me = nodes[node];
for ( i in Cluster::nodes )
{
local n = nodes[i];
@ -22,35 +22,35 @@ event bro_init() &priority=9
Communication::nodes["control"] = [$host=n$ip, $zone_id=n$zone_id,
$connect=F, $class="control",
$events=control_events];
if ( me$node_type == MANAGER )
{
if ( n$node_type == WORKER && n$manager == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F,
$class=i, $events=worker2manager_events, $request_logs=T];
if ( n$node_type == PROXY && n$manager == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F,
$class=i, $events=proxy2manager_events, $request_logs=T];
if ( n$node_type == TIME_MACHINE && me?$time_machine && me$time_machine == i )
Communication::nodes["time-machine"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T, $retry=1min,
$connect=T, $retry=retry_interval,
$events=tm2manager_events];
}
else if ( me$node_type == PROXY )
{
if ( n$node_type == WORKER && n$proxy == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F, $class=i,
$sync=T, $auth=T, $events=worker2proxy_events];
# accepts connections from the previous one.
# accepts connections from the previous one.
# (This is not ideal for setups with many proxies)
# FIXME: Once we're using multiple proxies, we should also figure out some $class scheme ...
if ( n$node_type == PROXY )
@ -58,49 +58,49 @@ event bro_init() &priority=9
if ( n?$proxy )
Communication::nodes[i]
= [$host=n$ip, $zone_id=n$zone_id, $p=n$p,
$connect=T, $auth=F, $sync=T, $retry=1mins];
$connect=T, $auth=F, $sync=T, $retry=retry_interval];
else if ( me?$proxy && me$proxy == i )
Communication::nodes[me$proxy]
= [$host=nodes[i]$ip, $zone_id=nodes[i]$zone_id,
$connect=F, $auth=T, $sync=T];
}
# Finally the manager, to send it status updates.
if ( n$node_type == MANAGER && me$manager == i )
Communication::nodes["manager"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T, $retry=1mins,
Communication::nodes["manager"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T, $retry=retry_interval,
$class=node,
$events=manager2proxy_events];
}
else if ( me$node_type == WORKER )
{
if ( n$node_type == MANAGER && me$manager == i )
Communication::nodes["manager"] = [$host=nodes[i]$ip,
Communication::nodes["manager"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T, $retry=1mins,
$class=node,
$connect=T, $retry=retry_interval,
$class=node,
$events=manager2worker_events];
if ( n$node_type == PROXY && me$proxy == i )
Communication::nodes["proxy"] = [$host=nodes[i]$ip,
Communication::nodes["proxy"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T, $retry=1mins,
$sync=T, $class=node,
$connect=T, $retry=retry_interval,
$sync=T, $class=node,
$events=proxy2worker_events];
if ( n$node_type == TIME_MACHINE &&
if ( n$node_type == TIME_MACHINE &&
me?$time_machine && me$time_machine == i )
Communication::nodes["time-machine"] = [$host=nodes[i]$ip,
Communication::nodes["time-machine"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,
$p=nodes[i]$p,
$connect=T,
$retry=1min,
$connect=T,
$retry=retry_interval,
$events=tm2worker_events];
}
}
}

View file

@ -2,7 +2,7 @@
# MPEG v3 audio
signature file-mpeg-audio {
file-mime "audio/mpeg", 20
file-magic /^\xff[\xe2\xe3\xf2\xf3\xf6\xf7\xfa\xfb\xfc\xfd]/
file-magic /^(ID3|\xff[\xe2\xe3\xf2\xf3\xf6\xf7\xfa\xfb\xfc\xfd])/
}
# MPEG v4 audio

View file

@ -9,53 +9,53 @@ signature file-plaintext {
signature file-json {
file-mime "text/json", 1
file-magic /^(\xef\xbb\xbf)?[\x0d\x0a[:blank:]]*\{[\x0d\x0a[:blank:]]*(["][^"]{1,}["]|[a-zA-Z][a-zA-Z0-9\\_]*)[\x0d\x0a[:blank:]]*:[\x0d\x0a[:blank:]]*(["]|\[|\{|[0-9]|true|false)/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?[\x0d\x0a[:blank:]]*\{[\x0d\x0a[:blank:]]*(["][^"]{1,}["]|[a-zA-Z][a-zA-Z0-9\\_]*)[\x0d\x0a[:blank:]]*:[\x0d\x0a[:blank:]]*(["]|\[|\{|[0-9]|true|false)/
}
signature file-json2 {
file-mime "text/json", 1
file-magic /^(\xef\xbb\xbf)?[\x0d\x0a[:blank:]]*\[[\x0d\x0a[:blank:]]*(((["][^"]{1,}["]|[0-9]{1,}(\.[0-9]{1,})?|true|false)[\x0d\x0a[:blank:]]*,)|\{|\[)[\x0d\x0a[:blank:]]*/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?[\x0d\x0a[:blank:]]*\[[\x0d\x0a[:blank:]]*(((["][^"]{1,}["]|[0-9]{1,}(\.[0-9]{1,})?|true|false)[\x0d\x0a[:blank:]]*,)|\{|\[)[\x0d\x0a[:blank:]]*/
}
# Match empty JSON documents.
signature file-json3 {
file-mime "text/json", 0
file-magic /^(\xef\xbb\xbf)?[\x0d\x0a[:blank:]]*(\[\]|\{\})[\x0d\x0a[:blank:]]*$/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?[\x0d\x0a[:blank:]]*(\[\]|\{\})[\x0d\x0a[:blank:]]*$/
}
signature file-xml {
file-mime "application/xml", 10
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<\?xml /
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*\x00?<\x00?\?\x00?x\x00?m\x00?l\x00? \x00?/
}
signature file-xhtml {
file-mime "text/html", 100
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<(![dD][oO][cC][tT][yY][pP][eE] {1,}[hH][tT][mM][lL]|[hH][tT][mM][lL]|[mM][eE][tT][aA] {1,}[hH][tT][tT][pP]-[eE][qQ][uU][iI][vV])/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<(![dD][oO][cC][tT][yY][pP][eE] {1,}[hH][tT][mM][lL]|[hH][tT][mM][lL]|[mM][eE][tT][aA] {1,}[hH][tT][tT][pP]-[eE][qQ][uU][iI][vV])/
}
signature file-html {
file-mime "text/html", 49
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<![dD][oO][cC][tT][yY][pP][eE] {1,}[hH][tT][mM][lL]/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<![dD][oO][cC][tT][yY][pP][eE] {1,}[hH][tT][mM][lL]/
}
signature file-html2 {
file-mime "text/html", 20
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<([hH][eE][aA][dD]|[hH][tT][mM][lL]|[tT][iI][tT][lL][eE]|[bB][oO][dD][yY])/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<([hH][eE][aA][dD]|[hH][tT][mM][lL]|[tT][iI][tT][lL][eE]|[bB][oO][dD][yY])/
}
signature file-rss {
file-mime "text/rss", 90
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[rR][sS][sS]/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[rR][sS][sS]/
}
signature file-atom {
file-mime "text/atom", 100
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<([rR][sS][sS][^>]*xmlns:atom|[fF][eE][eE][dD][^>]*xmlns=["']?http:\/\/www.w3.org\/2005\/Atom["']?)/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<([rR][sS][sS][^>]*xmlns:atom|[fF][eE][eE][dD][^>]*xmlns=["']?http:\/\/www.w3.org\/2005\/Atom["']?)/
}
signature file-soap {
file-mime "application/soap+xml", 49
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[sS][oO][aA][pP](-[eE][nN][vV])?:[eE][nN][vV][eE][lL][oO][pP][eE]/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[sS][oO][aA][pP](-[eE][nN][vV])?:[eE][nN][vV][eE][lL][oO][pP][eE]/
}
signature file-cross-domain-policy {
@ -70,7 +70,7 @@ signature file-cross-domain-policy2 {
signature file-xmlrpc {
file-mime "application/xml-rpc", 49
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[mM][eE][tT][hH][oO][dD][rR][eE][sS][pP][oO][nN][sS][eE]>/
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[mM][eE][tT][hH][oO][dD][rR][eE][sS][pP][oO][nN][sS][eE]>/
}
signature file-coldfusion {
@ -81,7 +81,13 @@ signature file-coldfusion {
# Adobe Flash Media Manifest
signature file-f4m {
file-mime "application/f4m", 49
file-magic /^(\xef\xbb\xbf)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[mM][aA][nN][iI][fF][eE][sS][tT][\x0d\x0a[:blank:]]{1,}xmlns=\"http:\/\/ns\.adobe\.com\/f4m\//
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*(<\?xml .*\?>)?([\x0d\x0a[:blank:]]*(<!--.*-->)?[\x0d\x0a[:blank:]]*)*<[mM][aA][nN][iI][fF][eE][sS][tT][\x0d\x0a[:blank:]]{1,}xmlns=\"http:\/\/ns\.adobe\.com\/f4m\//
}
# .ini style files
signature file-ini {
file-mime "text/ini", 20
file-magic /^(\xef\xbb\xbf|\xff\xfe|\xfe\xff)?[\x00\x0d\x0a[:blank:]]*\[[^\x0d\x0a]+\][[:blank:]\x00]*[\x0d\x0a]/
}
# Microsoft LNK files
@ -90,6 +96,41 @@ signature file-lnk {
file-magic /^\x4C\x00\x00\x00\x01\x14\x02\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x10\x00\x00\x00\x46/
}
# Microsoft Registry policies
signature file-pol {
file-mime "application/vnd.ms-pol", 49
file-magic /^PReg/
}
# Old style Windows registry file
signature file-reg {
file-mime "application/vnd.ms-reg", 49
file-magic /^REGEDIT4/
}
# Newer Windows registry file
signature file-reg-utf16 {
file-mime "application/vnd.ms-reg", 49
file-magic /^\xFF\xFEW\x00i\x00n\x00d\x00o\x00w\x00s\x00 \x00R\x00e\x00g\x00i\x00s\x00t\x00r\x00y\x00 \x00E\x00d\x00i\x00t\x00o\x00r\x00 \x00V\x00e\x00r\x00s\x00i\x00o\x00n\x00 \x005\x00\.\x000\x000/
}
# Microsoft Registry format (typically DESKTOP.DAT)
signature file-regf {
file-mime "application vnd.ms-regf", 49
file-magic /^\x72\x65\x67\x66/
}
# Microsoft Outlook PST files
signature file-pst {
file-mime "application/vnd.ms-outlook", 49
file-magic /!BDN......[\x0e\x0f\x15\x17][\x00-\x02]/
}
signature file-afpinfo {
file-mime "application/vnd.apple-afpinfo"
file-magic /^AFP/
}
signature file-jar {
file-mime "application/java-archive", 100
file-magic /^PK\x03\x04.{1,200}\x14\x00..META-INF\/MANIFEST\.MF/

View file

@ -71,6 +71,14 @@ signature file-mp2p {
file-magic /\x00\x00\x01\xba([\x40-\x7f\xc0-\xff])/
}
# MPEG transport stream data. These files typically have the extension "ts".
# Note: The 0x47 repeats every 188 bytes. Using four as the number of
# occurrences for the test here is arbitrary.
signature file-mp2t {
file-mime "video/mp2t", 40
file-magic /^(\x47.{187}){4}/
}
# Silicon Graphics video
signature file-sgi-movie {
file-mime "video/x-sgi-movie", 70
@ -94,3 +102,4 @@ signature file-3gpp {
file-mime "video/3gpp", 60
file-magic /^....ftyp(3g[egps2]|avc1|mmp4)/
}

View file

@ -1,18 +1,25 @@
##! The input framework provides a way to read previously stored data either
##! as an event stream or into a bro table.
##! as an event stream or into a Bro table.
module Input;
export {
type Event: enum {
## New data has been imported.
EVENT_NEW = 0,
## Existing data has been changed.
EVENT_CHANGED = 1,
## Previously existing data has been removed.
EVENT_REMOVED = 2,
};
## Type that defines the input stream read mode.
type Mode: enum {
## Do not automatically reread the file after it has been read.
MANUAL = 0,
## Reread the entire file each time a change is found.
REREAD = 1,
## Read data from end of file each time new data is appended.
STREAM = 2
};
@ -24,20 +31,20 @@ export {
## Separator between fields.
## Please note that the separator has to be exactly one character long.
## Can be overwritten by individual writers.
## Individual readers can use a different value.
const separator = "\t" &redef;
## Separator between set elements.
## Please note that the separator has to be exactly one character long.
## Can be overwritten by individual writers.
## Individual readers can use a different value.
const set_separator = "," &redef;
## String to use for empty fields.
## Can be overwritten by individual writers.
## Individual readers can use a different value.
const empty_field = "(empty)" &redef;
## String to use for an unset &optional field.
## Can be overwritten by individual writers.
## Individual readers can use a different value.
const unset_field = "-" &redef;
## Flag that controls if the input framework accepts records
@ -47,11 +54,11 @@ export {
## abort. Defaults to false (abort).
const accept_unsupported_types = F &redef;
## TableFilter description type used for the `table` method.
## A table input stream type used to send data to a Bro table.
type TableDescription: record {
# Common definitions for tables and events
## String that allows the reader to find the source.
## String that allows the reader to find the source of the data.
## For `READER_ASCII`, this is the filename.
source: string;
@ -61,7 +68,8 @@ export {
## Read mode to use for this stream.
mode: Mode &default=default_mode;
## Descriptive name. Used to remove a stream at a later time.
## Name of the input stream. This is used by some functions to
## manipulate the stream.
name: string;
# Special definitions for tables
@ -73,31 +81,35 @@ export {
idx: any;
## Record that defines the values used as the elements of the table.
## If this is undefined, then *destination* has to be a set.
## If this is undefined, then *destination* must be a set.
val: any &optional;
## Defines if the value of the table is a record (default), or a single value.
## When this is set to false, then *val* can only contain one element.
## Defines if the value of the table is a record (default), or a single
## value. When this is set to false, then *val* can only contain one
## element.
want_record: bool &default=T;
## The event that is raised each time a value is added to, changed in or removed
## from the table. The event will receive an Input::Event enum as the first
## argument, the *idx* record as the second argument and the value (record) as the
## third argument.
ev: any &optional; # event containing idx, val as values.
## The event that is raised each time a value is added to, changed in,
## or removed from the table. The event will receive an
## Input::TableDescription as the first argument, an Input::Event
## enum as the second argument, the *idx* record as the third argument
## and the value (record) as the fourth argument.
ev: any &optional;
## Predicate function that can decide if an insertion, update or removal should
## really be executed. Parameters are the same as for the event. If true is
## returned, the update is performed. If false is returned, it is skipped.
## Predicate function that can decide if an insertion, update or removal
## should really be executed. Parameters have same meaning as for the
## event.
## If true is returned, the update is performed. If false is returned,
## it is skipped.
pred: function(typ: Input::Event, left: any, right: any): bool &optional;
## A key/value table that will be passed on the reader.
## Interpretation of the values is left to the writer, but
## A key/value table that will be passed to the reader.
## Interpretation of the values is left to the reader, but
## usually they will be used for configuration purposes.
config: table[string] of string &default=table();
config: table[string] of string &default=table();
};
## EventFilter description type used for the `event` method.
## An event input stream type used to send input data to a Bro event.
type EventDescription: record {
# Common definitions for tables and events
@ -116,19 +128,26 @@ export {
# Special definitions for events
## Record describing the fields to be retrieved from the source input.
## Record type describing the fields to be retrieved from the input
## source.
fields: any;
## If this is false, the event receives each value in fields as a separate argument.
## If this is set to true (default), the event receives all fields in a single record value.
## If this is false, the event receives each value in *fields* as a
## separate argument.
## If this is set to true (default), the event receives all fields in
## a single record value.
want_record: bool &default=T;
## The event that is raised each time a new line is received from the reader.
## The event will receive an Input::Event enum as the first element, and the fields as the following arguments.
## The event that is raised each time a new line is received from the
## reader. The event will receive an Input::EventDescription record
## as the first argument, an Input::Event enum as the second
## argument, and the fields (as specified in *fields*) as the following
## arguments (this will either be a single record value containing
## all fields, or each field value as a separate argument).
ev: any;
## A key/value table that will be passed on the reader.
## Interpretation of the values is left to the writer, but
## A key/value table that will be passed to the reader.
## Interpretation of the values is left to the reader, but
## usually they will be used for configuration purposes.
config: table[string] of string &default=table();
};
@ -155,28 +174,29 @@ export {
## field will be the same value as the *source* field.
name: string;
## A key/value table that will be passed on the reader.
## Interpretation of the values is left to the writer, but
## A key/value table that will be passed to the reader.
## Interpretation of the values is left to the reader, but
## usually they will be used for configuration purposes.
config: table[string] of string &default=table();
};
## Create a new table input from a given source.
## Create a new table input stream from a given source.
##
## description: `TableDescription` record describing the source.
##
## Returns: true on success.
global add_table: function(description: Input::TableDescription) : bool;
## Create a new event input from a given source.
## Create a new event input stream from a given source.
##
## description: `EventDescription` record describing the source.
##
## Returns: true on success.
global add_event: function(description: Input::EventDescription) : bool;
## Create a new file analysis input from a given source. Data read from
## the source is automatically forwarded to the file analysis framework.
## Create a new file analysis input stream from a given source. Data read
## from the source is automatically forwarded to the file analysis
## framework.
##
## description: A record describing the source.
##
@ -199,7 +219,11 @@ export {
## Event that is called when the end of a data source has been reached,
## including after an update.
global end_of_data: event(name: string, source:string);
##
## name: Name of the input stream.
##
## source: String that identifies the data source (such as the filename).
global end_of_data: event(name: string, source: string);
}
@load base/bif/input.bif

View file

@ -11,7 +11,9 @@ export {
##
## name: name of the input stream.
## source: source of the input stream.
## exit_code: exit code of the program, or number of the signal that forced the program to exit.
## signal_exit: false when program exited normally, true when program was forced to exit by a signal.
## exit_code: exit code of the program, or number of the signal that forced
## the program to exit.
## signal_exit: false when program exited normally, true when program was
## forced to exit by a signal.
global process_finished: event(name: string, source:string, exit_code:count, signal_exit:bool);
}

View 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

View 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);
}

View 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

View 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;
}

View 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);
}

View 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);
}

View 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;
};
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;
}

View 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
};
}

View 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

View 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);
}

View 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,
};
}

View 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();
}

View 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);
}

View file

@ -0,0 +1,3 @@
@load ./ryu
@load ./log
@load ./broker

View 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
{
Broker::event(state$broker_topic, Broker::event_args(broker_flow_mod, state$_name, state$broker_dpid, match, flow_mod));
return T;
}
function broker_flow_clear_fun(state: OpenFlow::ControllerState): bool
{
Broker::event(state$broker_topic, Broker::event_args(broker_flow_clear, state$_name, state$broker_dpid));
return T;
}
function broker_init(state: OpenFlow::ControllerState)
{
Broker::enable();
Broker::connect(cat(state$broker_host), state$broker_port, 1sec);
Broker::subscribe_to_events(state$broker_topic); # openflow success and failure events are directly sent back via the other plugin via broker.
}
event Broker::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;
}

View 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;
}

View 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;
}

View 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;
};
}

View file

@ -138,7 +138,7 @@ redef enum PcapFilterID += {
function test_filter(filter: string): bool
{
if ( ! precompile_pcap_filter(FilterTester, filter) )
if ( ! Pcap::precompile_pcap_filter(FilterTester, filter) )
{
# The given filter was invalid
# TODO: generate a notice.
@ -273,7 +273,7 @@ function install(): bool
return F;
local ts = current_time();
if ( ! precompile_pcap_filter(DefaultPcapFilter, tmp_filter) )
if ( ! Pcap::precompile_pcap_filter(DefaultPcapFilter, tmp_filter) )
{
NOTICE([$note=Compile_Failure,
$msg=fmt("Compiling packet filter failed"),
@ -303,7 +303,7 @@ function install(): bool
}
info$filter = current_filter;
if ( ! install_pcap_filter(DefaultPcapFilter) )
if ( ! Pcap::install_pcap_filter(DefaultPcapFilter) )
{
# Installing the filter failed for some reason.
info$success = F;

View file

@ -280,6 +280,13 @@ function parse_mozilla(unparsed_version: string): Description
v = parse(parts[1])$version;
}
}
else if ( /AdobeAIR\/[0-9\.]*/ in unparsed_version )
{
software_name = "AdobeAIR";
parts = split_string_all(unparsed_version, /AdobeAIR\/[0-9\.]*/);
if ( 1 in parts )
v = parse(parts[1])$version;
}
else if ( /AppleWebKit\/[0-9\.]*/ in unparsed_version )
{
software_name = "Unspecified WebKit";

View file

@ -39,6 +39,13 @@ type count_set: set[count];
## directly and then remove this alias.
type index_vec: vector of count;
## A vector of subnets.
##
## .. todo:: We need this type definition only for declaring builtin functions
## via ``bifcl``. We should extend ``bifcl`` to understand composite types
## directly and then remove this alias.
type subnet_vec: vector of subnet;
## A vector of any, used by some builtin functions to store a list of varying
## types.
##
@ -120,6 +127,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.
} &log;
## Specifics about an ICMP conversation. ICMP events typically pass this in
## addition to :bro:type:`conn_id`.
##
@ -345,6 +364,12 @@ type connection: record {
## for the connection unless the :bro:id:`tunnel_changed` event is
## handled and reassigns this field to the new encapsulation.
tunnel: EncapsulatingConnVector &optional;
## The outer VLAN, if applicable, for this connection.
vlan: int &optional;
## The inner VLAN, if applicable, for this connection.
inner_vlan: int &optional;
};
## Default amount of time a file can be inactive before the file analysis
@ -768,71 +793,6 @@ type entropy_test_result: record {
serial_correlation: double; ##< Serial correlation coefficient.
};
# Prototypes of Bro built-in functions.
@load base/bif/strings.bif
@load base/bif/bro.bif
@load base/bif/reporter.bif
## Deprecated. This is superseded by the new logging framework.
global log_file_name: function(tag: string): string &redef;
## Deprecated. This is superseded by the new logging framework.
global open_log_file: function(tag: string): file &redef;
## Specifies a directory for Bro to store its persistent state. All globals can
## be declared persistent via the :bro:attr:`&persistent` attribute.
const state_dir = ".state" &redef;
## Length of the delays inserted when storing state incrementally. To avoid
## dropping packets when serializing larger volumes of persistent state to
## disk, Bro interleaves the operation with continued packet processing.
const state_write_delay = 0.01 secs &redef;
global done_with_network = F;
event net_done(t: time) { done_with_network = T; }
function log_file_name(tag: string): string
{
local suffix = getenv("BRO_LOG_SUFFIX") == "" ? "log" : getenv("BRO_LOG_SUFFIX");
return fmt("%s.%s", tag, suffix);
}
function open_log_file(tag: string): file
{
return open(log_file_name(tag));
}
## Internal function.
function add_interface(iold: string, inew: string): string
{
if ( iold == "" )
return inew;
else
return fmt("%s %s", iold, inew);
}
## Network interfaces to listen on. Use ``redef interfaces += "eth0"`` to
## extend.
global interfaces = "" &add_func = add_interface;
## Internal function.
function add_signature_file(sold: string, snew: string): string
{
if ( sold == "" )
return snew;
else
return cat(sold, " ", snew);
}
## Signature files to read. Use ``redef signature_files += "foo.sig"`` to
## extend. Signature files added this way will be searched relative to
## ``BROPATH``. Using the ``@load-sigs`` directive instead is preferred
## since that can search paths relative to the current script.
global signature_files = "" &add_func = add_signature_file;
## ``p0f`` fingerprint file to use. Will be searched relative to ``BROPATH``.
const passive_fingerprint_file = "base/misc/p0f.fp" &redef;
# TCP values for :bro:see:`endpoint` *state* field.
# todo:: these should go into an enum to make them autodoc'able.
const TCP_INACTIVE = 0; ##< Endpoint is still inactive.
@ -1511,6 +1471,7 @@ type l2_hdr: record {
src: string &optional; ##< L2 source (if Ethernet).
dst: string &optional; ##< L2 destination (if Ethernet).
vlan: count &optional; ##< Outermost VLAN tag if any (and Ethernet).
inner_vlan: count &optional; ##< Innermost VLAN tag if any (and Ethernet).
eth_type: count &optional; ##< Innermost Ethertype (if Ethernet).
proto: layer3_proto; ##< L3 protocol.
};
@ -1742,6 +1703,71 @@ type gtp_delete_pdp_ctx_response_elements: record {
ext: gtp_private_extension &optional;
};
# Prototypes of Bro built-in functions.
@load base/bif/strings.bif
@load base/bif/bro.bif
@load base/bif/reporter.bif
## Deprecated. This is superseded by the new logging framework.
global log_file_name: function(tag: string): string &redef;
## Deprecated. This is superseded by the new logging framework.
global open_log_file: function(tag: string): file &redef;
## Specifies a directory for Bro to store its persistent state. All globals can
## be declared persistent via the :bro:attr:`&persistent` attribute.
const state_dir = ".state" &redef;
## Length of the delays inserted when storing state incrementally. To avoid
## dropping packets when serializing larger volumes of persistent state to
## disk, Bro interleaves the operation with continued packet processing.
const state_write_delay = 0.01 secs &redef;
global done_with_network = F;
event net_done(t: time) { done_with_network = T; }
function log_file_name(tag: string): string
{
local suffix = getenv("BRO_LOG_SUFFIX") == "" ? "log" : getenv("BRO_LOG_SUFFIX");
return fmt("%s.%s", tag, suffix);
}
function open_log_file(tag: string): file
{
return open(log_file_name(tag));
}
## Internal function.
function add_interface(iold: string, inew: string): string
{
if ( iold == "" )
return inew;
else
return fmt("%s %s", iold, inew);
}
## Network interfaces to listen on. Use ``redef interfaces += "eth0"`` to
## extend.
global interfaces = "" &add_func = add_interface;
## Internal function.
function add_signature_file(sold: string, snew: string): string
{
if ( sold == "" )
return snew;
else
return cat(sold, " ", snew);
}
## Signature files to read. Use ``redef signature_files += "foo.sig"`` to
## extend. Signature files added this way will be searched relative to
## ``BROPATH``. Using the ``@load-sigs`` directive instead is preferred
## since that can search paths relative to the current script.
global signature_files = "" &add_func = add_signature_file;
## ``p0f`` fingerprint file to use. Will be searched relative to ``BROPATH``.
const passive_fingerprint_file = "base/misc/p0f.fp" &redef;
## Definition of "secondary filters". A secondary filter is a BPF filter given
## as index in this table. For each such filter, the corresponding event is
## raised for all matching packets.
@ -2502,7 +2528,7 @@ global dns_skip_all_addl = T &redef;
## If a DNS request includes more than this many queries, assume it's non-DNS
## traffic and do not process it. Set to 0 to turn off this functionality.
global dns_max_queries = 5;
global dns_max_queries = 25 &redef;
## HTTP session statistics.
##
@ -3655,20 +3681,11 @@ export {
## Toggle whether to do GRE decapsulation.
const enable_gre = T &redef;
## With this option set, the Teredo analysis will first check to see if
## other protocol analyzers have confirmed that they think they're
## parsing the right protocol and only continue with Teredo tunnel
## decapsulation if nothing else has yet confirmed. This can help
## reduce false positives of UDP traffic (e.g. DNS) that also happens
## to have a valid Teredo encapsulation.
const yielding_teredo_decapsulation = T &redef;
## With this set, the Teredo analyzer waits until it sees both sides
## of a connection using a valid Teredo encapsulation before issuing
## a :bro:see:`protocol_confirmation`. If it's false, the first
## occurrence of a packet with valid Teredo encapsulation causes a
## confirmation. Both cases are still subject to effects of
## :bro:see:`Tunnel::yielding_teredo_decapsulation`.
## confirmation.
const delay_teredo_confirmation = T &redef;
## With this set, the GTP analyzer waits until the most-recent upflow
@ -3684,7 +3701,6 @@ export {
## (includes GRE tunnels).
const ip_tunnel_timeout = 24hrs &redef;
} # end export
module GLOBAL;
module Reporter;
export {
@ -3703,10 +3719,18 @@ export {
## external harness and shouldn't output anything to the console.
const errors_to_stderr = T &redef;
}
module GLOBAL;
## Number of bytes per packet to capture from live interfaces.
const snaplen = 8192 &redef;
module Pcap;
export {
## Number of bytes per packet to capture from live interfaces.
const snaplen = 8192 &redef;
## Number of Mbytes to provide as buffer space when capturing from live
## interfaces.
const bufsize = 128 &redef;
} # end export
module GLOBAL;
## Seed for hashes computed internally for probabilistic data structures. Using
## the same value here will make the hashes compatible between independent Bro

View file

@ -37,6 +37,10 @@
@load base/frameworks/reporter
@load base/frameworks/sumstats
@load base/frameworks/tunnels
@ifdef ( Broker::enable )
@load base/frameworks/openflow
@load base/frameworks/netcontrol
@endif
@load base/protocols/conn
@load base/protocols/dhcp
@ -52,6 +56,7 @@
@load base/protocols/pop3
@load base/protocols/radius
@load base/protocols/rdp
@load base/protocols/rfb
@load base/protocols/sip
@load base/protocols/snmp
@load base/protocols/smtp

View file

@ -47,7 +47,7 @@ export {
## S2 Connection established and close attempt by originator seen (but no reply from responder).
## S3 Connection established and close attempt by responder seen (but no reply from originator).
## RSTO Connection established, originator aborted (sent a RST).
## RSTR Established, responder aborted.
## RSTR Responder sent a RST.
## RSTOS0 Originator sent a SYN followed by a RST, we never saw a SYN-ACK from the responder.
## RSTRH Responder sent a SYN ACK followed by a RST, we never saw a SYN from the (purported) originator.
## SH Originator sent a SYN followed by a FIN, we never saw a SYN ACK from the responder (hence the connection was "half" open).
@ -87,7 +87,8 @@ export {
## f packet with FIN bit set
## r packet with RST bit set
## c packet with a bad checksum
## i inconsistent packet (e.g. SYN+RST bits both set)
## i inconsistent packet (e.g. FIN+RST bits set)
## q multi-flag packet (SYN+FIN or SYN+RST bits set)
## ====== ====================================================
##
## If the event comes from the originator, the letter is in

View file

@ -26,6 +26,7 @@ export {
[49] = "DHCID", [99] = "SPF", [100] = "DINFO", [101] = "UID",
[102] = "GID", [103] = "UNSPEC", [249] = "TKEY", [250] = "TSIG",
[251] = "IXFR", [252] = "AXFR", [253] = "MAILB", [254] = "MAILA",
[257] = "CAA",
[32768] = "TA", [32769] = "DLV",
[ANY] = "*",
} &default = function(n: count): string { return fmt("query-%d", n); };

View file

@ -213,7 +213,7 @@ event ftp_reply(c: connection, code: count, msg: string, cont_resp: bool) &prior
# on a different file could be checked, but the file size will
# be overwritten by the server response to the RETR command
# if that's given as well which would be more correct.
c$ftp$file_size = extract_count(msg);
c$ftp$file_size = extract_count(msg, F);
}
# PASV and EPSV processing

View file

@ -41,6 +41,8 @@ export {
## misspelled like the standard declares, but the name used here
## is "referrer" spelled correctly.
referrer: string &log &optional;
## Value of the version portion of the request.
version: string &log &optional;
## Value of the User-Agent header from the client.
user_agent: string &log &optional;
## Actual uncompressed content size of the data transferred from
@ -222,6 +224,8 @@ event http_reply(c: connection, version: string, code: count, reason: string) &p
c$http$status_code = code;
c$http$status_msg = reason;
c$http$version = version;
if ( code_in_range(code, 100, 199) )
{
c$http$info_code = code;
@ -270,7 +274,7 @@ event http_header(c: connection, is_orig: bool, name: string, value: string) &pr
{
if ( /^[bB][aA][sS][iI][cC] / in value )
{
local userpass = decode_base64(sub(value, /[bB][aA][sS][iI][cC][[:blank:]]/, ""));
local userpass = decode_base64_conn(c$id, sub(value, /[bB][aA][sS][iI][cC][[:blank:]]/, ""));
local up = split_string(userpass, /:/);
if ( |up| >= 2 )
{

View file

@ -0,0 +1 @@
Support for Remote FrameBuffer analysis. This includes all VNC servers.

View file

@ -0,0 +1,3 @@
# Generated by binpac_quickstart
@load ./main
@load-sigs ./dpd.sig

View file

@ -0,0 +1,12 @@
signature dpd_rfb_server {
ip-proto == tcp
payload /^RFB/
requires-reverse-signature dpd_rfb_client
enable "rfb"
}
signature dpd_rfb_client {
ip-proto == tcp
payload /^RFB/
tcp-state originator
}

View file

@ -0,0 +1,164 @@
module RFB;
export {
redef enum Log::ID += { LOG };
type Info: record {
## Timestamp for when the event happened.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
## Major version of the client.
client_major_version: string &log &optional;
## Minor version of the client.
client_minor_version: string &log &optional;
## Major version of the server.
server_major_version: string &log &optional;
## Major version of the client.
server_minor_version: string &log &optional;
## Identifier of authentication method used.
authentication_method: string &log &optional;
## Whether or not authentication was succesful.
auth: bool &log &optional;
## Whether the client has an exclusive or a shared session.
share_flag: bool &log &optional;
## Name of the screen that is being shared.
desktop_name: string &log &optional;
## Width of the screen that is being shared.
width: count &log &optional;
## Height of the screen that is being shared.
height: count &log &optional;
## Internally used value to determine if this connection
## has already been logged.
done: bool &default=F;
};
global log_rfb: event(rec: Info);
}
function friendly_auth_name(auth: count): string
{
switch (auth) {
case 0:
return "Invalid";
case 1:
return "None";
case 2:
return "VNC";
case 16:
return "Tight";
case 17:
return "Ultra";
case 18:
return "TLS";
case 19:
return "VeNCrypt";
case 20:
return "GTK-VNC SASL";
case 21:
return "MD5 hash authentication";
case 22:
return "Colin Dean xvp";
case 30:
return "Apple Remote Desktop";
}
return "RealVNC";
}
redef record connection += {
rfb: Info &optional;
};
event bro_init() &priority=5
{
Log::create_stream(RFB::LOG, [$columns=Info, $ev=log_rfb, $path="rfb"]);
}
function write_log(c:connection)
{
local state = c$rfb;
if ( state$done )
{
return;
}
Log::write(RFB::LOG, c$rfb);
c$rfb$done = T;
}
function set_session(c: connection)
{
if ( ! c?$rfb )
{
local info: Info;
info$ts = network_time();
info$uid = c$uid;
info$id = c$id;
c$rfb = info;
}
}
event rfb_event(c: connection) &priority=5
{
set_session(c);
}
event rfb_client_version(c: connection, major_version: string, minor_version: string) &priority=5
{
set_session(c);
c$rfb$client_major_version = major_version;
c$rfb$client_minor_version = minor_version;
}
event rfb_server_version(c: connection, major_version: string, minor_version: string) &priority=5
{
set_session(c);
c$rfb$server_major_version = major_version;
c$rfb$server_minor_version = minor_version;
}
event rfb_authentication_type(c: connection, authtype: count) &priority=5
{
set_session(c);
c$rfb$authentication_method = friendly_auth_name(authtype);
}
event rfb_server_parameters(c: connection, name: string, width: count, height: count) &priority=5
{
set_session(c);
c$rfb$desktop_name = name;
c$rfb$width = width;
c$rfb$height = height;
}
event rfb_server_parameters(c: connection, name: string, width: count, height: count) &priority=-5
{
write_log(c);
}
event rfb_auth_result(c: connection, result: bool) &priority=5
{
c$rfb$auth = !result;
}
event rfb_share_flag(c: connection, flag: bool) &priority=5
{
c$rfb$share_flag = flag;
}
event connection_state_remove(c: connection) &priority=-5
{
if ( c?$rfb )
{
write_log(c);
}
}

View file

@ -60,9 +60,9 @@ export {
## Contents of the Warning: header
warning: string &log &optional;
## Contents of the Content-Length: header from the client
request_body_len: string &log &optional;
request_body_len: count &log &optional;
## Contents of the Content-Length: header from the server
response_body_len: string &log &optional;
response_body_len: count &log &optional;
## Contents of the Content-Type: header from the server
content_type: string &log &optional;
};
@ -80,7 +80,7 @@ export {
## that the SIP analyzer will only accept methods consisting solely
## of letters ``[A-Za-z]``.
const sip_methods: set[string] = {
"REGISTER", "INVITE", "ACK", "CANCEL", "BYE", "OPTIONS"
"REGISTER", "INVITE", "ACK", "CANCEL", "BYE", "OPTIONS", "NOTIFY", "SUBSCRIBE"
} &redef;
## Event that can be handled to access the SIP record as it is sent on
@ -127,17 +127,6 @@ function set_state(c: connection, is_request: bool)
c$sip_state = s;
}
# These deal with new requests and responses.
if ( is_request && c$sip_state$current_request !in c$sip_state$pending )
c$sip_state$pending[c$sip_state$current_request] = new_sip_session(c);
if ( ! is_request && c$sip_state$current_response !in c$sip_state$pending )
c$sip_state$pending[c$sip_state$current_response] = new_sip_session(c);
if ( is_request )
c$sip = c$sip_state$pending[c$sip_state$current_request];
else
c$sip = c$sip_state$pending[c$sip_state$current_response];
if ( is_request )
{
if ( c$sip_state$current_request !in c$sip_state$pending )
@ -152,7 +141,6 @@ function set_state(c: connection, is_request: bool)
c$sip = c$sip_state$pending[c$sip_state$current_response];
}
}
function flush_pending(c: connection)
@ -163,7 +151,9 @@ function flush_pending(c: connection)
for ( r in c$sip_state$pending )
{
# We don't use pending elements at index 0.
if ( r == 0 ) next;
if ( r == 0 )
next;
Log::write(SIP::LOG, c$sip_state$pending[r]);
}
}
@ -205,16 +195,39 @@ event sip_header(c: connection, is_request: bool, name: string, value: string) &
if ( c$sip_state$current_request !in c$sip_state$pending )
++c$sip_state$current_request;
set_state(c, is_request);
if ( name == "CALL-ID" ) c$sip$call_id = value;
else if ( name == "CONTENT-LENGTH" || name == "L" ) c$sip$request_body_len = value;
else if ( name == "CSEQ" ) c$sip$seq = value;
else if ( name == "DATE" ) c$sip$date = value;
else if ( name == "FROM" || name == "F" ) c$sip$request_from = split_string1(value, /;[ ]?tag=/)[0];
else if ( name == "REPLY-TO" ) c$sip$reply_to = value;
else if ( name == "SUBJECT" || name == "S" ) c$sip$subject = value;
else if ( name == "TO" || name == "T" ) c$sip$request_to = value;
else if ( name == "USER-AGENT" ) c$sip$user_agent = value;
else if ( name == "VIA" || name == "V" ) c$sip$request_path[|c$sip$request_path|] = split_string1(value, /;[ ]?branch/)[0];
switch ( name )
{
case "CALL-ID":
c$sip$call_id = value;
break;
case "CONTENT-LENGTH", "L":
c$sip$request_body_len = to_count(value);
break;
case "CSEQ":
c$sip$seq = value;
break;
case "DATE":
c$sip$date = value;
break;
case "FROM", "F":
c$sip$request_from = split_string1(value, /;[ ]?tag=/)[0];
break;
case "REPLY-TO":
c$sip$reply_to = value;
break;
case "SUBJECT", "S":
c$sip$subject = value;
break;
case "TO", "T":
c$sip$request_to = value;
break;
case "USER-AGENT":
c$sip$user_agent = value;
break;
case "VIA", "V":
c$sip$request_path[|c$sip$request_path|] = split_string1(value, /;[ ]?branch/)[0];
break;
}
c$sip_state$pending[c$sip_state$current_request] = c$sip;
}
@ -222,13 +235,29 @@ event sip_header(c: connection, is_request: bool, name: string, value: string) &
{
if ( c$sip_state$current_response !in c$sip_state$pending )
++c$sip_state$current_response;
set_state(c, is_request);
if ( name == "CONTENT-LENGTH" || name == "L" ) c$sip$response_body_len = value;
else if ( name == "CONTENT-TYPE" || name == "C" ) c$sip$content_type = value;
else if ( name == "WARNING" ) c$sip$warning = value;
else if ( name == "FROM" || name == "F" ) c$sip$response_from = split_string1(value, /;[ ]?tag=/)[0];
else if ( name == "TO" || name == "T" ) c$sip$response_to = value;
else if ( name == "VIA" || name == "V" ) c$sip$response_path[|c$sip$response_path|] = split_string1(value, /;[ ]?branch/)[0];
switch ( name )
{
case "CONTENT-LENGTH", "L":
c$sip$response_body_len = to_count(value);
break;
case "CONTENT-TYPE", "C":
c$sip$content_type = value;
break;
case "WARNING":
c$sip$warning = value;
break;
case "FROM", "F":
c$sip$response_from = split_string1(value, /;[ ]?tag=/)[0];
break;
case "TO", "T":
c$sip$response_to = value;
break;
case "VIA", "V":
c$sip$response_path[|c$sip$response_path|] = split_string1(value, /;[ ]?branch/)[0];
break;
}
c$sip_state$pending[c$sip_state$current_response] = c$sip;
}

View file

@ -29,6 +29,8 @@ export {
from: string &log &optional;
## Contents of the To header.
to: set[string] &log &optional;
## Contents of the CC header.
cc: set[string] &log &optional;
## Contents of the ReplyTo header.
reply_to: string &log &optional;
## Contents of the MsgID header.
@ -239,6 +241,16 @@ event mime_one_header(c: connection, h: mime_header_rec) &priority=5
add c$smtp$to[to_parts[i]];
}
else if ( h$name == "CC" )
{
if ( ! c$smtp?$cc )
c$smtp$cc = set();
local cc_parts = split_string(h$value, /[[:blank:]]*,[[:blank:]]*/);
for ( i in cc_parts )
add c$smtp$cc[cc_parts[i]];
}
else if ( h$name == "X-ORIGINATING-IP" )
{
local addresses = extract_ip_addresses(h$value);

View file

@ -46,11 +46,10 @@ export {
## authentication success or failure when compression is enabled.
const compression_algorithms = set("zlib", "zlib@openssh.com") &redef;
## If true, we tell the event engine to not look at further data
## packets after the initial SSH handshake. Helps with performance
## (especially with large file transfers) but precludes some
## kinds of analyses. Defaults to T.
const skip_processing_after_detection = T &redef;
## If true, after detection detach the SSH analyzer from the connection
## to prevent continuing to process encrypted traffic. Helps with performance
## (especially with large file transfers).
const disable_analyzer_after_detection = T &redef;
## Event that can be handled to access the SSH record as it is sent on
## to the logging framework.
@ -70,6 +69,8 @@ redef record Info += {
# Store capabilities from the first host for
# comparison with the second (internal use)
capabilities: Capabilities &optional;
## Analzyer ID
analyzer_id: count &optional;
};
redef record connection += {
@ -130,11 +131,8 @@ event ssh_auth_successful(c: connection, auth_method_none: bool) &priority=5
c$ssh$auth_success = T;
if ( skip_processing_after_detection)
{
skip_further_processing(c$id);
set_record_packets(c$id, F);
}
if ( disable_analyzer_after_detection )
disable_analyzer(c$id, c$ssh$analyzer_id);
}
event ssh_auth_successful(c: connection, auth_method_none: bool) &priority=-5
@ -179,7 +177,7 @@ function find_bidirectional_alg(client_prefs: Algorithm_Prefs, server_prefs: Alg
# Usually these are the same, but if they're not, return the details
return c_to_s == s_to_c ? c_to_s : fmt("To server: %s, to client: %s", c_to_s, s_to_c);
}
event ssh_capabilities(c: connection, cookie: string, capabilities: Capabilities)
{
if ( !c?$ssh || ( c$ssh?$capabilities && c$ssh$capabilities$is_server == capabilities$is_server ) )
@ -233,3 +231,12 @@ event ssh2_server_host_key(c: connection, key: string) &priority=5
{
generate_fingerprint(c, key);
}
event protocol_confirmation(c: connection, atype: Analyzer::Tag, aid: count) &priority=20
{
if ( atype == Analyzer::ANALYZER_SSH )
{
set_session(c);
c$ssh$analyzer_id = aid;
}
}

View file

@ -109,7 +109,7 @@ export {
[7] = "client_authz",
[8] = "server_authz",
[9] = "cert_type",
[10] = "elliptic_curves",
[10] = "elliptic_curves", # new name: supported_groups - draft-ietf-tls-negotiated-ff-dhe
[11] = "ec_point_formats",
[12] = "srp",
[13] = "signature_algorithms",
@ -120,9 +120,10 @@ export {
[18] = "signed_certificate_timestamp",
[19] = "client_certificate_type",
[20] = "server_certificate_type",
[21] = "padding", # temporary till 2016-03-12
[21] = "padding",
[22] = "encrypt_then_mac",
[23] = "extended_master_secret",
[24] = "token_binding", # temporary till 2017-02-04 - draft-ietf-tokbind-negotiation
[35] = "SessionTicket TLS",
[40] = "extended_random",
[13172] = "next_protocol_negotiation",
@ -165,7 +166,10 @@ export {
[26] = "brainpoolP256r1",
[27] = "brainpoolP384r1",
[28] = "brainpoolP512r1",
# draft-ietf-tls-negotiated-ff-dhe-05
# Temporary till 2017-03-01 - draft-ietf-tls-rfc4492bis
[29] = "ecdh_x25519",
[30] = "ecdh_x448",
# draft-ietf-tls-negotiated-ff-dhe-10
[256] = "ffdhe2048",
[257] = "ffdhe3072",
[258] = "ffdhe4096",

View file

@ -1,7 +1,7 @@
signature dpd_ssl_server {
ip-proto == tcp
# Server hello.
payload /^(\x16\x03[\x00\x01\x02\x03]..\x02...\x03[\x00\x01\x02\x03]|...?\x04..\x00\x02).*/
payload /^((\x15\x03[\x00\x01\x02\x03]....)?\x16\x03[\x00\x01\x02\x03]..\x02...\x03[\x00\x01\x02\x03]|...?\x04..\x00\x02).*/
requires-reverse-signature dpd_ssl_client
enable "ssl"
tcp-state responder

View file

@ -9,6 +9,6 @@ signature dpd_ayiya {
signature dpd_teredo {
ip-proto = udp
payload /^(\x00\x00)|(\x00\x01)|([\x60-\x6f])/
payload /^(\x00\x00)|(\x00\x01)|([\x60-\x6f].{7}((\x20\x01\x00\x00)).{28})|([\x60-\x6f].{23}((\x20\x01\x00\x00))).{12}/
enable "teredo"
}

105
scripts/base/utils/json.bro Normal file
View file

@ -0,0 +1,105 @@
##! Functions to assist with generating JSON data from Bro data scructures.
# We might want to implement this in core somtime, this looks... hacky at best.
@load base/utils/strings
## A function to convert arbitrary Bro data into a JSON string.
##
## v: The value to convert to JSON. Typically a record.
##
## only_loggable: If the v value is a record this will only cause
## fields with the &log attribute to be included in the JSON.
##
## returns: a JSON formatted string.
function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/): string
{
local tn = type_name(v);
switch ( tn )
{
case "type":
return "";
case "string":
return cat("\"", gsub(gsub(clean(v), /\\/, "\\\\"), /\"/, "\\\""), "\"");
case "port":
return cat(port_to_count(to_port(cat(v))));
case "addr":
fallthrough;
case "subnet":
return cat("\"", v, "\"");
case "int":
fallthrough;
case "count":
fallthrough;
case "time":
fallthrough;
case "double":
fallthrough;
case "bool":
fallthrough;
case "enum":
return cat(v);
default:
break;
}
if ( /^record/ in tn )
{
local rec_parts: string_vec = vector();
local ft = record_fields(v);
for ( field in ft )
{
local field_desc = ft[field];
# replace the escape pattern in the field.
if( field_escape_pattern in field )
field = cat(sub(field, field_escape_pattern, ""));
if ( field_desc?$value && (!only_loggable || field_desc$log) )
{
local onepart = cat("\"", field, "\": ", to_json(field_desc$value, only_loggable));
rec_parts[|rec_parts|] = onepart;
}
}
return cat("{", join_string_vec(rec_parts, ", "), "}");
}
# None of the following are supported.
else if ( /^set/ in tn )
{
local set_parts: string_vec = vector();
local sa: set[bool] = v;
for ( sv in sa )
{
set_parts[|set_parts|] = to_json(sv, only_loggable);
}
return cat("[", join_string_vec(set_parts, ", "), "]");
}
else if ( /^table/ in tn )
{
local tab_parts: vector of string = vector();
local ta: table[bool] of any = v;
for ( ti in ta )
{
local ts = to_json(ti);
local if_quotes = (ts[0] == "\"") ? "" : "\"";
tab_parts[|tab_parts|] = cat(if_quotes, ts, if_quotes, ": ", to_json(ta[ti], only_loggable));
}
return cat("{", join_string_vec(tab_parts, ", "), "}");
}
else if ( /^vector/ in tn )
{
local vec_parts: string_vec = vector();
local va: vector of any = v;
for ( vi in va )
{
vec_parts[|vec_parts|] = to_json(va[vi], only_loggable);
}
return cat("[", join_string_vec(vec_parts, ", "), "]");
}
return "\"\"";
}

View file

@ -1,10 +1,26 @@
## Extract the first integer found in the given string.
## If no integer can be found, 0 is returned.
function extract_count(s: string): count
## Extract an integer from a string.
##
## s: The string to search for a number.
##
## get_first: Provide `F` if you would like the last number found.
##
## Returns: The request integer from the given string or 0 if
## no integer was found.
function extract_count(s: string, get_first: bool &default=T): count
{
local parts = split_string_n(s, /[0-9]+/, T, 1);
if ( 1 in parts )
return to_count(parts[1]);
local extract_num_pattern = /[0-9]+/;
if ( get_first )
{
local first_parts = split_string_n(s, extract_num_pattern, T, 1);
if ( 1 in first_parts )
return to_count(first_parts[1]);
}
else
return 0;
{
local last_parts = split_string_all(s, extract_num_pattern);
if ( |last_parts| > 1 )
return to_count(last_parts[|last_parts|-2]);
}
return 0;
}