From 15115ce86a11d8c31f93454e088d824b9734543b Mon Sep 17 00:00:00 2001 From: lupo89 <19lupo89@gmail.com> Date: Mon, 8 Sep 2014 09:52:40 -0700 Subject: [PATCH 1/9] Backward Flag implementation in Set State action --- ryu/ofproto/ofproto_v1_3.py | 2 +- ryu/ofproto/ofproto_v1_3_parser.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ryu/ofproto/ofproto_v1_3.py b/ryu/ofproto/ofproto_v1_3.py index d5e04942..a24740f0 100644 --- a/ryu/ofproto/ofproto_v1_3.py +++ b/ryu/ofproto/ofproto_v1_3.py @@ -310,7 +310,7 @@ OFP_ACTION_EXPERIMENTER_HEADER_SIZE) #struct ofp_action_set_state -OFP_ACTION_SET_STATE_PACK_STR = '!HHIB7x' +OFP_ACTION_SET_STATE_PACK_STR = '!HHIBB6x' OFP_ACTION_SET_STATE_SIZE = 16 assert calcsize(OFP_ACTION_SET_STATE_PACK_STR) == OFP_ACTION_SET_STATE_SIZE diff --git a/ryu/ofproto/ofproto_v1_3_parser.py b/ryu/ofproto/ofproto_v1_3_parser.py index b289e439..b947b513 100644 --- a/ryu/ofproto/ofproto_v1_3_parser.py +++ b/ryu/ofproto/ofproto_v1_3_parser.py @@ -3188,25 +3188,27 @@ class OFPActionSetState(OFPAction): ================ ====================================================== state State instance stage_id Stage ID + bw_flag Backward Flag ================ ====================================================== """ - def __init__(self, state=0,stage_id=0,type_=None, len_=None): + def __init__(self, state=0, stage_id=0, bw_flag=0, type_=None, len_=None): super(OFPActionSetState, self).__init__() self.type = ofproto.OFPAT_SET_STATE self.len = ofproto.OFP_ACTION_SET_STATE_SIZE self.state= state self.stage_id= stage_id + self.bw_flag= bw_flag @classmethod def parser(cls, buf, offset): - (type_, len_, state, stage_id) = struct.unpack_from( + (type_, len_, state, stage_id, bw_flag) = struct.unpack_from( ofproto.OFP_ACTION_SET_STATE_PACK_STR, buf, offset) - return cls(state, stage_id) + return cls(state, stage_id, bw_flag) def serialize(self, buf, offset): msg_pack_into(ofproto.OFP_ACTION_SET_STATE_PACK_STR, - buf, offset, self.type, self.len, self.state, self.stage_id) + buf, offset, self.type, self.len, self.state, self.stage_id, self.bw_flag) class OFPBucket(StringifyMixin): def __init__(self, weight=0, watch_port=ofproto.OFPP_ANY, From 2d43bbbc2ec7869c6f5127f1eedec435436ab53c Mon Sep 17 00:00:00 2001 From: Davide Sanvito Date: Mon, 8 Sep 2014 15:38:22 -0700 Subject: [PATCH 2/9] Forwarding Consistency Many-to-Many --- ryu/app/openstate/forw_cons_m_to_m_topo.py | 39 ++ .../forwarding_consistency_many_to_many.py | 546 ++++++++++++++++++ ryu/app/openstate/start_many_to_many.py | 77 +++ 3 files changed, 662 insertions(+) create mode 100644 ryu/app/openstate/forw_cons_m_to_m_topo.py create mode 100644 ryu/app/openstate/forwarding_consistency_many_to_many.py create mode 100644 ryu/app/openstate/start_many_to_many.py diff --git a/ryu/app/openstate/forw_cons_m_to_m_topo.py b/ryu/app/openstate/forw_cons_m_to_m_topo.py new file mode 100644 index 00000000..0c60eeda --- /dev/null +++ b/ryu/app/openstate/forw_cons_m_to_m_topo.py @@ -0,0 +1,39 @@ +from mininet.topo import Topo + +class MyTopo( Topo ): + "Simple topology example." + + def __init__( self): + "Create custom topo." + + # Add default members to class. + Topo.__init__(self) + + # Add nodes + + Host1=self.addHost('h1', ip='10.0.0.1/24') + Host2=self.addHost('h2', ip='10.0.0.2/24') + switch1=self.addSwitch('s1') + switch2=self.addSwitch('s2') + switch3=self.addSwitch('s3') + switch4=self.addSwitch('s4') + switch5=self.addSwitch('s5') + switch6=self.addSwitch('s6') + switch7=self.addSwitch('s7') + + # Add edges + self.addLink( Host1, switch1, 1, 1) + self.addLink( switch1, switch2, 2, 1) + self.addLink( switch1, switch3, 4, 1) + self.addLink( switch1, switch4, 3, 2) + self.addLink( switch2, switch4, 2, 1) + self.addLink( switch3, switch4, 2, 3) + self.addLink( switch4, switch5, 4, 1) + self.addLink( switch4, switch7, 5, 2) + self.addLink( switch4, switch6, 6, 1) + self.addLink( switch5, switch7, 2, 1) + self.addLink( switch6, switch7, 2, 3) + self.addLink( switch7, Host2, 4, 1) + + +topos = { 'mytopo': ( lambda: MyTopo() ) } \ No newline at end of file diff --git a/ryu/app/openstate/forwarding_consistency_many_to_many.py b/ryu/app/openstate/forwarding_consistency_many_to_many.py new file mode 100644 index 00000000..347a17ad --- /dev/null +++ b/ryu/app/openstate/forwarding_consistency_many_to_many.py @@ -0,0 +1,546 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import struct + +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, \ + HANDSHAKE_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet +from ryu.lib.packet import ethernet +from ryu.topology import event + +LOG = logging.getLogger('app.openstate.forwarding_consistency_1_to_many') + +SWITCH_PORTS = 4 + +class OSLoadBalancing(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + LOG.info("OpenState Forwarding Consistency sample app initialized") + LOG.info("Supporting MAX %d ports per switch" % SWITCH_PORTS) + super(OSLoadBalancing, self).__init__(*args, **kwargs) + self.mac_to_port = {} + + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) + def switch_features_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + options ={1: self.one_to_many_switch, + 2: self.middleswitch, + 3: self.middleswitch, + 4: self.many_to_many_switch, + 5: self.middleswitch, + 6: self.middleswitch, + 7: self.many_to_one_switch + } + + options[datapath.id](datapath) + + + def one_to_many_switch(self, datapath): + self.send_features_request(datapath) + self.send_group_mod(datapath) + self.send_table_mod(datapath) + + self.send_key_lookup_1(datapath) + self.send_key_update_1(datapath) + + # install table-miss flow entry (if no rule matching, send it to controller) + # self.add_flow_1(datapath, True) + # install other flow entries + self.add_flow_1(datapath, False) + + def middleswitch(self, datapath): + self.send_features_request(datapath) + # install table-miss flow entry (if no rule matching, send it to controller) + # self.add_flow_2(datapath, True) + # install other flow entries + self.add_flow_2(datapath, False) + + def many_to_one_switch(self, datapath): + self.send_features_request(datapath) + self.send_table_mod(datapath) + + self.send_key_lookup_2(datapath) + self.send_key_update_2(datapath) + + # install table-miss flow entry (if no rule matching, send it to controller) + #self.add_flow_3(datapath, True) + # install other flow entries + self.add_flow_3(datapath, False) + + def many_to_many_switch(self, datapath): + self.send_features_request(datapath) + self.send_group_mod_2(datapath) + self.send_table_mod_2(datapath) + + self.send_key_lookup_3(datapath) + self.send_key_update_3(datapath) + + # install table-miss flow entry (if no rule matching, send it to controller) + # self.add_flow_1(datapath, True) + # install other flow entries + self.add_flow_4(datapath, False) + + def add_flow_1(self, datapath, table_miss=False): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring flow table for switch %d" % datapath.id) + + if table_miss: + LOG.debug("Installing table miss...") + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + else: + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + # Reverse path flow + for in_port in range(2, SWITCH_PORTS + 1): + match = parser.OFPMatch(in_port=in_port) + actions = [ + parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # the state of a flow is the selected output port for that flow + for state in range(SWITCH_PORTS): + if state == 0: + # if state=DEFAULT => send it to the first group entry in the group table + actions = [ + parser.OFPActionGroup(1)] + match = parser.OFPMatch( + in_port=1, metadata=state, eth_type=0x800) + else: + # state x means output port x+1 + actions = [ + parser.OFPActionOutput(state+1, 0)] + match = parser.OFPMatch( + in_port=1, metadata=state, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def add_flow_2(self, datapath, table_miss=False): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring flow table for switch %d" % datapath.id) + + if table_miss: + LOG.debug("Installing table miss...") + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + else: + + match = parser.OFPMatch(in_port=1) + actions = [ + parser.OFPActionOutput(2,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + match = parser.OFPMatch(in_port=2) + actions = [ + parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_3(self, datapath, table_miss=False): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring flow table for switch %d" % datapath.id) + + if table_miss: + LOG.debug("Installing table miss...") + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + else: + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + # Reverse path flow + for state in range(1,SWITCH_PORTS): + match = parser.OFPMatch(in_port=4, metadata=state, eth_type=0x800) + actions = [ + parser.OFPActionOutput(state,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # the state of a flow is the selected output port for that flow + for in_port in range(1,SWITCH_PORTS): + # state x means output port x+1 + actions = [ + parser.OFPActionOutput(4, 0), + parser.OFPActionSetState(in_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_4(self, datapath, table_miss=False): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring flow table for switch %d" % datapath.id) + + if table_miss: + LOG.debug("Installing table miss...") + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + else: + + #SETUP TABLE 0 + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + for in_port in range(4, 7): + match = parser.OFPMatch(in_port=in_port) + actions = [] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions), + parser.OFPInstructionGotoTable(1)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # the state of a flow is the selected output port for that flow + for in_port in range(1,4): + # if state=DEFAULT => send it to the first group entry in the group table + actions = [ + parser.OFPActionGroup(1), + parser.OFPActionSetState(in_port, 1)] + match = parser.OFPMatch( + in_port=in_port, metadata=0, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + for state in range(4,7): + # state x means output port x+1 + actions = [ + parser.OFPActionOutput(state, 0)] + match = parser.OFPMatch( + in_port=in_port, metadata=state, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + #SETUP TABLE 1 + + # the state of a flow is the selected output port for that flow + for in_port in range(4,7): + + for state in range(1,4): + # state x means output port x+1 + actions = [ + parser.OFPActionOutput(state, 0)] + match = parser.OFPMatch( + in_port=in_port, metadata=state, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=1, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def send_group_mod(self, datapath): + ofp = datapath.ofproto + ofp_parser = datapath.ofproto_parser + buckets = [] + # Action Bucket: Date: Fri, 19 Sep 2014 09:17:12 -0700 Subject: [PATCH 3/9] Link protection app fix --- ryu/app/openstate/link_protection.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ryu/app/openstate/link_protection.py b/ryu/app/openstate/link_protection.py index 77c149d5..6fc3ddab 100644 --- a/ryu/app/openstate/link_protection.py +++ b/ryu/app/openstate/link_protection.py @@ -46,12 +46,11 @@ def switch_features_handler(self, ev): self.add_flow(datapath) ''' - time.sleep(10) + #After 10 seconds h1 will be able to ping h3 self.send_reset_flag_mod(datapath) time.sleep(10) - string="1*00*1101" - offset=1 - self.send_modify_flag_mod(datapath,string,offset) + flags_string="1*00*1101" + self.send_modify_flag_mod(datapath,flags_string) ''' @@ -88,7 +87,7 @@ def add_flow(self, datapath, table_miss=False): datapath.send_msg(mod) match = parser.OFPMatch(in_port=2,eth_type=0x0800,ip_proto=6, tcp_dst=33333) - (flag, flag_mask) = ofp_parser.maskedflags("1") + (flag, flag_mask) = parser.maskedflags("1") actions = [ parser.OFPActionSetFlag(flag, flag_mask)] inst = [parser.OFPInstructionActions( @@ -103,7 +102,7 @@ def add_flow(self, datapath, table_miss=False): datapath.send_msg(mod) match = parser.OFPMatch(in_port=3,eth_type=0x0800,ip_proto=6, tcp_dst=22222) - (flag, flag_mask) = ofp_parser.maskedflags("0") + (flag, flag_mask) = parser.maskedflags("0") actions = [ parser.OFPActionSetFlag(flag, flag_mask)] inst = [parser.OFPInstructionActions( @@ -156,10 +155,10 @@ def send_reset_flag_mod(self, datapath): datapath, ofproto.OFPSC_RESET_FLAGS) datapath.send_msg(msg) - def send_modify_flag_mod(self, datapath, flag_string, offset_value=1): + def send_modify_flag_mod(self, datapath, flags_string, offset_value=0): ofproto = datapath.ofproto ofp_parser = datapath.ofproto_parser - (flag, flag_mask) = ofp_parser.maskedflags(flag_string,offset_value) + (flag, flag_mask) = ofp_parser.maskedflags(flags_string,offset_value) msg = datapath.ofproto_parser.OFPFlagMod( datapath, ofproto.OFPSC_MODIFY_FLAGS, flag, flag_mask) datapath.send_msg(msg) From 7de7ef110ea2ddcb3aa1bbdf1c25e3c5b628aea5 Mon Sep 17 00:00:00 2001 From: lupo89 <19lupo89@gmail.com> Date: Wed, 1 Oct 2014 17:42:48 -0700 Subject: [PATCH 4/9] Ryu GUI topology discovery --- ryu/app/ofctl_rest.py | 4 +- ryu/gui/controller.py | 93 +++ ryu/gui/models/__init__.py | 0 ryu/gui/models/proxy.py | 56 ++ ryu/gui/models/topology.py | 277 +++++++ ryu/gui/static/css/ryu.css | 367 ++++++++ ryu/gui/static/img/ryu_logo.gif | Bin 0 -> 2034 bytes ryu/gui/static/img/switch.png | Bin 0 -> 46690 bytes ryu/gui/static/img/ui-bg_org_0070c0.png | Bin 0 -> 464 bytes .../static/js/contrib/jquery.mousewheel.js | 84 ++ .../static/js/contrib/perfect-scrollbar.js | 313 +++++++ ryu/gui/static/js/ryu.js | 783 ++++++++++++++++++ ryu/gui/templates/base.html | 26 + ryu/gui/templates/topology.html | 81 ++ ryu/gui/views/__init__.py | 0 ryu/gui/views/flow.py | 88 ++ ryu/gui/views/topology.py | 26 + ryu/gui/views/view_base.py | 37 + ryu/gui/views/websocket.py | 174 ++++ ryu/topology/switches.py | 3 +- 20 files changed, 2409 insertions(+), 3 deletions(-) create mode 100644 ryu/gui/controller.py create mode 100644 ryu/gui/models/__init__.py create mode 100644 ryu/gui/models/proxy.py create mode 100644 ryu/gui/models/topology.py create mode 100644 ryu/gui/static/css/ryu.css create mode 100644 ryu/gui/static/img/ryu_logo.gif create mode 100644 ryu/gui/static/img/switch.png create mode 100644 ryu/gui/static/img/ui-bg_org_0070c0.png create mode 100644 ryu/gui/static/js/contrib/jquery.mousewheel.js create mode 100644 ryu/gui/static/js/contrib/perfect-scrollbar.js create mode 100644 ryu/gui/static/js/ryu.js create mode 100644 ryu/gui/templates/base.html create mode 100644 ryu/gui/templates/topology.html create mode 100644 ryu/gui/views/__init__.py create mode 100644 ryu/gui/views/flow.py create mode 100644 ryu/gui/views/topology.py create mode 100644 ryu/gui/views/view_base.py create mode 100644 ryu/gui/views/websocket.py diff --git a/ryu/app/ofctl_rest.py b/ryu/app/ofctl_rest.py index edef9a75..a79cec9d 100644 --- a/ryu/app/ofctl_rest.py +++ b/ryu/app/ofctl_rest.py @@ -433,10 +433,12 @@ def __init__(self, *args, **kwargs): controller=StatsController, action='get_desc_stats', conditions=dict(method=['GET'])) + ''' uri = path + '/flow/{dpid}' mapper.connect('stats', uri, controller=StatsController, action='get_flow_stats', conditions=dict(method=['GET'])) + ''' uri = path + '/port/{dpid}' mapper.connect('stats', uri, @@ -472,7 +474,7 @@ def __init__(self, *args, **kwargs): mapper.connect('stats', uri, controller=StatsController, action='get_group_stats', conditions=dict(method=['GET'])) - + uri = path + '/flowentry/{cmd}' mapper.connect('stats', uri, controller=StatsController, action='mod_flow_entry', diff --git a/ryu/gui/controller.py b/ryu/gui/controller.py new file mode 100644 index 00000000..56b07512 --- /dev/null +++ b/ryu/gui/controller.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from argparse import ArgumentParser +import sys +import logging +import inspect +from gevent import pywsgi +from geventwebsocket.handler import WebSocketHandler +from flask import Flask, request, abort +from views.view_base import ViewBase + + +parser = ArgumentParser() +parser.add_argument('--host', dest='host', default='0.0.0.0') +parser.add_argument('--port', dest='port', type=int, default=8000) +args = parser.parse_args() + +app = Flask(__name__.split('.')[0]) +logging.basicConfig(level=logging.DEBUG, + stream=sys.stderr, + format="%(asctime)-15s [%(levelname)-4s] %(message)s") +#handler = logging.FileHandler("/tmp/ryu_gui.log", encoding="utf8") +#app.logger.addHandler(handler) + + +@app.before_request +def before_request_trigger(): + pass + + +@app.after_request +def after_request_trigger(response): + return response + + +@app.route('/') +def index(): + return _view('topology') + + +@app.route('/stats/flow', methods=['POST']) +def flow_mod(): + return _view('flow', request.form.get('host'), request.form.get('port'), + request.form.get('dpid'), request.form.get('flows')) + + +@app.route('/websocket') +def websocket(): + if request.environ.get('wsgi.websocket'): + ws = request.environ['wsgi.websocket'] + return _view('websocket', ws) + abort(404) + + +def _view(view_name, *args, **kwargs): + view_name = 'views.' + view_name + try: + __import__(view_name) + except ImportError: + app.logger.error('ImportError (%s)', view_name) + abort(500) + + mod = sys.modules.get(view_name) + clases = inspect.getmembers(mod, lambda cls: (inspect.isclass(cls) and + issubclass(cls, ViewBase))) + try: + view = clases[0][1](*args, **kwargs) + except IndexError: + app.logger.error('has not View class (%s)', view_name) + abort(500) + app.logger.debug('view loaded. %s', view_name) + return view.run() + + +if __name__ == '__main__': + server = pywsgi.WSGIServer((args.host, args.port), + app, handler_class=WebSocketHandler) + app.logger.info('Running on %s', server.address) + server.serve_forever() diff --git a/ryu/gui/models/__init__.py b/ryu/gui/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/gui/models/proxy.py b/ryu/gui/models/proxy.py new file mode 100644 index 00000000..25bb29ef --- /dev/null +++ b/ryu/gui/models/proxy.py @@ -0,0 +1,56 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import json +import httplib + +LOG = logging.getLogger('ryu.gui') + +_FLOW_PATH_BASE = '/stats/flow/' + + +def get_flows(address, dpid): + assert type(dpid) == int + + flows = [] + try: + path = '%s%d' % (_FLOW_PATH_BASE, dpid) + flows = json.loads(_do_request(address, path).read())[str(dpid)] + except IOError as e: + LOG.error('REST API(%s) is not available.', address) + raise + except httplib.HTTPException as e: + if e[0].status == httplib.NOT_FOUND: + pass # switch already deleted + else: + LOG.error('REST API(%s, path=%s) request error.', address, path) + raise + return flows + + +def _do_request(address, path): + conn = httplib.HTTPConnection(address) + conn.request('GET', path) + res = conn.getresponse() + if res.status in (httplib.OK, + httplib.CREATED, + httplib.ACCEPTED, + httplib.NO_CONTENT): + return res + + raise httplib.HTTPException( + res, 'code %d reason %s' % (res.status, res.reason), + res.getheaders(), res.read()) diff --git a/ryu/gui/models/topology.py b/ryu/gui/models/topology.py new file mode 100644 index 00000000..d44243ee --- /dev/null +++ b/ryu/gui/models/topology.py @@ -0,0 +1,277 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import json +from socket import error as SocketError +from httplib import HTTPException + +import gevent +import gevent.monkey +gevent.monkey.patch_all() + +from ryu.lib.dpid import str_to_dpid +from ryu.lib.port_no import str_to_port_no +from ryu.app.client import TopologyClient + +LOG = logging.getLogger('ryu.gui') + + +class Port(object): + def __init__(self, dpid, port_no, hw_addr, name): + assert type(dpid) == int + assert type(port_no) == int + assert type(hw_addr) == str or type(hw_addr) == unicode + assert type(name) == str or type(name) == unicode + + self.dpid = dpid + self.port_no = port_no + self.hw_addr = hw_addr + self.name = name + + def to_dict(self): + return {'dpid': self.dpid, + 'port_no': self.port_no, + 'hw_addr': self.hw_addr, + 'name': self.name} + + @classmethod + def from_rest_dict(cls, p): + return cls(str_to_dpid(p['dpid']), + str_to_port_no(p['port_no']), + p['hw_addr'], + p['name']) + + def __eq__(self, other): + return self.dpid == other.dpid and self.port_no == other.port_no + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.dpid, self.port_no)) + + def __str__(self): + return 'Port' % \ + (self.dpid, self.port_no, self.hw_addr, self.name) + + +class Switch(object): + def __init__(self, dpid, ports): + assert type(dpid) == int + assert type(ports) == list + + self.dpid = dpid + self.ports = ports + + def to_dict(self): + return {'dpid': self.dpid, + 'ports': [port.to_dict() for port in self.ports]} + + def __eq__(self, other): + return self.dpid == other.dpid + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(self.dpid) + + def __str__(self): + return 'Switch' % (self.dpid) + + +class Link(object): + def __init__(self, src, dst): + assert type(src) == Port + assert type(dst) == Port + + self.src = src + self.dst = dst + + def to_dict(self): + return {'src': self.src.to_dict(), + 'dst': self.dst.to_dict()} + + def __eq__(self, other): + return self.src == other.src and self.dst == other.dst + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.src, self.dst)) + + def __str__(self): + return 'Link<%s to %s>' % (self.src, self.dst) + + +class Topology(dict): + def __init__(self, switches_json=None, links_json=None): + super(Topology, self).__init__() + + self['switches'] = [] + if switches_json: + for s in json.loads(switches_json): + ports = [] + for p in s['ports']: + ports.append(Port.from_rest_dict(p)) + switch = Switch(str_to_dpid(s['dpid']), ports) + self['switches'].append(switch) + + self['links'] = [] + if links_json: + for l in json.loads(links_json): + link = Link(Port.from_rest_dict(l['src']), + Port.from_rest_dict(l['dst'])) + self['links'].append(link) + + self['ports'] = [] + for switch in self['switches']: + self['ports'].extend(switch.ports) + + def peer(self, port): + for link in self['links']: + if link.src == port: + return link.dst + elif link.dst == port: + return link.src + + return None + + def attached(self, port): + for switch in self['switches']: + if port in switch.port: + return switch + + return None + + def neighbors(self, switch): + ns = [] + for port in switch.port: + ns.append(self.attached(self.peer(port))) + + return ns + + # TopologyDelta = new_Topology - old_Topology + def __sub__(self, old): + assert type(old) == Topology + + added = Topology() + deleted = Topology() + for k in self.iterkeys(): + new_set = set(self[k]) + old_set = set(old[k]) + + added[k] = list(new_set - old_set) + deleted[k] = list(old_set - new_set) + + return TopologyDelta(added, deleted) + + def __str__(self): + return 'Topology' % ( + len(self['switches']), + len(self['ports']), + len(self['links'])) + + +class TopologyDelta(object): + def __init__(self, added, deleted): + self.added = added + self.deleted = deleted + + def __str__(self): + return 'TopologyDelta' % \ + (self.added, self.deleted) + + +class TopologyWatcher(object): + _LOOP_WAIT = 3 + _REST_RETRY_WAIT = 10 + + def __init__(self, update_handler=None, rest_error_handler=None): + self.update_handler = update_handler + self.rest_error_handler = rest_error_handler + self.address = None + self.tc = None + + self.threads = [] + self.topo = Topology() + self.prev_switches_json = '' + self.prev_links_json = '' + + def start(self, address): + LOG.debug('TopologyWatcher: start') + self.address = address + self.tc = TopologyClient(address) + self.is_active = True + self.threads.append(gevent.spawn(self._polling_loop)) + + def stop(self): + LOG.debug('TopologyWatcher: stop') + self.is_active = False + + def _polling_loop(self): + LOG.debug('TopologyWatcher: Enter polling loop') + while self.is_active: + try: + switches_json = self.tc.list_switches().read() + links_json = self.tc.list_links().read() + except (SocketError, HTTPException) as e: + LOG.debug('TopologyWatcher: REST API(%s) is not avaliable.' % + self.address) + LOG.debug(' wait %d secs...' % + self._REST_RETRY_WAIT) + self._call_rest_error_handler(e) + gevent.sleep(self._REST_RETRY_WAIT) + continue + + if self._is_updated(switches_json, links_json): + LOG.debug('TopologyWatcher: topology updated') + new_topo = Topology(switches_json, links_json) + delta = new_topo - self.topo + self.topo = new_topo + + self._call_update_handler(delta) + + gevent.sleep(self._LOOP_WAIT) + + def _is_updated(self, switches_json, links_json): + updated = ( + self.prev_switches_json != switches_json or + self.prev_links_json != links_json) + + self.prev_switches_json = switches_json + self.prev_links_json = links_json + + return updated + + def _call_rest_error_handler(self, e): + if self.rest_error_handler: + self.rest_error_handler(self.address, e) + + def _call_update_handler(self, delta): + if self.update_handler: + self.update_handler(self.address, delta) + + +def handler(address, delta): + print delta + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + watcher = TopologyWatcher(handler) + watcher.start('127.0.0.1:8080') + gevent.joinall(watcher.threads) diff --git a/ryu/gui/static/css/ryu.css b/ryu/gui/static/css/ryu.css new file mode 100644 index 00000000..cd0ada68 --- /dev/null +++ b/ryu/gui/static/css/ryu.css @@ -0,0 +1,367 @@ +/** + * Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **/ + +* { + color: #808080; + font-size: 12px; +} +html { + width: 100%; + height: 100%; +} +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + text-align: center; + font-family: Arial, Helvetica,"メイリオ",Meiryo,"ヒラギノ角ゴ Pro W3","Hiragino Kaku Gothic Pro","HiraKakuPro-W3",">MS Pゴシック","MS PGothic",sans-serif; +} +body a { + text-decoration: none; +} + + +/** + * header footer + **/ +#header { + width: 100%; + text-align: left; + font-size: 20px; + margin: 0px; + padding: 0px; + border-bottom: solid 2px #808080; +} +#header #ryu-logo { + width: 100px; + height: 50px; +} +#header #page-title { + font-weight: bold; + margin-left: 10px; + vertical-align: 15px; + font-size: 20px; +} +#footer { + position: fixed; + right: 10px; + bottom: 5px; + color: #808080; + font-size: 12px; + z-index: 100px; +} + + +/** + * main + **/ +#main { +} + +/** + * input form + */ +#jquery-ui-dialog-table { +} +#jquery-ui-dialog-table th , #jquery-ui-dialog-table td { + border: 1px solid gray; +} +#input-err-msg { + color: red; + display: none; + -webkit-border-radius: 5px; +} + + +/** + * contents + **/ +.content { + position: absolute; + border: solid 1px #808080; + color: #808080; + padding-bottom: 1.2em; + -webkit-border-radius: 5px; +} +.content .content-title { + height: 15px; +/* font-size: 1.1em; */ + margin: 0px; + padding: auto; + padding-left: 1.1em; + -webkit-border-radius: 5px; + background: #0070c0 url(../img/ui-bg_org_0070c0.png) 50% 50% repeat-x; + letter-spacing: 0.15em; + cursor: move; + color: #FFF; +} +.content .content-title .content-title-text { + color: #FFF; + float: left; +} +.content .content-title .content-title-close { + float: right; + width: 20px; + cursor: pointer; +} +.content .content-body{ + position: relative; + overflow: hidden; + width: 100%; + height: 95%; + white-space: nowrap; + -webkit-border-radius: 5px; +} +.content .content-end { + height: 0px; + display: none; +} +.content .watching { + margin-left: 5px; + color: #FFF; +} + + +/** + * list table + **/ +.content-body table { + margin-top: 2px; + padding-right: 5px; + width: 100%; + border-collapse:separate; + border-spacing:1px; +} +.content-body table th { + border:0.25px solid #FFFFFF; + background-color: #483D8B; + color: #FFF; + text-align: center; + font-weight: bold; + letter-spacing: 0.15em; +} +.content-body table td { + border:0.25px solid #FFFFFF; + background-color: #EEEEEE; + white-space: nowrap; +} + + +/** + * Menu + **/ +#menu { + top: 60px; + left: 5px; + width: 150px; + height: 100px; + text-align: left; + background-color: #EEE; + z-index: 100; +} +#menu .content-body { + background-color: #EEE; +} +#menu .menu-item { + text-align: left; + padding-left: 5%; /** width + padding-left = #demomenu.width **/ + padding-top: 1px; + padding-bottom: 1px; + margin-top: 1px; + margin-bottom: 1px; + font-weight: bold; + color: #0070c0; + background-color: #EEE; + -webkit-border-radius: 5px; +} + + +/** + * link-list + **/ +#link-list { + top: 250px; + left: 5px; + width: 200px; + height: 100px; + text-align: left; +} +#link-list td { + text-align: center; +} +#link-list .peer-port-name { +} +#link-list .peer-switch-name { + padding-left: 5px; +} + + +/** + * flow-list + */ +#flow-list { + top: 540px; + left: 5px; + width: 825px; + height: 100px; + text-align: left; +} +#flow-list td { + padding-left: 5px; + line-height: 1.4; +/* white-space: normal; */ +/* letter-spacing: 0.15em; */ +} +#flow-list .flow-item-line{ + width: 100%; + border-bottom: dashed 1px #808080; +} +#flow-list .flow-item-line .flow-item-title{ +/** + float: left; + width: 5em; +**/ + color: #000; + font-weight: bold; + padding-left: 0.5em; +} +#flow-list .flow-item-line .flow-item-value{ +/** + float: right; + width: auto; +**/ + padding-left: 0.5em; +} +/** + * Topology + **/ +#topology { + top: 60px; + left: 230px; + width: 600px; + height: 450px; +} +#topology .rest-status { + position: relative; + top: 0; + left: 0; + text-align: left; + font-weight:bold; + padding-left: 5px; +} +#topology .content-body{ + height: 100%; +} +#topology .rest-status .rest-url{ + margin-left: 10px; + font-size: 12px; + color: #808080; + letter-spacing: 0.15em; + font-weight: normal; +} +#topology .switch { + z-index: 1; + border: 0px solid #FFF; + +} +#topology .switch img { + z-index: 2; +} +#topology .switch-label { + position: relative; +/* font-weight:bold; */ +/* background-color: red; */ +/* color: white; */ + text-align: center; + border: 0px solid #FFF; +/* height: 15px; */ + z-index: 3; +} +#topology .port-no { + -webkit-border-radius: 10px; + text-align: center; + font-weight:bold; + background-color: #E6FFE9; + border: 1px solid #808080; + width: 14px; + height: 14px; + z-index: 4; + margin: auto auto; +} + +/** + * scrollbar + **/ +.ps-container .ps-scrollbar-x { + position: absolute; /* please don't change 'position' */ + bottom: 3px; /* there must be 'bottom' for ps-scrollbar-x */ + height: 8px; + background-color: #aaa; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + opacity: 0; + filter: alpha(opacity = 0); + -webkit-transition: opacity.2s linear; + -moz-transition: opacity .2s linear; + transition: opacity .2s linear; +} +.ps-container:hover .ps-scrollbar-x { + opacity: 0.6; + filter: alpha(opacity = 60); +} +.ps-container .ps-scrollbar-x:hover { + opacity: 0.9; + filter: alpha(opacity = 90); +/* cursor:default; */ + cursor: pointer; +} +.ps-container .ps-scrollbar-x.in-scrolling { + opacity: 0.9; + filter: alpha(opacity = 90); +} + +.ps-container .ps-scrollbar-y { + position: absolute; /* please don't change 'position' */ + right: 3px; /* there must be 'right' for ps-scrollbar-y */ + width: 8px; + background-color: #aaa; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + opacity: 0; + filter: alpha(opacity = 0); + -webkit-transition: opacity.2s linear; + -moz-transition: opacity .2s linear; + transition: opacity .2s linear; +} +.ps-container:hover .ps-scrollbar-y { + opacity: 0.6; + filter: alpha(opacity = 60); +} +.ps-container .ps-scrollbar-y:hover { + opacity: 0.9; + filter: alpha(opacity = 90); +/* cursor: default; */ + cursor: pointer; +} +.ps-container .ps-scrollbar-y.in-scrolling { + opacity: 0.9; + filter: alpha(opacity = 90); +} diff --git a/ryu/gui/static/img/ryu_logo.gif b/ryu/gui/static/img/ryu_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..8bc7cc353d38385d324cb13eaf4f8cc1da422131 GIT binary patch literal 2034 zcmXYwc~n$K7RE1%Ac)E)vQ46>h&*(ZG6~w+fo2uakl+)IFgcp;Vi`eP4|>e_6l_Pv zlS4$%0u>vv=`I~dP>GUp45EakL>zqvwwr|Ow302BfW1%-hkKtbRD zoB*f58E^p{K?P6=s1#HNssI(Ww*d(t1!RB%P&)ttLVyxr1cX*25)g?Hi4utsNe~H% z3`8bGrbK2$7DPs(08t20C{Y+u1W}N1Ae<0R31@^0!jY&zR6jq}{1tzb5v~H%|<;#{6LC$+)!bToQI8*GhH*nTfOImV%w{QKx z>~~WR=l!6#-aOIwkEGpby)w=HX=cM}!zqu5b-Ny$@-v34vOgG_D`F>}zBpsX)IgKt z@R1+r7m!;$ErZTnt@a!C+4+|{7FI60Id{>8j-5@owk!Q;x^7T!?uMqFNBg_mANl%4 zl+RorG4GlB{ZeE8_Zyp9Gj^q0r;M#w9{Wqp^;~t_;P?%9P2=~j8uef5=`J6;=Z zWL+ssQ@3XLna`f8Q+D6uj}!OlZ)S1(kQLXfYpnYIfzE{kzIjv-;COJXbDzht&n7UrVd4|7P8z zI=jy{|89NiCEh#SX_?#7wnpx8q$)9>s6Nsddg^C2z`4Dz-MthKnk*N4qqiOJZ1Brp zJSsZi-@D!ZwLYh6`ZJdaHr@1h#z&i+bfNBp_O#a>{@iPJ&yJ$)S63Z3`$TsZ_x#$M zRckR^t|;3(EVbBrGTVQ8{$9P!ytw?Q=em+g?|UQ7g`qBEVk}9qB@0_zcU+pSpV)UR ze&U2H4+4(+Y;jM!k@{Wf!`wOEu7~by_W9z4?}%N$OY`;ldew^-lWD~K1v9H#-@Izy z>>rr-;G<=ezINXuj8ghZSBDSJlok{Qg)MsGW@xB#>!@~mZ}HluHwMpWLw&LQnkeKDmE0XrK>hw#a4TdEyM z^!3L_q7|^f5gN9 literal 0 HcmV?d00001 diff --git a/ryu/gui/static/img/switch.png b/ryu/gui/static/img/switch.png new file mode 100644 index 0000000000000000000000000000000000000000..38868d43c4a5056a230a5114506555d1bbe3fda8 GIT binary patch literal 46690 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 DDbAVB literal 0 HcmV?d00001 diff --git a/ryu/gui/static/js/contrib/jquery.mousewheel.js b/ryu/gui/static/js/contrib/jquery.mousewheel.js new file mode 100644 index 00000000..38b60951 --- /dev/null +++ b/ryu/gui/static/js/contrib/jquery.mousewheel.js @@ -0,0 +1,84 @@ +/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) + * Licensed under the MIT License (LICENSE.txt). + * + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * Thanks to: Seamus Leahy for adding deltaX and deltaY + * + * Version: 3.0.6 + * + * Requires: 1.2.2+ + */ + +(function($) { + +var types = ['DOMMouseScroll', 'mousewheel']; + +if ($.event.fixHooks) { + for ( var i=types.length; i; ) { + $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; + } +} + +$.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) { + for ( var i=types.length; i; ) { + this.addEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = handler; + } + }, + + teardown: function() { + if ( this.removeEventListener ) { + for ( var i=types.length; i; ) { + this.removeEventListener( types[--i], handler, false ); + } + } else { + this.onmousewheel = null; + } + } +}; + +$.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } +}); + + +function handler(event) { + var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; + event = $.event.fix(orgEvent); + event.type = "mousewheel"; + + // Old school scrollwheel delta + if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; } + if ( orgEvent.detail ) { delta = -orgEvent.detail/3; } + + // New school multidimensional scroll (touchpads) deltas + deltaY = delta; + + // Gecko + if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { + deltaY = 0; + deltaX = -1*delta; + } + + // Webkit + if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; } + if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; } + + // Add event and delta to the front of the arguments + args.unshift(event, delta, deltaX, deltaY); + + return ($.event.dispatch || $.event.handle).apply(this, args); +} + +})(jQuery); diff --git a/ryu/gui/static/js/contrib/perfect-scrollbar.js b/ryu/gui/static/js/contrib/perfect-scrollbar.js new file mode 100644 index 00000000..be056ddf --- /dev/null +++ b/ryu/gui/static/js/contrib/perfect-scrollbar.js @@ -0,0 +1,313 @@ +/* Copyright (c) 2012 HyeonJe Jun (http://github.com/noraesae) + * Licensed under the MIT License + */ +((function($) { + + // The default settings for the plugin + var defaultSettings = { + wheelSpeed: 10, + wheelPropagation: false + }; + + $.fn.perfectScrollbar = function(suppliedSettings, option) { + + // Use the default settings + var settings = $.extend( true, {}, defaultSettings ); + if (typeof suppliedSettings === "object") { + // But over-ride any supplied + $.extend( true, settings, suppliedSettings ); + } else { + // If no settings were supplied, then the first param must be the option + option = suppliedSettings; + } + + if(option === 'update') { + if($(this).data('perfect_scrollbar_update')) { + $(this).data('perfect_scrollbar_update')(); + } + return $(this); + } + else if(option === 'destroy') { + if($(this).data('perfect_scrollbar_destroy')) { + $(this).data('perfect_scrollbar_destroy')(); + } + return $(this); + } + + if($(this).data('perfect_scrollbar')) { + // if there's already perfect_scrollbar + return $(this).data('perfect_scrollbar'); + } + + var $this = $(this).addClass('ps-container'), + $content = $(this).children(), + $scrollbar_x = $("
").appendTo($this), + $scrollbar_y = $("
").appendTo($this), + container_width, + container_height, + content_width, + content_height, + scrollbar_x_width, + scrollbar_x_left, + scrollbar_x_bottom = parseInt($scrollbar_x.css('bottom'), 10), + scrollbar_y_height, + scrollbar_y_top, + scrollbar_y_right = parseInt($scrollbar_y.css('right'), 10); + + var updateContentScrollTop = function() { + var scroll_top = parseInt(scrollbar_y_top * content_height / container_height, 10); + $this.scrollTop(scroll_top); + $scrollbar_x.css({bottom: scrollbar_x_bottom - scroll_top}); + }; + + var updateContentScrollLeft = function() { + var scroll_left = parseInt(scrollbar_x_left * content_width / container_width, 10); + $this.scrollLeft(scroll_left); + $scrollbar_y.css({right: scrollbar_y_right - scroll_left}); + }; + + var updateBarSizeAndPosition = function() { + container_width = $this.width(); + container_height = $this.height(); + content_width = $content.outerWidth(false); + content_height = $content.outerHeight(false); + if(container_width < content_width) { + scrollbar_x_width = parseInt(container_width * container_width / content_width, 10); + scrollbar_x_left = parseInt($this.scrollLeft() * container_width / content_width, 10); + } + else { + scrollbar_x_width = 0; + scrollbar_x_left = 0; + $this.scrollLeft(0); + } + if(container_height < content_height) { + scrollbar_y_height = parseInt(container_height * container_height / content_height, 10); + scrollbar_y_top = parseInt($this.scrollTop() * container_height / content_height, 10); + } + else { + scrollbar_y_height = 0; + scrollbar_y_left = 0; + $this.scrollTop(0); + } + + $scrollbar_x.css({left: scrollbar_x_left + $this.scrollLeft(), bottom: scrollbar_x_bottom - $this.scrollTop(), width: scrollbar_x_width}); + $scrollbar_y.css({top: scrollbar_y_top + $this.scrollTop(), right: scrollbar_y_right - $this.scrollLeft(), height: scrollbar_y_height}); + }; + + var moveBarX = function(current_left, delta_x) { + var new_left = current_left + delta_x, + max_left = container_width - scrollbar_x_width; + + if(new_left < 0) { + scrollbar_x_left = 0; + } + else if(new_left > max_left) { + scrollbar_x_left = max_left; + } + else { + scrollbar_x_left = new_left; + } + $scrollbar_x.css({left: scrollbar_x_left + $this.scrollLeft()}); + }; + + var moveBarY = function(current_top, delta_y) { + var new_top = current_top + delta_y, + max_top = container_height - scrollbar_y_height; + + if(new_top < 0) { + scrollbar_y_top = 0; + } + else if(new_top > max_top) { + scrollbar_y_top = max_top; + } + else { + scrollbar_y_top = new_top; + } + $scrollbar_y.css({top: scrollbar_y_top + $this.scrollTop()}); + }; + + var bindMouseScrollXHandler = function() { + var current_left, + current_page_x; + + $scrollbar_x.bind('mousedown.perfect-scroll', function(e) { + current_page_x = e.pageX; + current_left = $scrollbar_x.position().left; + $scrollbar_x.addClass('in-scrolling'); + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).bind('mousemove.perfect-scroll', function(e) { + if($scrollbar_x.hasClass('in-scrolling')) { + moveBarX(current_left, e.pageX - current_page_x); + updateContentScrollLeft(); + e.stopPropagation(); + e.preventDefault(); + } + }); + + $(document).bind('mouseup.perfect-scroll', function(e) { + if($scrollbar_x.hasClass('in-scrolling')) { + $scrollbar_x.removeClass('in-scrolling'); + } + }); + }; + + var bindMouseScrollYHandler = function() { + var current_top, + current_page_y; + + $scrollbar_y.bind('mousedown.perfect-scroll', function(e) { + current_page_y = e.pageY; + current_top = $scrollbar_y.position().top; + $scrollbar_y.addClass('in-scrolling'); + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).bind('mousemove.perfect-scroll', function(e) { + if($scrollbar_y.hasClass('in-scrolling')) { + moveBarY(current_top, e.pageY - current_page_y); + updateContentScrollTop(); + e.stopPropagation(); + e.preventDefault(); + } + }); + + $(document).bind('mouseup.perfect-scroll', function(e) { + if($scrollbar_y.hasClass('in-scrolling')) { + $scrollbar_y.removeClass('in-scrolling'); + } + }); + }; + + // bind handlers + var bindMouseWheelHandler = function() { + var shouldPreventDefault = function(deltaX, deltaY) { + var scrollTop = $this.scrollTop(); + if(scrollTop === 0 && deltaY > 0 && deltaX === 0) { + return !settings.wheelPropagation; + } + else if(scrollTop >= content_height - container_height && deltaY < 0 && deltaX === 0) { + return !settings.wheelPropagation; + } + + var scrollLeft = $this.scrollLeft(); + if(scrollLeft === 0 && deltaX < 0 && deltaY === 0) { + return !settings.wheelPropagation; + } + else if(scrollLeft >= content_width - container_width && deltaX > 0 && deltaY === 0) { + return !settings.wheelPropagation; + } + return true; + }; + + $this.mousewheel(function(e, delta, deltaX, deltaY) { + $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); + $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); + + // update bar position + updateBarSizeAndPosition(); + + if(shouldPreventDefault(deltaX, deltaY)) { + e.preventDefault(); + } + }); + }; + + // bind mobile touch handler + var bindMobileTouchHandler = function() { + var applyTouchMove = function(difference_x, difference_y) { + $this.scrollTop($this.scrollTop() - difference_y); + $this.scrollLeft($this.scrollLeft() - difference_x); + + // update bar position + updateBarSizeAndPosition(); + }; + + var start_coords = {}, + start_time = 0, + speed = {}, + breaking_process = null; + + $this.bind("touchstart.perfect-scroll", function(e) { + var touch = e.originalEvent.targetTouches[0]; + + start_coords.pageX = touch.pageX; + start_coords.pageY = touch.pageY; + + start_time = (new Date()).getTime(); + + if (breaking_process !== null) { + clearInterval(breaking_process); + } + }); + $this.bind("touchmove.perfect-scroll", function(e) { + var touch = e.originalEvent.targetTouches[0]; + + var current_coords = {}; + current_coords.pageX = touch.pageX; + current_coords.pageY = touch.pageY; + + var difference_x = current_coords.pageX - start_coords.pageX, + difference_y = current_coords.pageY - start_coords.pageY; + + applyTouchMove(difference_x, difference_y); + start_coords = current_coords; + + var current_time = (new Date()).getTime(); + speed.x = difference_x / (current_time - start_time); + speed.y = difference_y / (current_time - start_time); + start_time = current_time; + + e.preventDefault(); + }); + $this.bind("touchend.perfect-scroll", function(e) { + breaking_process = setInterval(function() { + if(Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { + clearInterval(breaking_process); + return; + } + + applyTouchMove(speed.x * 30, speed.y * 30); + + speed.x *= 0.8; + speed.y *= 0.8; + }, 10); + }); + }; + + var destroy = function() { + $scrollbar_x.remove(); + $scrollbar_y.remove(); + $this.unbind('mousewheel'); + $this.unbind('touchstart.perfect-scroll'); + $this.unbind('touchmove.perfect-scroll'); + $this.unbind('touchend.perfect-scroll'); + $(window).unbind('mousemove.perfect-scroll'); + $(window).unbind('mouseup.perfect-scroll'); + $this.data('perfect_scrollbar', null); + $this.data('perfect_scrollbar_update', null); + $this.data('perfect_scrollbar_destroy', null); + }; + + var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); + + var initialize = function() { + updateBarSizeAndPosition(); + bindMouseScrollXHandler(); + bindMouseScrollYHandler(); + if(isMobile) bindMobileTouchHandler(); + if($this.mousewheel) bindMouseWheelHandler(); + $this.data('perfect_scrollbar', $this); + $this.data('perfect_scrollbar_update', updateBarSizeAndPosition); + $this.data('perfect_scrollbar_destroy', destroy); + }; + + // initialize + initialize(); + + return $this; + }; +})(jQuery)); diff --git a/ryu/gui/static/js/ryu.js b/ryu/gui/static/js/ryu.js new file mode 100644 index 00000000..8ed82ac7 --- /dev/null +++ b/ryu/gui/static/js/ryu.js @@ -0,0 +1,783 @@ +/** + * Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + **/ +var conf = { + URL_GET_FLOWS: 'stats/flow', + LABEL_FONT_SIZE: 10, + EVENT_LOOP_INTERVAL: 500, + REPLACE_FLOW_INTERVAL: 5000, + CONNECTION_REPAINT_INTERVAL: 500, + IMG_SW: {"x": 50, "y": 30, "img": "static/img/switch.png"}, + DEFAULT_REST_PORT: '8080', + ID_PRE_SW: 'node-switch-', + ID_PRE_LINK_LIST: 'link-list-item-', + ID_PRE_FLOW_LIST: 'flow-list-item-' +}; + + +var _EVENTS = []; // [fnc, arg] + + +var _DATA = { + timer: {}, // ids of setTimeout() and setInterval() + watching: null, // dpid of select switch + input: {}, + switches: {} // topology data + // switches[].dpid + // .ports[].dpid + // .port_no + // .name + // .peer.dpid + // .port_no + // .name +}; + + + +/////////////////////////////////// +// topo +/////////////////////////////////// +var topo = { + init: function(){ + utils.restDisconnected(); + utils.event_loop(); + + // scrollbar update + setInterval(function(){ + $("#link-list-body").perfectScrollbar('update'); + $("#flow-list-body").perfectScrollbar('update'); + }, 100); + + // connections repaint + setInterval(function(){ + jsPlumb.repaint($("div .switch")) + }, conf.CONNECTION_REPAINT_INTERVAL); + + // open dialog + topo.setInput({'port': conf.DEFAULT_REST_PORT}); + $('#jquery-ui-dialog').dialog('open'); + }, + + registerHandler: function(){ + $('#jquery-ui-dialog').dialog({ + autoOpen: false, + width: 450, + show: 'explode', + hide: 'explode', + modal: true, + buttons: { + 'Launch': function() { + topo.restConnect(); + $(this).dialog('close'); + }, + 'cancel': function(){ + $(this).dialog('close'); + }, + }, + open: function(){ + topo.openInputForm(); + }, + }); + + // Contents draggable + $('#menu').draggable({ handle: '#menu, .content-title' }); + $('#link-list').draggable({ handle: '#link-list, .content-title' }); + $('#flow-list').draggable({ handle: '#flow-list, .content-title' }); + $('#topology').draggable({ handle: '#topology, .content-title' }); + + // Contents resize + $("#menu").resizable( { autoHide : true } ); + $("#topology").resizable( { autoHide : true } ); + $("#flow-list").resizable( { autoHide : true } ); + $("#link-list").resizable( { autoHide : true } ); + + // Contents scrollbar + $("#link-list-body").perfectScrollbar(); + $("#flow-list-body").perfectScrollbar(); + + // Contents active + $(".content").click(function(){topo.contentActive(this.id)}); + + // Contents close + $(".content-title-close").click(function(){ + return topo.contentClose($(this).closest("div .content").attr("id")) + }); + $(".content-title-close").hover(function(){topo.closeMouseOver(this)}, function(){topo.closeMouseOut(this)}); + + // Menu mouseouver/mouseout + $('#menu a div').hover(function(){ topo.menuMouseOver(this); }, function(){ topo.menuMouseOut(this); }); + + // Menu action + $('#jquery-ui-dialog-opener').click(function(){$('#jquery-ui-dialog').dialog('open');}); + $("#menu-flow-entries").click(function(){topo.contentActive('flow-list');}); + $("#menu-link-status").click(function(){topo.contentActive('link-list');}); + $("#menu-redesign").click(function(){topo.redesignTopology();}); + }, + + setInput: function(input) { + if (typeof input.host !== "undefined") _DATA.input.host = input.host; + if (typeof input.port !== "undefined") _DATA.input.port = input.port; + if (typeof input.err !== "undefined") _DATA.input.err = input.err; + }, + + openInputForm: function() { + if (_DATA.input.host) $('#jquery-ui-dialog-form-host').val(_DATA.input.host); + if (_DATA.input.port) $('#jquery-ui-dialog-form-port').val(_DATA.input.port); + if (_DATA.input.err) { + $("#input-err-msg").text(_DATA.input.err).css('display', 'block'); + } else { + $("#input-err-msg").css('display', 'none'); + } + }, + + restConnect: function() { + var input = {}; + input.host = $('#jquery-ui-dialog-form-host').val(); + input.port = conf.DEFAULT_REST_PORT; + if ($('#jquery-ui-dialog-form-port').val()) input.port = $('#jquery-ui-dialog-form-port').val(); + + // not changed + if (_DATA.input.host == input.host + && _DATA.input.port == input.port + && !_DATA.timer.restStatus) return; + + input.err = ''; + topo.setInput(input); + _EVENTS = []; + utils.restDisconnected(); + + // topology cleanup + utils.topologyCleanup(); + websocket.sendRestUpdate(input.host, input.port); + }, + + menuMouseOver: function(el) { + el.style.backgroundColor = "#0070c0"; + el.style.color = "#FFF"; + }, + + menuMouseOut: function(el) { + el.style.backgroundColor = "#EEE"; + el.style.color = "#0070c0"; + }, + + closeMouseOver: function(id) { + }, + + closeMouseOut: function(id) { + }, + + contentClose: function(id) { + $("#" + id).hide(); + return false; + }, + + contentActive: function(id) { + if (id == "menu") return; + var contents = $("#main").find(".content"); + for (var i=0; i < contents.length; i++) { + var content = contents[i]; + if (content.id != "menu") { + if (content.id == id) { + content.style.display = 'block'; + content.style.zIndex = $("#main").children(".content").length * 10; + } else if (content.style.zIndex > 10) { + content.style.zIndex = content.style.zIndex - 1 * 10; + } + } + } + }, + + watchingSwitch: function(dpid) { + if (typeof dpid === "undefined") dpid = ""; + else if (! dpid in _DATA.switches) return; + else if (dpid == _DATA.watching) return; + + $("#topology div").find(".switch").css("border", "0px solid #FFF"); + $("#" + utils._switchId(dpid)).css("border", "3px solid red"); + _DATA.watching = dpid; + utils.refreshLinkList(); + utils.clearFlowList(); + + if (dpid) { + // loop for flowList update + if (_DATA.timer.replaceFlowList) clearInterval(_DATA.timer.replaceFlowList) + var intervalfnc = function() { + if (_DATA.watching == dpid) { + rest.getFlows(_DATA.input.host, _DATA.input.port, _DATA.watching, function(data) { + if (data.host != _DATA.input.host || data.port != _DATA.input.port) return; + utils.replaceFlowList(data.dpid, data.flows); + }, function(data){utils.replaceFlowList(false)}); + } else { + clearInterval(_DATA.timer.replaceFlowList); + } + }; + intervalfnc(); + _DATA.timer.replaceFlowList = setInterval(intervalfnc, conf.REPLACE_FLOW_INTERVAL); + } +// websocket.sendWatchingSwitch(dpid); + }, + + redesignTopology: function(){ + var base = {x: $("#topology").width() / 2, + y: $("#topology").height() / 2} + var radii = {x: $("#topology").width() / 4, + y: $("#topology").height() / 4} + var cnt = 0; + var len = 0; + for (var i in _DATA.switches) len ++; + + for (var i in _DATA.switches) { + var sw = _DATA.switches[i]; + var position = utils._calTh(cnt, len, base, radii); + utils.addSwitch(sw, position) + cnt ++; + } + }, + + emphasisList: function(conn) { + // TODO: + return; + } +}; + + +/////////////////////////////////// +// utils +/////////////////////////////////// +var utils = { + topologyCleanup: function() { + topo.watchingSwitch(); + jsPlumb.reset(); + $("#topology .switch").remove(); + _DATA.switches = {}; + }, + + _switchId: function(dpid) { + return conf.ID_PRE_SW + dpid; + }, + + _linkListId: function(dpid, port_no) { + return conf.ID_PRE_LINK_LIST + dpid + '-' + port_no; + }, + + _flowListId: function(dpid, no) { + return conf.ID_PRE_FLOW_LIST + dpid + '-' + no; + }, + + _connectionUUID: function(dpid, port_no) { + return utils._switchId(dpid) + '-' + port_no; + }, + + ///// + // Event + ///// + event_loop: function() { + if (_EVENTS.length) { + var ev = _EVENTS.shift(); + if (ev.length == 1) ev[0]() + else ev[0](ev[1]); + } + setTimeout(utils.event_loop, conf.EVENT_LOOP_INTERVAL); + }, + + registerEvent: function(func, arg){ + if (typeof arg === "undefined") _EVENTS.push([func]) + else _EVENTS.push([func, arg]) + }, + + ///// + // Rest + ///// + restDisconnected: function(host, port) { + $("#topology").find(".rest-status").css('color', 'red').text('Disconnected'); + if (typeof host !== "undefined" && typeof port !== "undefined") { + var rest = '(' + host + ':' + port + ')'; + $("#topology").find(".rest-status").append(rest); + } + if (_DATA.timer.restStatus) return; + _DATA.timer.restStatus = setInterval(function(){ + $("#topology").find(".rest-status").fadeTo(1000, 0.25).fadeTo(1000, 1) + }, 1500) + }, + + restConnected: function(host, port) { + if (_DATA.timer.restStatus) { + clearInterval(_DATA.timer.restStatus); + _DATA.timer.restStatus = null; + $("#topology").find(".rest-status").css('color', '#808080').text('Connected'); + var rest = '(' + host + ':' + port + ')'; + $("#topology").find(".rest-status").append(rest); + } + }, + + ///// + // Node + ///// + _addNode: function(id, position, img, className) { + var topo_div = $("#topology .content-body")[0]; + var node_div = document.createElement('div'); + var node_img = document.createElement('img'); + node_div.appendChild(node_img); + topo_div.appendChild(node_div); + + node_div.style.position = 'absolute'; + node_div.id = id; + if (typeof className !== 'undefined') node_div.className = className; + node_div.style.width = img.x; + node_div.style.height = img.y; + node_div.style.left = position.x; + node_div.style.top = position.y; + + node_img.id = id + "-img"; + node_img.src = img.img; + node_img.style.width = img.x; + node_img.style.height = img.y; + + // jsPlumb drag + jsPlumb.draggable(node_div, {"containment": "parent"}); + }, + + _moveNode: function(id, position) { + // move position + $("#" + id).animate({left: position.x, top: position.y}, 300, 'swing'); + }, + + _delNode: function(id) { + var points = jsPlumb.getEndpoints(id); + for (var i in points) { + jsPlumb.deleteEndpoint(points[i]); + } + $("#" + id).remove(); + }, + + _calTh: function(no, len, base, radii) { + var th = 3.14159; + var p = {}; + p['x'] = base.x + radii.x * Math.sin(th * 2 * (len - no) / len); + p['y'] = base.y + radii.y * Math.cos(th * 2 * (len - no) / len); + return p + }, + + ///// + // Node (switch) + ///// + addSwitch: function(sw, position) { + var id = utils._switchId(sw.dpid); + if (document.getElementById(id)) { + utils._moveNode(id, position); + return + } + + var img = conf.IMG_SW; + utils._addNode(id, position, img, 'switch'); + var node_div = document.getElementById(id) + node_div.setAttribute("onClick","topo.watchingSwitch('" + sw.dpid + "')"); + +// var labelStr = 'dpid:' + ("0000000000000000" + sw.dpid.toString(16)).slice(-16); + var labelStr = 'dpid: ' + sw.dpid.toString(); + $(node_div).find("img").attr('title', labelStr); + var fontSize = conf.LABEL_FONT_SIZE; + var label_div = document.createElement('div'); + label_div.className = "switch-label"; + label_div.id = id + "-label"; + label_div.style.width = labelStr.length * fontSize; +// label_div.style.marginTop = 0 - (img.y + fontSize) / 2; + label_div.style.marginLeft = (img.x - labelStr.length * fontSize) / 2; + var label_text = document.createTextNode(labelStr); + label_div.appendChild(label_text); + node_div.appendChild(label_div); + }, + + delSwitch: function(dpid) { + utils._delNode(utils._switchId(dpid)); + }, + + ///// + // List + ///// + _repainteRows: function(list_table_id) { + var rows = $("#main").find(".content"); + for (var i=0; i < $("#" + list_table_id).find(".content-table-item").length; i++) { + var tr = $("#" + list_table_id).find(".content-table-item")[i]; + if (i % 2) { + $(tr).find("td").css('background-color', '#D6D6D6'); + $(tr).find("td").css('color', '#535353'); + } else { + $(tr).find("td").css('background-color', '#EEEEEE'); + $(tr).find("td").css('color', '#808080'); + } + } + }, + + ///// + // List (links) + ///// + appendLinkList: function(link){ + var list_table = document.getElementById('link-list-table'); + var tr = list_table.insertRow(-1); + tr.className = 'content-table-item'; + tr.id = utils._linkListId(link.dpid, link.port_no); + + // port-no + var no_td = tr.insertCell(-1); + no_td.className = 'port-no'; + no_td.innerHTML = link.port_no; + + // name + var name_td = tr.insertCell(-1); + name_td.className = 'port-name'; + name_td.innerHTML = link.name; + + // peer + var peer_td = tr.insertCell(-1); + var peer_port_span = document.createElement('span'); + peer_td.className = 'port-peer'; + peer_port_span.className = 'peer-port-name'; + peer_td.appendChild(peer_port_span); + + var peer_port = ''; + if (link.peer) { + if (link.peer.dpid) { + var peer = _DATA.switches[link.peer.dpid]; + if (peer) { + if (peer.ports[link.peer.port_no]) { + peer_port = peer.ports[link.peer.port_no].name; + } + } + } + } + peer_port_span.innerHTML = peer_port; + utils._repainteRows('link-list-table'); + }, + + refreshLinkList: function() { + utils.clearLinkList(); + if (_DATA.watching) { + var sw = _DATA.switches[_DATA.watching]; + for (var i in sw.ports) utils.appendLinkList(sw.ports[i]); + } + }, + + clearLinkList: function(){ + $('#link-list tr').remove('.content-table-item'); + }, + + ///// + // List (flows) + ///// + clearFlowList: function(){ + $('#flow-list tr').remove('.content-table-item'); + }, + + replaceFlowList: function(dpid, flows){ + if (dpid === false) { + utils.clearFlowList(); + return + } + if (dpid != _DATA.watching) return; + utils.clearFlowList() + + // sorted duration + flows.sort(function(a, b){ + if (a.stats.table_id < b.stats.table_id) return -1; + if (a.stats.table_id > b.stats.table_id) return 1; + if (a.stats.priority > b.stats.priority) return -1; + if (a.stats.priority < b.stats.priority) return 1; + if (a.stats.duration_sec > b.stats.duration_sec) return -1; + if (a.stats.duration_sec < b.stats.duration_sec) return 1; + if (a.stats.duration_nsec > b.stats.duration_nsec) return -1; + if (a.stats.duration_nsec < b.stats.duration_nsec) return 1; + return 0; + }); + + var list_table = document.getElementById("flow-list-table"); + for (var i in flows) { + var tr = list_table.insertRow(-1); + tr.className = 'content-table-item'; + tr.id = utils._flowListId(dpid, i); + var td = tr.insertCell(-1); + td.className = 'flow'; + + // stats + var stats = document.createElement('div'); + stats.className = 'flow-item-line'; + td.appendChild(stats); + var statsTitle = document.createElement('span'); + statsTitle.className = 'flow-item-title'; + statsTitle.innerHTML = 'stats:'; + stats.appendChild(statsTitle); + var statsVal = document.createElement('span'); + statsVal.className = 'flow-item-value'; + // sort key + var texts = []; + var sortKey = ['table_id', 'priority', 'duration_sec', 'duration_nsec']; + for (var k in sortKey) { + texts.push(sortKey[k] + '=' + flows[i].stats[sortKey[k]]); + delete flows[i].stats[sortKey[k]]; + } + for (var key in flows[i].stats) { + texts.push(key + '=' + flows[i].stats[key]); + } + statsVal.innerHTML = texts.join(', '); + stats.appendChild(statsVal); + + // rules + var rules = document.createElement('div'); + rules.className = 'flow-item-line'; + td.appendChild(rules); + var rulesTitle = document.createElement('span'); + rulesTitle.className = 'flow-item-title'; + rulesTitle.innerHTML = 'rules:'; + rules.appendChild(rulesTitle); + var rulesVal = document.createElement('span'); + rulesVal.className = 'flow-item-value'; + var texts = []; + for (var key in flows[i].rules) { + texts.push(key + '=' + flows[i].rules[key]); + } + rulesVal.innerHTML = texts.join(', '); + rules.appendChild(rulesVal); + + // actions + var actions = document.createElement('div'); + actions.className = 'flow-item-line'; + td.appendChild(actions); + var actionsTitle = document.createElement('span'); + actionsTitle.className = 'flow-item-title'; + actionsTitle.innerHTML = 'actions:'; + actions.appendChild(actionsTitle); + var actionsVal = document.createElement('span'); + actionsVal.className = 'flow-item-value'; + actionsVal.innerHTML = flows[i].actions.join(', '); + actions.appendChild(actionsVal); + + utils._repainteRows('flow-list-table'); + } + }, + + ///// + // Connections + ///// + addConnect: function(p1, p2) { + var endpoint1 = utils._switchId(p1.dpid); + var endpoint2 = utils._switchId(p2.dpid); + var uuids = [utils._connectionUUID(p1.dpid, p1.port_no), + utils._connectionUUID(p2.dpid, p2.port_no)] + + var overlays = [["Label", {label: p1.port_no.toString(), location: 0.02, cssClass: "port-no"}], + ["Label", {label: p2.port_no.toString(), location: 0.98, cssClass: "port-no"}]]; + + var connector = 'Straight'; + var endpoint = 'Blank'; + var anchors = ["Continuous", "Continuous"]; + var paintStyle = {"lineWidth": 3, + "strokeStyle": '#35FF35', + "outlineWidth": 0.5, + "outlineColor": '#AAA', + "dashstyle": "0 0 0 0"} + + var conn = jsPlumb.connect({source: endpoint1, + target: endpoint2, + uuids: uuids, + endpoint: endpoint, + paintStyle: paintStyle, + connector: connector, + anchors: anchors, + overlays: overlays}); + + var click = function(c) { topo.emphasisList(c) } + conn.bind('click', click); + }, + + delConnect: function(dpid, port_no) { + jsPlumb.deleteEndpoint(utils._connectionUUID(dpid, port_no)) + } +}; + + +/////////////////////////////////// +// rest +/////////////////////////////////// +var rest = { + getFlows: function(host, port, dpid, successfnc, errorfnc) { + if (typeof errorfnc === "undefined") errorfnc = function(){return false} + $.ajax({ + 'type': 'POST', + 'url': conf.URL_GET_FLOWS, + 'data': {"host": host, "port": port, "dpid": dpid}, + 'dataType': 'json', + 'success': successfnc, + 'error': errorfnc + }); + } +}; + +/////////////////////////////////// +// websocket +/////////////////////////////////// +var websocket = { + _sendMessage: function(msg) { + ws.send(JSON.stringify(msg)); + }, + + onMessage: function(msg) { + var msg = JSON.parse(msg); + + // user already updated to URL + if (msg.host != _DATA.input.host || msg.port != _DATA.input.port) return; + + if (msg.message == 'rest_disconnected') { + utils.restDisconnected(msg.host, msg.port); + return; + } + + utils.restConnected(msg.host, msg.port); + if (msg.message == 'add_switches') { + for (var i in msg.body) { + utils.registerEvent(websocket._addSwitch, msg.body[i]); + }; + + } else if (msg.message == 'del_switches') { + for (var i in msg.body) { + utils.registerEvent(websocket._delSwitch, msg.body[i]); + }; + + } else if (msg.message == 'add_ports') { + utils.registerEvent(function(ports){ + for (var i in ports) websocket._addPort(ports[i]); + }, msg.body) + + } else if (msg.message == 'del_ports') { + utils.registerEvent(function(ports){ + for (var i in ports) websocket._delPort(ports[i]); + }, msg.body) + + } else if (msg.message == 'add_links') { + for (var i in msg.body) { + utils.registerEvent(websocket._addLink, msg.body[i]); + }; + + } else if (msg.message == 'del_links') { + for (var i in msg.body) { + utils.registerEvent(websocket._delLink, msg.body[i]); + }; + + } else if (msg.message == 'replace_flows') { + utils.registerEvent(websocket._replaceFlows, msg.body); + } else { + // unknown message + return; + } + }, + + //// + // send messages + //// + sendRestUpdate: function(host, port){ + var msg = {}; + msg.message = 'rest_update'; + msg.body = {}; + msg.body.host = host; + msg.body.port = port; + websocket._sendMessage(msg); + }, + + sendWatchingSwitch: function(dpid){ + msg = {}; + msg.message = "watching_switch_update"; + msg.body = {}; + msg.body.dpid = dpid; + websocket._sendMessage(msg); + }, + + //// + // recive messages + //// + _addSwitch: function(sw) { + if (_DATA.switches[sw.dpid]) return; + _DATA.switches[sw.dpid] = sw; + topo.redesignTopology(); + }, + + _delSwitch: function(sw) { + if (_DATA.watching == sw.dpid) topo.watchingSwitch(); + + // connections + for (var p in _DATA.switches[sw.dpid].ports) { + websocket._delPort(_DATA.switches[sw.dpid].ports[p]); + } + + // node + utils.delSwitch(sw.dpid) + delete _DATA.switches[sw.dpid] + topo.redesignTopology(); + }, + + _addPort: function(port) { + if (_DATA.switches[port.dpid]) _DATA.switches[port.dpid].ports[port.port_no] = port; + utils.refreshLinkList(); + }, + + _delPort: function(port) { + // delConnection + utils.delConnect(port.dpid, port.port_no); + + // delete memory + for (var dpid in _DATA.switches) { + for (var port_no in _DATA.switches[dpid].ports) { + var target = _DATA.switches[dpid].ports[port_no]; + if (target.peer) { + if (target.peer.dpid == port.dpid && target.peer.port_no == port.port_no) { + _DATA.switches[dpid].ports[port_no].peer = {}; + break; + } + } + } + } + delete _DATA.switches[port.dpid].ports[port.port_no]; + + // refreshLinkList + utils.refreshLinkList(); + }, + + _addLink: function(link) { + _DATA.switches[link.p1.dpid].ports[link.p1.port_no].peer = link.p2; + _DATA.switches[link.p2.dpid].ports[link.p2.port_no].peer = link.p1; + utils.addConnect(link.p1, link.p2); + utils.refreshLinkList(); + }, + + _delLink: function(link) { + if (_DATA.switches[link.p1.dpid]) { + if (_DATA.switches[link.p1.dpid].ports[link.p1.port_no]) { + _DATA.switches[link.p1.dpid].ports[link.p1.port_no].peer = {}; + } + } + if (_DATA.switches[link.p2.dpid]) { + if (_DATA.switches[link.p2.dpid].ports[link.p2.port_no]) { + _DATA.switches[link.p2.dpid].ports[link.p2.port_no].peer = {}; + } + } + utils.delConnect(link.p1.dpid, link.p1.port_no); + utils.refreshLinkList(); + }, + + _replaceFlows: function(data) { + utils.replaceFlowList(data.dpid, data.flows); + } +}; diff --git a/ryu/gui/templates/base.html b/ryu/gui/templates/base.html new file mode 100644 index 00000000..52ed0303 --- /dev/null +++ b/ryu/gui/templates/base.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + {% block head %}{% endblock %} + + + +
+ {% block body %}{% endblock %} +
+ + + diff --git a/ryu/gui/templates/topology.html b/ryu/gui/templates/topology.html new file mode 100644 index 00000000..e3ad550f --- /dev/null +++ b/ryu/gui/templates/topology.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% block head %} + +{% endblock%} + +{% block body %} + +
+
+
+

