diff --git a/scripts/base/frameworks/openflow/main.bro b/scripts/base/frameworks/openflow/main.bro index 3f17b65753..198374a3bb 100644 --- a/scripts/base/frameworks/openflow/main.bro +++ b/scripts/base/frameworks/openflow/main.bro @@ -159,10 +159,10 @@ export { ## this should never change, but there are not ## constants available in records ## defaults to OFPAT_OUTPUT - _type: ofp_action_type &default=OFPAT_OUTPUT; + type_: ofp_action_type &default=OFPAT_OUTPUT; #_len: count &default=8; ## Output port. - _port: count &default=OFPP_FLOOD; + port_: count &default=OFPP_FLOOD; #_max_len: count &optional; }; @@ -239,95 +239,94 @@ export { 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 - &default=function(dpid: count, flow_mod: ofp_flow_mod): bool - { - Reporter::warning("Openflow::flow_mod function not implemented. Please load the right Openflow plugin"); - return F; - }; - - ## Function to get flow stats from the openflow switch/router - ## - ## dpid: The openflow controller datapath id. - ## - ## Returns: list with the installed flows and their statistics - global flow_stats: function(dpid: count): vector of ofp_flow_stats - &default=function(dpid: count): vector of ofp_flow_stats - { - Reporter::warning("Openflow::flow_stats function not implemented. Please load the right Openflow plugin"); - return vector(); - }; - - ## 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 + ## Function to get the unique id out of a given cookie. ## ## cookie: The openflow match cookie. ## - ## Returns: The cookie group id + ## Returns: The cookie unique id. + global get_cookie_uid: function(cookie: count): count; + + ## Function to get the group id out of a given cookie. + ## + ## cookie: The openflow match cookie. + ## + ## Returns: The cookie group id. global get_cookie_gid: function(cookie: count): count; + ## Function to get the group id out of a given cookie. + ## + ## cookie: The openflow match cookie. + ## + ## 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"); + 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. + ## 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"); + 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. + ip: addr &optional; + ## Controller listen port. + 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; } - -# Flow Modification function prototype -type FlowModFunc: function(dpid: count, flow_mod: ofp_flow_mod): bool; - - -# Flow Statistics function prototype -type FlowStatsFunc: function(dpid: count): vector of ofp_flow_stats; - - -# Hook for registering openflow plugins -global register_openflow_plugin: hook(); - - -# Function for plugins to call when they register their flow_mod function. -function register_openflow_mod_func(func: FlowModFunc) +# the flow_mod function wrapper +function flow_mod(controller: Controller, flow_mod: ofp_flow_mod): bool { - flow_mod = func; - } - - -# Function for plugins to call when they register their flow_stats function. -function register_openflow_stats_func(func: FlowStatsFunc) - { - flow_stats = func; + return controller$flow_mod(controller$state, flow_mod); } # 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 +function generate_cookie(cookie: count &default=0): count { local c = BRO_COOKIE_ID * COOKIE_BID_START; if(cookie >= COOKIE_UID_SIZE) @@ -366,10 +365,3 @@ function get_cookie_gid(cookie: count): count ); 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 a3360c3098..156168782e 100644 --- a/scripts/base/frameworks/openflow/plugins/ryu.bro +++ b/scripts/base/frameworks/openflow/plugins/ryu.bro @@ -4,19 +4,16 @@ @load base/utils/active-http -module Openflow; +module OpenflowRyu; export { - ## 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; + redef enum Openflow::Plugin += { + Openflow::RYU, + }; ## Ryu error definitions. - type RyuError: enum { - ## The controller IP needs to be redefined. - CONTROLLER_IP_REDEF, + type Error: enum { ## The openflow command type is not available ## for this ryu openflow plugin. COMMAND_TYPE_NOT_AVAILABLE, @@ -33,7 +30,18 @@ export { ## 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=""); + global OpenflowRyu::error: event(flow_mod: Openflow::ofp_flow_mod, error: Error, msg: string &default=""); + + ## Ryu controller constructor. + ## + ## ip: Controller ip. + ## + ## port_: Controller listen port. + ## + ## dpid: Openflow switch datapath id. + ## + ## Returns: Openflow::Controller record + global new: function(ip: addr, port_: count, dpid: count): Openflow::Controller; } @@ -62,7 +70,7 @@ type ryu_flow_action_output: record { # https://media.readthedocs.org/pdf/ryu/latest/ryu.pdf # on page 278-299 (30.10.2014) # Ryu ReST API flow_mod type. -type ryu_flow_mod: record { +type ryu_ofp_flow_mod: record { dpid: count; cookie: count &optional; cookie_mask: count &optional; @@ -77,82 +85,73 @@ type ryu_flow_mod: record { }; -# Hook to register the Ryu openflow plugin's flow_mod function -# as the one the openflow framework should use. -hook register_openflow_plugin() +# Ryu flow_mod function +function flow_mod(state: Openflow::ControllerState, flow_mod: Openflow::ofp_flow_mod): bool { - register_openflow_mod_func( - function(dpid: count, flow_mod: 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) + { + switch(flow_mod$actions[i]$type_) { - # Check if the controller_ip has been redefined. - if(controller_ip == "0.0.0.0") - { - Reporter::warning("The constant Openflow::controller_ip must be redefined"); - event Openflow::ryu_error(flow_mod, CONTROLLER_IP_REDEF, cat(controller_ip)); + 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; - } - # 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) - { - case 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 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 ReST API call. - local _flow_mod: ryu_flow_mod = ryu_flow_mod( - $dpid=dpid, - $cookie=generate_cookie(flow_mod$cookie), - $idle_timeout=flow_mod$idle_timeout, - $hard_timeout=flow_mod$hard_timeout, - $match=flow_mod$match, - $actions=_flow_actions - ); - # Type of the command - local command_type: string; - switch(flow_mod$command) - { - case OFPFC_ADD: - command_type = "add"; - break; - case OFPFC_DELETE: - command_type = "delete"; - break; - default: - Reporter::warning(fmt("The given Openflow command type '%s' is not available", cat(flow_mod$command))); - 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 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 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; - } - } - - return T; } + } + # 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, + $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 ); + # 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: + 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; + } + # 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$ip), ":", cat(state$port_), RYU_FLOWENTRY_PATH, command_type), + $method="POST", + $client_data=OpenflowJSON::convert(_flow_mod) + ); + # 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; + } + } - # TODO: implement when a JSON -> record converter exists - # register_openflow_stats_func(); + return T; } + + +# Ryu controller constructor +function new(ip: addr, port_: count, dpid: count): Openflow::Controller + { + return [$state=[$ip=ip, $port_=port_, $type_=Openflow::RYU, $dpid=dpid], $flow_mod=flow_mod]; + } \ No newline at end of file diff --git a/scripts/base/frameworks/openflow/utils/json.bro b/scripts/base/frameworks/openflow/utils/json.bro index 7ec77fdb60..b29619afc3 100644 --- a/scripts/base/frameworks/openflow/utils/json.bro +++ b/scripts/base/frameworks/openflow/utils/json.bro @@ -1,6 +1,6 @@ @load base/utils/strings -module JSON; +module OpenflowJSON; export { ## A function to convert arbitrary Bro data into a JSON string. @@ -72,7 +72,7 @@ function convert(v: any, only_loggable: bool &default=F, field_escape_pattern: p field = cat(sub(field, field_escape_pattern, "")); if ( field_desc?$value && (!only_loggable || field_desc$log) ) { - local onepart = cat("\"", field, "\": ", JSON::convert(field_desc$value, only_loggable)); + local onepart = cat("\"", field, "\": ", OpenflowJSON::convert(field_desc$value, only_loggable)); rec_parts[|rec_parts|] = onepart; } } @@ -86,7 +86,7 @@ function convert(v: any, only_loggable: bool &default=F, field_escape_pattern: p local sa: set[bool] = v; for ( sv in sa ) { - set_parts[|set_parts|] = JSON::convert(sv, only_loggable); + set_parts[|set_parts|] = OpenflowJSON::convert(sv, only_loggable); } return cat("[", join_string_vec(set_parts, ", "), "]"); } @@ -96,9 +96,9 @@ function convert(v: any, only_loggable: bool &default=F, field_escape_pattern: p local ta: table[bool] of any = v; for ( ti in ta ) { - local ts = JSON::convert(ti); + local ts = OpenflowJSON::convert(ti); local if_quotes = (ts[0] == "\"") ? "" : "\""; - tab_parts[|tab_parts|] = cat(if_quotes, ts, if_quotes, ": ", JSON::convert(ta[ti], only_loggable)); + tab_parts[|tab_parts|] = cat(if_quotes, ts, if_quotes, ": ", OpenflowJSON::convert(ta[ti], only_loggable)); } return cat("{", join_string_vec(tab_parts, ", "), "}"); } @@ -108,7 +108,7 @@ function convert(v: any, only_loggable: bool &default=F, field_escape_pattern: p local va: vector of any = v; for ( vi in va ) { - vec_parts[|vec_parts|] = JSON::convert(va[vi], only_loggable); + vec_parts[|vec_parts|] = OpenflowJSON::convert(va[vi], only_loggable); } return cat("[", join_string_vec(vec_parts, ", "), "]"); } diff --git a/scripts/site/openflow-shunt.bro b/scripts/site/openflow-shunt.bro index 0a46b14a4d..193f80e4f2 100644 --- a/scripts/site/openflow-shunt.bro +++ b/scripts/site/openflow-shunt.bro @@ -2,8 +2,10 @@ @load base/frameworks/notice @load base/frameworks/openflow + module OpenflowShunt; + # pox # global param_dpid = "00-24-a8-5c-0c-00|15" &redef; # global param_port = "\"OFPP_ALL\"" &redef; @@ -12,7 +14,6 @@ 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; @@ -38,6 +39,7 @@ export { function size_callback(c: connection, cnt: count): interval { + local controller = OpenflowRyu::new(10.255.0.20, 8080, dpid); # print Openflow::flow_stats(dpid); # if traffic exceeds the given threshold, remove flow. if ( c$orig$num_bytes_ip + c$resp$num_bytes_ip >= size_threshold ) @@ -45,8 +47,8 @@ function size_callback(c: connection, cnt: count): interval # create openflow flow_mod add records from connection data and give 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); + 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)) @@ -93,9 +95,8 @@ function size_callback(c: connection, cnt: count): interval ]; # call openflow framework - if(Openflow::flow_mod(dpid, flow_mod) && Openflow::flow_mod(dpid, reverse_flow_mod)) { + if(Openflow::flow_mod(controller, flow_mod) && Openflow::flow_mod(controller, reverse_flow_mod)) event shunt_triggered(c); - } if(delete_flow) { @@ -132,7 +133,7 @@ event Openflow::flow_mod_failure(flow_mod: Openflow::ofp_flow_mod, msg: string) } -event Openflow::ryu_error(flow_mod: Openflow::ofp_flow_mod, error: Openflow::RyuError, msg: string) +event OpenflowRyu::error(flow_mod: Openflow::ofp_flow_mod, error: OpenflowRyu::Error, msg: string) { print fmt("ERROR: %s, msg: %s\n%s", error, msg, flow_mod); }