def test_mapper(self): self.assertEqual("tapangmzseeic01", mac_to_tun_name("02:11:22:33:33:01")) self.assertEqual("tapangmzseeicfa", mac_to_tun_name("02:11:22:33:33:fa")) self.assertEqual("tapangmaceeia00", mac_to_tun_name("00:11:22:00:33:00")) with self.assertRaises(AssertionError): mac_to_tun_name("02:11:22:33:33:QQ")
def sync_ovs_from_tunnels(self, mac_to_tunswitch: MACToTunnelAndSwitch) -> None: logger.warning( "negotiator tunnels are:\n%s\n%s\n%s", "-" * 40, "\n\n".join( f"- {mac} {mac_to_tun_name(mac)} {swi.hostname}:\n |--{tun}\n |--{swi}" for mac, (tun, swi) in sorted(mac_to_tunswitch.items())), "-" * 40, ) tun_to_tunswitch: TUNToTunnelAndSwitch = { mac_to_tun_name(mac): (tunnel, switch) for mac, (tunnel, switch) in mac_to_tunswitch.items() } # TODO clean the dict? Eventually it might eat up a lot of memory. self._tun_to_dest_mac.update( (tun, tunnel.dst_mac) for tun, (tunnel, _) in tun_to_tunswitch.items()) self._update_relay_mac_in_ovs_sync(mac_to_tunswitch) self._add_ports_to_bridge(tun_to_tunswitch) self.flow_hysteresis.update(tun_to_tunswitch) self._remove_extraneous_ports_in_ovs_sync(tun_to_tunswitch) # Create a new eventlet's green thread (akin to asyncio.ensure_future). hub.spawn_after( self.flow_hysteresis.hysteresis_seconds + 0.1, self._update_port_flows, list(tun_to_tunswitch.keys()), )
def _update_relay_mac_in_ovs_sync( self, mac_to_tunswitch: MACToTunnelAndSwitch) -> None: relays = [ tunnel for tunnel, switch in mac_to_tunswitch.values() if switch.is_relay ] assert len(relays) <= 1 # TODO maybe drop all except one instead of failing? self.relay_tun = None if relays: self.relay_tun = mac_to_tun_name(relays[0].dst_mac)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._process_transport = None self.tun_dev_name = mac_to_tun_name(self._dst_mac)
def test_packet_in_on_board(self): ovs_manager = self.ovs_manager OFPORT_RELAYER = 41 fl = FlowsLogic( is_relay=False, ovs_manager=ovs_manager, tunnel_intentions_provider=self.tunnel_intentions_provider, topology_database=self.topology_database, ) fl.flow_hysteresis.update({ mac_to_tun_name(SECOND_MAC): ( TunnelModel.from_dict({ **TUNNEL_MODEL_RELAY_DATA, "dst_mac": SECOND_MAC }), SwitchEntity.from_dict({ **SWITCH_ENTITY_RELAY_DATA, "mac": SECOND_MAC }), ) }) self.inc_hysteresis_clock() # Incoming packet from a switch msg = self._build_ofp_packet_in(dst_mac=LOCAL_MAC, src_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(ryu_ofproto.OFPP_LOCAL, packet_out_msg.actions[0].port) # Flow should be installed self.assertEqual(ryu_ofproto.OFPP_LOCAL, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst=LOCAL_MAC), flow_add_msg.match) # Outgoing to a connected switch ovs_manager.get_ofport.side_effect = lambda tun: { mac_to_tun_name(SECOND_MAC): OFPORT_RELAYER }[tun] msg = self._build_ofp_packet_in(dst_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once() ovs_manager.get_ofport.reset_mock() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(OFPORT_RELAYER, packet_out_msg.actions[0].port) # Flow should be installed self.assertEqual(OFPORT_RELAYER, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst=SECOND_MAC), flow_add_msg.match) # Incoming broadcast packet msg = self._build_ofp_packet_in(dst_mac="ff:ff:ff:ff:ff:ff", src_mac=SECOND_MAC, in_port=12399) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(ryu_ofproto.OFPP_LOCAL, packet_out_msg.actions[0].port) self.assertEqual(12399, packet_out_msg.in_port) # Flow should be installed self.assertEqual(ryu_ofproto.OFPP_LOCAL, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst="ff:ff:ff:ff:ff:ff", in_port=12399), flow_add_msg.match, ) # Without a connected relay - do nothing. ovs_manager.get_ofport.side_effect = Exception fl.relay_tun = None # Without a connected relay: Outgoing to a not connected switch msg = self._build_ofp_packet_in(dst_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once_with( mac_to_tun_name(SECOND_MAC)) ovs_manager.get_ofport.reset_mock() msg.datapath.send_msg.assert_not_called() self.tunnel_intentions_provider.ask_for_tunnel.assert_called_once_with( SECOND_MAC) self.tunnel_intentions_provider.ask_for_tunnel.reset_mock() # Without a connected relay: Outgoing broadcast packet msg = self._build_ofp_packet_in( dst_mac="ff:ff:ff:ff:ff:ff", src_mac=LOCAL_MAC, in_port=ryu_ofproto.OFPP_LOCAL, ) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() msg.datapath.send_msg.assert_not_called() # With a connected relay fl.relay_tun = mac_to_tun_name(SECOND_MAC) ovs_manager.get_ofport.side_effect = lambda tun: { mac_to_tun_name(SECOND_MAC): OFPORT_RELAYER }[tun] # With a connected relay: Outgoing to a not connected switch msg = self._build_ofp_packet_in(dst_mac=THIRD_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once_with( mac_to_tun_name(SECOND_MAC)) ovs_manager.get_ofport.reset_mock() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(OFPORT_RELAYER, packet_out_msg.actions[0].port) # Flow should be installed self.assertEqual(OFPORT_RELAYER, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst=THIRD_MAC), flow_add_msg.match) self.tunnel_intentions_provider.ask_for_tunnel.assert_called_once_with( THIRD_MAC) self.tunnel_intentions_provider.ask_for_tunnel.reset_mock() # With a connected relay: Outgoing broadcast packet msg = self._build_ofp_packet_in( dst_mac="ff:ff:ff:ff:ff:ff", src_mac=LOCAL_MAC, in_port=ryu_ofproto.OFPP_LOCAL, ) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once() ovs_manager.get_ofport.reset_mock() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(OFPORT_RELAYER, packet_out_msg.actions[0].port) self.assertEqual(ryu_ofproto.OFPP_LOCAL, packet_out_msg.in_port) # Flow should be installed self.assertEqual(OFPORT_RELAYER, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst="ff:ff:ff:ff:ff:ff", in_port=ryu_ofproto.OFPP_LOCAL), flow_add_msg.match, ) # Outgoing to a foreign mac address msg = self._build_ofp_packet_in(dst_mac=UNK_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() msg.datapath.send_msg.assert_not_called() self.tunnel_intentions_provider.ask_for_tunnel.assert_not_called()
def test_packet_in_on_relay(self): ovs_manager = self.ovs_manager OFPORT_BOARD = 41 fl = FlowsLogic( is_relay=True, ovs_manager=ovs_manager, tunnel_intentions_provider=self.tunnel_intentions_provider, topology_database=self.topology_database, ) fl.flow_hysteresis.update({ mac_to_tun_name(SECOND_MAC): ( TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA), SwitchEntity.from_dict(SWITCH_ENTITY_BOARD_DATA), ) }) self.inc_hysteresis_clock() # Incoming packet from a switch msg = self._build_ofp_packet_in(dst_mac=LOCAL_MAC, src_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(ryu_ofproto.OFPP_LOCAL, packet_out_msg.actions[0].port) # Flow should be installed self.assertEqual(ryu_ofproto.OFPP_LOCAL, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst=LOCAL_MAC), flow_add_msg.match) # Outgoing to a connected switch ovs_manager.get_ofport.side_effect = lambda tun: { mac_to_tun_name(SECOND_MAC): OFPORT_BOARD }[tun] msg = self._build_ofp_packet_in(dst_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once() ovs_manager.get_ofport.reset_mock() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(OFPORT_BOARD, packet_out_msg.actions[0].port) # Flow should be installed self.assertEqual(OFPORT_BOARD, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst=SECOND_MAC), flow_add_msg.match) # Outgoing to a not connected switch ovs_manager.get_ofport.side_effect = Exception msg = self._build_ofp_packet_in(dst_mac=SECOND_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_called_once() ovs_manager.get_ofport.reset_mock() msg.datapath.send_msg.assert_not_called() # Outgoing to a foreign mac address ovs_manager.get_ofport.side_effect = Exception msg = self._build_ofp_packet_in(dst_mac=UNK_MAC) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() msg.datapath.send_msg.assert_not_called() # Incoming broadcast packet msg = self._build_ofp_packet_in(dst_mac="ff:ff:ff:ff:ff:ff", src_mac=SECOND_MAC, in_port=12399) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(ryu_ofproto.OFPP_FLOOD, packet_out_msg.actions[0].port) self.assertEqual(12399, packet_out_msg.in_port) # Flow should be installed self.assertEqual(ryu_ofproto.OFPP_FLOOD, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst="ff:ff:ff:ff:ff:ff"), flow_add_msg.match) # Outgoing broadcast packet msg = self._build_ofp_packet_in( dst_mac="ff:ff:ff:ff:ff:ff", src_mac=LOCAL_MAC, in_port=ryu_ofproto.OFPP_LOCAL, ) fl.packet_in(msg) ovs_manager.get_ofport.assert_not_called() flow_add_msg, packet_out_msg = self.extract_msg_from_call_args_list( msg.datapath.send_msg) # PACKET_OUT should be sent self.assertEqual(ryu_ofproto.OFPP_FLOOD, packet_out_msg.actions[0].port) self.assertEqual(ryu_ofproto.OFPP_LOCAL, packet_out_msg.in_port) # Flow should be installed self.assertEqual(ryu_ofproto.OFPP_FLOOD, flow_add_msg.instructions[0].actions[0].port) self.assertOFPMatchEquals( ryu_ofproto_parser.OFPMatch(eth_dst="ff:ff:ff:ff:ff:ff"), flow_add_msg.match) # TODO !! incoming multicast packet self.tunnel_intentions_provider.ask_for_tunnel.assert_not_called()
def test_tunnel_add(self, mock_add_flow): OFPORT_BOARD = 41 OFPORT_RELAY = 42 ovs_manager = self.ovs_manager ovs_manager.get_ports_in_bridge.return_value = [] fl = FlowsLogic( is_relay=False, ovs_manager=ovs_manager, tunnel_intentions_provider=self.tunnel_intentions_provider, topology_database=self.topology_database, ) fl.set_datapath(MagicMock()()) self.assertIsNone(fl.relay_tun) # Update with one switch (w/o a relay) mac_to_tunswitch = { SWITCH_ENTITY_BOARD_DATA["mac"]: ( TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA), SwitchEntity.from_dict(SWITCH_ENTITY_BOARD_DATA), ) } ovs_manager.get_ofport.side_effect = lambda tun: { mac_to_tun_name(SWITCH_ENTITY_BOARD_DATA["mac"]): OFPORT_BOARD }[tun] fl.sync_ovs_from_tunnels(mac_to_tunswitch) ovs_manager.add_port_to_bridge.assert_called_with( mac_to_tun_name(SWITCH_ENTITY_BOARD_DATA["mac"])) ovs_manager.add_port_to_bridge.reset_mock() self.assertIsNone(fl.relay_tun) mock_add_flow.assert_not_called() self.hub_spawn_after.assert_called_once() self.inc_hysteresis_clock() self.perform_last_spawn_after() self.hub_spawn_after.assert_called_once() self.hub_spawn_after.reset_mock() mock_add_flow.assert_called_once() mock_add_flow.reset_mock() ovs_manager.del_port_from_bridge.assert_not_called() ovs_manager.get_ofport.assert_called_once() ovs_manager.get_ofport.reset_mock() # Update with 2 switches (one is a relay) mac_to_tunswitch = { SWITCH_ENTITY_BOARD_DATA["mac"]: ( TunnelModel.from_dict(TUNNEL_MODEL_BOARD_DATA), SwitchEntity.from_dict(SWITCH_ENTITY_BOARD_DATA), ), SWITCH_ENTITY_RELAY_DATA["mac"]: ( TunnelModel.from_dict(TUNNEL_MODEL_RELAY_DATA), SwitchEntity.from_dict(SWITCH_ENTITY_RELAY_DATA), ), } ovs_manager.get_ofport.side_effect = lambda tun: { mac_to_tun_name(SWITCH_ENTITY_BOARD_DATA["mac"]): OFPORT_BOARD, mac_to_tun_name(SWITCH_ENTITY_RELAY_DATA["mac"]): OFPORT_RELAY, }[tun] fl.sync_ovs_from_tunnels(mac_to_tunswitch) self.assertEqual(fl.relay_tun, mac_to_tun_name(SWITCH_ENTITY_RELAY_DATA["mac"])) self.assertListEqual( [ tun for (tun, ), _ in ovs_manager.add_port_to_bridge.call_args_list ], [ mac_to_tun_name(SWITCH_ENTITY_BOARD_DATA["mac"]), mac_to_tun_name(SWITCH_ENTITY_RELAY_DATA["mac"]), ], ) ovs_manager.add_port_to_bridge.reset_mock() mock_add_flow.assert_not_called() self.hub_spawn_after.assert_called_once() self.inc_hysteresis_clock() self.perform_last_spawn_after() self.hub_spawn_after.assert_called_once() self.hub_spawn_after.reset_mock() self.assertEqual(2, mock_add_flow.call_count) mock_add_flow.reset_mock() ovs_manager.del_port_from_bridge.assert_not_called() self.assertEqual(2, ovs_manager.get_ofport.call_count) ovs_manager.get_ofport.reset_mock() # Update with empty tunnels, make sure that ports get removed ovs_manager.get_ports_in_bridge.return_value = [ mac_to_tun_name(SWITCH_ENTITY_BOARD_DATA["mac"]) ] mac_to_tunswitch = {} fl.sync_ovs_from_tunnels(mac_to_tunswitch) self.assertIsNone(fl.relay_tun) ovs_manager.add_port_to_bridge.assert_not_called() mock_add_flow.assert_not_called() ovs_manager.del_port_from_bridge.assert_called_once() self.hub_spawn_after.assert_called_once() self.inc_hysteresis_clock() self.perform_last_spawn_after() self.hub_spawn_after.assert_called_once() self.hub_spawn_after.reset_mock() mock_add_flow.assert_not_called() ovs_manager.del_port_from_bridge.assert_called_once() ovs_manager.get_ofport.assert_not_called() ovs_manager.get_ofport.reset_mock()
def packet_in(self, msg: OFPPacketIn) -> None: datapath: Datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser in_port: OFPort = OFPort(msg.match["in_port"]) pkt = packet.Packet(msg.data) eth = pkt.get_protocols(ethernet.ethernet)[0] if eth.ethertype == ether_types.ETH_TYPE_LLDP: # ignore lldp packet return dst: MACAddress = eth.dst src: MACAddress = eth.src dpid: int = datapath.id # Bridge identifier in OVS, usually derived from MAC. # self.mac_to_port.setdefault(dpid, {}) logger.info( "packet in [%s %s %s %s] [dpid src dst in_port]", dpid, src, dst, self._ofport_to_string(in_port, ofproto), ) # self.mac_to_port[dpid][src] = in_port is_broadcast = is_group_mac(dst) out_port: OFPort = OFPort(-1) match = parser.OFPMatch(eth_dst=dst) priority: OFPriority = OFPriority.DIRECT if dst == self.ovs_manager.bridge_mac: # This packet is specifically for us - capture it! out_port = ofproto.OFPP_LOCAL elif not is_broadcast: # This packet is for someone else - send it right away # if receiver is locally connected. tun = mac_to_tun_name(dst) try: out_port = self.flow_hysteresis.get_ofport_if_tun_is_ready(tun) except PortNotReadyError: pass # Either receiver is not directly reachable, # or it's a broadcast/multicast packet. if out_port < 0: try: if self.is_relay: out_port, priority, match = self._packet_in_relay( dst, ofproto, parser) else: out_port, priority, match = self._packet_in_board( dst, src, in_port, ofproto, parser) except NoSuitableOutPortError as e: logger.info("PACKET_IN: dropping packet: %s", str(e)) # TODO ICMP unreachable? return assert out_port >= 0 assert priority in (OFPriority.DIRECT, OFPriority.RELAY) logger.info( "PACKET_IN: decision: out_port %s", self._ofport_to_string(out_port, ofproto), ) # if dst in self.mac_to_port[dpid]: # out_port = self.mac_to_port[dpid][dst] actions = [parser.OFPActionOutput(out_port)] is_direct = priority == OFPriority.DIRECT # install a flow to avoid packet_in next time self.add_flow(datapath, priority, match, actions, is_direct_flow=is_direct) logger.info( "Added %s flow to %s via %s", "direct" if is_direct else "indirect", dst, self._ofport_to_string(out_port, ofproto), ) data = None 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)