Enter the address of the controller to begin.

+ + +

+

+ + +

+

+
+
+
+ + + +
+
+
Flow entries
+
close
+
+
+ +
+
+
+
+ +
+
Topology
+
+
Unconnected
+
+
+ + +{% endblock %} diff --git a/ryu/gui/views/__init__.py b/ryu/gui/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ryu/gui/views/flow.py b/ryu/gui/views/flow.py new file mode 100644 index 00000000..2bdd1b76 --- /dev/null +++ b/ryu/gui/views/flow.py @@ -0,0 +1,88 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import re +import logging +import httplib + +import view_base +from models import proxy + + +LOG = logging.getLogger('ryu.gui') + + +class FlowView(view_base.ViewBase): + def __init__(self, host, port, dpid, flows=None): + super(FlowView, self).__init__() + self.host = host + self.port = port + self.dpid = dpid + self.flows = flows + + def run(self): + if not self.flows: + # dump flows + return self._dump_flows() + + # TODO: flow-mod + return self.null_response() + + def _dump_flows(self): + address = '%s:%s' % (self.host, self.port) + res = {'host': self.host, + 'port': self.port, + 'dpid': self.dpid, + 'flows': []} + + flows = proxy.get_flows(address, int(self.dpid)) + for flow in flows: + actions = self._to_client_actions(flow.pop('actions')) + rules = self._to_client_rules(flow.pop('match')) + stats = self._to_client_stats(flow) + res['flows'].append({'stats': stats, + 'rules': rules, + 'actions': actions}) + return self.json_response(res) + + def _to_client_actions(self, actions): + # TODO:XXX + return actions + + def _to_client_rules(self, rules): + for name, val in rules.items(): + # hide default values for GUI + if name in ['in_port', 'dl_type', 'nw_proto', 'tp_src', 'tp_dst', + 'dl_vlan', 'dl_vlan_pcp']: + if val == 0: + del rules[name] + + if name in ['nw_dst', 'nw_src']: + if val == '0.0.0.0': + del rules[name] + + if name in ['dl_dst', 'dl_src']: + if val == '00:00:00:00:00:00': + del rules[name] + return rules + + def _to_client_stats(self, stats): + for name, val in stats.items(): + # hide default values for GUI + if name in ['hard_timeout', 'idle_timeout', + 'cookie']: + if val == 0: + del stats[name] + return stats diff --git a/ryu/gui/views/topology.py b/ryu/gui/views/topology.py new file mode 100644 index 00000000..c403737a --- /dev/null +++ b/ryu/gui/views/topology.py @@ -0,0 +1,26 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from flask import render_template, request +import view_base + + +class IndexView(view_base.ViewBase): + def __init__(self): + super(IndexView, self).__init__() + + def run(self): + host, port = request.host.split(':') + return render_template('topology.html', host=host, port=port) diff --git a/ryu/gui/views/view_base.py b/ryu/gui/views/view_base.py new file mode 100644 index 00000000..0d5aa14b --- /dev/null +++ b/ryu/gui/views/view_base.py @@ -0,0 +1,37 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from flask import abort, make_response, jsonify + + +LOG = logging.getLogger('ryu_gui') + + +class ViewBase(object): + def __init__(self, *args, **kwargs): + pass + + def run(self): + LOG.error('run() is not defined.') + abort(500) + + def json_response(self, data): + return jsonify(**data) + + def null_response(self): + res = make_response() + res.headers['Content-type'] = 'text/plain' + return res diff --git a/ryu/gui/views/websocket.py b/ryu/gui/views/websocket.py new file mode 100644 index 00000000..804c5436 --- /dev/null +++ b/ryu/gui/views/websocket.py @@ -0,0 +1,174 @@ +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import json + +import view_base +from models.topology import TopologyWatcher + +LOG = logging.getLogger('ryu.gui') + + +class WebsocketView(view_base.ViewBase): + def __init__(self, ws): + super(WebsocketView, self).__init__() + self.ws = ws + self.address = None + self.watcher = None + + def run(self): + while True: + msg = self.ws.receive() + if msg is not None: + try: + msg = json.loads(msg) + except: + LOG.debug("json parse error: %s", msg) + continue + self._recv_message(msg) + else: + if self.watcher: + self.watcher.stop() + break + + self.ws.close() + LOG.info('Websocket: closed.') + return self.null_response() + + def _send_message(self, msg_name, address, body=None): + message = {} + message['message'] = msg_name + message['host'], message['port'] = address.split(':') + message['body'] = body + LOG.debug("Websocket: send msg.\n%s", json.dumps(message, indent=2)) + self.ws.send(json.dumps(message)) + + def _recv_message(self, msg): + LOG.debug('Websocket: recv msg.\n%s', json.dumps(msg, indent=2)) + + message = msg.get('message') + body = msg.get('body') + + if message == 'rest_update': + self._watcher_start(body) + elif message == 'watching_switch_update': + self._watching_switch_update(body) + else: + return + + def _watcher_start(self, body): + address = '%s:%s' % (body['host'], body['port']) + self.address = address + if self.watcher: + self.watcher.stop() + + self.watcher = TopologyWatcher( + update_handler=self.update_handler, + rest_error_handler=self.rest_error_handler) + self.watcher.start(address) + + def _watching_switch_update(self, body): + pass + + # called by watcher when topology update + def update_handler(self, address, delta): + if self.address != address: + # user be watching the another controller already + return + + LOG.debug(delta) + self._send_message('rest_connected', address) + self._send_del_links(address, delta.deleted) + self._send_del_ports(address, delta.deleted) + self._send_del_switches(address, delta.deleted) + self._send_add_switches(address, delta.added) + self._send_add_ports(address, delta.added) + self._send_add_links(address, delta.added) + + def _send_add_switches(self, address, topo): + body = self._build_switches_message(topo) + if body: + self._send_message('add_switches', address, body) + + def _send_del_switches(self, address, topo): + body = self._build_switches_message(topo) + if body: + self._send_message('del_switches', address, body) + + def _build_switches_message(self, topo): + body = [] + for s in topo['switches']: + S = {'dpid': s.dpid, 'ports': {}} + for p in s.ports: + S['ports'][p.port_no] = p.to_dict() + + body.append(S) + + return body + + def _send_add_ports(self, address, topo): + body = self._build_ports_message(topo) + if body: + self._send_message('add_ports', address, body) + + def _send_del_ports(self, address, topo): + body = self._build_ports_message(topo) + if body: + self._send_message('del_ports', address, body) + + def _build_ports_message(self, topo): + # send only except new added switches + ports = set(topo['ports']) + for s in topo['switches']: + ports -= set(s.ports) + + body = [] + for p in ports: + body.append(p.to_dict()) + + return body + + def _send_add_links(self, address, topo): + body = self._build_links_message(topo) + if body: + self._send_message('add_links', address, body) + + def _send_del_links(self, address, topo): + body = self._build_links_message(topo) + if body: + self._send_message('del_links', address, body) + + def _build_links_message(self, topo): + body = [] + for link in topo['links']: + # handle link as undirected + if link.src.dpid > link.dst.dpid: + continue + + p1 = link.src.to_dict() + p2 = link.dst.to_dict() + L = {'p1': p1.copy(), 'p2': p2.copy()} + L['p1']['peer'] = p2.copy() + L['p2']['peer'] = p1.copy() + + body.append(L) + + return body + + # called by watcher when rest api error + def rest_error_handler(self, address, e): + LOG.debug('REST API Error: %s', e) + self._send_message('rest_disconnected', address) diff --git a/ryu/topology/switches.py b/ryu/topology/switches.py index 70226f19..b7dd3235 100644 --- a/ryu/topology/switches.py +++ b/ryu/topology/switches.py @@ -674,8 +674,7 @@ def _drop_packet(msg): if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION: dp.send_packet_out(buffer_id, msg.in_port, []) else: - LOG.error('cannot drop_packet. unsupported version. %x', - dp.ofproto.OFP_VERSION) + dp.send_packet_out(buffer_id, msg.match['in_port'], []) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): From 5aa7aed3b2803b86d3475b863e6c26d21f9b6a19 Mon Sep 17 00:00:00 2001 From: Davide Sanvito Date: Tue, 7 Oct 2014 11:01:22 -0700 Subject: [PATCH 5/9] GUI update --- ryu/gui/static/css/ryu.css | 4 ++-- ryu/gui/static/js/ryu.js | 42 ++++++++++++++++++++++++++++++--- ryu/gui/templates/topology.html | 1 + ryu/gui/views/websocket.py | 36 +++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/ryu/gui/static/css/ryu.css b/ryu/gui/static/css/ryu.css index cd0ada68..209f64ee 100644 --- a/ryu/gui/static/css/ryu.css +++ b/ryu/gui/static/css/ryu.css @@ -254,8 +254,8 @@ body a { #topology { top: 60px; left: 230px; - width: 600px; - height: 450px; + width: 800px; + height: 600px; } #topology .rest-status { position: relative; diff --git a/ryu/gui/static/js/ryu.js b/ryu/gui/static/js/ryu.js index 8ed82ac7..4cb53278 100644 --- a/ryu/gui/static/js/ryu.js +++ b/ryu/gui/static/js/ryu.js @@ -18,7 +18,7 @@ var conf = { URL_GET_FLOWS: 'stats/flow', LABEL_FONT_SIZE: 10, - EVENT_LOOP_INTERVAL: 500, + EVENT_LOOP_INTERVAL: 10, REPLACE_FLOW_INTERVAL: 5000, CONNECTION_REPAINT_INTERVAL: 500, IMG_SW: {"x": 50, "y": 30, "img": "static/img/switch.png"}, @@ -237,6 +237,22 @@ var topo = { y: $("#topology").height() / 2} var radii = {x: $("#topology").width() / 4, y: $("#topology").height() / 4} + + var max_w,max_h; + max_w=$("#topology").width(); + max_h=$("#topology").height() + + for (var i in _DATA.switches) { + var sw = _DATA.switches[i]; + var p = {}; + p['x'] = _DATA.switches[sw.dpid].pos['x']; + p['y'] = _DATA.switches[sw.dpid].pos['y']; + if (p['x']>max_w) max_w=p['x']+60; + if (p['y']>max_h) max_h=p['y']+10; + } + $("#topology").width(max_w); + $("#topology").height(max_h); + var cnt = 0; var len = 0; for (var i in _DATA.switches) len ++; @@ -244,7 +260,14 @@ var topo = { for (var i in _DATA.switches) { var sw = _DATA.switches[i]; var position = utils._calTh(cnt, len, base, radii); - utils.addSwitch(sw, position) + var p = {}; + p['x'] = _DATA.switches[sw.dpid].pos['x']; + p['y'] = $("#topology").height() -_DATA.switches[sw.dpid].pos['y']; + if (p['x']==-1 && _DATA.switches[sw.dpid].pos['y']==-1){ + utils.addSwitch(sw, position) + } else{ + utils.addSwitch(sw, p) + } cnt ++; } }, @@ -354,7 +377,7 @@ var utils = { _moveNode: function(id, position) { // move position - $("#" + id).animate({left: position.x, top: position.y}, 300, 'swing'); + $("#" + id).animate({left: position.x, top: position.y}, 100, 'swing'); }, _delNode: function(id) { @@ -463,6 +486,11 @@ var utils = { } peer_port_span.innerHTML = peer_port; utils._repainteRows('link-list-table'); + + // wireshark + var wireshark_td = tr.insertCell(-1); + wireshark_td.className = 'wireshark'; + wireshark_td.innerHTML = ""; }, refreshLinkList: function() { @@ -705,6 +733,14 @@ var websocket = { websocket._sendMessage(msg); }, + sendOpenWireshark: function(interface){ + msg = {}; + msg.message = "open_wireshark"; + msg.body = {}; + msg.body.interface = interface; + websocket._sendMessage(msg); + }, + //// // recive messages //// diff --git a/ryu/gui/templates/topology.html b/ryu/gui/templates/topology.html index e3ad550f..5d2e0786 100644 --- a/ryu/gui/templates/topology.html +++ b/ryu/gui/templates/topology.html @@ -43,6 +43,7 @@ no name peer + diff --git a/ryu/gui/views/websocket.py b/ryu/gui/views/websocket.py index 804c5436..f6829233 100644 --- a/ryu/gui/views/websocket.py +++ b/ryu/gui/views/websocket.py @@ -18,6 +18,7 @@ import view_base from models.topology import TopologyWatcher +from xml.dom import minidom LOG = logging.getLogger('ryu.gui') @@ -66,6 +67,8 @@ def _recv_message(self, msg): self._watcher_start(body) elif message == 'watching_switch_update': self._watching_switch_update(body) + elif message == 'open_wireshark': + self._open_wireshark(body) else: return @@ -83,6 +86,11 @@ def _watcher_start(self, body): def _watching_switch_update(self, body): pass + def _open_wireshark(self, body): + interface = '%s' % (body['interface']) + import os + os.system("sudo wireshark -i "+interface+" -k &") + # called by watcher when topology update def update_handler(self, address, delta): if self.address != address: @@ -110,13 +118,35 @@ def _send_del_switches(self, address, topo): def _build_switches_message(self, topo): body = [] + + # Read switch positions from network.xml + from xml.dom import minidom + position={} + xmldoc = None + try: + xmldoc = minidom.parse('network.xml') + itemlist = xmldoc.getElementsByTagName('node') + for s in itemlist: + n = s.attributes['id'].value + # Remove the N char at the beginning + n = int(n[1:]) + x = s.getElementsByTagName('x')[0].firstChild.data + y = s.getElementsByTagName('y')[0].firstChild.data + position[n]=(int(float(x)), int(float(y))) + except Exception, detail: + print("Failure to parse network.xml: %s" % str(detail)) + for s in topo['switches']: + position[int(float(s.dpid))]=(-1,-1) + + # Nodes creation for s in topo['switches']: - S = {'dpid': s.dpid, 'ports': {}} + pos = {} + pos['x']=position[int(float(s.dpid))][0] + pos['y']=position[int(float(s.dpid))][1] + S = {'dpid': s.dpid, 'ports': {}, 'pos': pos} for p in s.ports: S['ports'][p.port_no] = p.to_dict() - body.append(S) - return body def _send_add_ports(self, address, topo): From eff56fe68b79c4b72bb4b0fcc55bab5332a930d3 Mon Sep 17 00:00:00 2001 From: lupo89 <19lupo89@gmail.com> Date: Fri, 10 Oct 2014 10:40:57 -0700 Subject: [PATCH 6/9] forwarding consistency comparison app --- .../forwarding_consistency_1_to_many_ctrl.py | 207 ++++++++++++++++++ ryu/gui/static/img/wireshark-logo.png | Bin 0 -> 1648 bytes 2 files changed, 207 insertions(+) create mode 100644 ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py create mode 100644 ryu/gui/static/img/wireshark-logo.png diff --git a/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py b/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py new file mode 100644 index 00000000..bdffaa3d --- /dev/null +++ b/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py @@ -0,0 +1,207 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import struct +import socket +import random + +from ryu.lib.packet import ethernet +from ryu.lib.packet import ipv4 +from ryu.lib.packet import tcp +from ryu.lib import addrconv +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, \ + HANDSHAKE_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet +from ryu.topology import event + + + +LOG = logging.getLogger('app.openstate.forwarding_consistency_1_to_many_ctrl') + +SWITCH_PORTS = 4 +IPV4 = ipv4.ipv4.__name__ +TCP = tcp.tcp.__name__ + +class OSLoadBalancing(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + LOG.info("OpenState Forwarding Consistency sample app initialized") + LOG.info("Supporting MAX %d ports per switch" % SWITCH_PORTS) + super(OSLoadBalancing, self).__init__(*args, **kwargs) + self.mac_to_port = {} + self.counter = 0 + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def _packet_in_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + in_port = msg.match['in_port'] + ip_dst = None + ip_src = None + tcp_dst = None + tcp_src = None + data = None + + pkt = packet.Packet(msg.data) + + header_list = dict((p.protocol_name, p) + for p in pkt.protocols if type(p) != str) + + out_port = random.randint(2,SWITCH_PORTS) + + if IPV4 in header_list: + ip_dst = self.ip_addr_ntoa(header_list[IPV4].dst) + ip_src = self.ip_addr_ntoa(header_list[IPV4].src) + + if TCP in header_list: + tcp_src = header_list[TCP].src_port + tcp_dst = header_list[TCP].dst_port + + self.add_flow(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + + dest_ip="10.0.0."+str(out_port) + dest_eth="00:00:00:00:00:0"+str(out_port) + dest_tcp=out_port*100 + actions = [ + parser.OFPActionSetField(ipv4_dst=dest_ip), + parser.OFPActionSetField(eth_dst=dest_eth), + parser.OFPActionSetField(tcp_dst=dest_tcp), + parser.OFPActionOutput(out_port, 0)] + + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, + in_port=in_port, actions=actions, data=data) + datapath.send_msg(out) + + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) + def switch_features_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + self.send_features_request(datapath) + + # install table miss - ARP - reverse path rules + self.add_flow_default(datapath) + + + def add_flow_default(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + #table miss + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + # Reverse path flow + for in_port in range(2, SWITCH_PORTS + 1): + src_ip="10.0.0."+str(in_port) + src_eth="00:00:00:00:00:0"+str(in_port) + src_tcp=in_port*100 + # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() + match = parser.OFPMatch(in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=src_ip,eth_src=src_eth,tcp_src=src_tcp) + actions = [parser.OFPActionSetField(ipv4_src="10.0.0.2"), + parser.OFPActionSetField(eth_src="00:00:00:00:00:02"), + parser.OFPActionSetField(tcp_src=80), + parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def add_flow(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): + + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + self.counter+=1 + LOG.info('Installing new forward rule for switch %d (rule # %d)' % (datapath.id, self.counter)) + dest_ip="10.0.0."+str(out_port) + dest_eth="00:00:00:00:00:0"+str(out_port) + dest_tcp=out_port*100 + actions = [ + parser.OFPActionSetField(ipv4_dst=dest_ip), + parser.OFPActionSetField(eth_dst=dest_eth), + parser.OFPActionSetField(tcp_dst=dest_tcp), + parser.OFPActionOutput(out_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_src, ipv4_dst=ip_dst, tcp_src=tcp_src, tcp_dst=tcp_dst) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def send_features_request(self, datapath): + ofp_parser = datapath.ofproto_parser + + req = ofp_parser.OFPFeaturesRequest(datapath) + datapath.send_msg(req) + + def ip_addr_ntoa(self,ip): + return socket.inet_ntoa(addrconv.ipv4.text_to_bin(ip)) + + diff --git a/ryu/gui/static/img/wireshark-logo.png b/ryu/gui/static/img/wireshark-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2e2458fc307fc732572445289caa263b176fc8c3 GIT binary patch literal 1648 zcmV-$29NoPP)eSqwp03_)5ALR$_)TnM_&N~0}nMv z9)q(DLtGqzu_lhcE}6?OoX#tZxehEl0099IGD0SJpD>)x9bkYYfU78x!y}8l7<{T9 zhPEk_#4DD_Cx52~5*G&)8zy#|HKW!vqSZ5@)H0vZ4J$beDmN^a$SIS>C5^o)gRKM% z5DXksoXlM-Z`n= zH>TPwhOi}hp%6Am1Pu`|lE65q+&in^JgwqAt>Pwhm@JRICy>AoMPDLul_G15Hl^4( zsN6fO;6AV9Jgng(YKs8{1}~PzE0xCyASw(dGBcplHl)}iX^H{`2On*cBz2lHpwlIB zlmGz&EReqrF+vF)Coh`JF`m&iqt`g6+9-dgCx56ViMIj<2pCguGojNilfoTigc@at zBz~wdozOC$(lVgZF`m!|7atQtSr0u@1r!@Eo6atp%@8(ai9VTkbXlWYi$!7TRVFPM@L6{J6kgoeQP5p zD-{Wd0#!95YkgC5J4a_1S0He4cC<4$1*$Mqg(z@y_t4ZgwY7Kl^z!!c_4W1h^7Ib~ z1gQv;0xK|Z(g-%Ouy+pe3k?eo508k9ijIkOu#Jm1PLKvEa0@a@)HO?T4)IA&Nli=7 zV93nM&dGJQH`mKE$_FV>bt;HA4J>r^2`eftDJ?6nsI08YsjexkHPtqj1uBrNQ@5@+ zvv+A|EUIj3ZfR|6@96C6?&)>uGuO2SDUb>>(lW7iboC8S>z^=j(&WigrcRqaBiy%U zW}u0d97uthMzCq^tdP*6*>mR3o4;Ve!bOW2mJ~HMINO=VgB2KR#GBeVH}s?~UABD1 z%2lgZu2@sHHZ{3not>%ndWZsTkb>k5Wg9nb-m-P;wuRew>`V*mbxE?(mq#*T*X})g z_km5=y#K&XWD}rnILL75aNCs4AU7O2dJN)*U^ygT96xdLRLAKllg~_r1bFq z!^cmb-@f_s>MJPKeglK=Kaf+@-7<(M1GY5jU~d-)PLn_3X%cs;hATkN7uJw`fuum? zSDm4{QGum35NI0zRyX|fO9iSxTm=gL)~PwEd;I(7q3)zsrwS1hhi5%7a8tFa0|G16 uUr+&MNap-6fdG;~0O$M{gXV91*&hHh=P)Hac6lQJ0000 Date: Fri, 17 Oct 2014 15:02:20 -0700 Subject: [PATCH 7/9] forwarding consistency comparison app m_t_m m_t_1 and start scripts --- .../forwarding_consistency_1_to_many_ctrl.py | 62 ++-- .../forwarding_consistency_many_to_1_ctrl.py | 314 +++++++++++++++++ ...orwarding_consistency_many_to_many_ctrl.py | 327 ++++++++++++++++++ ryu/app/openstate/start_1_to_many.py | 2 +- ryu/app/openstate/start_1_to_many_ctrl.py | 39 +++ ryu/app/openstate/start_many_to_1_ctrl.py | 68 ++++ ryu/app/openstate/start_many_to_many.py | 2 +- ryu/app/openstate/start_many_to_many_ctrl.py | 77 +++++ 8 files changed, 858 insertions(+), 33 deletions(-) create mode 100644 ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py create mode 100644 ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py create mode 100644 ryu/app/openstate/start_1_to_many_ctrl.py create mode 100644 ryu/app/openstate/start_many_to_1_ctrl.py create mode 100644 ryu/app/openstate/start_many_to_many_ctrl.py diff --git a/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py b/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py index bdffaa3d..8070eee4 100644 --- a/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py +++ b/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py @@ -76,23 +76,23 @@ def _packet_in_handler(self, ev): tcp_src = header_list[TCP].src_port tcp_dst = header_list[TCP].dst_port - self.add_flow(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) - - dest_ip="10.0.0."+str(out_port) - dest_eth="00:00:00:00:00:0"+str(out_port) - dest_tcp=out_port*100 - actions = [ - parser.OFPActionSetField(ipv4_dst=dest_ip), - parser.OFPActionSetField(eth_dst=dest_eth), - parser.OFPActionSetField(tcp_dst=dest_tcp), - parser.OFPActionOutput(out_port, 0)] - - if msg.buffer_id == ofproto.OFP_NO_BUFFER: - data = msg.data - - out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, - in_port=in_port, actions=actions, data=data) - datapath.send_msg(out) + self.add_flow(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + + dest_ip="10.0.0."+str(out_port) + dest_eth="00:00:00:00:00:0"+str(out_port) + dest_tcp=out_port*100 + actions = [ + parser.OFPActionSetField(ipv4_dst=dest_ip), + parser.OFPActionSetField(eth_dst=dest_eth), + parser.OFPActionSetField(tcp_dst=dest_tcp), + parser.OFPActionOutput(out_port, 0)] + + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, + in_port=in_port, actions=actions, data=data) + datapath.send_msg(out) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) @@ -150,20 +150,20 @@ def add_flow_default(self, datapath): flags=0, match=match, instructions=inst) datapath.send_msg(mod) - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) def add_flow(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): diff --git a/ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py b/ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py new file mode 100644 index 00000000..8b1b34e5 --- /dev/null +++ b/ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py @@ -0,0 +1,314 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import struct +import socket +import random + +from ryu.lib.packet import ethernet +from ryu.lib.packet import ipv4 +from ryu.lib.packet import tcp +from ryu.lib import addrconv +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, \ + HANDSHAKE_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet +from ryu.topology import event + + + +LOG = logging.getLogger('app.openstate.forwarding_consistency_many_to_1_ctrl') + +SWITCH_PORTS = 4 +IPV4 = ipv4.ipv4.__name__ +TCP = tcp.tcp.__name__ + +class OSLoadBalancing(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + LOG.info("OpenState Forwarding Consistency sample app initialized") + LOG.info("Supporting MAX %d ports per switch" % SWITCH_PORTS) + super(OSLoadBalancing, self).__init__(*args, **kwargs) + self.mac_to_port = {} + self.counter = 0 + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def _packet_in_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + in_port = msg.match['in_port'] + ip_dst = None + ip_src = None + tcp_dst = None + tcp_src = None + data = None + + pkt = packet.Packet(msg.data) + + header_list = dict((p.protocol_name, p) + for p in pkt.protocols if type(p) != str) + + out_port = random.randint(2,SWITCH_PORTS) + + if IPV4 in header_list: + ip_dst = self.ip_addr_ntoa(header_list[IPV4].dst) + ip_src = self.ip_addr_ntoa(header_list[IPV4].src) + + if TCP in header_list: + tcp_src = header_list[TCP].src_port + tcp_dst = header_list[TCP].dst_port + + if datapath.id==1: + self.add_flow_1_to_many(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + + actions = [parser.OFPActionOutput(out_port, 0)] + + + elif datapath.id==5: + out_port = 4 + self.add_flow_many_to_1(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + actions = [parser.OFPActionOutput(out_port, 0)] + + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, + in_port=in_port, actions=actions, data=data) + datapath.send_msg(out) + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) + def switch_features_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + options ={1: self.one_to_many_switch, + 2: self.middleswitch, + 3: self.middleswitch, + 4: self.middleswitch, + 5: self.many_to_one_switch + } + + options[datapath.id](datapath) + + def one_to_many_switch(self, datapath): + self.send_features_request(datapath) + self.add_flow_default_1_to_many(datapath) + + def middleswitch(self, datapath): + self.send_features_request(datapath) + self.add_middleswitch_default(datapath) + + def many_to_one_switch(self, datapath): + self.send_features_request(datapath) + self.add_flow_default_many_to_1(datapath) + + def add_flow_default_1_to_many(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + #table miss + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + # Reverse path flow + for in_port in range(2, SWITCH_PORTS + 1): + # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() + match = parser.OFPMatch(in_port=in_port, eth_type=0x800) + actions = [parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_1_to_many(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): + + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + self.counter+=1 + LOG.info('Installing new forward rule for switch %d (rule # %d)' % (datapath.id, self.counter)) + actions = [parser.OFPActionOutput(out_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_src, ipv4_dst=ip_dst, tcp_src=tcp_src, tcp_dst=tcp_dst) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def add_flow_many_to_1(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): + + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + self.counter+=1 + LOG.info('Installing new forward rule for switch %d (rule # %d)' % (datapath.id, self.counter)) + + # reverse path rule + actions = [parser.OFPActionOutput(in_port, 0)] + match = parser.OFPMatch( + in_port=out_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_dst, ipv4_dst=ip_src, tcp_src=tcp_dst, tcp_dst=tcp_src) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # forward path rule + actions = [parser.OFPActionOutput(out_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_src, ipv4_dst=ip_dst, tcp_src=tcp_src, tcp_dst=tcp_dst) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_default_many_to_1(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + #table miss + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_middleswitch_default(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + match = parser.OFPMatch(in_port=1) + actions = [ + parser.OFPActionOutput(2,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + match = parser.OFPMatch(in_port=2) + actions = [ + parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def send_features_request(self, datapath): + ofp_parser = datapath.ofproto_parser + + req = ofp_parser.OFPFeaturesRequest(datapath) + datapath.send_msg(req) + + def ip_addr_ntoa(self,ip): + return socket.inet_ntoa(addrconv.ipv4.text_to_bin(ip)) + \ No newline at end of file diff --git a/ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py b/ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py new file mode 100644 index 00000000..505bd786 --- /dev/null +++ b/ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py @@ -0,0 +1,327 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import struct +import socket +import random + +from ryu.lib.packet import ethernet +from ryu.lib.packet import ipv4 +from ryu.lib.packet import tcp +from ryu.lib import addrconv +from ryu.base import app_manager +from ryu.controller import ofp_event +from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, \ + HANDSHAKE_DISPATCHER +from ryu.controller.handler import set_ev_cls +from ryu.ofproto import ofproto_v1_3 +from ryu.lib.packet import packet +from ryu.topology import event + + + +LOG = logging.getLogger('app.openstate.forwarding_consistency_many_to_many_ctrl') + +SWITCH_PORTS = 4 +IPV4 = ipv4.ipv4.__name__ +TCP = tcp.tcp.__name__ + +class OSLoadBalancing(app_manager.RyuApp): + OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] + + def __init__(self, *args, **kwargs): + LOG.info("OpenState Forwarding Consistency sample app initialized") + LOG.info("Supporting MAX %d ports per switch" % SWITCH_PORTS) + super(OSLoadBalancing, self).__init__(*args, **kwargs) + self.mac_to_port = {} + self.counter = 0 + + @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) + def _packet_in_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + in_port = msg.match['in_port'] + ip_dst = None + ip_src = None + tcp_dst = None + tcp_src = None + data = None + + pkt = packet.Packet(msg.data) + + header_list = dict((p.protocol_name, p) + for p in pkt.protocols if type(p) != str) + + out_port= random.randint(2,SWITCH_PORTS) + out_port_m_to_m = random.randint(4,6) + + if IPV4 in header_list: + ip_dst = self.ip_addr_ntoa(header_list[IPV4].dst) + ip_src = self.ip_addr_ntoa(header_list[IPV4].src) + + if TCP in header_list: + tcp_src = header_list[TCP].src_port + tcp_dst = header_list[TCP].dst_port + + if datapath.id==1: + self.add_flow_1_to_many(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + actions = [parser.OFPActionOutput(out_port, 0)] + + elif datapath.id==4: + #the rule entries are the same as many to 1 case + self.add_flow_many_to_1(datapath, in_port, out_port_m_to_m, ip_src, ip_dst, tcp_src, tcp_dst) + actions = [parser.OFPActionOutput(out_port_m_to_m, 0)] + + elif datapath.id==7: + out_port = 4 + self.add_flow_many_to_1(datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst) + actions = [parser.OFPActionOutput(out_port, 0)] + + if msg.buffer_id == ofproto.OFP_NO_BUFFER: + data = msg.data + + out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, + in_port=in_port, actions=actions, data=data) + datapath.send_msg(out) + + @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) + def switch_features_handler(self, ev): + msg = ev.msg + datapath = msg.datapath + ofproto = datapath.ofproto + + options ={1: self.one_to_many_switch, + 2: self.middleswitch, + 3: self.middleswitch, + 4: self.many_to_many_switch, + 5: self.middleswitch, + 6: self.middleswitch, + 7: self.many_to_one_switch + } + + + options[datapath.id](datapath) + + def one_to_many_switch(self, datapath): + self.send_features_request(datapath) + self.add_flow_default_1_to_many(datapath) + + def middleswitch(self, datapath): + self.send_features_request(datapath) + self.add_middleswitch_default(datapath) + + def many_to_many_switch(self, datapath): + self.send_features_request(datapath) + #we use the same default many to one rules + self.add_flow_default_many_to_1(datapath) + + def many_to_one_switch(self, datapath): + self.send_features_request(datapath) + self.add_flow_default_many_to_1(datapath) + + def add_flow_default_1_to_many(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + #table miss + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + # Reverse path flow + for in_port in range(2, SWITCH_PORTS + 1): + # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() + match = parser.OFPMatch(in_port=in_port, eth_type=0x800) + actions = [parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_1_to_many(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): + + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + self.counter+=1 + LOG.info('Installing new forward rule for switch %d (rule # %d)' % (datapath.id, self.counter)) + actions = [parser.OFPActionOutput(out_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_src, ipv4_dst=ip_dst, tcp_src=tcp_src, tcp_dst=tcp_dst) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def add_flow_many_to_1(self, datapath, in_port, out_port, ip_src, ip_dst, tcp_src, tcp_dst): + + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + self.counter+=1 + LOG.info('Installing new forward rule for switch %d (rule # %d)' % (datapath.id, self.counter)) + + # reverse path rule + actions = [parser.OFPActionOutput(in_port, 0)] + match = parser.OFPMatch( + in_port=out_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_dst, ipv4_dst=ip_src, tcp_src=tcp_dst, tcp_dst=tcp_src) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + # forward path rule + actions = [parser.OFPActionOutput(out_port, 0)] + match = parser.OFPMatch( + in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=ip_src, ipv4_dst=ip_dst, tcp_src=tcp_src, tcp_dst=tcp_dst) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def add_flow_default_many_to_1(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + #table miss + actions = [parser.OFPActionOutput( + ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] + match = parser.OFPMatch() + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=0, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + + datapath.send_msg(mod) + + # ARP packets flooding + match = parser.OFPMatch(eth_type=0x0806) + actions = [ + parser.OFPActionOutput(ofproto.OFPP_FLOOD)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + + def add_middleswitch_default(self, datapath): + ofproto = datapath.ofproto + parser = datapath.ofproto_parser + LOG.info("Configuring default flow entries for switch %d" % datapath.id) + + match = parser.OFPMatch(in_port=1) + actions = [ + parser.OFPActionOutput(2,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + match = parser.OFPMatch(in_port=2) + actions = [ + parser.OFPActionOutput(1,0)] + inst = [parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, + priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, + out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) + + def send_features_request(self, datapath): + ofp_parser = datapath.ofproto_parser + + req = ofp_parser.OFPFeaturesRequest(datapath) + datapath.send_msg(req) + + def ip_addr_ntoa(self,ip): + return socket.inet_ntoa(addrconv.ipv4.text_to_bin(ip)) + \ No newline at end of file diff --git a/ryu/app/openstate/start_1_to_many.py b/ryu/app/openstate/start_1_to_many.py index f4752499..8b3d5b3d 100644 --- a/ryu/app/openstate/start_1_to_many.py +++ b/ryu/app/openstate/start_1_to_many.py @@ -25,7 +25,7 @@ print("In order to test new path selection, close and reopen netcat") print("\nTo exit type \"ctrl+D\" or exit") print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634) +net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634,autoStaticArp=True) net.start() h1,h2,h3,h4 = net.hosts[0], net.hosts[1], net.hosts[2], net.hosts[3] for i in range(3): diff --git a/ryu/app/openstate/start_1_to_many_ctrl.py b/ryu/app/openstate/start_1_to_many_ctrl.py new file mode 100644 index 00000000..711f4c3a --- /dev/null +++ b/ryu/app/openstate/start_1_to_many_ctrl.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +from mininet.net import Mininet +from mininet.topo import Topo,SingleSwitchTopo +from mininet.cli import CLI +from mininet.node import UserSwitch,RemoteController +from mininet.term import makeTerm +import os, time +######Starting controller + + +os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py'&") + + + +######Starting mininet + +mytopo=SingleSwitchTopo(4) +time.sleep(1) +print("\n********************************** HELP *********************************************") +print("\nType \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") +print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 300\" in h3's xterm") +print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 400\" in h4's xterm") +print("Type \"nc 10.0.0.2 80\" in all h1's xterms\n") +print("In order to test new path selection, close and reopen netcat") +print("\nTo exit type \"ctrl+D\" or exit") +print("*************************************************************************************") +net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634,autoStaticArp=True) +net.start() +h1,h2,h3,h4 = net.hosts[0], net.hosts[1], net.hosts[2], net.hosts[3] +for i in range(3): + makeTerm(h1) +makeTerm(h2) +makeTerm(h3) +makeTerm(h4) +CLI(net) +net.stop() +os.system("sudo mn -c") +os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_1_ctrl.py b/ryu/app/openstate/start_many_to_1_ctrl.py new file mode 100644 index 00000000..2e10b7ac --- /dev/null +++ b/ryu/app/openstate/start_many_to_1_ctrl.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.cli import CLI +from mininet.node import UserSwitch,RemoteController +from mininet.term import makeTerm +import os, time + +class MyTopo( Topo ): + "Simple topology example." + + def __init__( self): + "Create custom topo." + + # Add default members to class. + Topo.__init__(self) + + # Add nodes + + Host1=self.addHost('h1', ip='10.0.0.1/24') + Host2=self.addHost('h2', ip='10.0.0.2/24') + switch1=self.addSwitch('s1') + switch2=self.addSwitch('s2') + switch3=self.addSwitch('s3') + switch4=self.addSwitch('s4') + switch5=self.addSwitch('s5') + + # Add edges + self.addLink( Host1, switch1, 1, 1) + self.addLink( switch1, switch2, 2, 1) + self.addLink( switch1, switch3, 3, 1) + self.addLink( switch1, switch4, 4, 1) + self.addLink( switch2, switch5, 2, 1) + self.addLink( switch3, switch5, 2, 2) + self.addLink( switch4, switch5, 2, 3) + self.addLink( switch5, Host2, 4, 1) + +######Starting controller + + +os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py'&") + + + +######Starting mininet +topos = { 'mytopo': ( lambda: MyTopo() ) } +mytopo=MyTopo() +time.sleep(1) +print("\n********************************** HELP *********************************************") +print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") +print("Type \"nc 10.0.0.2 200\" in h1's xterm") +print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed\n" + "In order to test new path selection, close and reopen netcat") +print("\nTo exit type \"ctrl+D\" or exit") +print("*************************************************************************************") +net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) +net.start() +os.system("xterm -e 'tcpdump -i s2-eth1'&") +os.system("xterm -e 'tcpdump -i s3-eth1'&") +os.system("xterm -e 'tcpdump -i s4-eth1'&") +h1,h2 = net.hosts[0], net.hosts[1] +makeTerm(h1) +makeTerm(h2) +CLI(net) +net.stop() +os.system("sudo mn -c") +os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_many.py b/ryu/app/openstate/start_many_to_many.py index fe265ecc..cda17b7b 100644 --- a/ryu/app/openstate/start_many_to_many.py +++ b/ryu/app/openstate/start_many_to_many.py @@ -60,7 +60,7 @@ def __init__( self): "In order to test new path selection, close and reopen netcat") print("\nTo exit type \"ctrl+D\" or exit") print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True) +net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) net.start() os.system("xterm -e 'tcpdump -i s4-eth1'&") os.system("xterm -e 'tcpdump -i s4-eth2'&") diff --git a/ryu/app/openstate/start_many_to_many_ctrl.py b/ryu/app/openstate/start_many_to_many_ctrl.py new file mode 100644 index 00000000..b5b44dd4 --- /dev/null +++ b/ryu/app/openstate/start_many_to_many_ctrl.py @@ -0,0 +1,77 @@ +#!/usr/bin/python + +from mininet.net import Mininet +from mininet.topo import Topo +from mininet.cli import CLI +from mininet.node import UserSwitch,RemoteController +from mininet.term import makeTerm +import os, time + +class MyTopo( Topo ): + "Simple topology example." + + def __init__( self): + "Create custom topo." + + # Add default members to class. + Topo.__init__(self) + + # Add nodes + + Host1=self.addHost('h1', ip='10.0.0.1/24') + Host2=self.addHost('h2', ip='10.0.0.2/24') + switch1=self.addSwitch('s1') + switch2=self.addSwitch('s2') + switch3=self.addSwitch('s3') + switch4=self.addSwitch('s4') + switch5=self.addSwitch('s5') + switch6=self.addSwitch('s6') + switch7=self.addSwitch('s7') + + # Add edges + self.addLink( Host1, switch1, 1, 1) + self.addLink( switch1, switch2, 2, 1) + self.addLink( switch1, switch3, 4, 1) + self.addLink( switch1, switch4, 3, 2) + self.addLink( switch2, switch4, 2, 1) + self.addLink( switch3, switch4, 2, 3) + self.addLink( switch4, switch5, 4, 1) + self.addLink( switch4, switch7, 5, 2) + self.addLink( switch4, switch6, 6, 1) + self.addLink( switch5, switch7, 2, 1) + self.addLink( switch6, switch7, 2, 3) + self.addLink( switch7, Host2, 4, 1) + +######Starting controller + + +os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py'&") + + + +######Starting mininet +topos = { 'mytopo': ( lambda: MyTopo() ) } +mytopo=MyTopo() +time.sleep(1) +print("\n********************************** HELP *********************************************") +print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") +print("Type \"nc 10.0.0.2 200\" in h1's xterm") +print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed\n" + "In order to test new path selection, close and reopen netcat") +print("\nTo exit type \"ctrl+D\" or exit") +print("*************************************************************************************") +net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) +net.start() +os.system("xterm -e 'tcpdump -i s4-eth1'&") +os.system("xterm -e 'tcpdump -i s4-eth2'&") +os.system("xterm -e 'tcpdump -i s4-eth3'&") +os.system("xterm -e 'tcpdump -i s4-eth4'&") +os.system("xterm -e 'tcpdump -i s4-eth5'&") +os.system("xterm -e 'tcpdump -i s4-eth6'&") +h1,h2 = net.hosts[0], net.hosts[1] +makeTerm(h1) +makeTerm(h2) +CLI(net) +net.stop() +os.system("sudo mn -c") +os.system("kill -9 $(pidof -x ryu-manager)") From db0320078011172194327bb17bbd587e86b01662 Mon Sep 17 00:00:00 2001 From: Davide Sanvito Date: Mon, 20 Oct 2014 14:44:46 -0700 Subject: [PATCH 8/9] many-to-many Ryu app with BW_FLAG --- ryu/app/openstate/echo_server.py | 58 --- ryu/app/openstate/forw_cons_m_to_m_topo.py | 39 -- ryu/app/openstate/forw_cons_topo.py | 33 -- .../forwarding_consistency_1_to_many.py | 233 ----------- .../forwarding_consistency_1_to_many_ctrl.py | 207 ---------- ...ing_consistency_1_to_many_with_2_tables.py | 250 ------------ ...stency_1_to_many_with_multiple_setState.py | 243 ----------- .../forwarding_consistency_many_to_1.py | 353 ---------------- ...rding_consistency_many_to_1_alternative.py | 382 ------------------ .../forwarding_consistency_many_to_1_ctrl.py | 314 -------------- ...forwarding_consistency_many_to_many_BW.py} | 61 +-- ...orwarding_consistency_many_to_many_ctrl.py | 327 --------------- ryu/app/openstate/link_protection.py | 164 -------- ryu/app/openstate/maclearning.py | 157 ------- ryu/app/openstate/portknock.py | 160 -------- ryu/app/openstate/start_1_to_many.py | 39 -- ryu/app/openstate/start_1_to_many_ctrl.py | 39 -- ryu/app/openstate/start_link_protection.py | 37 -- ryu/app/openstate/start_many_to_1.py | 68 ---- .../openstate/start_many_to_1_alternative.py | 68 ---- ryu/app/openstate/start_many_to_1_ctrl.py | 68 ---- ...ny_to_many.py => start_many_to_many_BW.py} | 2 +- ryu/app/openstate/start_many_to_many_ctrl.py | 77 ---- ryu/app/openstate/test_FFSM.py | 219 ---------- ryu/app/openstate/test_port_knocking.sh | 15 - 25 files changed, 7 insertions(+), 3606 deletions(-) delete mode 100644 ryu/app/openstate/echo_server.py delete mode 100644 ryu/app/openstate/forw_cons_m_to_m_topo.py delete mode 100644 ryu/app/openstate/forw_cons_topo.py delete mode 100644 ryu/app/openstate/forwarding_consistency_1_to_many.py delete mode 100644 ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py delete mode 100644 ryu/app/openstate/forwarding_consistency_1_to_many_with_2_tables.py delete mode 100644 ryu/app/openstate/forwarding_consistency_1_to_many_with_multiple_setState.py delete mode 100644 ryu/app/openstate/forwarding_consistency_many_to_1.py delete mode 100644 ryu/app/openstate/forwarding_consistency_many_to_1_alternative.py delete mode 100644 ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py rename ryu/app/openstate/{forwarding_consistency_many_to_many.py => forwarding_consistency_many_to_many_BW.py} (87%) delete mode 100644 ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py delete mode 100644 ryu/app/openstate/link_protection.py delete mode 100644 ryu/app/openstate/maclearning.py delete mode 100644 ryu/app/openstate/portknock.py delete mode 100644 ryu/app/openstate/start_1_to_many.py delete mode 100644 ryu/app/openstate/start_1_to_many_ctrl.py delete mode 100644 ryu/app/openstate/start_link_protection.py delete mode 100644 ryu/app/openstate/start_many_to_1.py delete mode 100644 ryu/app/openstate/start_many_to_1_alternative.py delete mode 100644 ryu/app/openstate/start_many_to_1_ctrl.py rename ryu/app/openstate/{start_many_to_many.py => start_many_to_many_BW.py} (98%) delete mode 100644 ryu/app/openstate/start_many_to_many_ctrl.py delete mode 100644 ryu/app/openstate/test_FFSM.py delete mode 100755 ryu/app/openstate/test_port_knocking.sh diff --git a/ryu/app/openstate/echo_server.py b/ryu/app/openstate/echo_server.py deleted file mode 100644 index af098341..00000000 --- a/ryu/app/openstate/echo_server.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python - -""" -An echo server that uses select to handle multiple clients at a time. -Command-line parameter: port=listening port -""" - -import select -import socket -import sys - - -if len(sys.argv)!=2: - print("You need to specify a listening port!") - sys.exit() - -host = '' -port = int(sys.argv[1]) -backlog = 5 # maximum number of queued connections -size = 1024 # buffer size -server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -server.bind((host,port)) -server.listen(backlog) -input = [server,sys.stdin] -running = 1 -print("Press any key to stop the server...") -while running: - # The Python select module allows an application to wait for input from multiple sockets at a time. - inputready,outputready,exceptready = select.select(input,[],[]) - - for s in inputready: - - if s == server: - # handle the server socket - client, address = server.accept() - print("New client at "+address[0]+":"+str(address[1])) - input.append(client) - - elif s == sys.stdin: - # handle standard input - junk = sys.stdin.readline() - running = 0 - - else: - # handle all other sockets - data = "[from h"+sys.argv[1][0]+"]: " - data = data+s.recv(size) - if data: - try: - s.send(data) - except socket.error, e: - s.close() - input.remove(s) - break - else: - s.close() - input.remove(s) -server.close() diff --git a/ryu/app/openstate/forw_cons_m_to_m_topo.py b/ryu/app/openstate/forw_cons_m_to_m_topo.py deleted file mode 100644 index 0c60eeda..00000000 --- a/ryu/app/openstate/forw_cons_m_to_m_topo.py +++ /dev/null @@ -1,39 +0,0 @@ -from mininet.topo import Topo - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - switch6=self.addSwitch('s6') - switch7=self.addSwitch('s7') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 4, 1) - self.addLink( switch1, switch4, 3, 2) - self.addLink( switch2, switch4, 2, 1) - self.addLink( switch3, switch4, 2, 3) - self.addLink( switch4, switch5, 4, 1) - self.addLink( switch4, switch7, 5, 2) - self.addLink( switch4, switch6, 6, 1) - self.addLink( switch5, switch7, 2, 1) - self.addLink( switch6, switch7, 2, 3) - self.addLink( switch7, Host2, 4, 1) - - -topos = { 'mytopo': ( lambda: MyTopo() ) } \ No newline at end of file diff --git a/ryu/app/openstate/forw_cons_topo.py b/ryu/app/openstate/forw_cons_topo.py deleted file mode 100644 index 9c29bd33..00000000 --- a/ryu/app/openstate/forw_cons_topo.py +++ /dev/null @@ -1,33 +0,0 @@ -from mininet.topo import Topo - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 3, 1) - self.addLink( switch1, switch4, 4, 1) - self.addLink( switch2, switch5, 2, 1) - self.addLink( switch3, switch5, 2, 2) - self.addLink( switch4, switch5, 2, 3) - self.addLink( switch5, Host2, 4, 1) - - -topos = { 'mytopo': ( lambda: MyTopo() ) } \ No newline at end of file diff --git a/ryu/app/openstate/forwarding_consistency_1_to_many.py b/ryu/app/openstate/forwarding_consistency_1_to_many.py deleted file mode 100644 index 7d1396f1..00000000 --- a/ryu/app/openstate/forwarding_consistency_1_to_many.py +++ /dev/null @@ -1,233 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import struct - -from ryu.base import app_manager -from ryu.controller import ofp_event -from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, \ - HANDSHAKE_DISPATCHER -from ryu.controller.handler import set_ev_cls -from ryu.ofproto import ofproto_v1_3 -from ryu.lib.packet import packet -from ryu.lib.packet import ethernet -from ryu.topology import event - -LOG = logging.getLogger('app.openstate.forwarding_consistency_1_to_many') - -SWITCH_PORTS = 4 - -class OSLoadBalancing(app_manager.RyuApp): - OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] - - def __init__(self, *args, **kwargs): - LOG.info("OpenState Forwarding Consistency sample app initialized") - LOG.info("Supporting MAX %d ports per switch" % SWITCH_PORTS) - super(OSLoadBalancing, self).__init__(*args, **kwargs) - self.mac_to_port = {} - - @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) - def switch_features_handler(self, ev): - msg = ev.msg - datapath = msg.datapath - ofproto = datapath.ofproto - - self.send_features_request(datapath) - self.send_group_mod(datapath) - self.send_table_mod(datapath) - - self.send_key_lookup(datapath) - self.send_key_update(datapath) - - # install table-miss flow entry (if no rule matching, send it to controller) - # self.add_flow(datapath, True) - - # install other flow entries - self.add_flow(datapath, False) - - ''' - STATEFUL TABLE 0 - - Lookup-scope=IPV4_DST,IPV4_SRC,TCP_DST,TCP_SRC - Update-scope=IPV4_DST,IPV4_SRC,TCP_DST,TCP_SRC - - _______ - | |--h2 - h1--| S1 |--h3 - |_______|--h4 - - h1 is the client 10.0.0.1 - h1 connects to an EchoServer 10.0.0.2:80 - h2, h3, h4 are 3 replicas of the server: - h2 is listening at 10.0.0.2:200 - h3 is listening at 10.0.0.3:300 - h4 is listening at 10.0.0.4:400 - - $ ryu-manager ryu/ryu/app/openstate/forwarding_consistency_1_to_many.py - $ sudo mn --topo single,4 --switch user --mac --controller remote - mininet> xterm h1 h1 h1 h2 h3 h4 - h2# python ryu/ryu/app/openstate/echo_server.py 200 - h3# python ryu/ryu/app/openstate/echo_server.py 300 - h4# python ryu/ryu/app/openstate/echo_server.py 400 - - Let's try to connect from h1 to the EchoServer and send some message: - h1# nc 10.0.0.2 80 - If we keep the connection open, the responding EchoServer is always the same. - If we open another connection (from the 2nd terminal of h1) maybe we get connected to another replica. - If we close it and re-connect, maybe we are connected to another replica. - ''' - - def add_flow(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - # Reverse path flow - for in_port in range(2, SWITCH_PORTS + 1): - src_ip="10.0.0."+str(in_port) - src_eth="00:00:00:00:00:0"+str(in_port) - src_tcp=in_port*100 - # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() - match = parser.OFPMatch(in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=src_ip,eth_src=src_eth,tcp_src=src_tcp) - actions = [parser.OFPActionSetField(ipv4_src="10.0.0.2"), - parser.OFPActionSetField(eth_src="00:00:00:00:00:02"), - parser.OFPActionSetField(tcp_src=80), - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # the state of a flow is the selected output port for that flow - for state in range(SWITCH_PORTS): - if state == 0: - # if state=DEFAULT => send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - else: - # state x means output port x+1 - dest_ip="10.0.0."+str(state+1) - dest_eth="00:00:00:00:00:0"+str(state+1) - dest_tcp=(state+1)*100 - actions = [ - parser.OFPActionSetField(ipv4_dst=dest_ip), - parser.OFPActionSetField(eth_dst=dest_eth), - parser.OFPActionSetField(tcp_dst=dest_tcp), - parser.OFPActionOutput(state+1, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_group_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - buckets = [] - # Action Bucket: xterm h1 h1 h1 h2 h3 h4 - h2# python ryu/ryu/app/openstate/echo_server.py 200 - h3# python ryu/ryu/app/openstate/echo_server.py 300 - h4# python ryu/ryu/app/openstate/echo_server.py 400 - - Let's try to connect from h1 to the EchoServer and send some message: - h1# nc 10.0.0.2 80 - If we keep the connection open, the responding EchoServer is always the same. - If we open another connection (from the 2nd terminal of h1) maybe we get connected to another replica. - If we close it and re-connect, maybe we are connected to another replica. - - With respect to basic application, the first table write the action set, while the second apply it. - We want to test the SetState() parametrization. - ''' - - def add_flow(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - # Reverse path flow - for in_port in range(2, SWITCH_PORTS + 1): - src_ip="10.0.0."+str(in_port) - src_eth="00:00:00:00:00:0"+str(in_port) - src_tcp=in_port*100 - # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() - match = parser.OFPMatch(in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=src_ip,eth_src=src_eth,tcp_src=src_tcp) - actions = [parser.OFPActionSetField(ipv4_src="10.0.0.2"), - parser.OFPActionSetField(eth_src="00:00:00:00:00:02"), - parser.OFPActionSetField(tcp_src=80), - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # the state of a flow is the selected output port for that flow - for state in range(SWITCH_PORTS): - if state == 0: - # if state=DEFAULT => send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - else: - # state x means output port x+1 - dest_ip="10.0.0."+str(state+1) - dest_eth="00:00:00:00:00:0"+str(state+1) - dest_tcp=(state+1)*100 - actions = [ - parser.OFPActionSetField(ipv4_dst=dest_ip), - parser.OFPActionSetField(eth_dst=dest_eth), - parser.OFPActionSetField(tcp_dst=dest_tcp), - parser.OFPActionOutput(state+1, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_WRITE_ACTIONS, actions), - parser.OFPInstructionGotoTable(1)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # Table 1 entry - match = parser.OFPMatch() - inst = [] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=1, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_group_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - buckets = [] - # Action Bucket: xterm h1 h1 h1 h2 h3 h4 - h2# python ryu/ryu/app/openstate/echo_server.py 200 - h3# python ryu/ryu/app/openstate/echo_server.py 300 - h4# python ryu/ryu/app/openstate/echo_server.py 400 - - Let's try to connect from h1 to the EchoServer and send some message: - h1# nc 10.0.0.2 80 - If we keep the connection open, the responding EchoServer is always the same. - If we open another connection (from the 2nd terminal of h1) maybe we get connected to another replica. - If we close it and re-connect, maybe we are connected to another replica. - - With respect to basic application, here we want to test if we can put many SetState() actions in the same Instruction. - ''' - - def add_flow(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - # Reverse path flow - for in_port in range(2, SWITCH_PORTS + 1): - src_ip="10.0.0."+str(in_port) - src_eth="00:00:00:00:00:0"+str(in_port) - src_tcp=in_port*100 - # we need to match an IPv4 (0x800) TCP (6) packet to do SetField() - match = parser.OFPMatch(in_port=in_port, eth_type=0x800, ip_proto=6, ipv4_src=src_ip,eth_src=src_eth,tcp_src=src_tcp) - actions = [parser.OFPActionSetField(ipv4_src="10.0.0.2"), - parser.OFPActionSetField(eth_src="00:00:00:00:00:02"), - parser.OFPActionSetField(tcp_src=80), - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # the state of a flow is the selected output port for that flow - for state in range(SWITCH_PORTS): - if state == 0: - # if state=DEFAULT => send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - else: - # state x means output port x+1 - dest_ip="10.0.0."+str(state+1) - dest_eth="00:00:00:00:00:0"+str(state+1) - dest_tcp=(state+1)*100 - actions = [ - parser.OFPActionSetField(ipv4_dst=dest_ip), - parser.OFPActionSetField(eth_dst=dest_eth), - parser.OFPActionSetField(tcp_dst=dest_tcp), - parser.OFPActionOutput(state+1, 0), - parser.OFPActionSetState(state, 0), - parser.OFPActionSetState(0, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800, ip_proto=6) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_group_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - buckets = [] - # Action Bucket: send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800) - else: - # state x means output port x+1 - actions = [ - parser.OFPActionOutput(state+1, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - def add_flow_2(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - match = parser.OFPMatch(in_port=1) - actions = [ - parser.OFPActionOutput(2,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch(in_port=2) - actions = [ - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def add_flow_3(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - # Reverse path flow - for state in range(1,SWITCH_PORTS): - match = parser.OFPMatch(in_port=4, state=state, eth_type=0x800) - actions = [ - parser.OFPActionOutput(state,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # the state of a flow is the selected output port for that flow - for in_port in range(1,SWITCH_PORTS): - # state x means output port x+1 - actions = [ - parser.OFPActionOutput(4, 0), - parser.OFPActionSetState(in_port, 0)] - match = parser.OFPMatch( - in_port=in_port, eth_type=0x800) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_group_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - buckets = [] - # Action Bucket: send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800) - else: - # state x means output port x+1 - actions = [ - parser.OFPActionOutput(state+1, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=1, state=state, eth_type=0x800) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - def add_flow_2(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - match = parser.OFPMatch(in_port=1) - actions = [ - parser.OFPActionOutput(2,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch(in_port=2) - actions = [ - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def add_flow_3(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring flow table for switch %d" % datapath.id) - - if table_miss: - LOG.debug("Installing table miss...") - actions = [parser.OFPActionOutput( - ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - else: - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - # forward path flow - for in_port in range(1, SWITCH_PORTS): - match = parser.OFPMatch(in_port=in_port) - actions = [ - parser.OFPActionOutput(4,0)] - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # the state of a flow is the selected output port for that flow - for state in range(SWITCH_PORTS): - if state == 0: - # if state=DEFAULT => send it to the first group entry in the group table - actions = [ - parser.OFPActionGroup(1)] - match = parser.OFPMatch( - in_port=4, state=state, eth_type=0x800) - else: - # state x means output port x+1 - actions = [ - parser.OFPActionOutput(state, 0), - parser.OFPActionSetState(state, 0)] - match = parser.OFPMatch( - in_port=4, state=state, eth_type=0x800) - inst = [ - parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32767, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_group_mod_1(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - buckets = [] - # Action Bucket: send it to the first group entry in the group table actions = [ parser.OFPActionGroup(1), - parser.OFPActionSetState(in_port, 1)] + parser.OFPActionSetState(in_port, 0, bw_flag=1)] match = parser.OFPMatch( in_port=in_port, state=0, eth_type=0x800) inst = [ @@ -390,7 +375,7 @@ def add_flow_4(self, datapath, table_miss=False): datapath.send_msg(mod) for state in range(4,7): - # state x means output port x+1 + # state x means output port x actions = [ parser.OFPActionOutput(state, 0)] match = parser.OFPMatch( @@ -407,8 +392,6 @@ def add_flow_4(self, datapath, table_miss=False): flags=0, match=match, instructions=inst) datapath.send_msg(mod) - #SETUP TABLE 1 - # the state of a flow is the selected output port for that flow for in_port in range(4,7): @@ -422,7 +405,7 @@ def add_flow_4(self, datapath, table_miss=False): parser.OFPInstructionActions( ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=1, + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, @@ -472,24 +455,6 @@ def send_group_mod_2(self, datapath): req = ofp_parser.OFPGroupMod(datapath, ofp.OFPGC_ADD, ofp.OFPGT_RANDOM, group_id, buckets) datapath.send_msg(req) - # second entry - buckets = [] - # Action Bucket: action: set_state(i) & flood() - match: state=j & in_port=i => action: set_state(i) & output(j) - - ''' - - for in_port in range(1, SWITCH_PORTS + 1): # for each port (from 1 to #ports) - LOG.info("Installing flow rule for port %d..." % in_port) - for state in range(SWITCH_PORTS + 1): # for each state (from 0 to #ports) - - if state == 0: # DEFAULT state - actions = [ - parser.OFPActionOutput( - ofproto.OFPP_FLOOD), - parser.OFPActionSetState(in_port,0)] - match = parser.OFPMatch( - in_port=in_port, state=state) - - else: - actions = [ - parser.OFPActionOutput(state, 0), - parser.OFPActionSetState(in_port,0)] - match = parser.OFPMatch( - in_port=in_port, state=state) - - inst = [parser.OFPInstructionActions( - ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, - hard_timeout=0, priority=32768, - buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - def send_table_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - - req = ofp_parser.OFPTableMod(datapath, 0, ofp.OFPTC_TABLE_STATEFUL) - datapath.send_msg(req) - - def add_state_entry(self, datapath): - ofproto = datapath.ofproto - state = datapath.ofproto_parser.OFPStateEntry( - datapath, ofproto.OFPSC_ADD_FLOW_STATE, 6, 4, [0,0,0,0,0,2], - cookie=0, cookie_mask=0, table_id=0) - datapath.send_msg(state) - - def send_features_request(self, datapath): - ofp_parser = datapath.ofproto_parser - - req = ofp_parser.OFPFeaturesRequest(datapath) - datapath.send_msg(req) - - def send_key_lookup(self, datapath): - ofp = datapath.ofproto - - key_lookup_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_L_EXTRACTOR, 1, [ofp.OXM_OF_ETH_DST]) - datapath.send_msg(key_lookup_extractor) - - def send_key_update(self, datapath): - ofp = datapath.ofproto - - key_update_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_U_EXTRACTOR, 1, [ofp.OXM_OF_ETH_SRC]) - datapath.send_msg(key_update_extractor) - diff --git a/ryu/app/openstate/portknock.py b/ryu/app/openstate/portknock.py deleted file mode 100644 index c7e66aa3..00000000 --- a/ryu/app/openstate/portknock.py +++ /dev/null @@ -1,160 +0,0 @@ - -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import struct - -from ryu.base import app_manager -from ryu.controller import ofp_event -from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, HANDSHAKE_DISPATCHER -from ryu.controller.handler import set_ev_cls -from ryu.ofproto import ofproto_v1_3 -from ryu.lib.packet import packet -from ryu.lib.packet import ethernet -from ryu.topology import event - -LOG = logging.getLogger('app.openstate.portknock_') - -# Last port is the one to be opened after knoking all the others -PORT_LIST = [5123, 6234, 7345, 8456, 2000] - -class OSPortKnocking(app_manager.RyuApp): - OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] - - def __init__(self, *args, **kwargs): - super(OSPortKnocking, self).__init__(*args, **kwargs) - self.mac_to_port = {} - LOG.info("OpenState Port Knocking sample app initialized") - LOG.info("Port knock sequence is %s" % PORT_LIST) - - @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) - def switch_features_handler(self, ev): - msg = ev.msg - datapath = msg.datapath - ofproto = datapath.ofproto - - self.send_features_request(datapath) - self.send_table_mod(datapath) - - self.send_key_lookup(datapath) - self.send_key_update(datapath) - - # install the xfsm machine rules: - self.add_flow(datapath) - - ''' - STATEFUL TABLE 0 - - Lookup-scope=IPV4_SRC - Update-scope=IPV4_SRC - - $ sudo mn --topo single,4 --switch user --mac --controller remote - - h2# nc -ul 2000 - - h1# ./ryu/ryu/app/openstate/test_port_knocking.sh - - ''' - - def add_flow(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - LOG.info("Configuring XFSM for switch %d" % datapath.id) - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32760, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # Flow entries for port knocking (UDP ports) - ''' - state=0 (DEFAULT) - state=1,2,3 (Stage 1,2,3) - state=4 (OPEN) - - eth_type=0x0800, ip_proto=17 --> IP+UDP packet - - match: state=j => action: set_state(j+1) - match: state=4 => action: set_state(4),output(2) - - ''' - for state in range(len(PORT_LIST)): - match = parser.OFPMatch( - state=state, eth_type=0x0800, ip_proto=17, udp_dst=PORT_LIST[state]) - if not state == 4: - actions = [parser.OFPActionSetState(state +1,0)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - else: - actions = [parser.OFPActionOutput(2, 0), - parser.OFPActionSetState(state,0)] - inst = [ parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - # se sbaglio sequenza, torno allo stato DEFAULT - actions = [parser.OFPActionSetState(0,0)] - match = parser.OFPMatch() - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=0, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - - datapath.send_msg(mod) - - def send_table_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - req = ofp_parser.OFPTableMod(datapath, 0, ofp.OFPTC_TABLE_STATEFUL) - datapath.send_msg(req) - - def send_features_request(self, datapath): - ofp_parser = datapath.ofproto_parser - req = ofp_parser.OFPFeaturesRequest(datapath) - datapath.send_msg(req) - - def send_key_lookup(self, datapath): - ofp = datapath.ofproto - key_lookup_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_L_EXTRACTOR, 1, [ofp.OXM_OF_IPV4_SRC]) - datapath.send_msg(key_lookup_extractor) - - def send_key_update(self, datapath): - ofp = datapath.ofproto - key_update_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_U_EXTRACTOR, 1, [ofp.OXM_OF_IPV4_SRC]) - datapath.send_msg(key_update_extractor) diff --git a/ryu/app/openstate/start_1_to_many.py b/ryu/app/openstate/start_1_to_many.py deleted file mode 100644 index 8b3d5b3d..00000000 --- a/ryu/app/openstate/start_1_to_many.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo,SingleSwitchTopo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_1_to_many.py'&") - - - -######Starting mininet - -mytopo=SingleSwitchTopo(4) -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("\nType \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 300\" in h3's xterm") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 400\" in h4's xterm") -print("Type \"nc 10.0.0.2 80\" in all h1's xterms\n") -print("In order to test new path selection, close and reopen netcat") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634,autoStaticArp=True) -net.start() -h1,h2,h3,h4 = net.hosts[0], net.hosts[1], net.hosts[2], net.hosts[3] -for i in range(3): - makeTerm(h1) -makeTerm(h2) -makeTerm(h3) -makeTerm(h4) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_1_to_many_ctrl.py b/ryu/app/openstate/start_1_to_many_ctrl.py deleted file mode 100644 index 711f4c3a..00000000 --- a/ryu/app/openstate/start_1_to_many_ctrl.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo,SingleSwitchTopo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_1_to_many_ctrl.py'&") - - - -######Starting mininet - -mytopo=SingleSwitchTopo(4) -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("\nType \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 300\" in h3's xterm") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 400\" in h4's xterm") -print("Type \"nc 10.0.0.2 80\" in all h1's xterms\n") -print("In order to test new path selection, close and reopen netcat") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634,autoStaticArp=True) -net.start() -h1,h2,h3,h4 = net.hosts[0], net.hosts[1], net.hosts[2], net.hosts[3] -for i in range(3): - makeTerm(h1) -makeTerm(h2) -makeTerm(h3) -makeTerm(h4) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_link_protection.py b/ryu/app/openstate/start_link_protection.py deleted file mode 100644 index 86a93f5f..00000000 --- a/ryu/app/openstate/start_link_protection.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo,SingleSwitchTopo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/link_protection.py'&") - -######Starting mininet - -mytopo=SingleSwitchTopo(4) -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("\nType \"ping 10.0.0.2\" in h1's first xterm") -print("Type \"ping 10.0.0.3\" in h1's second xterm") -print("In order to change the outport from 2 to 3 and viceversa\n") -print("Type \"nc -w 1 10.0.0.1 33333\" in h2's xterm or \"nc -w 1 10.0.0.1 22222\" in h3's xterm") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,listenPort=6634,autoStaticArp=True) -net.start() -h1,h2,h3,h4 = net.hosts[0], net.hosts[1], net.hosts[2], net.hosts[3] - -makeTerm(h1) -makeTerm(h1) -makeTerm(h2) -makeTerm(h3) - -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_1.py b/ryu/app/openstate/start_many_to_1.py deleted file mode 100644 index 002277da..00000000 --- a/ryu/app/openstate/start_many_to_1.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 3, 1) - self.addLink( switch1, switch4, 4, 1) - self.addLink( switch2, switch5, 2, 1) - self.addLink( switch3, switch5, 2, 2) - self.addLink( switch4, switch5, 2, 3) - self.addLink( switch5, Host2, 4, 1) - -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_1.py'&") - - - -######Starting mininet -topos = { 'mytopo': ( lambda: MyTopo() ) } -mytopo=MyTopo() -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"nc 10.0.0.2 200\" in h1's xterm") -print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed\n" - "In order to test new path selection, close and reopen netcat") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) -net.start() -os.system("xterm -e 'tcpdump -i s2-eth1'&") -os.system("xterm -e 'tcpdump -i s3-eth1'&") -os.system("xterm -e 'tcpdump -i s4-eth1'&") -h1,h2 = net.hosts[0], net.hosts[1] -makeTerm(h1) -makeTerm(h2) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_1_alternative.py b/ryu/app/openstate/start_many_to_1_alternative.py deleted file mode 100644 index 9023eb3d..00000000 --- a/ryu/app/openstate/start_many_to_1_alternative.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 3, 1) - self.addLink( switch1, switch4, 4, 1) - self.addLink( switch2, switch5, 2, 1) - self.addLink( switch3, switch5, 2, 2) - self.addLink( switch4, switch5, 2, 3) - self.addLink( switch5, Host2, 4, 1) - -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_1_alternative.py'&") - - - -######Starting mininet -topos = { 'mytopo': ( lambda: MyTopo() ) } -mytopo=MyTopo() -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"nc 10.0.0.2 200\" in h1's xterm") -print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed IN EACH DIRECTION.\n" - "In order to test new path selection, close and reopen netcat.") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) -net.start() -os.system("xterm -e 'tcpdump -i s2-eth1'&") -os.system("xterm -e 'tcpdump -i s3-eth1'&") -os.system("xterm -e 'tcpdump -i s4-eth1'&") -h1,h2 = net.hosts[0], net.hosts[1] -makeTerm(h1) -makeTerm(h2) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_1_ctrl.py b/ryu/app/openstate/start_many_to_1_ctrl.py deleted file mode 100644 index 2e10b7ac..00000000 --- a/ryu/app/openstate/start_many_to_1_ctrl.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 3, 1) - self.addLink( switch1, switch4, 4, 1) - self.addLink( switch2, switch5, 2, 1) - self.addLink( switch3, switch5, 2, 2) - self.addLink( switch4, switch5, 2, 3) - self.addLink( switch5, Host2, 4, 1) - -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_1_ctrl.py'&") - - - -######Starting mininet -topos = { 'mytopo': ( lambda: MyTopo() ) } -mytopo=MyTopo() -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"nc 10.0.0.2 200\" in h1's xterm") -print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed\n" - "In order to test new path selection, close and reopen netcat") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) -net.start() -os.system("xterm -e 'tcpdump -i s2-eth1'&") -os.system("xterm -e 'tcpdump -i s3-eth1'&") -os.system("xterm -e 'tcpdump -i s4-eth1'&") -h1,h2 = net.hosts[0], net.hosts[1] -makeTerm(h1) -makeTerm(h2) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/start_many_to_many.py b/ryu/app/openstate/start_many_to_many_BW.py similarity index 98% rename from ryu/app/openstate/start_many_to_many.py rename to ryu/app/openstate/start_many_to_many_BW.py index cda17b7b..5674e71b 100644 --- a/ryu/app/openstate/start_many_to_many.py +++ b/ryu/app/openstate/start_many_to_many_BW.py @@ -45,7 +45,7 @@ def __init__( self): ######Starting controller -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_many.py'&") +os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_many_BW.py'&") diff --git a/ryu/app/openstate/start_many_to_many_ctrl.py b/ryu/app/openstate/start_many_to_many_ctrl.py deleted file mode 100644 index b5b44dd4..00000000 --- a/ryu/app/openstate/start_many_to_many_ctrl.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/python - -from mininet.net import Mininet -from mininet.topo import Topo -from mininet.cli import CLI -from mininet.node import UserSwitch,RemoteController -from mininet.term import makeTerm -import os, time - -class MyTopo( Topo ): - "Simple topology example." - - def __init__( self): - "Create custom topo." - - # Add default members to class. - Topo.__init__(self) - - # Add nodes - - Host1=self.addHost('h1', ip='10.0.0.1/24') - Host2=self.addHost('h2', ip='10.0.0.2/24') - switch1=self.addSwitch('s1') - switch2=self.addSwitch('s2') - switch3=self.addSwitch('s3') - switch4=self.addSwitch('s4') - switch5=self.addSwitch('s5') - switch6=self.addSwitch('s6') - switch7=self.addSwitch('s7') - - # Add edges - self.addLink( Host1, switch1, 1, 1) - self.addLink( switch1, switch2, 2, 1) - self.addLink( switch1, switch3, 4, 1) - self.addLink( switch1, switch4, 3, 2) - self.addLink( switch2, switch4, 2, 1) - self.addLink( switch3, switch4, 2, 3) - self.addLink( switch4, switch5, 4, 1) - self.addLink( switch4, switch7, 5, 2) - self.addLink( switch4, switch6, 6, 1) - self.addLink( switch5, switch7, 2, 1) - self.addLink( switch6, switch7, 2, 3) - self.addLink( switch7, Host2, 4, 1) - -######Starting controller - - -os.system("xterm -e 'ryu-manager ~/ryu/ryu/app/openstate/forwarding_consistency_many_to_many_ctrl.py'&") - - - -######Starting mininet -topos = { 'mytopo': ( lambda: MyTopo() ) } -mytopo=MyTopo() -time.sleep(1) -print("\n********************************** HELP *********************************************") -print("Type \"python ~/ryu/ryu/app/openstate/echo_server.py 200\" in h2's xterm") -print("Type \"nc 10.0.0.2 200\" in h1's xterm") -print("Watching the tcpdump results, it is possible to see that forwarding consistency is guaranteed\n" - "In order to test new path selection, close and reopen netcat") -print("\nTo exit type \"ctrl+D\" or exit") -print("*************************************************************************************") -net = Mininet(topo=mytopo,switch=UserSwitch,controller=RemoteController,cleanup=True,autoSetMacs=True,autoStaticArp=True,listenPort=6634) -net.start() -os.system("xterm -e 'tcpdump -i s4-eth1'&") -os.system("xterm -e 'tcpdump -i s4-eth2'&") -os.system("xterm -e 'tcpdump -i s4-eth3'&") -os.system("xterm -e 'tcpdump -i s4-eth4'&") -os.system("xterm -e 'tcpdump -i s4-eth5'&") -os.system("xterm -e 'tcpdump -i s4-eth6'&") -h1,h2 = net.hosts[0], net.hosts[1] -makeTerm(h1) -makeTerm(h2) -CLI(net) -net.stop() -os.system("sudo mn -c") -os.system("kill -9 $(pidof -x ryu-manager)") diff --git a/ryu/app/openstate/test_FFSM.py b/ryu/app/openstate/test_FFSM.py deleted file mode 100644 index bdb1fbe8..00000000 --- a/ryu/app/openstate/test_FFSM.py +++ /dev/null @@ -1,219 +0,0 @@ - -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import struct - -from ryu.base import app_manager -from ryu.controller import ofp_event -from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER, HANDSHAKE_DISPATCHER -from ryu.controller.handler import set_ev_cls -from ryu.ofproto import ofproto_v1_3 -from ryu.lib.packet import packet -from ryu.lib.packet import ethernet -from ryu.topology import event -import time - -''' -Applicazione di test che fa uso di Global States (flags), Flow States e Metadata contemporaneamente e dei comandi OFPSC_ADD_FLOW_STATE e OFPSC_DEL_FLOW_STATE - -Ci sono 4 host: -h1 e h2 si pingano sempre -h3 e h4 si pingano per 5 secondi, poi non riescono per altri 5 e infine riescono sempre - -TABLE 0 (stateless) - -ipv4_src=10.0.0.1, in_port=1 ---> SetState(state=0xfffffffa,stage_id=1), SetFlag("1*01********"), WriteMetadata(64954), GotoTable(1) -ipv4_src=10.0.0.2, in_port=2 ---> forward(1) -ipv4_src=10.0.0.3, in_port=3 ---> GotoTable(1) -ipv4_src=10.0.0.4, in_port=4 ---> forward(3) - -TABLE 1 (stateful) Lookup-scope=Update-scope=OXM_OF_IPV4_SRC) - -ipv4_src=10.0.0.1, metadata=64954, flags="1*01********", state=0xfffffffa ---> forward(2) -ipv4_src=10.0.0.3, state=2 ---> forward(4) -''' - -class OSTestFFSM(app_manager.RyuApp): - OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] - - def __init__(self, *args, **kwargs): - super(OSTestFFSM, self).__init__(*args, **kwargs) - - @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) - def switch_features_handler(self, ev): - msg = ev.msg - datapath = msg.datapath - ofproto = datapath.ofproto - - self.send_features_request(datapath) - self.send_table_mod(datapath) - - self.send_key_lookup(datapath) - self.send_key_update(datapath) - - self.add_flow(datapath) - self.add_state_entry(datapath) - time.sleep(5) - self.del_state_entry(datapath) - time.sleep(5) - self.add_state_entry(datapath) - - - def add_flow(self, datapath, table_miss=False): - ofproto = datapath.ofproto - parser = datapath.ofproto_parser - - # ARP packets flooding - match = parser.OFPMatch(eth_type=0x0806) - actions = [ - parser.OFPActionOutput(ofproto.OFPP_FLOOD)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32760, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - match = parser.OFPMatch( - ipv4_src="10.0.0.1", in_port=1, eth_type=0x0800) - (flag, flag_mask) = parser.maskedflags("1*01",8) - actions = [parser.OFPActionSetState(state=0xfffffffa,stage_id=1), - parser.OFPActionSetFlag(flag, flag_mask)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions), - parser.OFPInstructionGotoTable(1), - parser.OFPInstructionWriteMetadata(64954, 0xffffffffffffffff) - ] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch( - ipv4_src="10.0.0.1", eth_type=0x0800, metadata=64954, state=0xfffffffa, flags=parser.maskedflags("1*01",8)) - actions = [parser.OFPActionOutput(2)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=1, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch( - ipv4_src="10.0.0.3", in_port=3, eth_type=0x0800) - - inst = [parser.OFPInstructionGotoTable(1)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch( - ipv4_src="10.0.0.4", in_port=4, eth_type=0x0800) - actions = [parser.OFPActionOutput(3)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch( - ipv4_src="10.0.0.3", eth_type=0x0800, state=2) - actions = [parser.OFPActionOutput(4)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=1, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - match = parser.OFPMatch( - ipv4_src="10.0.0.2", in_port=2, eth_type=0x0800) - actions = [parser.OFPActionOutput(1)] - inst = [parser.OFPInstructionActions( - datapath.ofproto.OFPIT_APPLY_ACTIONS, actions)] - mod = parser.OFPFlowMod( - datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32768, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, - flags=0, match=match, instructions=inst) - datapath.send_msg(mod) - - - def send_table_mod(self, datapath): - ofp = datapath.ofproto - ofp_parser = datapath.ofproto_parser - req = ofp_parser.OFPTableMod(datapath, 1, ofp.OFPTC_TABLE_STATEFUL) - datapath.send_msg(req) - - def send_features_request(self, datapath): - ofp_parser = datapath.ofproto_parser - req = ofp_parser.OFPFeaturesRequest(datapath) - datapath.send_msg(req) - - def add_state_entry(self, datapath): - ofproto = datapath.ofproto - state = datapath.ofproto_parser.OFPStateEntry( - datapath, ofproto.OFPSC_ADD_FLOW_STATE, 4, 2, [10,0,0,3], - cookie=0, cookie_mask=0, table_id=1) - datapath.send_msg(state) - - def del_state_entry(self, datapath): - ofproto = datapath.ofproto - state = datapath.ofproto_parser.OFPStateEntry( - datapath, ofproto.OFPSC_DEL_FLOW_STATE, 4, 2, [10,0,0,3], - cookie=0, cookie_mask=0, table_id=1) - datapath.send_msg(state) - - def send_key_lookup(self, datapath): - ofp = datapath.ofproto - key_lookup_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_L_EXTRACTOR, 1, [ofp.OXM_OF_IPV4_SRC],table_id=1) - datapath.send_msg(key_lookup_extractor) - - def send_key_update(self, datapath): - ofp = datapath.ofproto - key_update_extractor = datapath.ofproto_parser.OFPKeyExtract( - datapath, ofp.OFPSC_SET_U_EXTRACTOR, 1, [ofp.OXM_OF_IPV4_SRC],table_id=1) - datapath.send_msg(key_update_extractor) diff --git a/ryu/app/openstate/test_port_knocking.sh b/ryu/app/openstate/test_port_knocking.sh deleted file mode 100755 index e7cac5a4..00000000 --- a/ryu/app/openstate/test_port_knocking.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -#Sequence: 5123, 6234, 7345, 8456, 2000 - -# Wrong sequence: -echo -n "*" | nc -q1 -u 10.0.0.2 5123 -echo -n "*" | nc -q1 -u 10.0.0.2 6234 -echo -n "*" | nc -q1 -u 10.0.0.2 73 - -# Correct Sequence -echo -n "*" | nc -q1 -u 10.0.0.2 5123 -echo -n "*" | nc -q1 -u 10.0.0.2 6234 -echo -n "*" | nc -q1 -u 10.0.0.2 7345 -echo -n "*" | nc -q1 -u 10.0.0.2 8456 -nc -u 10.0.0.2 2000 \ No newline at end of file From 019b84d3b8bcfeded9a05e7269d200479a50a53d Mon Sep 17 00:00:00 2001 From: lupo89 <19lupo89@gmail.com> Date: Wed, 20 May 2015 04:51:03 -0700 Subject: [PATCH 9/9] many to many bidirectional connections --- .../forwarding_consistency_many_to_many_BW.py | 120 +++++++++++++----- ryu/app/openstate/start_many_to_many_BW.py | 2 +- 2 files changed, 90 insertions(+), 32 deletions(-) diff --git a/ryu/app/openstate/forwarding_consistency_many_to_many_BW.py b/ryu/app/openstate/forwarding_consistency_many_to_many_BW.py index a9723272..a75eefa6 100644 --- a/ryu/app/openstate/forwarding_consistency_many_to_many_BW.py +++ b/ryu/app/openstate/forwarding_consistency_many_to_many_BW.py @@ -62,8 +62,8 @@ def one_to_many_switch(self, datapath): self.send_group_mod(datapath) self.send_table_mod(datapath) - self.send_key_lookup_1(datapath) - self.send_key_update_1(datapath) + self.send_key_lookup(datapath) + self.send_key_update(datapath) # install table-miss flow entry (if no rule matching, send it to controller) # self.add_flow_1(datapath, True) @@ -80,9 +80,10 @@ def middleswitch(self, datapath): def many_to_one_switch(self, datapath): self.send_features_request(datapath) self.send_table_mod(datapath) + self.send_group_mod_3(datapath) - self.send_key_lookup_2(datapath) - self.send_key_update_2(datapath) + self.send_key_lookup(datapath) + self.send_key_update(datapath) # install table-miss flow entry (if no rule matching, send it to controller) #self.add_flow_3(datapath, True) @@ -94,8 +95,8 @@ def many_to_many_switch(self, datapath): self.send_group_mod_2(datapath) self.send_table_mod_2(datapath) - self.send_key_lookup_1(datapath) - self.send_key_update_1(datapath) + self.send_key_lookup(datapath) + self.send_key_update(datapath) # install table-miss flow entry (if no rule matching, send it to controller) # self.add_flow_1(datapath, True) @@ -143,18 +144,22 @@ def add_flow_1(self, datapath, table_miss=False): # Reverse path flow - for in_port in range(2, SWITCH_PORTS + 1): - match = parser.OFPMatch(in_port=in_port) + for in_port in range(2,SWITCH_PORTS+1): + # if state=DEFAULT => send it to the first group entry in the group table actions = [ - parser.OFPActionOutput(1,0)] - inst = [parser.OFPInstructionActions( + parser.OFPActionOutput(1), + parser.OFPActionSetState(in_port-1, 0, bw_flag=1)] + match = parser.OFPMatch( + in_port=in_port, state=0, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = parser.OFPFlowMod( datapath=datapath, cookie=0, cookie_mask=0, table_id=0, - command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0, - priority=32767, buffer_id=ofproto.OFP_NO_BUFFER, - out_port=ofproto.OFPP_ANY, - out_group=ofproto.OFPG_ANY, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, flags=0, match=match, instructions=inst) datapath.send_msg(mod) @@ -278,10 +283,17 @@ def add_flow_3(self, datapath, table_miss=False): # Reverse path flow - for state in range(1,SWITCH_PORTS): - match = parser.OFPMatch(in_port=4, state=state, eth_type=0x800) - actions = [ - parser.OFPActionOutput(state,0)] + for state in range(SWITCH_PORTS): + if state == 0: + # if state=DEFAULT => send it to the first group entry in the group table + actions = [ + parser.OFPActionGroup(1)] + match = parser.OFPMatch( + in_port=4, state=state, eth_type=0x800) + else: + match = parser.OFPMatch(in_port=4, state=state, eth_type=0x800) + actions = [ + parser.OFPActionOutput(state,0)] inst = [parser.OFPInstructionActions( ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = parser.OFPFlowMod( @@ -298,7 +310,7 @@ def add_flow_3(self, datapath, table_miss=False): # state x means output port x+1 actions = [ parser.OFPActionOutput(4, 0), - parser.OFPActionSetState(in_port, 0)] + parser.OFPActionSetState(in_port, 0, bw_flag=1)] match = parser.OFPMatch( in_port=in_port, eth_type=0x800) inst = [ @@ -394,6 +406,23 @@ def add_flow_4(self, datapath, table_miss=False): # the state of a flow is the selected output port for that flow for in_port in range(4,7): + # if state=DEFAULT => send it to the first group entry in the group table + actions = [ + parser.OFPActionGroup(2), + parser.OFPActionSetState(in_port, 0, bw_flag=1)] + match = parser.OFPMatch( + in_port=in_port, state=0, eth_type=0x800) + inst = [ + parser.OFPInstructionActions( + ofproto.OFPIT_APPLY_ACTIONS, actions)] + mod = parser.OFPFlowMod( + datapath=datapath, cookie=0, cookie_mask=0, table_id=0, + command=ofproto.OFPFC_ADD, idle_timeout=0, + hard_timeout=0, priority=32767, + buffer_id=ofproto.OFP_NO_BUFFER, + out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY, + flags=0, match=match, instructions=inst) + datapath.send_msg(mod) for state in range(1,4): # state x means output port x+1 @@ -456,6 +485,45 @@ def send_group_mod_2(self, datapath): ofp.OFPGT_RANDOM, group_id, buckets) datapath.send_msg(req) + buckets = [] + # Action Bucket: