diff --git a/scripts/base/frameworks/openflow/main.bro b/scripts/base/frameworks/openflow/main.bro index 11152d2b67..e3cabcb939 100644 --- a/scripts/base/frameworks/openflow/main.bro +++ b/scripts/base/frameworks/openflow/main.bro @@ -1,87 +1,126 @@ @load ./utils/const.bro + module Openflow; + +# Some cookie specific constants. +# first 24 bits +const COOKIE_BID_SIZE = 16777216; +# start at bit 40 (1 << 40) +const COOKIE_BID_START = 1099511627776; +# bro specific cookie ID shall have the 42 bit set (1 << 42) +const BRO_COOKIE_ID = 4; +# 8 bits group identifier +const COOKIE_GID_SIZE = 256; +# start at bit 32 (1 << 32) +const COOKIE_GID_START = 4294967296; +# 32 bits unique identifier +const COOKIE_UID_SIZE = 4294967296; +# start at bit 0 (1 << 0) +const COOKIE_UID_START = 0; + + export { -# ofp_port: enum { - # Maximum number of physical switch ports. + ## Return value for a cookie from a flow + ## which is not added, modified or deleted + ## from the bro openflow framework + const INVALID_COOKIE = 0xffffffffffffffff; + + # Openflow pysical port definitions + ## Maximum number of physical switch ports. const OFPP_MAX = 0xff00; - ######################## - # Fake output "ports". # - ######################## - # Send the packet out the input port. This - # virual port must be explicitly used in - # order to send back out of the input port. + ## Send the packet out the input port. This + ## virual port must be explicitly used in + ## order to send back out of the input port. const OFPP_IN_PORT = 0xfff8; - # Perform actions in flow table. - # NB: This can only be the destination port - # for packet-out messages. + ## Perform actions in flow table. + ## NB: This can only be the destination port + ## for packet-out messages. const OFPP_TABLE = 0xfff9; - # Process with normal L2/L3 switching. + ## Process with normal L2/L3 switching. const OFPP_NORMAL = 0xfffa; - # All pysical ports except input port and - # those disabled by STP. + ## All pysical ports except input port and + ## those disabled by STP. const OFPP_FLOOD = 0xfffb; - # All pysical ports except input port. + ## All pysical ports except input port. const OFPP_ALL = 0xfffc; - # Send to controller. + ## Send to controller. const OFPP_CONTROLLER = 0xfffd; - # Local openflow "port". + ## Local openflow "port". const OFPP_LOCAL = 0xfffe; - # Not associated with a pysical port. + ## Not associated with a pysical port. const OFPP_NONE = 0xffff; -# } + + ## Openflow action_type definitions + ## + ## The openflow action type defines + ## what actions openflow can take + ## to modify a packet type ofp_action_type: enum { - # Output to switch port. + ## Output to switch port. OFPAT_OUTPUT = 0x0000, - # Set the 802.1q VLAN id. + ## Set the 802.1q VLAN id. OFPAT_SET_VLAN_VID = 0x0001, - # Set the 802.1q priority. + ## Set the 802.1q priority. OFPAT_SET_VLAN_PCP = 0x0002, - # Strip the 802.1q header. + ## Strip the 802.1q header. OFPAT_STRIP_VLAN = 0x0003, - # Ethernet source address. + ## Ethernet source address. OFPAT_SET_DL_SRC = 0x0004, - # Ethernet destination address. + ## Ethernet destination address. OFPAT_SET_DL_DST = 0x0005, - # IP source address + ## IP source address OFPAT_SET_NW_SRC = 0x0006, - # IP destination address. + ## IP destination address. OFPAT_SET_NW_DST = 0x0007, - # IP ToS (DSCP field, 6 bits). + ## IP ToS (DSCP field, 6 bits). OFPAT_SET_NW_TOS = 0x0008, - # TCP/UDP source port. + ## TCP/UDP source port. OFPAT_SET_TP_SRC = 0x0009, - # TCP/UDP destination port. + ## TCP/UDP destination port. OFPAT_SET_TP_DST = 0x000a, - # Output to queue. + ## Output to queue. OFPAT_ENQUEUE = 0x000b, + ## Vendor specific OFPAT_VENDOR = 0xffff, }; + ## Openflow flow_mod_command definitions + ## + ## The openflow flow_mod_command describes + ## of what kind an action is. type ofp_flow_mod_command: enum { - # New flow. - OFPFC_ADD, - # Modify all matching flows. - OFPFC_MODIFY, - # Modify entry strictly matching wildcards. - OFPFC_MODIFY_STRICT, - # Delete all matching flows. - OFPFC_DELETE, - # Strictly matching wildcards and priority. - OFPFC_DELETE_STRICT, + ## New flow. + OFPFC_ADD = 0x0, + ## Modify all matching flows. + OFPFC_MODIFY = 0x1, + ## Modify entry strictly matching wildcards. + OFPFC_MODIFY_STRICT = 0x2, + ## Delete all matching flows. + OFPFC_DELETE = 0x3, + ## Strictly matching wildcards and priority. + OFPFC_DELETE_STRICT = 0x4, }; + ## Openflow config flag definitions + ## + ## TODO: describe type ofp_config_flags: enum { - # No special handling for fragments. + ## No special handling for fragments. OFPC_FRAG_NORMAL = 0, - # Drop fragments. + ## Drop fragments. OFPC_FRAG_DROP = 1, - # Reassemble (only if OFPC_IP_REASM set). + ## Reassemble (only if OFPC_IP_REASM set). OFPC_FRAG_REASM = 2, 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; @@ -111,80 +150,199 @@ export { 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 + ## 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. + ## Output port. _port: count &default=OFPP_FLOOD; #_max_len: count &optional; }; -#type ofp_flow_mod_flags: enum { - # Send flow removed message when flow - # expires or is deleted. + # 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. + ## 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. + ## 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 + ## Fields to match match: ofp_match; - # Opaque controller-issued identifier. - cookie: count &optional; - + ## Opaque controller-issued identifier. + cookie: count &default=BRO_COOKIE_ID * COOKIE_BID_START; # Flow actions - - # One of OFPFC_*. + ## One of OFPFC_*. command: ofp_flow_mod_command &default=OFPFC_ADD; - # Idle time befor discarding (seconds). + ## Idle time befor discarding (seconds). idle_timeout: count &optional; - # Max time before discarding (seconds). + ## Max time before discarding (seconds). hard_timeout: count &optional; - # Priority level of flow entry. + ## Priority level of flow entry. priority: count &optional; - # Buffered packet to apply to (or -1). - # Not meaningful for OFPFC_DELETE*. + ## 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 + ## 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_*. + ## 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; + }; + + ## Function to modify flows in a openflow flow table. + ## + ## dpid: The openflow controller datapath id. + ## + ## flow_mod: The openflow flow_mod record which describes + ## the flow to delete, modify or add. + ## + ## Returns: T, if successful, else F. global flow_mod: function(dpid: count, flow_mod: ofp_flow_mod): bool; + + ## Function to get the unique id out of a given cookie + ## + ## cookie: The openflow match cookie + ## + ## Returns: The cookie unique id + global get_cookie_uid: function(cookie: count): count; + + ## Function to get the group id out of a given cookie + ## + ## cookie: The openflow match cookie. + ## + ## Returns: The cookie group id + global get_cookie_gid: function(cookie: count): count; + + ## 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 ord 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"); } + # Flow Modification function prototype type FlowModFunc: function(dpid: count, flow_mod: ofp_flow_mod): bool; -# Flow Modification function -global FlowMod: FlowModFunc; # Hook for registering openflow plugins global register_openflow_plugin: hook(); -function register_openflow_mod_func(func: FlowModFunc) { - FlowMod = func; -} -function flow_mod(dpid: count, flow_mod: ofp_flow_mod): bool { - return FlowMod(dpid, flow_mod); -} +# Function for plugins to call when they +# register their flow_mod function. +function register_openflow_mod_func(func: FlowModFunc) + { + flow_mod = func; + } -event bro_init() &priority=100000 { + +# local function to forge a flow_mod cookie for this framework. +# all flow entries from the openflow framework should have the +# 42 bit of the cookie set. +function generate_cookie(cookie: count &default = 0): count + { + local c = BRO_COOKIE_ID * COOKIE_BID_START; + if(cookie >= COOKIE_UID_SIZE) + Reporter::warning(fmt("The given cookie uid '%d' is > 32bit and will be discarded", cookie)); + else + c += cookie; + return c; + } + + +# local function to check if a given flow_mod cookie is forged from this framework. +function _is_valid_cookie(cookie: count): bool + { + if (cookie / COOKIE_BID_START == BRO_COOKIE_ID) + return T; + Reporter::warning(fmt("The given Openflow cookie '%d' is not a valid", cookie)); + return F; + } + + +function get_cookie_uid(cookie: count): count + { + if(_is_valid_cookie(cookie)) + return (cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START)); + return INVALID_COOKIE; + } + + +function get_cookie_gid(cookie: count): count + { + if(_is_valid_cookie(cookie)) + return ( + (cookie - (COOKIE_BID_START * BRO_COOKIE_ID) - + (cookie - ((cookie / COOKIE_GID_START) * COOKIE_GID_START))) / + COOKIE_GID_START + ); + return INVALID_COOKIE; + } + + +event bro_init() + { # Call all of the plugin registration hooks hook register_openflow_plugin(); -} + } diff --git a/scripts/base/frameworks/openflow/plugins/ryu.bro b/scripts/base/frameworks/openflow/plugins/ryu.bro index a0905ba1a1..6d30a0cf6d 100644 --- a/scripts/base/frameworks/openflow/plugins/ryu.bro +++ b/scripts/base/frameworks/openflow/plugins/ryu.bro @@ -3,17 +3,51 @@ @load base/utils/exec @load base/utils/active-http + module Openflow; + export { - const controller_ip = "10.255.0.20" &redef; + ## The Ryu openflow controller IP. + const controller_ip = "0.0.0.0" &redef; + ## The port where the ReST API listens on. const controller_port = "8080" &redef; + + ## Ryu error definitions. + type RyuError: enum { + ## The controller IP needs to be redefined. + CONTROLLER_IP_REDEF, + ## 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 Openflow::ryu_error: event(flow_mod: ofp_flow_mod, error: RyuError, msg: string &default=""); } + +# 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 action_output type. type ryu_flow_action_output: 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"; @@ -21,9 +55,11 @@ type ryu_flow_action_output: record { _port: count; }; -# The restAPI documentation can be found at + +# The ReST API documentation can be found at # https://media.readthedocs.org/pdf/ryu/latest/ryu.pdf -# on page 278-299 +# on page 278-299 (30.10.2014) +# Ryu ReST API flow_mod type. type ryu_flow_mod: record { dpid: count; cookie: count &optional; @@ -38,26 +74,40 @@ type ryu_flow_mod: record { actions: vector of ryu_flow_action_output; }; -# register the ryu openflow plugin flow_mod function -hook register_openflow_plugin() { + +# Hook to register the Ryu openflow plugin's flow_mod function +# as the one the openflow framework should use. +hook register_openflow_plugin() + { register_openflow_mod_func( - function(dpid: count, flow_mod: ofp_flow_mod): bool { + function(dpid: count, flow_mod: ofp_flow_mod): bool + { + # Check if the controller_ip has been redefined. + if(controller_ip == "0.0.0.0") + { + Reporter::warning(fmt("The constant Openflow::controller_ip must be redefined")); + event Openflow::ryu_error(flow_mod, CONTROLLER_IP_REDEF, cat(controller_ip)); + return F; + } # 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) { - switch(flow_mod$actions[i]$_type) { + for(i in flow_mod$actions) + { + switch(flow_mod$actions[i]$_type) + { case OFPAT_OUTPUT: _flow_actions[|_flow_actions|] = ryu_flow_action_output($_port=flow_mod$actions[i]$_port); break; default: - print fmt("Error: flow action '%s' not available", flow_mod$actions[i]$_type); + Reporter::warning(fmt("The given Openflow action type '%s' is not available", flow_mod$actions[i]$_type)); + event Openflow::ryu_error(flow_mod, ACTION_TYPE_NOT_AVAILABLE, cat(flow_mod$actions[i]$_type)); return F; + } } - } - # Generate our ryu_flow_mod record for the restAPI call. + # Generate our ryu_flow_mod record for the ReST API call. local _flow_mod: ryu_flow_mod = ryu_flow_mod( $dpid=dpid, - $cookie=flow_mod$cookie, + $cookie=generate_cookie(flow_mod$cookie), $idle_timeout=flow_mod$idle_timeout, $hard_timeout=flow_mod$hard_timeout, $match=flow_mod$match, @@ -65,7 +115,8 @@ hook register_openflow_plugin() { ); # Type of the command local command_type: string; - switch(flow_mod$command) { + switch(flow_mod$command) + { case OFPFC_ADD: command_type = "add"; break; @@ -73,76 +124,30 @@ hook register_openflow_plugin() { command_type = "delete"; break; default: - print fmt("Error: command type '%s' not available", flow_mod$command); + Reporter::warning(fmt("The given Openflow command type '%s' is not available", result$body)); + event Openflow::ryu_error(flow_mod, COMMAND_TYPE_NOT_AVAILABLE, cat(flow_mod$command)); return F; - } - # Create the ActiveHTTP request and convert the record to a ryu restAPI JSON string + } + # Create the ActiveHTTP request and convert the record to a Ryu ReST API JSON string local request: ActiveHTTP::Request = ActiveHTTP::Request( $url=cat("http://", controller_ip, ":", controller_port, RYU_FLOWENTRY_PATH, command_type), $method="POST", $client_data=JSON::convert(_flow_mod) ); - # Execute call to ryu's restAPI - when(local result = ActiveHTTP::request(request)) { - if(result$code == 200) { - print fmt( - "%sed flow %s:%s -> %s:%s", - command_type, - flow_mod$match$nw_src, - flow_mod$match$tp_src, - flow_mod$match$nw_dst, - flow_mod$match$tp_dst - ); - } else { - print fmt("Error: could not %s flow, restAPI returned:\n%s", command_type, result); + # Execute call to Ryu's ReST API + when(local result = ActiveHTTP::request(request)) + { + if(result$code == 200) + event Openflow::flow_mod_success(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); return F; - } - } - - # Add reverse flow because openflow only uses unidirectional flows. - if(|flow_mod$actions| == 1 && (flow_mod$match$dl_type == ETH_IPv4 || flow_mod$match$dl_type == ETH_IPv6)) { - local reverse_flow_match: ofp_match; - local reverse_flow_actions: vector of ryu_flow_action_output; - reverse_flow_actions[|reverse_flow_actions|] = ryu_flow_action_output($_port=flow_mod$match$in_port); - reverse_flow_match = ofp_match( - $in_port=flow_mod$actions[0]$_port, - $dl_type=flow_mod$match$dl_type, - $nw_proto=flow_mod$match$nw_proto, - $nw_src=flow_mod$match$nw_dst, - $nw_dst=flow_mod$match$nw_src, - $tp_src=flow_mod$match$tp_dst, - $tp_dst=flow_mod$match$tp_src - ); - local reverse_flow_mod: ryu_flow_mod = ryu_flow_mod( - $dpid=dpid, - $cookie=flow_mod$cookie, - $idle_timeout=flow_mod$idle_timeout, - $hard_timeout=flow_mod$hard_timeout, - $match=reverse_flow_match, - $actions=reverse_flow_actions - ); - local reverse_request: ActiveHTTP::Request = ActiveHTTP::Request( - $url=cat("http://", controller_ip, ":", controller_port, RYU_FLOWENTRY_PATH, command_type), - $method="POST", - $client_data=JSON::convert(reverse_flow_mod) - ); - when(local result2 = ActiveHTTP::request(reverse_request)) { - if(result2$code == 200) { - print fmt( - "%sed flow %s:%s -> %s:%s", - command_type, - reverse_flow_match$nw_src, - reverse_flow_match$tp_src, - reverse_flow_match$nw_dst, - reverse_flow_match$tp_dst - ); - } else { - print fmt("Error: could not %s flow, restAPI returned:\n%s", command_type, result2); - return F; } } - } + return T; - } + } ); -} + } diff --git a/scripts/site/openflow-shunt.bro b/scripts/site/openflow-shunt.bro index 92fab94e65..d10ce853e8 100644 --- a/scripts/site/openflow-shunt.bro +++ b/scripts/site/openflow-shunt.bro @@ -12,6 +12,7 @@ module OpenflowShunt; # default constants which are not automatically gathered. +redef Openflow::controller_ip = "10.255.0.20"; const dpid = 4222282094087168; const cookie = 0; const idle_timeout = 30; @@ -34,7 +35,8 @@ export { global shunt_triggered: event(c: connection); } -function size_callback(c: connection, cnt: count): interval { +function size_callback(c: connection, cnt: count): interval + { # print flow traffic. print fmt( "%s:%s <-> %s:%s reached %s/%s", @@ -46,17 +48,20 @@ function size_callback(c: connection, cnt: count): interval { size_threshold ); # if traffic exceeds the given threshold, remove flow. - if ( c$orig$num_bytes_ip + c$resp$num_bytes_ip >= size_threshold ) { + if ( c$orig$num_bytes_ip + c$resp$num_bytes_ip >= size_threshold ) + { # create openflow flow_mod add records from connection data and given default constants local actions: vector of Openflow::ofp_action_output; + local reverse_actions: vector of Openflow::ofp_action_output; actions[|actions|] = Openflow::ofp_action_output($_port=out_port); + reverse_actions[|reverse_actions|] = Openflow::ofp_action_output($_port=in_port); # flow layer 4 protocol local nw_proto = Openflow::IP_TCP; - if(is_udp_port(c$id$orig_p)) { + if(is_udp_port(c$id$orig_p)) nw_proto = Openflow::IP_UDP; - } else if(is_icmp_port(c$id$orig_p)) { + else if(is_icmp_port(c$id$orig_p)) nw_proto = Openflow::IP_ICMP; - } + local match: Openflow::ofp_match = [ $in_port=in_port, $nw_src=c$id$orig_h, @@ -65,10 +70,19 @@ function size_callback(c: connection, cnt: count): interval { $tp_src=c$id$orig_p, $tp_dst=c$id$resp_p ]; + + local reverse_match: Openflow::ofp_match = [ + $in_port=out_port, + $nw_src=c$id$resp_h, + $nw_dst=c$id$orig_h, + $nw_proto=nw_proto, + $tp_src=c$id$resp_p, + $tp_dst=c$id$orig_p + ]; + local command = Openflow::OFPFC_ADD; - if(delete_flow) { + if(delete_flow) command = Openflow::OFPFC_DELETE; - } local flow_mod: Openflow::ofp_flow_mod = [ $match=match, $cookie=cookie, @@ -77,26 +91,51 @@ function size_callback(c: connection, cnt: count): interval { $hard_timeout=hard_timeout, $actions=actions ]; + local reverse_flow_mod: Openflow::ofp_flow_mod = [ + $match=reverse_match, + $cookie=cookie, + $command=command, + $idle_timeout=idle_timeout, + $hard_timeout=hard_timeout, + $actions=reverse_actions + ]; # call openflow framework - when ( local result = Openflow::flow_mod(dpid, flow_mod) ) { - if(result) { - event OpenflowShunt::shunt_triggered(c); + if(Openflow::flow_mod(dpid, flow_mod) && Openflow::flow_mod(dpid, reverse_flow_mod)) { + event shunt_triggered(c); + } + + if(delete_flow) + { + delete_flow = F; + return -1sec; + } + else + { + delete_flow = T; + return 15sec; } } - if(delete_flow) { - return -1sec; - } else { - delete_flow = T; - return 15sec; - } + return poll_interval; } - return poll_interval; -} - -event connection_established(c: connection) { +event connection_established(c: connection) + { print fmt("new connection"); ConnPolling::watch(c, size_callback, 0, 0secs); -} \ No newline at end of file + } + +event Openflow::flow_mod_success(flow_mod: Openflow::ofp_flow_mod, msg: string) + { + print fmt("succsess, %s", cat(flow_mod)); + } + +event Openflow::flow_mod_failure(flow_mod: Openflow::ofp_flow_mod, msg: string) + { + print fmt("failed, %s", cat(flow_mod)); + } +event Openflow::ryu_error(flow_mod: Openflow::ofp_flow_mod, error: Openflow::RyuError, msg: string) + { + print fmt("ERROR: %s, msg: %s\n%s", error, msg, flow_mod); + }