mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00

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