Merge remote-tracking branch 'origin/master' into topic/seth/log-framework-ext

This commit is contained in:
Seth Hall 2016-08-10 10:28:04 -04:00
commit a60ce35103
885 changed files with 141119 additions and 120109 deletions

View file

@ -28,6 +28,14 @@ redef Communication::listen_port = Cluster::nodes[Cluster::node]$p;
@if ( Cluster::local_node_type() == Cluster::MANAGER )
@load ./nodes/manager
# If no logger is defined, then the manager receives logs.
@if ( Cluster::manager_is_logger )
@load ./nodes/logger
@endif
@endif
@if ( Cluster::local_node_type() == Cluster::LOGGER )
@load ./nodes/logger
@endif
@if ( Cluster::local_node_type() == Cluster::PROXY )

View file

@ -31,7 +31,9 @@ export {
## A node type which is allowed to view/manipulate the configuration
## of other nodes in the cluster.
CONTROL,
## A node type responsible for log and policy management.
## A node type responsible for log management.
LOGGER,
## A node type responsible for policy management.
MANAGER,
## A node type for relaying worker node communication and synchronizing
## worker node state.
@ -50,12 +52,21 @@ export {
## Events raised by a manager and handled by proxies.
const manager2proxy_events = /EMPTY/ &redef;
## Events raised by a manager and handled by loggers.
const manager2logger_events = /EMPTY/ &redef;
## Events raised by proxies and handled by loggers.
const proxy2logger_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 loggers.
const worker2logger_events = /EMPTY/ &redef;
## Events raised by workers and handled by a manager.
const worker2manager_events = /(TimeMachine::command|Drop::.*)/ &redef;
@ -86,6 +97,8 @@ export {
p: port;
## Identifier for the interface a worker is sniffing.
interface: string &optional;
## Name of the logger node this node uses. For manager, proxies and workers.
logger: string &optional;
## Name of the manager node this node uses. For workers and proxies.
manager: string &optional;
## Name of the proxy node this node uses. For workers and managers.
@ -123,6 +136,12 @@ export {
## Note that BroControl handles all of this automatically.
const nodes: table[string] of Node = {} &redef;
## Indicates whether or not the manager will act as the logger and receive
## logs. This value should be set in the cluster-layout.bro script (the
## value should be true only if no logger is specified in Cluster::nodes).
## Note that BroControl handles this automatically.
const manager_is_logger = T &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;

View file

@ -0,0 +1,29 @@
##! This is the core Bro script to support the notion of a cluster logger.
##!
##! The logger is passive (other Bro instances connect to us), and once
##! connected the logger receives logs from other Bro instances.
##! This script will be automatically loaded if necessary based on the
##! type of node being started.
##! This is where the cluster logger sets it's specific settings for other
##! frameworks and in the core.
@prefixes += cluster-logger
## Turn on local logging.
redef Log::enable_local_logging = T;
## Turn off remote logging since this is the logger and should only log here.
redef Log::enable_remote_logging = F;
## Log rotation interval.
redef Log::default_rotation_interval = 1 hrs;
## Alarm summary mail interval.
redef Log::default_mail_alarms_interval = 24 hrs;
## Use the cluster's archive logging script.
redef Log::default_rotation_postprocessor_cmd = "archive-log";
## We're processing essentially *only* remote events.
redef max_remote_events_processed = 10000;

View file

@ -10,17 +10,17 @@
@prefixes += cluster-manager
## Turn off remote logging since this is the manager and should only log here.
redef Log::enable_remote_logging = F;
## Don't do any local logging since the logger handles writing logs.
redef Log::enable_local_logging = F;
## Turn on remote logging since the logger handles writing logs.
redef Log::enable_remote_logging = T;
## Log rotation interval.
redef Log::default_rotation_interval = 1 hrs;
redef Log::default_rotation_interval = 24 hrs;
## Alarm summary mail interval.
redef Log::default_mail_alarms_interval = 24 hrs;
## Use the cluster's archive logging script.
redef Log::default_rotation_postprocessor_cmd = "archive-log";
## Use the cluster's delete-log script.
redef Log::default_rotation_postprocessor_cmd = "delete-log";
## We're processing essentially *only* remote events.
redef max_remote_events_processed = 10000;

View file

@ -1,6 +1,6 @@
##! Redefines some options common to all worker nodes within a Bro cluster.
##! In particular, worker nodes do not produce logs locally, instead they
##! send them off to a manager node for processing.
##! send them off to a logger node for processing.
@prefixes += cluster-worker

View file

@ -23,17 +23,40 @@ event bro_init() &priority=9
$connect=F, $class="control",
$events=control_events];
if ( me$node_type == MANAGER )
if ( me$node_type == LOGGER )
{
if ( n$node_type == MANAGER && n$logger == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F,
$class=i, $events=manager2logger_events, $request_logs=T];
if ( n$node_type == PROXY && n$logger == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F,
$class=i, $events=proxy2logger_events, $request_logs=T];
if ( n$node_type == WORKER && n$logger == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F,
$class=i, $events=worker2logger_events, $request_logs=T];
}
else if ( me$node_type == MANAGER )
{
if ( n$node_type == LOGGER && me$logger == i )
Communication::nodes["logger"] =
[$host=n$ip, $zone_id=n$zone_id, $p=n$p,
$connect=T, $retry=retry_interval,
$class=node];
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];
$class=i, $events=worker2manager_events,
$request_logs=Cluster::manager_is_logger];
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];
$class=i, $events=proxy2manager_events,
$request_logs=Cluster::manager_is_logger];
if ( n$node_type == TIME_MACHINE && me?$time_machine && me$time_machine == i )
Communication::nodes["time-machine"] = [$host=nodes[i]$ip,
@ -45,6 +68,12 @@ event bro_init() &priority=9
else if ( me$node_type == PROXY )
{
if ( n$node_type == LOGGER && me$logger == i )
Communication::nodes["logger"] =
[$host=n$ip, $zone_id=n$zone_id, $p=n$p,
$connect=T, $retry=retry_interval,
$class=node];
if ( n$node_type == WORKER && n$proxy == node )
Communication::nodes[i] =
[$host=n$ip, $zone_id=n$zone_id, $connect=F, $class=i,
@ -76,6 +105,12 @@ event bro_init() &priority=9
}
else if ( me$node_type == WORKER )
{
if ( n$node_type == LOGGER && me$logger == i )
Communication::nodes["logger"] =
[$host=n$ip, $zone_id=n$zone_id, $p=n$p,
$connect=T, $retry=retry_interval,
$class=node];
if ( n$node_type == MANAGER && me$manager == i )
Communication::nodes["manager"] = [$host=nodes[i]$ip,
$zone_id=nodes[i]$zone_id,

View file

@ -27,6 +27,9 @@ export {
disabled_aids: set[count];
};
## Analyzers which you don't want to throw
const ignore_violations: set[Analyzer::Tag] = set() &redef;
## Ignore violations which go this many bytes into the connection.
## Set to 0 to never ignore protocol violations.
const ignore_violations_after = 10 * 1024 &redef;
@ -82,8 +85,11 @@ event protocol_violation(c: connection, atype: Analyzer::Tag, aid: count, reason
if ( ignore_violations_after > 0 && size > ignore_violations_after )
return;
if ( atype in ignore_violations )
return;
# Disable the analyzer that raised the last core-generated event.
disable_analyzer(c$id, aid);
disable_analyzer(c$id, aid, F);
add c$dpd$disabled_aids[aid];
}

View file

@ -174,3 +174,8 @@ signature file-lzma {
file-magic /^\x5d\x00\x00/
}
# ACE archive file.
signature file-ace-archive {
file-mime "application/x-ace", 100
file-magic /^.{7}\*\*ACE\*\*/
}

View file

@ -93,7 +93,7 @@ signature file-ini {
# Microsoft LNK files
signature file-lnk {
file-mime "application/x-ms-shortcut", 49
file-magic /^\x4C\x00\x00\x00\x01\x14\x02\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x10\x00\x00\x00\x46/
file-magic /^\x4c\x00\x00\x00\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46/
}
# Microsoft Registry policies
@ -310,4 +310,4 @@ signature file-elf-sharedlib {
signature file-elf-coredump {
file-mime "application/x-coredump", 50
file-magic /\x7fELF[\x01\x02](\x01.{10}\x04\x00|\x02.{10}\x00\x04)/
}
}

View file

@ -103,6 +103,17 @@ export {
## it is skipped.
pred: function(typ: Input::Event, left: any, right: any): bool &optional;
## Error event that is raised when an information, warning or error
## is raised by the input stream. If the level is error, the stream will automatically
## be closed.
## The event receives the Input::TableDescription as the first argument, the
## message as the second argument and the Reporter::Level as the third argument.
##
## The event is raised like if it had been declared as follows:
## error_ev: function(desc: TableDescription, message: string, level: Reporter::Level) &optional;
## The actual declaration uses the ``any`` type because of deficiencies of the Bro type system.
error_ev: any &optional;
## 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.
@ -146,6 +157,17 @@ export {
## all fields, or each field value as a separate argument).
ev: any;
## Error event that is raised when an information, warning or error
## is raised by the input stream. If the level is error, the stream will automatically
## be closed.
## The event receives the Input::EventDescription as the first argument, the
## message as the second argument and the Reporter::Level as the third argument.
##
## The event is raised like it had been declared as follows:
## error_ev: function(desc: EventDescription, message: string, level: Reporter::Level) &optional;
## The actual declaration uses the ``any`` type because of deficiencies of the Bro type system.
error_ev: any &optional;
## 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.

View file

@ -1,5 +1,8 @@
@load ./main
# File analysis framework integration.
@load ./files
# The cluster framework must be loaded first.
@load base/frameworks/cluster

View file

@ -1,8 +1,8 @@
##! Cluster transparency support for the intelligence framework. This is mostly
##! oriented toward distributing intelligence information across clusters.
@load ./main
@load base/frameworks/cluster
@load ./input
module Intel;
@ -17,19 +17,17 @@ redef record Item += {
redef have_full_data = F;
@endif
# Internal event for cluster data distribution.
global cluster_new_item: event(item: Item);
# Primary intelligence distribution comes from manager.
redef Cluster::manager2worker_events += /^Intel::(cluster_new_item)$/;
# If a worker finds intelligence and adds it, it should share it back to the manager.
redef Cluster::worker2manager_events += /^Intel::(cluster_new_item|match_no_items)$/;
# Primary intelligence management is done by the manager:
# The manager informs the workers about new items and item removal.
redef Cluster::manager2worker_events += /^Intel::(cluster_new_item|purge_item)$/;
# A worker queries the manager to insert, remove or indicate the match of an item.
redef Cluster::worker2manager_events += /^Intel::(cluster_new_item|remove_item|match_no_items)$/;
@if ( Cluster::local_node_type() == Cluster::MANAGER )
event Intel::match_no_items(s: Seen) &priority=5
{
event Intel::match(s, Intel::get_items(s));
}
# Handling of new worker nodes.
event remote_connection_handshake_done(p: event_peer)
{
# When a worker connects, send it the complete minimal data store.
@ -39,15 +37,22 @@ event remote_connection_handshake_done(p: event_peer)
send_id(p, "Intel::min_data_store");
}
}
@endif
event Intel::cluster_new_item(item: Intel::Item) &priority=5
# Handling of matches triggered by worker nodes.
event Intel::match_no_items(s: Seen) &priority=5
{
# Ignore locally generated events to avoid event storms.
if ( is_remote_event() )
Intel::insert(item);
if ( Intel::find(s) )
event Intel::match(s, Intel::get_items(s));
}
# Handling of item removal triggered by worker nodes.
event Intel::remove_item(item: Item, purge_indicator: bool)
{
remove(item, purge_indicator);
}
@endif
# Handling of item insertion.
event Intel::new_item(item: Intel::Item) &priority=5
{
# The cluster manager always rebroadcasts intelligence.
@ -59,3 +64,11 @@ event Intel::new_item(item: Intel::Item) &priority=5
event Intel::cluster_new_item(item);
}
}
# Handling of item insertion by remote node.
event Intel::cluster_new_item(item: Intel::Item) &priority=5
{
# Ignore locally generated events to avoid event storms.
if ( is_remote_event() )
Intel::insert(item);
}

View file

@ -0,0 +1,84 @@
##! File analysis framework integration for the intelligence framework. This
##! script manages file information in intelligence framework datastructures.
@load ./main
module Intel;
export {
## Enum type to represent various types of intelligence data.
redef enum Type += {
## File hash which is non-hash type specific. It's up to the
## user to query for any relevant hash types.
FILE_HASH,
## File name. Typically with protocols with definite
## indications of a file name.
FILE_NAME,
};
## Information about a piece of "seen" data.
redef record Seen += {
## If the data was discovered within a file, the file record
## should go here to provide context to the data.
f: fa_file &optional;
## If the data was discovered within a file, the file uid should
## go here to provide context to the data. If the file record *f*
## is provided, this will be automatically filled out.
fuid: string &optional;
};
## Record used for the logging framework representing a positive
## hit within the intelligence framework.
redef record Info += {
## If a file was associated with this intelligence hit,
## this is the uid for the file.
fuid: string &log &optional;
## A mime type if the intelligence hit is related to a file.
## If the $f field is provided this will be automatically filled
## out.
file_mime_type: string &log &optional;
## Frequently files can be "described" to give a bit more context.
## If the $f field is provided this field will be automatically
## filled out.
file_desc: string &log &optional;
};
}
# Add file information to matches if available.
hook extend_match(info: Info, s: Seen, items: set[Item]) &priority=5
{
if ( s?$f )
{
s$fuid = s$f$id;
if ( s$f?$conns && |s$f$conns| == 1 )
{
for ( cid in s$f$conns )
s$conn = s$f$conns[cid];
}
if ( ! info?$file_mime_type && s$f?$info && s$f$info?$mime_type )
info$file_mime_type = s$f$info$mime_type;
if ( ! info?$file_desc )
info$file_desc = Files::describe(s$f);
}
if ( s?$fuid )
info$fuid = s$fuid;
if ( s?$conn )
{
s$uid = s$conn$uid;
info$id = s$conn$id;
}
if ( s?$uid )
info$uid = s$uid;
for ( item in items )
{
add info$sources[item$meta$source];
add info$matched[item$indicator_type];
}
}

View file

@ -1,11 +1,14 @@
##! Input handling for the intelligence framework. This script implements the
##! import of intelligence data from files using the input framework.
@load ./main
module Intel;
export {
## Intelligence files that will be read off disk. The files are
## reread every time they are updated so updates must be atomic with
## "mv" instead of writing the file in place.
## Intelligence files that will be read off disk. The files are
## reread every time they are updated so updates must be atomic
## with "mv" instead of writing the file in place.
const read_files: set[string] = {} &redef;
}

View file

@ -1,7 +1,6 @@
##! The intelligence framework provides a way to store and query IP addresses,
##! and strings (with a str_type). Metadata can
##! also be associated with the intelligence, like for making more informed
##! decisions about matching and handling of intelligence.
##! The intelligence framework provides a way to store and query intelligence data
##! (e.g. IP addresses, URLs and hashes). The intelligence items can be associated
##! with metadata to allow informed decisions about matching and handling.
@load base/frameworks/notice
@ -14,6 +13,8 @@ export {
type Type: enum {
## An IP address.
ADDR,
## A subnet in CIDR notation.
SUBNET,
## A complete URL without the prefix ``"http://"``.
URL,
## Software name.
@ -24,24 +25,20 @@ export {
DOMAIN,
## A user name.
USER_NAME,
## File hash which is non-hash type specific. It's up to the
## user to query for any relevant hash types.
FILE_HASH,
## File name. Typically with protocols with definite
## indications of a file name.
FILE_NAME,
## Certificate SHA-1 hash.
CERT_HASH,
## Public key MD5 hash. (SSH server host keys are a good example.)
PUBKEY_HASH,
};
## Set of intelligence data types.
type TypeSet: set[Type];
## Data about an :bro:type:`Intel::Item`.
type MetaData: record {
## An arbitrary string value representing the data source.
## Typically, the convention for this field will be the source
## name and feed name separated by a hyphen.
## For example: "source1-c&c".
## An arbitrary string value representing the data source. This
## value is used as unique key to identify a metadata record in
## the scope of a single intelligence item.
source: string;
## A freeform description for the data.
desc: string &optional;
@ -57,7 +54,7 @@ export {
## The type of data that the indicator field represents.
indicator_type: Type;
## Metadata for the item. Typically represents more deeply
## Metadata for the item. Typically represents more deeply
## descriptive data for a piece of intelligence.
meta: MetaData;
};
@ -96,15 +93,6 @@ export {
## If the *conn* field is provided, this will be automatically
## filled out.
uid: string &optional;
## If the data was discovered within a file, the file record
## should go here to provide context to the data.
f: fa_file &optional;
## If the data was discovered within a file, the file uid should
## go here to provide context to the data. If the *f* field is
## provided, this will be automatically filled out.
fuid: string &optional;
};
## Record used for the logging framework representing a positive
@ -120,41 +108,70 @@ export {
## this is the conn_id for the connection.
id: conn_id &log &optional;
## If a file was associated with this intelligence hit,
## this is the uid for the file.
fuid: string &log &optional;
## A mime type if the intelligence hit is related to a file.
## If the $f field is provided this will be automatically filled
## out.
file_mime_type: string &log &optional;
## Frequently files can be "described" to give a bit more context.
## If the $f field is provided this field will be automatically
## filled out.
file_desc: string &log &optional;
## Where the data was seen.
seen: Seen &log;
## Which indicator types matched.
matched: TypeSet &log;
## Sources which supplied data that resulted in this match.
sources: set[string] &log &default=string_set();
};
## Intelligence data manipulation function.
## Function to insert intelligence data. If the indicator is already
## present, the associated metadata will be added to the indicator. If
## the indicator already contains a metadata record from the same source,
## the existing metadata record will be updated.
global insert: function(item: Item);
## Function to remove intelligence data. If purge_indicator is set, the
## given metadata is ignored and the indicator is removed completely.
global remove: function(item: Item, purge_indicator: bool &default = F);
## Function to declare discovery of a piece of data in order to check
## it against known intelligence for matches.
global seen: function(s: Seen);
## Event to represent a match in the intelligence data from data that
## was seen. On clusters there is no assurance as to where this event
## was seen. On clusters there is no assurance as to when this event
## will be generated so do not assume that arbitrary global state beyond
## the given data will be available.
##
## This is the primary mechanism where a user will take actions based on
## data within the intelligence framework.
## This is the primary mechanism where a user may take actions based on
## data provided by the intelligence framework.
global match: event(s: Seen, items: set[Item]);
## This hook can be used to influence the logging of intelligence hits
## (e.g. by adding data to the Info record). The default information is
## added with a priority of 5.
##
## info: The Info record that will be logged.
##
## s: Information about the data seen.
##
## items: The intel items that match the seen data.
##
## In case the hook execution is terminated using break, the match will
## not be logged.
global extend_match: hook(info: Info, s: Seen, items: set[Item]);
## The expiration timeout for intelligence items. Once an item expires, the
## :bro:id:`Intel::item_expired` hook is called. Reinsertion of an item
## resets the timeout. A negative value disables expiration of intelligence
## items.
const item_expiration = -1 min &redef;
## This hook can be used to handle expiration of intelligence items.
##
## indicator: The indicator of the expired item.
##
## indicator_type: The indicator type of the expired item.
##
## metas: The set of metadata describing the expired item.
##
## If all hook handlers are executed, the expiration timeout will be reset.
## Otherwise, if one of the handlers terminates using break, the item will
## be removed.
global item_expired: hook(indicator: string, indicator_type: Type, metas: set[MetaData]);
global log_intel: event(rec: Info);
}
@ -163,16 +180,26 @@ global match_no_items: event(s: Seen);
# Internal events for cluster data distribution.
global new_item: event(item: Item);
global updated_item: event(item: Item);
global remove_item: event(item: Item, purge_indicator: bool);
global purge_item: event(item: Item);
# Optionally store metadata. This is used internally depending on
# if this is a cluster deployment or not.
const have_full_data = T &redef;
# Table of metadata, indexed by source string.
type MetaDataTable: table[string] of MetaData;
# Expiration handlers.
global expire_host_data: function(data: table[addr] of MetaDataTable, idx: addr): interval;
global expire_subnet_data: function(data: table[subnet] of MetaDataTable, idx: subnet): interval;
global expire_string_data: function(data: table[string, Type] of MetaDataTable, idx: any): interval;
# The in memory data structure for holding intelligence.
type DataStore: record {
host_data: table[addr] of set[MetaData];
string_data: table[string, Type] of set[MetaData];
host_data: table[addr] of MetaDataTable &write_expire=item_expiration &expire_func=expire_host_data;
subnet_data: table[subnet] of MetaDataTable &write_expire=item_expiration &expire_func=expire_subnet_data;
string_data: table[string, Type] of MetaDataTable &write_expire=item_expiration &expire_func=expire_string_data;
};
global data_store: DataStore &redef;
@ -181,6 +208,7 @@ global data_store: DataStore &redef;
# a minimal amount of data for the full match to happen on the manager.
type MinDataStore: record {
host_data: set[addr];
subnet_data: set[subnet];
string_data: set[string, Type];
};
global min_data_store: MinDataStore &redef;
@ -191,33 +219,78 @@ event bro_init() &priority=5
Log::create_stream(LOG, [$columns=Info, $ev=log_intel, $path="intel"]);
}
# Function that abstracts expiration of different types.
function expire_item(indicator: string, indicator_type: Type, metas: set[MetaData]): interval
{
if ( hook item_expired(indicator, indicator_type, metas) )
return item_expiration;
else
remove([$indicator=indicator, $indicator_type=indicator_type, $meta=[$source=""]], T);
return 0 sec;
}
# Expiration handler definitions.
function expire_host_data(data: table[addr] of MetaDataTable, idx: addr): interval
{
local meta_tbl: MetaDataTable = data[idx];
local metas: set[MetaData];
for ( src in meta_tbl )
add metas[meta_tbl[src]];
return expire_item(cat(idx), ADDR, metas);
}
function expire_subnet_data(data: table[subnet] of MetaDataTable, idx: subnet): interval
{
local meta_tbl: MetaDataTable = data[idx];
local metas: set[MetaData];
for ( src in meta_tbl )
add metas[meta_tbl[src]];
return expire_item(cat(idx), ADDR, metas);
}
function expire_string_data(data: table[string, Type] of MetaDataTable, idx: any): interval
{
local indicator: string;
local indicator_type: Type;
[indicator, indicator_type] = idx;
local meta_tbl: MetaDataTable = data[indicator, indicator_type];
local metas: set[MetaData];
for ( src in meta_tbl )
add metas[meta_tbl[src]];
return expire_item(indicator, indicator_type, metas);
}
# Function to check for intelligence hits.
function find(s: Seen): bool
{
local ds = have_full_data ? data_store : min_data_store;
if ( s?$host )
{
return ((s$host in min_data_store$host_data) ||
(have_full_data && s$host in data_store$host_data));
}
else if ( ([to_lower(s$indicator), s$indicator_type] in min_data_store$string_data) ||
(have_full_data && [to_lower(s$indicator), s$indicator_type] in data_store$string_data) )
{
return T;
return ((s$host in ds$host_data) ||
(|matching_subnets(addr_to_subnet(s$host), ds$subnet_data)| > 0));
}
else
{
return F;
return ([to_lower(s$indicator), s$indicator_type] in ds$string_data);
}
}
# Function to retrieve intelligence items while abstracting from different
# data stores for different indicator types.
function get_items(s: Seen): set[Item]
{
local return_data: set[Item];
local mt: MetaDataTable;
if ( ! have_full_data )
{
# A reporter warning should be generated here because this function
# should never be called from a host that doesn't have the full data.
# TODO: do a reporter warning.
Reporter::warning(fmt("Intel::get_items was called from a host (%s) that doesn't have the full data.",
peer_description));
return return_data;
}
@ -226,11 +299,23 @@ function get_items(s: Seen): set[Item]
# See if the host is known about and it has meta values
if ( s$host in data_store$host_data )
{
for ( m in data_store$host_data[s$host] )
mt = data_store$host_data[s$host];
for ( m in mt )
{
add return_data[Item($indicator=cat(s$host), $indicator_type=ADDR, $meta=m)];
add return_data[Item($indicator=cat(s$host), $indicator_type=ADDR, $meta=mt[m])];
}
}
# See if the host is part of a known subnet, which has meta values
local nets: table[subnet] of MetaDataTable;
nets = filter_subnet_table(addr_to_subnet(s$host), data_store$subnet_data);
for ( n in nets )
{
mt = nets[n];
for ( m in mt )
{
add return_data[Item($indicator=cat(n), $indicator_type=SUBNET, $meta=mt[m])];
}
}
}
else
{
@ -238,9 +323,10 @@ function get_items(s: Seen): set[Item]
# See if the string is known about and it has meta values
if ( [lower_indicator, s$indicator_type] in data_store$string_data )
{
for ( m in data_store$string_data[lower_indicator, s$indicator_type] )
mt = data_store$string_data[lower_indicator, s$indicator_type];
for ( m in mt )
{
add return_data[Item($indicator=s$indicator, $indicator_type=s$indicator_type, $meta=m)];
add return_data[Item($indicator=s$indicator, $indicator_type=s$indicator_type, $meta=mt[m])];
}
}
}
@ -275,64 +361,20 @@ function Intel::seen(s: Seen)
}
}
function has_meta(check: MetaData, metas: set[MetaData]): bool
{
local check_hash = md5_hash(check);
for ( m in metas )
{
if ( check_hash == md5_hash(m) )
return T;
}
# The records must not be equivalent if we made it this far.
return F;
}
event Intel::match(s: Seen, items: set[Item]) &priority=5
{
local info = Info($ts=network_time(), $seen=s);
local info = Info($ts=network_time(), $seen=s, $matched=TypeSet());
if ( s?$f )
{
s$fuid = s$f$id;
if ( s$f?$conns && |s$f$conns| == 1 )
{
for ( cid in s$f$conns )
s$conn = s$f$conns[cid];
}
if ( ! info?$file_mime_type && s$f?$info && s$f$info?$mime_type )
info$file_mime_type = s$f$info$mime_type;
if ( ! info?$file_desc )
info$file_desc = Files::describe(s$f);
}
if ( s?$fuid )
info$fuid = s$fuid;
if ( s?$conn )
{
s$uid = s$conn$uid;
info$id = s$conn$id;
}
if ( s?$uid )
info$uid = s$uid;
for ( item in items )
add info$sources[item$meta$source];
Log::write(Intel::LOG, info);
if ( hook extend_match(info, s, items) )
Log::write(Intel::LOG, info);
}
function insert(item: Item)
{
# Create and fill out the meta data item.
# Create and fill out the metadata item.
local meta = item$meta;
local metas: set[MetaData];
local meta_tbl: table [string] of MetaData;
local is_new: bool = T;
# All intelligence is case insensitive at the moment.
local lower_indicator = to_lower(item$indicator);
@ -343,51 +385,133 @@ function insert(item: Item)
if ( have_full_data )
{
if ( host !in data_store$host_data )
data_store$host_data[host] = set();
data_store$host_data[host] = table();
else
is_new = F;
metas = data_store$host_data[host];
meta_tbl = data_store$host_data[host];
}
add min_data_store$host_data[host];
}
else if ( item$indicator_type == SUBNET )
{
local net = to_subnet(item$indicator);
if ( have_full_data )
{
if ( !check_subnet(net, data_store$subnet_data) )
data_store$subnet_data[net] = table();
else
is_new = F;
meta_tbl = data_store$subnet_data[net];
}
add min_data_store$subnet_data[net];
}
else
{
if ( have_full_data )
{
if ( [lower_indicator, item$indicator_type] !in data_store$string_data )
data_store$string_data[lower_indicator, item$indicator_type] = set();
data_store$string_data[lower_indicator, item$indicator_type] = table();
else
is_new = F;
metas = data_store$string_data[lower_indicator, item$indicator_type];
meta_tbl = data_store$string_data[lower_indicator, item$indicator_type];
}
add min_data_store$string_data[lower_indicator, item$indicator_type];
}
local updated = F;
if ( have_full_data )
{
for ( m in metas )
{
if ( meta$source == m$source )
{
if ( has_meta(meta, metas) )
{
# It's the same item being inserted again.
return;
}
else
{
# Same source, different metadata means updated item.
updated = T;
}
}
}
add metas[item$meta];
# Insert new metadata or update if already present
meta_tbl[meta$source] = meta;
}
if ( updated )
event Intel::updated_item(item);
else
if ( is_new )
# Trigger insert for cluster in case the item is new
# or insert was called on a worker
event Intel::new_item(item);
}
# Function to remove metadata of an item. The function returns T
# if there is no metadata left for the given indicator.
function remove_meta_data(item: Item): bool
{
if ( ! have_full_data )
{
Reporter::warning(fmt("Intel::remove_meta_data was called from a host (%s) that doesn't have the full data.",
peer_description));
return F;
}
switch ( item$indicator_type )
{
case ADDR:
local host = to_addr(item$indicator);
delete data_store$host_data[host][item$meta$source];
return (|data_store$host_data[host]| == 0);
case SUBNET:
local net = to_subnet(item$indicator);
delete data_store$subnet_data[net][item$meta$source];
return (|data_store$subnet_data[net]| == 0);
default:
delete data_store$string_data[item$indicator, item$indicator_type][item$meta$source];
return (|data_store$string_data[item$indicator, item$indicator_type]| == 0);
}
}
function remove(item: Item, purge_indicator: bool)
{
# Delegate removal if we are on a worker
if ( !have_full_data )
{
event Intel::remove_item(item, purge_indicator);
return;
}
# Remove metadata from manager's data store
local no_meta_data = remove_meta_data(item);
# Remove whole indicator if necessary
if ( no_meta_data || purge_indicator )
{
switch ( item$indicator_type )
{
case ADDR:
local host = to_addr(item$indicator);
delete data_store$host_data[host];
break;
case SUBNET:
local net = to_subnet(item$indicator);
delete data_store$subnet_data[net];
break;
default:
delete data_store$string_data[item$indicator, item$indicator_type];
break;
}
# Trigger deletion in minimal data stores
event Intel::purge_item(item);
}
}
# Handling of indicator removal in minimal data stores.
event purge_item(item: Item)
{
switch ( item$indicator_type )
{
case ADDR:
local host = to_addr(item$indicator);
delete min_data_store$host_data[host];
break;
case SUBNET:
local net = to_subnet(item$indicator);
delete min_data_store$subnet_data[net];
break;
default:
delete min_data_store$string_data[item$indicator, item$indicator_type];
break;
}
}

View file

@ -0,0 +1,3 @@
The NetControl framework provides a way for Bro to interact with networking
hard- and software, e.g. for dropping and shunting IP addresses/connections,
etc.

View file

@ -2,103 +2,522 @@
module NetControl;
@load base/frameworks/cluster
@load ./main
@load ./drop
export {
redef enum Log::ID += { CATCH_RELEASE };
## Thhis record is used is used for storing information about current blocks that are
## part of catch and release.
type BlockInfo: record {
## Absolute time indicating until when a block is inserted using NetControl
block_until: time &optional;
## Absolute time indicating until when an IP address is watched to reblock it
watch_until: time;
## Number of times an IP address was reblocked
num_reblocked: count &default=0;
## Number indicating at which catch and release interval we currently are
current_interval: count;
## ID of the inserted block, if any.
current_block_id: string;
## User specified string
location: string &optional;
};
## The enum that contains the different kinds of messages that are logged by
## catch and release
type CatchReleaseActions: enum {
## Log lines marked with info are purely informational; no action was taken
INFO,
## A rule for the specified IP address already existed in NetControl (outside
## of catch-and-release). Catch and release did not add a new rule, but is now
## watching the IP address and will add a new rule after the current rule expired.
ADDED,
## A drop was requested by catch and release
DROP,
## A address was succesfully blocked by catch and release
DROPPED,
## An address was unblocked after the timeout expired
UNBLOCK,
## An address was forgotten because it did not reappear within the `watch_until` interval
FORGOTTEN,
## A watched IP address was seen again; catch and release will re-block it.
SEEN_AGAIN
};
## The record type that is used for representing and logging
type CatchReleaseInfo: record {
## The absolute time indicating when the action for this log-line occured.
ts: time &log;
## The rule id that this log lone refers to.
rule_id: string &log &optional;
## The IP address that this line refers to.
ip: addr &log;
## The action that was taken in this log-line.
action: CatchReleaseActions &log;
## The current block_interaval (for how long the address is blocked).
block_interval: interval &log &optional;
## The current watch_interval (for how long the address will be watched and re-block if it reappears).
watch_interval: interval &log &optional;
## The absolute time until which the address is blocked.
blocked_until: time &log &optional;
## The absolute time until which the address will be monitored.
watched_until: time &log &optional;
## Number of times that this address was blocked in the current cycle.
num_blocked: count &log &optional;
## The user specified location string.
location: string &log &optional;
## Additional informational string by the catch and release framework about this log-line.
message: string &log &optional;
};
## 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.
##
## In cluster mode, this function works on workers as well as the manager. On managers,
## the returned :bro:see:`NetControl::BlockInfo` record will not contain the block ID,
## which will be assigned on the manager.
##
## 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;
## Returns: The :bro:see:`NetControl::BlockInfo` record containing information about
## the inserted block.
global drop_address_catch_release: function(a: addr, location: string &default="") : BlockInfo;
## Time intervals for which a subsequent drops of the same IP take
## effect.
## Removes an address from being watched with catch and release. Returns true if the
## address was found and removed; returns false if it was unknown to catch and release.
##
## If the address is currently blocked, and the block was inserted by catch and release,
## the block is removed.
##
## a: The address to be unblocked.
##
## reason: A reason for the unblock
##
## Returns: True if the address was unblocked.
global unblock_address_catch_release: function(a: addr, reason: string &default="") : bool;
## This function can be called to notify the cach and release script that activity by
## an IP address was seen. If the respective IP address is currently monitored by catch and
## release and not blocked, the block will be re-instated. See the documentation of watch_new_connection
## which events the catch and release functionality usually monitors for activity.
##
## a: The address that was seen and should be re-dropped if it is being watched
global catch_release_seen: function(a: addr);
## Get the :bro:see:`NetControl::BlockInfo` record for an address currently blocked by catch and release.
## If the address is unknown to catch and release, the watch_until time will be set to 0.
##
## In cluster mode, this function works on the manager and workers. On workers, the data will
## lag slightly behind the manager; if you add a block, it will not be instantly available via
## this function.
##
## a: The address to get information about.
##
## Returns: The :bro:see:`NetControl::BlockInfo` record containing information about
## the inserted block.
global get_catch_release_info: function(a: addr) : BlockInfo;
## Event is raised when catch and release cases management of an IP address because no
## activity was seen within the watch_until period.
##
## a: The address that is no longer being managed.
##
## bi: The :bro:see:`NetControl::BlockInfo` record containing information about the block.
global catch_release_forgotten: event(a: addr, bi: BlockInfo);
## If true, catch_release_seen is called on the connection originator in new_connection,
## connection_established, partial_connection, connection_attempt, connection_rejected,
## connection_reset and connection_pending
const watch_connections = T &redef;
## If true, catch and release warns if packets of an IP address are still seen after it
## should have been blocked.
const catch_release_warn_blocked_ip_encountered = F &redef;
## 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;
## Event that can be handled to access the :bro:type:`NetControl::CatchReleaseInfo`
## record as it is sent on to the logging framework.
global log_netcontrol_catch_release: event(rec: CatchReleaseInfo);
# Cluster events for catch and release
global catch_release_block_new: event(a: addr, b: BlockInfo);
global catch_release_block_delete: event(a: addr);
global catch_release_add: event(a: addr, location: string);
global catch_release_delete: event(a: addr, reason: string);
global catch_release_encountered: event(a: addr);
}
function per_block_interval(t: table[addr] of count, idx: addr): interval
# set that is used to only send seen notifications to the master every ~30 seconds.
global catch_release_recently_notified: set[addr] &create_expire=30secs;
event bro_init() &priority=5
{
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;
Log::create_stream(NetControl::CATCH_RELEASE, [$columns=CatchReleaseInfo, $ev=log_netcontrol_catch_release, $path="netcontrol_catch_release"]);
}
# This is the internally maintained table containing all the currently going on catch-and-release
# blocks.
global blocks: table[addr] of count = {}
function get_watch_interval(current_interval: count): interval
{
if ( (current_interval + 1) in catch_release_intervals )
return catch_release_intervals[current_interval+1];
else
return catch_release_intervals[current_interval];
}
function populate_log_record(ip: addr, bi: BlockInfo, action: CatchReleaseActions): CatchReleaseInfo
{
local log = CatchReleaseInfo($ts=network_time(), $ip=ip, $action=action,
$block_interval=catch_release_intervals[bi$current_interval],
$watch_interval=get_watch_interval(bi$current_interval),
$watched_until=bi$watch_until,
$num_blocked=bi$num_reblocked+1
);
if ( bi?$block_until )
log$blocked_until = bi$block_until;
if ( bi?$current_block_id && bi$current_block_id != "" )
log$rule_id = bi$current_block_id;
if ( bi?$location )
log$location = bi$location;
return log;
}
function per_block_interval(t: table[addr] of BlockInfo, idx: addr): interval
{
local remaining_time = t[idx]$watch_until - network_time();
if ( remaining_time < 0secs )
remaining_time = 0secs;
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
if ( remaining_time == 0secs )
{
local log = populate_log_record(idx, t[idx], FORGOTTEN);
Log::write(CATCH_RELEASE, log);
event NetControl::catch_release_forgotten(idx, t[idx]);
}
@endif
return remaining_time;
}
# This is the internally maintained table containing all the addresses that are currently being
# watched to see if they will re-surface. After the time is reached, monitoring of that specific
# IP will stop.
global blocks: table[addr] of BlockInfo = {}
&create_expire=0secs
&expire_func=per_block_interval;
function current_block_interval(s: set[addr], idx: addr): interval
@if ( Cluster::is_enabled() )
@load base/frameworks/cluster
redef Cluster::manager2worker_events += /NetControl::catch_release_block_(new|delete)/;
redef Cluster::worker2manager_events += /NetControl::catch_release_(add|delete|encountered)/;
@endif
function cr_check_rule(r: Rule): bool
{
if ( idx !in blocks )
if ( r$ty == DROP && r$entity$ty == ADDRESS )
{
Reporter::error(fmt("Address %s not in blocks while inserting into current_blocks!", idx));
return 0sec;
local ip = r$entity$ip;
if ( ( is_v4_subnet(ip) && subnet_width(ip) == 32 ) || ( is_v6_subnet(ip) && subnet_width(ip) == 128 ) )
{
if ( subnet_to_addr(ip) in blocks )
return T;
}
}
return catch_release_intervals[blocks[idx]];
return F;
}
global current_blocks: set[addr] = set()
&create_expire=0secs
&expire_func=current_block_interval;
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
function drop_address_catch_release(a: addr, location: string &default=""): string
event rule_added(r: Rule, p: PluginState, msg: string &default="")
{
if ( !cr_check_rule(r) )
return;
local ip = subnet_to_addr(r$entity$ip);
local bi = blocks[ip];
local log = populate_log_record(ip, bi, DROPPED);
if ( msg != "" )
log$message = msg;
Log::write(CATCH_RELEASE, log);
}
event rule_timeout(r: Rule, i: FlowInfo, p: PluginState)
{
if ( !cr_check_rule(r) )
return;
local ip = subnet_to_addr(r$entity$ip);
local bi = blocks[ip];
local log = populate_log_record(ip, bi, UNBLOCK);
if ( bi?$block_until )
{
local difference: interval = network_time() - bi$block_until;
if ( interval_to_double(difference) > 60 || interval_to_double(difference) < -60 )
log$message = fmt("Difference between network_time and block time excessive: %f", difference);
}
Log::write(CATCH_RELEASE, log);
}
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER )
event catch_release_add(a: addr, location: string)
{
drop_address_catch_release(a, location);
}
event catch_release_delete(a: addr, reason: string)
{
unblock_address_catch_release(a, reason);
}
event catch_release_encountered(a: addr)
{
catch_release_seen(a);
}
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER )
event catch_release_block_new(a: addr, b: BlockInfo)
{
blocks[a] = b;
}
event catch_release_block_delete(a: addr)
{
if ( a in blocks )
delete blocks[a];
}
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER )
@endif
function get_catch_release_info(a: addr): BlockInfo
{
if ( a in blocks )
return blocks[a];
return BlockInfo($watch_until=double_to_time(0), $current_interval=0, $current_block_id="");
}
function drop_address_catch_release(a: addr, location: string &default=""): BlockInfo
{
local bi: BlockInfo;
local log: CatchReleaseInfo;
if ( a in blocks )
{
Reporter::warning(fmt("Address %s already blocked using catch-and-release - ignoring duplicate", a));
return "";
log = populate_log_record(a, blocks[a], INFO);
log$message = "Already blocked using catch-and-release - ignoring duplicate";
Log::write(CATCH_RELEASE, log);
return blocks[a];
}
local e = Entity($ty=ADDRESS, $ip=addr_to_subnet(a));
if ( [e,DROP] in rule_entities )
{
local r = rule_entities[e,DROP];
bi = BlockInfo($watch_until=network_time()+catch_release_intervals[1], $current_interval=0, $current_block_id=r$id);
if ( location != "" )
bi$location = location;
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
log = populate_log_record(a, bi, ADDED);
log$message = "Address already blocked outside of catch-and-release. Catch and release will monitor and only actively block if it appears in network traffic.";
Log::write(CATCH_RELEASE, log);
blocks[a] = bi;
event NetControl::catch_release_block_new(a, bi);
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER )
event NetControl::catch_release_add(a, location);
@endif
return bi;
}
local block_interval = catch_release_intervals[0];
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
local ret = drop_address(a, block_interval, location);
if ( ret != "" )
{
blocks[a] = 0;
add current_blocks[a];
bi = BlockInfo($watch_until=network_time()+catch_release_intervals[1], $block_until=network_time()+block_interval, $current_interval=0, $current_block_id=ret);
if ( location != "" )
bi$location = location;
blocks[a] = bi;
event NetControl::catch_release_block_new(a, bi);
blocks[a] = bi;
log = populate_log_record(a, bi, DROP);
Log::write(CATCH_RELEASE, log);
return bi;
}
Reporter::error(fmt("Catch and release could not add block for %s; failing.", a));
return BlockInfo($watch_until=double_to_time(0), $current_interval=0, $current_block_id="");
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER )
bi = BlockInfo($watch_until=network_time()+catch_release_intervals[1], $block_until=network_time()+block_interval, $current_interval=0, $current_block_id="");
event NetControl::catch_release_add(a, location);
return bi;
@endif
return ret;
}
function check_conn(a: addr)
function unblock_address_catch_release(a: addr, reason: string &default=""): bool
{
if ( a !in blocks )
return F;
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
local bi = blocks[a];
local log = populate_log_record(a, bi, UNBLOCK);
if ( reason != "" )
log$message = reason;
Log::write(CATCH_RELEASE, log);
delete blocks[a];
if ( bi?$block_until && bi$block_until > network_time() && bi$current_block_id != "" )
remove_rule(bi$current_block_id, reason);
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER )
event NetControl::catch_release_block_delete(a);
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER )
event NetControl::catch_release_delete(a, reason);
@endif
return T;
}
function catch_release_seen(a: addr)
{
local e = Entity($ty=ADDRESS, $ip=addr_to_subnet(a));
if ( a in blocks )
{
if ( a in current_blocks )
# block has not been applied yet?
@if ( ! Cluster::is_enabled() || ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER ) )
local bi = blocks[a];
local log: CatchReleaseInfo;
if ( [e,DROP] in rule_entities )
{
if ( catch_release_warn_blocked_ip_encountered == F )
return;
# This should be blocked - block has not been applied yet by hardware? Ignore for the moment...
log = populate_log_record(a, bi, INFO);
log$action = INFO;
log$message = "Block seen while in rule_entities. No action taken.";
Log::write(CATCH_RELEASE, log);
return;
}
# ok, this one returned again while still in the backoff period.
local try = blocks[a];
local try = bi$current_interval;
if ( (try+1) in catch_release_intervals )
++try;
blocks[a] = try;
add current_blocks[a];
bi$current_interval = try;
if ( (try+1) in catch_release_intervals )
bi$watch_until = network_time() + catch_release_intervals[try+1];
else
bi$watch_until = network_time() + catch_release_intervals[try];
bi$block_until = network_time() + catch_release_intervals[try];
++bi$num_reblocked;
local block_interval = catch_release_intervals[try];
drop_address(a, block_interval, "Re-drop by catch-and-release");
local location = "";
if ( bi?$location )
location = bi$location;
local drop = drop_address(a, block_interval, fmt("Re-drop by catch-and-release: %s", location));
bi$current_block_id = drop;
blocks[a] = bi;
log = populate_log_record(a, bi, SEEN_AGAIN);
Log::write(CATCH_RELEASE, log);
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() == Cluster::MANAGER )
event NetControl::catch_release_block_new(a, bi);
@endif
@if ( Cluster::is_enabled() && Cluster::local_node_type() != Cluster::MANAGER )
if ( a in catch_release_recently_notified )
return;
event NetControl::catch_release_encountered(a);
add catch_release_recently_notified[a];
@endif
return;
}
return;
}
event new_connection(c: connection)
{
# let's only check originating connections...
check_conn(c$id$orig_h);
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event connection_established(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event partial_connection(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event connection_attempt(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event connection_rejected(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event connection_reset(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}
event connection_pending(c: connection)
{
if ( watch_connections )
catch_release_seen(c$id$orig_h);
}

View file

@ -10,14 +10,16 @@ export {
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);
global cluster_netcontrol_remove_rule: event(id: string, reason: string);
## This is the event used to transport delete_rule calls to the manager.
global cluster_netcontrol_delete_rule: event(id: string, reason: string);
}
## Workers need ability to forward commands to manager.
redef Cluster::worker2manager_events += /NetControl::cluster_netcontrol_(add|remove)_rule/;
redef Cluster::worker2manager_events += /NetControl::cluster_netcontrol_(add|remove|delete)_rule/;
## Workers need to see the result events from the manager.
redef Cluster::manager2worker_events += /NetControl::rule_(added|removed|timeout|error)/;
redef Cluster::manager2worker_events += /NetControl::rule_(added|removed|timeout|error|exists|new|destroyed)/;
function activate(p: PluginState, priority: int)
{
@ -36,6 +38,16 @@ function add_rule(r: Rule) : string
return add_rule_impl(r);
else
{
# we sync rule entities accross the cluster, so we
# acually can test if the rule already exists. If yes,
# refuse insertion already at the node.
if ( [r$entity, r$ty] in rule_entities )
{
log_rule_no_plugin(r, FAILED, "discarded duplicate insertion");
return "";
}
if ( r$id == "" )
r$id = cat(Cluster::node, ":", ++local_rule_count);
@ -44,38 +56,60 @@ function add_rule(r: Rule) : string
}
}
function remove_rule(id: string) : bool
function delete_rule(id: string, reason: string &default="") : bool
{
if ( Cluster::local_node_type() == Cluster::MANAGER )
return remove_rule_impl(id);
return delete_rule_impl(id, reason);
else
{
event NetControl::cluster_netcontrol_remove_rule(id);
event NetControl::cluster_netcontrol_delete_rule(id, reason);
return T; # well, we can't know here. So - just hope...
}
}
function remove_rule(id: string, reason: string &default="") : bool
{
if ( Cluster::local_node_type() == Cluster::MANAGER )
return remove_rule_impl(id, reason);
else
{
event NetControl::cluster_netcontrol_remove_rule(id, reason);
return T; # well, we can't know here. So - just hope...
}
}
@if ( Cluster::local_node_type() == Cluster::MANAGER )
event NetControl::cluster_netcontrol_delete_rule(id: string, reason: string)
{
delete_rule_impl(id, reason);
}
event NetControl::cluster_netcontrol_add_rule(r: Rule)
{
add_rule_impl(r);
}
event NetControl::cluster_netcontrol_remove_rule(id: string)
event NetControl::cluster_netcontrol_remove_rule(id: string, reason: string)
{
remove_rule_impl(id);
remove_rule_impl(id, reason);
}
@endif
@if ( Cluster::local_node_type() == Cluster::MANAGER )
event rule_expire(r: Rule, p: PluginState) &priority=-5
{
rule_expire_impl(r, p);
}
event rule_exists(r: Rule, p: PluginState, msg: string &default="") &priority=5
{
rule_added_impl(r, p, T, msg);
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };
}
event rule_added(r: Rule, p: PluginState, msg: string &default="") &priority=5
{
rule_added_impl(r, p, msg);
rule_added_impl(r, p, F, msg);
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };
@ -97,3 +131,30 @@ event rule_error(r: Rule, p: PluginState, msg: string &default="") &priority=-5
}
@endif
# Workers use the events to keep track in their local state tables
@if ( Cluster::local_node_type() != Cluster::MANAGER )
event rule_new(r: Rule) &priority=5
{
if ( r$id in rules )
return;
rules[r$id] = r;
rule_entities[r$entity, r$ty] = r;
add_subnet_entry(r);
}
event rule_destroyed(r: Rule) &priority=5
{
if ( r$id !in rules )
return;
remove_subnet_entry(r);
if ( [r$entity, r$ty] in rule_entities )
delete rule_entities[r$entity, r$ty];
delete rules[r$id];
}
@endif

View file

@ -44,6 +44,12 @@ export {
location: string &log &optional;
};
## Hook that allows the modification of rules passed to drop_* before they
## are passed on. If one of the hooks uses break, the rule is ignored.
##
## r: The rule to be added
global NetControl::drop_rule_policy: hook(r: Rule);
## 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);
@ -59,6 +65,9 @@ function drop_connection(c: conn_id, t: interval, location: string &default="")
local e: Entity = [$ty=CONNECTION, $conn=c];
local r: Rule = [$ty=DROP, $target=FORWARD, $entity=e, $expire=t, $location=location];
if ( ! hook NetControl::drop_rule_policy(r) )
return "";
local id = add_rule(r);
# Error should already be logged
@ -80,6 +89,9 @@ function drop_address(a: addr, t: interval, location: string &default="") : stri
local e: Entity = [$ty=ADDRESS, $ip=addr_to_subnet(a)];
local r: Rule = [$ty=DROP, $target=FORWARD, $entity=e, $expire=t, $location=location];
if ( ! hook NetControl::drop_rule_policy(r) )
return "";
local id = add_rule(r);
# Error should already be logged

View file

@ -1,4 +1,4 @@
##! Bro's packet aquisition and control framework.
##! Bro's NetControl 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
@ -81,9 +81,11 @@ export {
## 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.
## Quarantines a host. This requires a special quarantine server, which runs a HTTP server explaining
## the quarantine and a DNS server which resolves all requests to the quarantine server. DNS queries
## from the host to the network DNS server will be rewritten and will be sent to the quarantine server
## instead. Only http communication infected to quarantinehost is allowed. All other network communication
## is blocked.
##
## infected: the host to quarantine
##
@ -96,7 +98,7 @@ export {
## 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.
## Flushes all state by calling :bro:see:`NetControl::remove_rule` on all currently active rules.
global clear: function();
# ###
@ -120,17 +122,36 @@ export {
## Removes a rule.
##
## id: The rule to remove, specified as the ID returned by :bro:id:`NetControl::add_rule`.
## id: The rule to remove, specified as the ID returned by :bro:see:`NetControl::add_rule`.
##
## reason: Optional string argument giving information on why the rule was removed.
##
## 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;
global remove_rule: function(id: string, reason: string &default="") : bool;
## Deletes a rule without removing in from the backends to which it has been
## added before. This mean that no messages will be sent to the switches to which
## the rule has been added; if it is not removed from them by a separate mechanism,
## it will stay installed and not be removed later.
##
## id: The rule to delete, specified as the ID returned by :bro:see:`add_rule` .
##
## reason: Optional string argument giving information on why the rule was deleted.
##
## Returns: True if removal is successful, or sent to manager.
## False if the rule could not be found.
global delete_rule: function(id: string, reason: string &default="") : bool;
## Searches all rules affecting a certain IP address.
##
## This function works on both the manager and workers of a cluster. Note that on
## the worker, the internal rule variables (starting with _) will not reflect the
## current state.
##
## ip: The ip address to search for
##
## Returns: vector of all rules affecting the IP address
@ -138,6 +159,18 @@ export {
## Searches all rules affecting a certain subnet.
##
## A rule affects a subnet, if it covers the whole subnet. Note especially that
## this function will not reveal all rules that are covered by a subnet.
##
## For example, a search for 192.168.17.0/8 will reveal a rule that exists for
## 192.168.0.0/16, since this rule affects the subnet. However, it will not reveal
## a more specific rule for 192.168.17.1/32, which does not directy affect the whole
## subnet.
##
## This function works on both the manager and workers of a cluster. Note that on
## the worker, the internal rule variables (starting with _) will not reflect the
## current state.
##
## sn: The subnet to search for
##
## Returns: vector of all rules affecting the subnet
@ -145,7 +178,7 @@ export {
###### Asynchronous feedback on rules.
## Confirms that a rule was put in place.
## Confirms that a rule was put in place by a plugin.
##
## r: The rule now in place.
##
@ -154,7 +187,21 @@ export {
## 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.
## Signals that a rule that was supposed to be put in place was already
## existing at the specified plugin. Rules that already have been existing
## continue to be tracked like normal, but no timeout calls will be sent
## to the specified plugins. Removal of the rule from the hardware can
## still be forced by manually issuing a remove_rule call.
##
## r: The rule that was already in place.
##
## p: The plugin that reported that the rule already was in place.
##
## msg: An optional informational message by the plugin.
global rule_exists: event(r: Rule, p: PluginState, msg: string &default="");
## Reports that a plugin reports a rule was removed due to a
## remove: function() vall.
##
## r: The rule now removed.
##
@ -164,7 +211,7 @@ export {
## 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.
## Reports that a rule was removed from a plugin due to a timeout.
##
## r: The rule now removed.
##
@ -185,6 +232,26 @@ export {
## msg: An optional informational message by the plugin.
global rule_error: event(r: Rule, p: PluginState, msg: string &default="");
## This event is raised when a new rule is created by the NetControl framework
## due to a call to add_rule. From this moment, until the rule_destroyed event
## is raised, the rule is tracked internally by the NetControl framewory.
##
## Note that this event does not mean that a rule was succesfully added by
## any backend; it just means that the rule has been accepted and addition
## to the specified backend is queued. To get information when rules are actually
## installed by the hardware, use the rule_added, rule_exists, rule_removed, rule_timeout
## and rule_error events.
global rule_new: event(r: Rule);
## This event is raised when a rule is deleted from the NetControl framework,
## because it is no longer in use. This can be caused by the fact that a rule
## was removed by all plugins to which it was added, by the fact that it timed out
## or due to rule errors.
##
## To get the cause or a rule remove, hook the rule_removed, rule_timeout and
## rule_error calls.
global rule_destroyed: event(r: Rule);
## 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.
@ -206,17 +273,18 @@ export {
MESSAGE,
## A log entry reflecting a framework message.
ERROR,
## A log entry about about a rule.
## A log entry about a rule.
RULE
};
## State of an entry in the NetControl log.
## State of an entry in the NetControl log.
type InfoState: enum {
REQUESTED,
SUCCEEDED,
FAILED,
REMOVED,
TIMEOUT,
REQUESTED, ##< The request to add/remove a rule was sent to the respective backend
SUCCEEDED, ##< A rule was succesfully added by a backend
EXISTS, ##< A backend reported that a rule was already existing
FAILED, ##< A rule addition failed
REMOVED, ##< A rule was succesfully removed by a backend
TIMEOUT, ##< A rule timeout was triggered by the NetControl framework or a backend
};
## The record type defining the column fields of the NetControl log.
@ -259,11 +327,13 @@ export {
}
redef record Rule += {
##< Internally set to the plugins handling the 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.
## 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.
## Internally set to plugins where the rule should not be removed upon timeout.
_no_expire_plugins: set[count] &default=count_set();
## Track if the rule was added succesfully by all responsible plugins.
_added: bool &default=F;
};
@ -535,6 +605,11 @@ function plugin_activated(p: PluginState)
log_error("unknown plugin activated", p);
return;
}
# Suppress duplicate activation
if ( plugin_ids[id]$_activated == T )
return;
plugin_ids[id]$_activated = T;
log_msg("activation finished", p);
@ -727,6 +802,8 @@ function add_rule_impl(rule: Rule) : string
add_subnet_entry(rule);
event NetControl::rule_new(rule);
return rule$id;
}
@ -734,25 +811,62 @@ function add_rule_impl(rule: Rule) : string
return "";
}
function remove_rule_plugin(r: Rule, p: PluginState): bool
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];
event NetControl::rule_destroyed(r);
}
function delete_rule_impl(id: string, reason: string): bool
{
if ( id !in rules )
{
Reporter::error(fmt("Rule %s does not exist in NetControl::delete_rule", id));
return F;
}
local rule = rules[id];
rule$_active_plugin_ids = set();
rule_cleanup(rule);
if ( reason != "" )
log_rule_no_plugin(rule, REMOVED, fmt("delete_rule: %s", reason));
else
log_rule_no_plugin(rule, REMOVED, "delete_rule");
return T;
}
function remove_rule_plugin(r: Rule, p: PluginState, reason: string &default=""): bool
{
local success = T;
if ( ! p$plugin$remove_rule(p, r) )
if ( ! p$plugin$remove_rule(p, r, reason) )
{
# still continue and send to other plugins
log_rule_error(r, "remove failed", p);
if ( reason != "" )
log_rule_error(r, fmt("remove failed (original reason: %s)", reason), p);
else
log_rule_error(r, "remove failed", p);
success = F;
}
else
{
log_rule(r, "REMOVE", REQUESTED, p);
log_rule(r, "REMOVE", REQUESTED, p, reason);
}
return success;
}
function remove_rule_impl(id: string) : bool
function remove_rule_impl(id: string, reason: string) : bool
{
if ( id !in rules )
{
@ -766,7 +880,7 @@ function remove_rule_impl(id: string) : bool
for ( plugin_id in r$_active_plugin_ids )
{
local p = plugin_ids[plugin_id];
success = remove_rule_plugin(r, p);
success = remove_rule_plugin(r, p, reason);
}
return success;
@ -782,10 +896,21 @@ function rule_expire_impl(r: Rule, p: PluginState) &priority=-5
# Removed already.
return;
event NetControl::rule_timeout(r, FlowInfo(), p); # timeout implementation will handle the removal
local rule = rules[r$id];
if ( p$_id in rule$_no_expire_plugins )
{
# in this case - don't log anything, just remove the plugin from the rule
# and cleaup
delete rule$_active_plugin_ids[p$_id];
delete rule$_no_expire_plugins[p$_id];
rule_cleanup(rule);
}
else
event NetControl::rule_timeout(r, FlowInfo(), p); # timeout implementation will handle the removal
}
function rule_added_impl(r: Rule, p: PluginState, msg: string &default="")
function rule_added_impl(r: Rule, p: PluginState, exists: bool, msg: string &default="")
{
if ( r$id !in rules )
{
@ -801,7 +926,15 @@ function rule_added_impl(r: Rule, p: PluginState, msg: string &default="")
return;
}
log_rule(r, "ADD", SUCCEEDED, p, msg);
# The rule was already existing on the backend. Mark this so we don't timeout
# it on this backend.
if ( exists )
{
add rule$_no_expire_plugins[p$_id];
log_rule(r, "ADD", EXISTS, p, msg);
}
else
log_rule(r, "ADD", SUCCEEDED, p, msg);
add rule$_active_plugin_ids[p$_id];
if ( |rule$_plugin_ids| == |rule$_active_plugin_ids| )
@ -811,17 +944,6 @@ function rule_added_impl(r: Rule, p: PluginState, msg: string &default="")
}
}
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 )

View file

@ -12,9 +12,14 @@ function add_rule(r: Rule) : string
return add_rule_impl(r);
}
function remove_rule(id: string) : bool
function delete_rule(id: string, reason: string &default="") : bool
{
return remove_rule_impl(id);
return delete_rule_impl(id, reason);
}
function remove_rule(id: string, reason: string &default="") : bool
{
return remove_rule_impl(id, reason);
}
event rule_expire(r: Rule, p: PluginState) &priority=-5
@ -22,9 +27,17 @@ event rule_expire(r: Rule, p: PluginState) &priority=-5
rule_expire_impl(r, p);
}
event rule_exists(r: Rule, p: PluginState, msg: string &default="") &priority=5
{
rule_added_impl(r, p, T, msg);
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };
}
event rule_added(r: Rule, p: PluginState, msg: string &default="") &priority=5
{
rule_added_impl(r, p, msg);
rule_added_impl(r, p, F, msg);
if ( r?$expire && r$expire > 0secs && ! p$plugin$can_expire )
schedule r$expire { rule_expire(r, p) };

View file

@ -1,11 +1,13 @@
##! Plugin interface for NetControl backends.
##! This file defines the plugin interface for NetControl.
module NetControl;
@load ./types
export {
## State for a plugin instance.
## This record keeps the per instance state of a plugin.
##
## Individual plugins commonly extend this record to suit their needs.
type PluginState: record {
## Table for a plugin to store custom, instance-specfific state.
config: table[string] of string &default=table();
@ -20,69 +22,63 @@ export {
_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.
## 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.
## 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,
## If true, plugin can expire rules itself. If false, the NetControl
## 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.
## 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.
## 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.
## 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;
## 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, reason: string) : bool &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.
## 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.)
## because of cyclic type dependency.)
plugin: Plugin &optional;
};

View file

@ -0,0 +1 @@
Plugins for the NetControl framework

View file

@ -66,6 +66,7 @@ export {
## 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_exists: event(id: count, r: Rule, msg: string);
global acld_rule_error: event(id: count, r: Rule, msg: string);
}
@ -76,7 +77,7 @@ global netcontrol_acld_current_id: count = 0;
const acld_add_to_remove: table[string] of string = {
["drop"] = "restore",
["whitelist"] = "remwhitelist",
["addwhitelist"] = "remwhitelist",
["blockhosthost"] = "restorehosthost",
["droptcpport"] = "restoretcpport",
["dropudpport"] = "restoreudpport",
@ -100,6 +101,19 @@ event NetControl::acld_rule_added(id: count, r: Rule, msg: string)
event NetControl::rule_added(r, p, msg);
}
event NetControl::acld_rule_exists(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_exists(r, p, msg);
}
event NetControl::acld_rule_removed(id: count, r: Rule, msg: string)
{
if ( id !in netcontrol_acld_id )
@ -155,7 +169,7 @@ function rule_to_acl_rule(p: PluginState, r: Rule) : AclRule
if ( r$ty == DROP )
command = "drop";
else if ( r$ty == WHITELIST )
command = "whitelist";
command = "addwhitelist";
arg = cat(e$ip);
}
else if ( e$ty == FLOW )
@ -233,7 +247,7 @@ function acld_add_rule_fun(p: PluginState, r: Rule) : bool
return T;
}
function acld_remove_rule_fun(p: PluginState, r: Rule) : bool
function acld_remove_rule_fun(p: PluginState, r: Rule, reason: string) : bool
{
if ( ! acld_check_rule(p, r) )
return F;
@ -244,6 +258,14 @@ function acld_remove_rule_fun(p: PluginState, r: Rule) : bool
else
return F;
if ( reason != "" )
{
if ( ar?$comment )
ar$comment = fmt("%s (%s)", reason, ar$comment);
else
ar$comment = reason;
}
Broker::send_event(p$acld_config$acld_topic, Broker::event_args(acld_remove_rule, p$acld_id, r, ar));
return T;
}

View file

@ -11,25 +11,46 @@ module NetControl;
@ifdef ( Broker::__enable )
export {
## This record specifies the configuration that is passed to :bro:see:`NetControl::create_broker`.
type BrokerConfig: record {
## The broker topic used to send events to
topic: string &optional;
## Broker host to connect to
host: addr &optional;
## Broker port to connect to
bport: port &optional;
## Do we accept rules for the monitor path? Default true
monitor: bool &default=T;
## 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 broker plugin.
global create_broker: function(host: addr, host_port: port, topic: string, can_expire: bool &default=F) : PluginState;
global create_broker: function(config: BrokerConfig, can_expire: bool) : PluginState;
redef record PluginState += {
## The broker topic used to send events to
broker_topic: string &optional;
## OpenFlow controller for NetControl Broker plugin
broker_config: BrokerConfig &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_remove_rule: event(id: count, r: Rule, reason: string);
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_exists: 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);
}
@ -52,6 +73,19 @@ event NetControl::broker_rule_added(id: count, r: Rule, msg: string)
event NetControl::rule_added(r, p, msg);
}
event NetControl::broker_rule_exists(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_exists(r, p, msg);
}
event NetControl::broker_rule_removed(id: count, r: Rule, msg: string)
{
if ( id !in netcontrol_broker_id )
@ -93,26 +127,48 @@ event NetControl::broker_rule_timeout(id: count, r: Rule, i: FlowInfo)
function broker_name(p: PluginState) : string
{
return fmt("Broker-%s", p$broker_topic);
return fmt("Broker-%s", p$broker_config$topic);
}
function broker_check_rule(p: PluginState, r: Rule) : bool
{
local c = p$broker_config;
if ( p$broker_config?$check_pred )
return p$broker_config$check_pred(p, r);
if ( r$target == MONITOR && c$monitor )
return T;
if ( r$target == FORWARD && c$forward )
return T;
return F;
}
function broker_add_rule_fun(p: PluginState, r: Rule) : bool
{
Broker::send_event(p$broker_topic, Broker::event_args(broker_add_rule, p$broker_id, r));
if ( ! broker_check_rule(p, r) )
return F;
Broker::send_event(p$broker_config$topic, Broker::event_args(broker_add_rule, p$broker_id, r));
return T;
}
function broker_remove_rule_fun(p: PluginState, r: Rule) : bool
function broker_remove_rule_fun(p: PluginState, r: Rule, reason: string) : bool
{
Broker::send_event(p$broker_topic, Broker::event_args(broker_remove_rule, p$broker_id, r));
if ( ! broker_check_rule(p, r) )
return F;
Broker::send_event(p$broker_config$topic, Broker::event_args(broker_remove_rule, p$broker_id, r, reason));
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);
Broker::connect(cat(p$broker_config$host), p$broker_config$bport, 1sec);
Broker::subscribe_to_events(p$broker_config$topic);
}
event Broker::outgoing_connection_established(peer_address: string, peer_port: port, peer_name: string)
@ -140,23 +196,23 @@ global broker_plugin_can_expire = Plugin(
$init = broker_init
);
function create_broker(host: addr, host_port: port, topic: string, can_expire: bool &default=F) : PluginState
function create_broker(config: BrokerConfig, can_expire: bool) : PluginState
{
if ( topic in netcontrol_broker_topics )
Reporter::warning(fmt("Topic %s was added to NetControl broker plugin twice. Possible duplication of commands", topic));
if ( config$topic in netcontrol_broker_topics )
Reporter::warning(fmt("Topic %s was added to NetControl broker plugin twice. Possible duplication of commands", config$topic));
else
add netcontrol_broker_topics[topic];
add netcontrol_broker_topics[config$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];
local p = PluginState($plugin=plugin, $broker_id=netcontrol_broker_current_id, $broker_config=config);
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));
if ( [config$bport, cat(config$host)] in netcontrol_broker_peers )
Reporter::warning(fmt("Peer %s:%s was added to NetControl broker plugin twice.", config$host, config$bport));
else
netcontrol_broker_peers[host_port, cat(host)] = p;
netcontrol_broker_peers[config$bport, cat(config$host)] = p;
netcontrol_broker_id[netcontrol_broker_current_id] = p;
++netcontrol_broker_current_id;

View file

@ -55,34 +55,22 @@ function debug_add_rule(p: PluginState, r: Rule) : bool
return F;
}
function debug_remove_rule(p: PluginState, r: Rule) : bool
function debug_remove_rule(p: PluginState, r: Rule, reason: string) : bool
{
local s = fmt("remove_rule: %s", r);
local s = fmt("remove_rule (%s): %s", reason, 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
$remove_rule = debug_remove_rule
);
function create_debug(do_something: bool) : PluginState

View file

@ -7,22 +7,46 @@
module NetControl;
export {
## This record specifies the configuration that is passed to :bro:see:`NetControl::create_openflow`.
type OfConfig: record {
monitor: bool &default=T;
forward: bool &default=T;
idle_timeout: count &default=0;
table_id: count &optional;
monitor: bool &default=T; ##< accept rules that target the monitor path
forward: bool &default=T; ##< accept rules that target the forward path
idle_timeout: count &default=0; ##< default OpenFlow idle timeout
table_id: count &optional; ##< default OpenFlow table ID.
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
## p: Current plugin state.
##
## r: The rule to be inserted or removed
## r: The rule to be inserted or removed.
##
## Returns: T if the rule can be handled by the current backend, F otherwhise
## Returns: T if the rule can be handled by the current backend, F otherwhise.
check_pred: function(p: PluginState, r: Rule): bool &optional;
## This predicate is called each time an OpenFlow match record is created.
## The predicate can modify the match structure before it is sent on to the
## device.
##
## p: Current plugin state.
##
## r: The rule to be inserted or removed.
##
## m: The openflow match structures that were generated for this rules.
##
## Returns: The modified OpenFlow match structures that will be used in place the structures passed in m.
match_pred: function(p: PluginState, e: Entity, m: vector of OpenFlow::ofp_match): vector of OpenFlow::ofp_match &optional;
## This predicate is called before an FlowMod message is sent to the OpenFlow
## device. It can modify the FlowMod message before it is passed on.
##
## p: Current plugin state.
##
## r: The rule to be inserted or removed.
##
## m: The OpenFlow FlowMod message.
##
## Returns: The modified FloMod message that is used in lieu of m.
flow_mod_pred: function(p: PluginState, r: Rule, m: OpenFlow::ofp_flow_mod): OpenFlow::ofp_flow_mod &optional;
};
@ -300,7 +324,7 @@ function openflow_add_rule(p: PluginState, r: Rule) : bool
return T;
}
function openflow_remove_rule(p: PluginState, r: Rule) : bool
function openflow_remove_rule(p: PluginState, r: Rule, reason: string) : bool
{
if ( ! openflow_check_rule(p, r) )
return F;
@ -420,8 +444,6 @@ global openflow_plugin = Plugin(
# $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

View file

@ -63,7 +63,7 @@ function packetfilter_add_rule(p: PluginState, r: Rule) : bool
return F;
}
function packetfilter_remove_rule(p: PluginState, r: Rule) : bool
function packetfilter_remove_rule(p: PluginState, r: Rule, reason: string) : bool
{
if ( ! packetfilter_check_rule(r) )
return F;

View file

@ -1,30 +1,45 @@
##! Types used by the NetControl framework.
##! This file defines the that are used by the NetControl framework.
##!
##! The most important type defined in this file is :bro:see:`NetControl::Rule`,
##! which is used to describe all rules that can be expressed by the NetControl framework.
module NetControl;
export {
## The default priority that is used when creating rules.
const default_priority: int = +0 &redef;
## The default priority that is used when using the high-level functions to
## push whitelist entries to the backends (:bro:see:`NetControl::whitelist_address` and
## :bro:see:`NetControl::whitelist_subnet`).
##
## Note that this priority is not automatically used when manually creating rules
## that have a :bro:see:`NetControl::RuleType` of :bro:enum:`NetControl::WHITELIST`.
const whitelist_priority: int = +5 &redef;
## Type of a :bro:id:`Entity` for defining an action.
## The EntityType is used in :bro:id:`Entity` for defining the entity that a rule
## applies to.
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.
CONNECTION, ##< Activity involving all of a bi-directional connection's activity.
FLOW, ##< Actitivy involving a uni-directional flow's activity. Can contain wildcards.
MAC, ##< Activity involving a MAC address.
};
## Type for defining a flow.
## Flow is used in :bro:id:`Entity` together with :bro:enum:`NetControl::FLOW` to specify
## a uni-directional flow that a :bro:id:`Rule` applies to.
##
## If optional fields are not set, they are interpreted as wildcarded.
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.
dst_p: port &optional; ##< The destination 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 defining the entity an :bro:id:`Rule` is operating on.
type Entity: record {
ty: EntityType; ##< Type of entity.
conn: conn_id &optional; ##< Used with :bro:enum:`NetControl::CONNECTION`.
@ -33,32 +48,36 @@ export {
mac: string &optional; ##< Used with :bro:enum:`NetControl::MAC`.
};
## Target of :bro:id:`Rule` action.
## The :bro:id`TargetType` defined the target of a :bro:id:`Rule`.
##
## Rules can either be applied to the forward path, affecting all network traffic, or
## on the monitor path, only affecting the traffic that is sent to Bro. The second
## is mostly used for shunting, which allows Bro to tell the networking hardware that
## it wants to no longer see traffic that it identified as benign.
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
## Type of rules that the framework supports. Each type lists the extra
## :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.
## Stop forwarding all packets matching the entity.
##
## No arguments.
## No additional arguments.
DROP,
## Begin modifying all packets matching entity.
## Modify all packets matching entity. The packets
## will be modified according to the `mod` entry of
## the rule.
##
## .. todo::
## Define arguments.
MODIFY,
## Begin redirecting all packets matching entity.
## Redirect all packets matching entity to a different switch port,
## given in the `out_port` argument of the rule.
##
## .. todo::
## c: output port to redirect traffic to.
REDIRECT,
## Whitelists all packets of an entity, meaning no restrictions will be applied.

View file

@ -2,13 +2,13 @@
##! dropping functionality.
@load ../main
@load base/frameworks/netcontrol
module Notice;
export {
redef enum Action += {
## Drops the address via Drop::drop_address, and generates an
## alarm.
## Drops the address via :bro:see:`NetControl::drop_address_catch_release`.
ACTION_DROP
};
@ -19,13 +19,17 @@ export {
};
}
hook notice(n: Notice::Info)
hook notice(n: Notice::Info) &priority=-5
{
if ( ACTION_DROP in n$actions )
{
#local drop = React::drop_address(n$src, "");
#local addl = drop?$sub ? fmt(" %s", drop$sub) : "";
#n$dropped = drop$note != Drop::AddressDropIgnored;
#n$msg += fmt(" [%s%s]", drop$note, addl);
local ci = NetControl::get_catch_release_info(n$src);
if ( ci$watch_until == double_to_time(0) )
{
# we have not seen this one yet. Drop it.
local addl = n?$msg ? fmt("ACTION_DROP: %s", n?$msg) : "ACTION_DROP";
local res = NetControl::drop_address_catch_release(n$src, addl);
n$dropped = res$watch_until != double_to_time(0);
}
}
}

View file

@ -16,31 +16,47 @@ module Weird;
export {
## The weird logging stream identifier.
redef enum Log::ID += { LOG };
redef enum Notice::Type += {
## Generic unusual but notice-worthy weird activity.
Activity,
};
## The record type which contains the column fields of the weird log.
## The record which is used for representing and logging weirds.
type Info: record {
## The time when the weird occurred.
ts: time &log;
## If a connection is associated with this weird, this will be
## the connection's unique ID.
uid: string &log &optional;
## conn_id for the optional connection.
id: conn_id &log &optional;
## A shorthand way of giving the uid and id to a weird.
conn: connection &optional;
## The name of the weird that occurred.
name: string &log;
## Additional information accompanying the weird if any.
addl: string &log &optional;
## Indicate if this weird was also turned into a notice.
notice: bool &log &default=F;
notice: bool &log &default=F;
## The peer that originated this weird. This is helpful in
## cluster deployments if a particular cluster node is having
## trouble to help identify which node is having trouble.
peer: string &log &optional;
peer: string &log &optional &default=peer_description;
## This field is to be provided when a weird is generated for
## the purpose of deduplicating weirds. The identifier string
## should be unique for a single instance of the weird. This field
## is used to define when a weird is conceptually a duplicate of
## a previous weird.
identifier: string &optional;
};
## Types of actions that may be taken when handling weird activity events.
@ -59,13 +75,13 @@ export {
## Log the weird event once per originator host.
ACTION_LOG_PER_ORIG,
## Always generate a notice associated with the weird event.
ACTION_NOTICE,
ACTION_NOTICE,
## Generate a notice associated with the weird event only once.
ACTION_NOTICE_ONCE,
## Generate a notice for the weird event once per connection.
ACTION_NOTICE_PER_CONN,
## Generate a notice for the weird event once per originator host.
ACTION_NOTICE_PER_ORIG,
ACTION_NOTICE_PER_ORIG,
};
## A table specifying default/recommended actions per weird type.
@ -246,7 +262,7 @@ export {
"bad_IP_checksum", "bad_TCP_checksum", "bad_UDP_checksum",
"bad_ICMP_checksum",
} &redef;
## This table is used to track identifier and name pairs that should be
## temporarily ignored because the problem has already been reported.
## This helps reduce the volume of high volume weirds by only allowing
@ -267,9 +283,11 @@ export {
##
## rec: The weird columns about to be logged to the weird stream.
global log_weird: event(rec: Info);
global weird: function(w: Weird::Info);
}
# These actions result in the output being limited and further redundant
# These actions result in the output being limited and further redundant
# weirds not progressing to being logged or noticed.
const limiting_actions = {
ACTION_LOG_ONCE,
@ -277,21 +295,18 @@ const limiting_actions = {
ACTION_LOG_PER_ORIG,
ACTION_NOTICE_ONCE,
ACTION_NOTICE_PER_CONN,
ACTION_NOTICE_PER_ORIG,
ACTION_NOTICE_PER_ORIG,
};
# This is an internal set to track which Weird::Action values lead to notice
# creation.
const notice_actions = {
ACTION_NOTICE,
ACTION_NOTICE_PER_CONN,
ACTION_NOTICE_PER_ORIG,
ACTION_NOTICE,
ACTION_NOTICE_PER_CONN,
ACTION_NOTICE_PER_ORIG,
ACTION_NOTICE_ONCE,
};
# Used to pass the optional connection into report().
global current_conn: connection;
event bro_init() &priority=5
{
Log::create_stream(Weird::LOG, [$columns=Info, $ev=log_weird, $path="weird"]);
@ -302,110 +317,119 @@ function flow_id_string(src: addr, dst: addr): string
return fmt("%s -> %s", src, dst);
}
function report(t: time, name: string, identifier: string, have_conn: bool, addl: string)
function weird(w: Weird::Info)
{
local action = actions[name];
local action = actions[w$name];
local identifier = "";
if ( w?$identifier )
identifier = w$identifier;
else
{
if ( w?$id )
identifier = id_string(w$id);
}
# If this weird is to be ignored let's drop out of here very early.
if ( action == ACTION_IGNORE || [name, identifier] in weird_ignore )
if ( action == ACTION_IGNORE || [w$name, identifier] in weird_ignore )
return;
if ( w?$conn )
{
w$uid = w$conn$uid;
w$id = w$conn$id;
}
if ( w?$id )
{
if ( [w$id$orig_h, w$name] in ignore_hosts ||
[w$id$resp_h, w$name] in ignore_hosts )
return;
}
if ( action in limiting_actions )
{
local notice_identifier = identifier;
if ( action in notice_actions )
{
# Handle notices
if ( have_conn && action == ACTION_NOTICE_PER_ORIG )
identifier = fmt("%s", current_conn$id$orig_h);
if ( w?$id && action == ACTION_NOTICE_PER_ORIG )
notice_identifier = fmt("%s", w$id$orig_h);
else if ( action == ACTION_NOTICE_ONCE )
identifier = "";
notice_identifier = "";
# If this weird was already noticed then we're done.
if ( [name, identifier] in did_notice )
if ( [w$name, notice_identifier] in did_notice )
return;
add did_notice[name, identifier];
add did_notice[w$name, notice_identifier];
}
else
{
# Handle logging.
if ( have_conn && action == ACTION_LOG_PER_ORIG )
identifier = fmt("%s", current_conn$id$orig_h);
if ( w?$id && action == ACTION_LOG_PER_ORIG )
notice_identifier = fmt("%s", w$id$orig_h);
else if ( action == ACTION_LOG_ONCE )
identifier = "";
notice_identifier = "";
# If this weird was already logged then we're done.
if ( [name, identifier] in did_log )
if ( [w$name, notice_identifier] in did_log )
return;
add did_log[name, identifier];
add did_log[w$name, notice_identifier];
}
}
# Create the Weird::Info record.
local info: Info;
info$ts = t;
info$name = name;
info$peer = peer_description;
if ( addl != "" )
info$addl = addl;
if ( have_conn )
{
info$uid = current_conn$uid;
info$id = current_conn$id;
}
if ( action in notice_actions )
{
info$notice = T;
w$notice = T;
local n: Notice::Info;
n$note = Activity;
n$msg = info$name;
if ( have_conn )
n$conn = current_conn;
if ( info?$addl )
n$sub = info$addl;
n$msg = w$name;
if ( w?$conn )
n$conn = w$conn;
else
{
if ( w?$uid )
n$uid = w$uid;
if ( w?$id )
n$id = w$id;
}
if ( w?$addl )
n$sub = w$addl;
NOTICE(n);
}
# This is for the temporary ignoring to reduce volume for identical weirds.
if ( name !in weird_do_not_ignore_repeats )
add weird_ignore[name, identifier];
Log::write(Weird::LOG, info);
if ( w$name !in weird_do_not_ignore_repeats )
add weird_ignore[w$name, identifier];
Log::write(Weird::LOG, w);
}
function report_conn(t: time, name: string, identifier: string, addl: string, c: connection)
{
local cid = c$id;
if ( [cid$orig_h, name] in ignore_hosts ||
[cid$resp_h, name] in ignore_hosts )
return;
current_conn = c;
report(t, name, identifier, T, addl);
}
function report_orig(t: time, name: string, identifier: string, orig: addr)
{
if ( [orig, name] in ignore_hosts )
return;
report(t, name, identifier, F, "");
}
# The following events come from core generated weirds typically.
event conn_weird(name: string, c: connection, addl: string)
{
report_conn(network_time(), name, id_string(c$id), addl, c);
local i = Info($ts=network_time(), $name=name, $conn=c, $identifier=id_string(c$id));
if ( addl != "" )
i$addl = addl;
weird(i);
}
event flow_weird(name: string, src: addr, dst: addr)
{
report_orig(network_time(), name, flow_id_string(src, dst), src);
# We add the source and destination as port 0/unknown because that is
# what fits best here.
local id = conn_id($orig_h=src, $orig_p=count_to_port(0, unknown_transport),
$resp_h=dst, $resp_p=count_to_port(0, unknown_transport));
local i = Info($ts=network_time(), $name=name, $id=id, $identifier=flow_id_string(src,dst));
weird(i);
}
event net_weird(name: string)
{
report(network_time(), name, "", F, "");
local i = Info($ts=network_time(), $name=name);
weird(i);
}

View file

@ -0,0 +1,2 @@
The OpenFlow framework exposes the datastructures and functions
necessary to interface to OpenFlow capable hardware.

View file

@ -0,0 +1 @@
Plugins for the OpenFlow framework.

View file

@ -11,7 +11,7 @@ export {
## Indicates packets were dropped by the packet filter.
Dropped_Packets,
};
## This is the interval between individual statistics collection.
const stats_collection_interval = 5min;
}
@ -29,7 +29,7 @@ event net_stats_update(last_stat: NetStats)
new_dropped, new_recvd + new_dropped,
new_link != 0 ? fmt(", %d on link", new_link) : "")]);
}
schedule stats_collection_interval { net_stats_update(ns) };
}

View file

@ -17,22 +17,14 @@ export {
## The reporter logging stream identifier.
redef enum Log::ID += { LOG };
## An indicator of reporter message severity.
type Level: enum {
## Informational, not needing specific attention.
INFO,
## Warning of a potential problem.
WARNING,
## A non-fatal error that should be addressed, but doesn't
## terminate program execution.
ERROR
};
## The record type which contains the column fields of the reporter log.
type Info: record {
## The network time at which the reporter event was generated.
ts: time &log;
## The severity of the reporter message.
## The severity of the reporter message. Levels are INFO for informational
## messages, not needing specific attention; WARNING for warning of a potential
## problem, and ERROR for a non-fatal error that should be addressed, but doesn't
## terminate program execution.
level: Level &log;
## An info/warning/error message that could have either been
## generated from the internal Bro core or at the scripting-layer.