diff --git a/scripts/base/frameworks/openflow/__load__.bro b/scripts/base/frameworks/openflow/__load__.bro index 377ed8fa48..64d6840c4d 100644 --- a/scripts/base/frameworks/openflow/__load__.bro +++ b/scripts/base/frameworks/openflow/__load__.bro @@ -1,3 +1,4 @@ @load ./consts +@load ./types @load ./main @load ./plugins diff --git a/scripts/base/frameworks/openflow/consts.bro b/scripts/base/frameworks/openflow/consts.bro index 0e08e11a47..776af5ad46 100644 --- a/scripts/base/frameworks/openflow/consts.bro +++ b/scripts/base/frameworks/openflow/consts.bro @@ -122,7 +122,6 @@ export { ## which is not added, modified or deleted ## from the bro openflow framework const INVALID_COOKIE = 0xffffffffffffffff; - # Openflow pysical port definitions ## Maximum number of physical switch ports. const OFPP_MAX = 0xff00; @@ -147,6 +146,17 @@ export { const OFPP_LOCAL = 0xfffe; ## Not associated with a pysical port. const OFPP_NONE = 0xffff; + # Openflow no buffer constant. + const OFP_NO_BUFFER = 0xffffffff; + ## Send flow removed message when flow + ## expires or is deleted. + const OFPFF_SEND_FLOW_REM = 0x1; + ## Check for overlapping entries first. + const OFPFF_CHECK_OVERLAP = 0x2; + ## Remark this is for emergency. + ## Flows added with this are only used + ## when the controller is disconnected. + const OFPFF_EMERG = 0x4; ## Openflow action_type definitions ## @@ -212,126 +222,4 @@ export { OFPC_FRAG_MASK = 3, }; - ## Openflow match definition. - ## - ## The openflow match record describes - ## which packets match to a specific - ## rule in a flow table. - type ofp_match: record { - # Wildcard fields. - #wildcards: count &optional; - # Input switch port. - in_port: count &optional; - # Ethernet source address. - dl_src: string &optional; - # Ethernet destination address. - dl_dst: string &optional; - # Input VLAN id. - dl_vlan: count &optional; - # Input VLAN priority. - dl_vlan_pcp: count &optional; - # Ethernet frame type. - dl_type: count &default=ETH_IPv4; - # IP ToS (actually DSCP field, 6bits). - nw_tos: count &optional; - # IP protocol or lower 8 bits of ARP opcode. - nw_proto: count &default=IP_TCP; - # IP source address. - nw_src: addr &optional; - # IP destination address. - nw_dst: addr &optional; - # TCP/UDP source port. - tp_src: port &optional; - # TCP/UDP destination port. - tp_dst: port &optional; - }; - - ## Openflow actions definition. - ## - ## A action describes what should - ## happen with packets of the matching - ## flow. - type ofp_action_output: record { - ## this should never change, but there are not - ## constants available in records - ## defaults to OFPAT_OUTPUT - type_: ofp_action_type &default=OFPAT_OUTPUT; - #_len: count &default=8; - ## Output port. - port_: count &default=OFPP_FLOOD; - #_max_len: count &optional; - }; - - # Openflow flow_mod_flags definition - ## Send flow removed message when flow - ## expires or is deleted. - const OFPFF_SEND_FLOW_REM = 0x1; - ## Check for overlapping entries first. - const OFPFF_CHECK_OVERLAP = 0x2; - ## Remark this is for emergency. - ## Flows added with this are only used - ## when the controller is disconnected. - const OFPFF_EMERG = 0x4; - - ## Openflow flow_mod definition. - ## It describes the flow to match and - ## how it should be modified. - type ofp_flow_mod: record { - # header: ofp_header; - ## Fields to match - match: ofp_match; - ## Opaque controller-issued identifier. - cookie: count &default=BRO_COOKIE_ID * COOKIE_BID_START; - # Flow actions - ## One of OFPFC_*. - command: ofp_flow_mod_command &default=OFPFC_ADD; - ## Idle time before discarding (seconds). - idle_timeout: count &optional; - ## Max time before discarding (seconds). - hard_timeout: count &optional; - ## Priority level of flow entry. - priority: count &optional; - ## Buffered packet to apply to (or -1). - ## Not meaningful for OFPFC_DELETE*. - buffer_id: count &optional; - ## For OFPFC_DELETE* commands, require - ## matching entries to include this as an - ## output port. A value of OFPP_NONE - ## indicates no restrictions. - out_port: count &optional; - ## One of OFPFF_*. - flags: count &optional; - ## A list of actions to perform. - actions: vector of ofp_action_output; - }; - - ## Body of reply to OFPST_FLOW request. - type ofp_flow_stats: record { - ## Length of this entry - _length: count; - ## ID of table flow came from. - table_id: count; - ## Description of fields. - match: ofp_match; - ## Time flow has been alive in seconds. - duration_sec: count; - ## Time flow has been alive in nanoseconds beyond - ## duration_sec. - duration_nsec: count; - ## Priority of the entry. Only meaningful - ## when this is not an exact-match entry. - priority: count; - ## Number of seconds idle before expiration. - idle_timeout: count; - ## Number of seconds before expiration. - hard_timeout: count; - ## Opaque controller-issued identifier. - cookie: count; - ## Number of packets in flow. - packet_count: count; - ## Number of bytes in flow. - byte_count: count; - ## Actions - actions: vector of ofp_action_output; - }; } diff --git a/scripts/base/frameworks/openflow/main.bro b/scripts/base/frameworks/openflow/main.bro index 6681229419..6132731f03 100644 --- a/scripts/base/frameworks/openflow/main.bro +++ b/scripts/base/frameworks/openflow/main.bro @@ -1,8 +1,59 @@ -@load ./consts +##! Bro's openflow control framework +##! +##! This plugin-based framework allows to control Openflow capable +##! switches by implementing communication to an Openflow controller +##! via plugins. The framework has to be instantiated via the new function +##! in one of the plugins. This framework only offers very low-level +##! functionality; if you want to use OpenFlow capable switches, e.g., +##! for shunting, please look at the PACF framework, which provides higher +##! level functions and can use the OpenFlow framework as a backend. module Openflow; +@load ./consts +@load ./types + export { + ## Global flow_mod function. + ## + ## controller: The controller which should execute the flow modification + ## + ## match: The ofp_match record which describes the flow to match. + ## + ## flow_mod: The openflow flow_mod record which describes the action to take. + ## + ## Returns: F on error or if the plugin does not support the operation, T when the operation was queued. + global flow_mod: function(controller: Controller, match: ofp_match, flow_mod: ofp_flow_mod): bool; + + ## Clear the current flow table of the controller. + ## + ## controller: The controller which should execute the flow modification + ## + ## Returns: F on error or if the plugin does not support the operation, T when the operation was queued. + global flow_clear: function(controller: Controller): bool; + + ## Event confirming successful modification of a flow rule. + ## + ## match: The ofp_match record which describes the flow to match. + ## + ## flow_mod: The openflow flow_mod record which describes the action to take. + ## + ## msg: An optional informational message by the plugin.. + global flow_mod_success: event(match: ofp_match, flow_mod: ofp_flow_mod, msg: string &default=""); + + ## Reports an error while installing a flow Rule. + ## + ## match: The ofp_match record which describes the flow to match. + ## + ## flow_mod: The openflow flow_mod record which describes the action to take. + ## + ## msg: Message to describe the event. + global flow_mod_failure: event(match: ofp_match, flow_mod: ofp_flow_mod, msg: string &default=""); + + # ### + # ### Low-level functions for cookie handling. + # ### + ## Function to get the unique id out of a given cookie. ## ## cookie: The openflow match cookie. @@ -23,69 +74,24 @@ export { ## ## Returns: The cookie group id. global generate_cookie: function(cookie: count &default=0): count; - - ## Event to signal that a flow has been successfully modified. - ## - ## flow_mod: The openflow flow_mod record which describes - ## the flow to delete, modify or add. - ## - ## msg: Message to describe the event. - global Openflow::flow_mod_success: event(flow_mod: ofp_flow_mod, msg: string &default="Flow successfully modified"); - - ## Event to signal that a flow mod has failed. - ## - ## flow_mod: The openflow flow_mod record which describes - ## the flow to delete, modify or add. - ## - ## msg: Message to describe the event. - global Openflow::flow_mod_failure: event(flow_mod: ofp_flow_mod, msg: string &default="Could not modify flow"); - - ## Available openflow plugins - type Plugin: enum { - PLACEHOLDER, - }; - - ## Controller related state. - ## Can be redefined by plugins to - ## add state. - type ControllerState: record { - ## Controller ip. - host: addr &optional; - ## Controller listen port. - host_port: count &optional; - ## Openflow switch datapath id. - dpid: count &optional; - ## Type of the openflow plugin. - type_: Plugin; - } &redef; - - ## Controller record representing an openflow controller - type Controller: record { - ## Controller related state. - state: ControllerState; - ## flow_mod function the plugin implements - flow_mod: function(state: ControllerState, flow_mod: ofp_flow_mod): bool; - ## flow_stats function the plugin implements if existing - ## flow_stats: function(state: ControllerState): vector of ofp_flow_stats &optional; - }; - - ## Global flow_mod function wrapper - ## - ## controller: The controller which should execute the flow modification - ## - ## flow_mod: The openflow flow_mod record which describes - ## the flow to delete, modify or add - ## - ## Returns: T if successfull, else F - global flow_mod: function(controller: Controller, flow_mod: ofp_flow_mod): bool; } # the flow_mod function wrapper -function flow_mod(controller: Controller, flow_mod: ofp_flow_mod): bool +function flow_mod(controller: Controller, match: ofp_match, flow_mod: ofp_flow_mod): bool { - return controller$flow_mod(controller$state, flow_mod); + if ( controller?$flow_mod ) + return controller$flow_mod(controller$state, match, flow_mod); + else + return F; } +function flow_clear(controller: Controller): bool + { + if ( controller?$flow_clear ) + return controller$flow_clear(controller$state); + else + return F; + } # local function to forge a flow_mod cookie for this framework. # all flow entries from the openflow framework should have the @@ -93,39 +99,42 @@ function flow_mod(controller: Controller, flow_mod: ofp_flow_mod): bool function generate_cookie(cookie: count &default=0): count { local c = BRO_COOKIE_ID * COOKIE_BID_START; - if(cookie >= COOKIE_UID_SIZE) + + if ( cookie >= COOKIE_UID_SIZE ) Reporter::warning(fmt("The given cookie uid '%d' is > 32bit and will be discarded", cookie)); else c += cookie; + return c; } - # local function to check if a given flow_mod cookie is forged from this framework. -function _is_valid_cookie(cookie: count): bool +function is_valid_cookie(cookie: count): bool { - if (cookie / COOKIE_BID_START == BRO_COOKIE_ID) + if ( cookie / COOKIE_BID_START == BRO_COOKIE_ID ) return T; + Reporter::warning(fmt("The given Openflow cookie '%d' is not valid", cookie)); + return F; } - function get_cookie_uid(cookie: count): count { - if(_is_valid_cookie(cookie)) + if( is_valid_cookie(cookie) ) return (cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START)); + return INVALID_COOKIE; } - function get_cookie_gid(cookie: count): count { - if(_is_valid_cookie(cookie)) + if( is_valid_cookie(cookie) ) return ( (cookie - (COOKIE_BID_START * BRO_COOKIE_ID) - (cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START))) / COOKIE_GID_START ); + return INVALID_COOKIE; } diff --git a/scripts/base/frameworks/openflow/plugins/ryu.bro b/scripts/base/frameworks/openflow/plugins/ryu.bro index 3721126754..feab410c86 100644 --- a/scripts/base/frameworks/openflow/plugins/ryu.bro +++ b/scripts/base/frameworks/openflow/plugins/ryu.bro @@ -3,33 +3,13 @@ @load base/utils/exec @load base/utils/json -module OpenflowRyu; +module Openflow; export { - redef enum Openflow::Plugin += { - Openflow::RYU, + redef enum Plugin += { + RYU, }; - ## Ryu error definitions. - type Error: enum { - ## The openflow command type is not available - ## for this ryu openflow plugin. - COMMAND_TYPE_NOT_AVAILABLE, - ## The openflow action type is not available - ## for this ryu openflow plugin. - ACTION_TYPE_NOT_AVAILABLE, - }; - - ## Ryu error event. - ## - ## flow_mod: The openflow flow_mod record which describes - ## the flow to delete, modify or add. - ## - ## error: The error why the plugin aborted. - ## - ## msg: More detailed error description. - global OpenflowRyu::error: event(flow_mod: Openflow::ofp_flow_mod, error: Error, msg: string &default=""); - ## Ryu controller constructor. ## ## host: Controller ip. @@ -39,34 +19,35 @@ export { ## dpid: Openflow switch datapath id. ## ## Returns: Openflow::Controller record - global new: function(host: addr, host_port: count, dpid: count): Openflow::Controller; + global ryu_new: function(host: addr, host_port: count, dpid: count): Openflow::Controller; + + redef record ControllerState += { + ## Controller ip. + ryu_host: addr &optional; + ## Controller listen port. + ryu_port: count &optional; + ## Openflow switch datapath id. + ryu_dpid: count &optional; + ## Enable debug mode - output JSON to stdout; do not perform actions + ryu_debug: bool &default=F; + }; } - -# Openflow no buffer constant. -const OFP_NO_BUFFER = 0xffffffff; - - # Ryu ReST API flow_mod URL-path const RYU_FLOWENTRY_PATH = "/stats/flowentry/"; # Ryu ReST API flow_stats URL-path -const RYU_FLOWSTATS_PATH = "/stats/flow/"; - +#const RYU_FLOWSTATS_PATH = "/stats/flow/"; # Ryu ReST API action_output type. -type ryu_flow_action_output: record { +type ryu_flow_action: record { # Ryu uses strings as its ReST API output action. - # The type should be never changed... - # but constants are not possible in a record. - _type: string &default="OUTPUT"; - # The output port - _port: count; + _type: string; + # The output port for type OUTPUT + _port: count &optional; }; - # The ReST API documentation can be found at # https://media.readthedocs.org/pdf/ryu/latest/ryu.pdf -# on page 278-299 (30.10.2014) # Ryu ReST API flow_mod type. type ryu_ofp_flow_mod: record { dpid: count; @@ -76,70 +57,84 @@ type ryu_ofp_flow_mod: record { idle_timeout: count &optional; hard_timeout: count &optional; priority: count &optional; - buffer_id: count &optional; flags: count &optional; match: Openflow::ofp_match; - actions: vector of ryu_flow_action_output; + actions: vector of ryu_flow_action; }; +# Mapping between ofp flow mod commands and ryu urls +const ryu_url: table[ofp_flow_mod_command] of string = { + [OFPFC_ADD] = "add", + [OFPFC_MODIFY] = "modify", + [OFPFC_MODIFY_STRICT] = "modify_strict", + [OFPFC_DELETE] = "delete", + [OFPFC_DELETE_STRICT] = "delete_strict", +}; # Ryu flow_mod function -function flow_mod(state: Openflow::ControllerState, flow_mod: Openflow::ofp_flow_mod): bool +function ryu_flow_mod(state: Openflow::ControllerState, match: ofp_match, flow_mod: Openflow::ofp_flow_mod): bool { - # Generate ryu_flow_actions because their type differs (using strings as type). - local _flow_actions: vector of ryu_flow_action_output; - for(i in flow_mod$actions) + if ( state$_plugin != RYU ) { - switch(flow_mod$actions[i]$type_) - { - case Openflow::OFPAT_OUTPUT: - _flow_actions[|_flow_actions|] = ryu_flow_action_output($_port=flow_mod$actions[i]$port_); - break; - default: - Reporter::warning(fmt("The given Openflow action type '%s' is not available", flow_mod$actions[i]$type_)); - event OpenflowRyu::error(flow_mod, ACTION_TYPE_NOT_AVAILABLE, cat(flow_mod$actions[i]$type_)); - return F; - } + Reporter::error("Ryu openflow plugin was called with state of non-ryu plugin"); + return F; } + + # Generate ryu_flow_actions because their type differs (using strings as type). + local flow_actions: vector of ryu_flow_action = vector(); + + for ( i in flow_mod$out_ports ) + flow_actions[|flow_actions|] = ryu_flow_action($_type="OUTPUT", $_port=flow_mod$out_ports[i]); + # Generate our ryu_flow_mod record for the ReST API call. - local _flow_mod: ryu_ofp_flow_mod = ryu_ofp_flow_mod( - $dpid=state$dpid, + local mod: ryu_ofp_flow_mod = ryu_ofp_flow_mod( + $dpid=state$ryu_dpid, $cookie=Openflow::generate_cookie(flow_mod$cookie), $idle_timeout=flow_mod$idle_timeout, $hard_timeout=flow_mod$hard_timeout, - $match=flow_mod$match, - $actions=_flow_actions + $priority=flow_mod$priority, + $flags=flow_mod$flags, + $match=match, + $actions=flow_actions ); + # Type of the command local command_type: string; - switch(flow_mod$command) - { - case Openflow::OFPFC_ADD: - command_type = "add"; - break; - case Openflow::OFPFC_DELETE: - command_type = "delete"; - break; - default: + + if ( flow_mod$command in ryu_url ) + command_type = ryu_url[flow_mod$command]; + else + { Reporter::warning(fmt("The given Openflow command type '%s' is not available", cat(flow_mod$command))); - event OpenflowRyu::error(flow_mod, COMMAND_TYPE_NOT_AVAILABLE, cat(flow_mod$command)); return F; + } + + local url=cat("http://", cat(state$ryu_host), ":", cat(state$ryu_port), RYU_FLOWENTRY_PATH, command_type); + + if ( state$ryu_debug ) + { + print url; + print to_json(mod); + event Openflow::flow_mod_success(match, flow_mod); + return T; } + # Create the ActiveHTTP request and convert the record to a Ryu ReST API JSON string local request: ActiveHTTP::Request = ActiveHTTP::Request( - $url=cat("http://", cat(state$host), ":", cat(state$host_port), RYU_FLOWENTRY_PATH, command_type), + $url=url, $method="POST", - $client_data=to_json(_flow_mod) + $client_data=to_json(mod) ); + # Execute call to Ryu's ReST API - when(local result = ActiveHTTP::request(request)) + when ( local result = ActiveHTTP::request(request) ) { if(result$code == 200) - event Openflow::flow_mod_success(flow_mod, result$body); + event Openflow::flow_mod_success(match, flow_mod, result$body); else { Reporter::warning(fmt("Flow modification failed with error: %s", result$body)); - event Openflow::flow_mod_failure(flow_mod, result$body); + event Openflow::flow_mod_failure(match, flow_mod, result$body); return F; } } @@ -147,8 +142,31 @@ function flow_mod(state: Openflow::ControllerState, flow_mod: Openflow::ofp_flow return T; } -# Ryu controller constructor -function new(host: addr, host_port: count, dpid: count): Openflow::Controller +function ryu_flow_clear(state: Openflow::ControllerState): bool { - return [$state=[$host=host, $host_port=host_port, $type_=Openflow::RYU, $dpid=dpid], $flow_mod=flow_mod]; + local url=cat("http://", cat(state$ryu_host), ":", cat(state$ryu_port), RYU_FLOWENTRY_PATH, "clear", "/", state$ryu_dpid); + + if ( state$ryu_debug ) + { + print url; + return T; + } + + local request: ActiveHTTP::Request = ActiveHTTP::Request( + $url=url, + $method="DELETE" + ); + + when ( local result = ActiveHTTP::request(request) ) + { + } + + return T; + } + +# Ryu controller constructor +function ryu_new(host: addr, host_port: count, dpid: count): Openflow::Controller + { + return [$state=[$ryu_host=host, $ryu_port=host_port, $ryu_dpid=dpid, $_plugin=Openflow::RYU], + $flow_mod=ryu_flow_mod, $flow_clear=ryu_flow_clear]; } diff --git a/scripts/base/frameworks/openflow/types.bro b/scripts/base/frameworks/openflow/types.bro new file mode 100644 index 0000000000..24f4e49562 --- /dev/null +++ b/scripts/base/frameworks/openflow/types.bro @@ -0,0 +1,111 @@ +module Openflow; + +@load ./consts + +export { + ## Available openflow plugins + type Plugin: enum { + ## Internal placeholder plugin + INVALID, + }; + + ## Controller related state. + ## Can be redefined by plugins to + ## add state. + type ControllerState: record { + ## Internally set to the plugin used. + _plugin: Plugin; + } &redef; + + ## Openflow match definition. + ## + ## The openflow match record describes + ## which packets match to a specific + ## rule in a flow table. + type ofp_match: record { + # Input switch port. + in_port: count &optional; + # Ethernet source address. + dl_src: string &optional; + # Ethernet destination address. + dl_dst: string &optional; + # Input VLAN id. + dl_vlan: count &optional; + # Input VLAN priority. + dl_vlan_pcp: count &optional; + # Ethernet frame type. + dl_type: count &optional; + # IP ToS (actually DSCP field, 6bits). + nw_tos: count &optional; + # IP protocol or lower 8 bits of ARP opcode. + nw_proto: count &optional; + # IP source address. + nw_src: addr &optional; + # IP destination address. + nw_dst: addr &optional; + # TCP/UDP source port. + tp_src: port &optional; + # TCP/UDP destination port. + tp_dst: port &optional; + }; + + ## Openflow flow_mod definition, describing the action to perform. + type ofp_flow_mod: record { + ## Opaque controller-issued identifier. + # This is optional in the specification - but let's force + # it so we always can identify our flows... + cookie: count; # &default=BRO_COOKIE_ID * COOKIE_BID_START; + # Flow actions + ## One of OFPFC_*. + command: ofp_flow_mod_command; # &default=OFPFC_ADD; + ## Idle time before discarding (seconds). + idle_timeout: count &default=0; + ## Max time before discarding (seconds). + hard_timeout: count &default=0; + ## Priority level of flow entry. + priority: count &default=0; + ## Bitmap of the OFPFF_* flags + flags: count &default=0; + ## Output ports to send data to. + out_ports: vector of count &default=vector(); + }; + +# Functionality using this is currently not implemented. At all. +# ## Body of reply to OFPST_FLOW request. +# type ofp_flow_stats: record { +# ## ID of table flow came from. +# table_id: count; +# ## Description of fields. +# match: ofp_match; +# ## Time flow has been alive in seconds. +# duration_sec: count; +# ## Time flow has been alive in nanoseconds beyond +# ## duration_sec. +# duration_nsec: count; +# ## Priority of the entry. Only meaningful +# ## when this is not an exact-match entry. +# priority: count; +# ## Number of seconds idle before expiration. +# idle_timeout: count; +# ## Number of seconds before expiration. +# hard_timeout: count; +# ## Opaque controller-issued identifier. +# cookie: count; +# ## Number of packets in flow. +# packet_count: count; +# ## Number of bytes in flow. +# byte_count: count; +# ## Actions +# actions: vector of ofp_action_output; +# }; + + ## Controller record representing an openflow controller + type Controller: record { + ## Controller related state. + state: ControllerState; + ## flow_mod function + flow_mod: function(state: ControllerState, match: ofp_match, flow_mod: ofp_flow_mod): bool &optional; + ## flow_clear function + flow_clear: function(state: ControllerState): bool &optional; + }; +} diff --git a/scripts/base/utils/json.bro b/scripts/base/utils/json.bro index f872ecac44..8ac25e3283 100644 --- a/scripts/base/utils/json.bro +++ b/scripts/base/utils/json.bro @@ -1,22 +1,17 @@ ##! Functions to assist with generating JSON data from Bro data scructures. - # We might want to implement this in core somtime, this looks... hacky at best. @load base/utils/strings -export { - ## A function to convert arbitrary Bro data into a JSON string. - ## - ## v: The value to convert to JSON. Typically a record. - ## - ## only_loggable: If the v value is a record this will only cause - ## fields with the &log attribute to be included in the JSON. - ## - ## returns: a JSON formatted string. - global to_json: function(v: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/): string; -} - -function convert(v: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/): string +## A function to convert arbitrary Bro data into a JSON string. +## +## v: The value to convert to JSON. Typically a record. +## +## only_loggable: If the v value is a record this will only cause +## fields with the &log attribute to be included in the JSON. +## +## returns: a JSON formatted string. +function to_json(v: any, only_loggable: bool &default=F, field_escape_pattern: pattern &default=/^_/): string { local tn = type_name(v); switch ( tn )