# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. # Copyright (C) 2012 Isaku Yamahata # # 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. # This module updates flow table for OpenStack integration. # Despite of the name, this module isn't GRE specific and # should work for VXLAN etc as well. import collections from ryu import exception as ryu_exc from ryu.app.rest_nw_id import (NW_ID_VPORT_GRE, RESERVED_NETWORK_IDS) from ryu.base import app_manager from ryu.controller import (dpset, event, handler, network, ofp_event, tunnels) from ryu.ofproto import nx_match from ryu.lib import dpid as dpid_lib from ryu.lib import mac def _is_reserved_port(ofproto, port_no): return port_no > ofproto.OFPP_MAX def _link_is_up(dpset_, dp, port_no): try: state = dpset_.get_port(dp.id, port_no).state return not (state & dp.ofproto.OFPPS_LINK_DOWN) except ryu_exc.PortNotFound: return False class PortSet(app_manager.RyuApp): # Those events are higher level events than events of network tenant, # tunnel ports as the race conditions are masked. # Add event is generated only when all necessary informations are gathered, # Del event is generated when any one of the informations are deleted. # # Example: ports for VMs # there is a race condition between ofp port add/del event and # register network_id for the port. class EventTunnelKeyDel(event.EventBase): def __init__(self, tunnel_key): super(PortSet.EventTunnelKeyDel, self).__init__() self.tunnel_key = tunnel_key class EventPortBase(event.EventBase): def __init__(self, dpid, port_no): super(PortSet.EventPortBase, self).__init__() self.dpid = dpid self.port_no = port_no class EventVMPort(EventPortBase): def __init__(self, network_id, tunnel_key, dpid, port_no, mac_address, add_del): super(PortSet.EventVMPort, self).__init__(dpid, port_no) self.network_id = network_id self.tunnel_key = tunnel_key self.mac_address = mac_address self.add_del = add_del def __str__(self): return ('EventVMPort' % (dpid_lib.dpid_to_str(self.dpid), self.port_no, self.network_id, self.tunnel_key, mac.haddr_to_str(self.mac_address), self.add_del)) class EventTunnelPort(EventPortBase): def __init__(self, dpid, port_no, remote_dpid, add_del): super(PortSet.EventTunnelPort, self).__init__(dpid, port_no) self.remote_dpid = remote_dpid self.add_del = add_del def __str__(self): return ('EventTunnelPort' % (dpid_lib.dpid_to_str(self.dpid), self.port_no, dpid_lib.dpid_to_str(self.remote_dpid), self.add_del)) def __init__(self, **kwargs): super(PortSet, self).__init__() self.nw = kwargs['network'] self.tunnels = kwargs['tunnels'] self.dpset = kwargs['dpset'] app_manager.register_app(self) def _check_link_state(self, dp, port_no, add_del): if add_del: # When adding port, the link should be UP. return _link_is_up(self.dpset, dp, port_no) else: # When deleting port, the link status isn't cared. return True # Tunnel port # of connecting: self.dpids by (dpid, port_no) # datapath: connected: EventDP event # port status: UP: port add/delete/modify event # remote dpid: self.tunnels by (dpid, port_no): tunnel port add/del even def _tunnel_port_handler(self, dpid, port_no, add_del): dp = self.dpset.get(dpid) if dp is None: return if not self._check_link_state(dp, port_no, add_del): return try: remote_dpid = self.tunnels.get_remote_dpid(dpid, port_no) except ryu_exc.PortNotFound: return self.send_event_to_observers(self.EventTunnelPort(dpid, port_no, remote_dpid, add_del)) # VM port # of connection: self.dpids by (dpid, port_no) # datapath: connected: EventDP event # port status: UP: Port add/delete/modify event # network_id: self.nw by (dpid, port_no): network port add/del event # mac_address: self.nw by (dpid, port_no): mac address add/del event # tunnel key: from self.tunnels by network_id: tunnel key add/del event def _vm_port_handler(self, dpid, port_no, network_id, mac_address, add_del): if network_id in RESERVED_NETWORK_IDS: return if mac_address is None: return dp = self.dpset.get(dpid) if dp is None: return if _is_reserved_port(dp.ofproto, port_no): return if not self._check_link_state(dp, port_no, add_del): return try: tunnel_key = self.tunnels.get_key(network_id) except tunnels.TunnelKeyNotFound: return self.send_event_to_observers(self.EventVMPort(network_id, tunnel_key, dpid, port_no, mac_address, add_del)) def _vm_port_mac_handler(self, dpid, port_no, network_id, add_del): if network_id == NW_ID_VPORT_GRE: self._tunnel_port_handler(dpid, port_no, add_del) return try: mac_address = self.nw.get_mac(dpid, port_no) except ryu_exc.PortNotFound: return self._vm_port_handler(dpid, port_no, network_id, mac_address, add_del) def _port_handler(self, dpid, port_no, add_del): """ :type add_del: bool :param add_del: True for add, False for del """ try: port = self.nw.get_port(dpid, port_no) except ryu_exc.PortNotFound: return if port.network_id is None: return if port.network_id == NW_ID_VPORT_GRE: self._tunnel_port_handler(dpid, port_no, add_del) return self._vm_port_handler(dpid, port_no, port.network_id, port.mac_address, add_del) def _tunnel_key_del(self, tunnel_key): self.send_event_to_observers(self.EventTunnelKeyDel(tunnel_key)) # nw: network del # port add/del (vm/tunnel port) # mac address add/del(only vm port) # tunnels: tunnel key add/del # tunnel port add/del # dpset: eventdp # port add/delete/modify @handler.set_ev_cls(network.EventNetworkDel) def network_del_handler(self, ev): network_id = ev.network_id if network_id in RESERVED_NETWORK_IDS: return try: tunnel_key = self.tunnels.get_key(network_id) except tunnels.TunnelKeyNotFound: return self._tunnel_key_del(tunnel_key) @handler.set_ev_cls(network.EventNetworkPort) def network_port_handler(self, ev): self._vm_port_mac_handler(ev.dpid, ev.port_no, ev.network_id, ev.add_del) @handler.set_ev_cls(network.EventMacAddress) def network_mac_address_handler(self, ev): self._vm_port_handler(ev.dpid, ev.port_no, ev.network_id, ev.mac_address, ev.add_del) @handler.set_ev_cls(tunnels.EventTunnelKeyAdd) def tunnel_key_add_handler(self, ev): network_id = ev.network_id for (dpid, port_no) in self.nw.list_ports_noraise(network_id): self._vm_port_mac_handler(dpid, port_no, network_id, True) @handler.set_ev_cls(tunnels.EventTunnelKeyDel) def tunnel_key_del_handler(self, ev): network_id = ev.network_id for (dpid, port_no) in self.nw.list_ports_noraise(network_id): self._vm_port_mac_handler(dpid, port_no, network_id, False) if self.nw.has_network(network_id): self._tunnel_key_del(ev.tunnel_key) @handler.set_ev_cls(tunnels.EventTunnelPort) def tunnel_port_handler(self, ev): self._port_handler(ev.dpid, ev.port_no, ev.add_del) @handler.set_ev_cls(dpset.EventDP) def dp_handler(self, ev): self.send_event_to_observers(ev) enter_leave = ev.enter if not enter_leave: # TODO:XXX # What to do on datapath disconnection? self.logger.debug('dp disconnection ev:%s', ev) dpid = ev.dp.id ports = set(port.port_no for port in ev.ports) ports.update(port.port_no for port in self.nw.get_ports(dpid)) for port_no in ports: self._port_handler(dpid, port_no, enter_leave) @handler.set_ev_cls(dpset.EventPortAdd) def port_add_handler(self, ev): self._port_handler(ev.dp.id, ev.port.port_no, True) @handler.set_ev_cls(dpset.EventPortDelete) def port_del_handler(self, ev): self._port_handler(ev.dp.id, ev.port.port_no, False) @handler.set_ev_cls(dpset.EventPortModify) def port_modify_handler(self, ev): # We don't know LINK status has been changed. # So VM/TUNNEL port event can be triggered many times. dp = ev.dp port = ev.port self._port_handler(dp.id, port.port_no, not (port.state & dp.ofproto.OFPPS_LINK_DOWN)) @handler.set_ev_cls(ofp_event.EventOFPPacketIn) def packet_in_handler(self, ev): # for debug self.send_event_to_observers(ev) def cls_rule(in_port=None, tun_id=None, dl_src=None, dl_dst=None): """Convenience function to initialize nx_match.ClsRule()""" rule = nx_match.ClsRule() if in_port is not None: rule.set_in_port(in_port) if tun_id is not None: rule.set_tun_id(tun_id) if dl_src is not None: rule.set_dl_src(dl_src) if dl_dst is not None: rule.set_dl_dst(dl_dst) return rule class GRETunnel(app_manager.RyuApp): """ app for L2/L3 with gre tunneling PORTS VM-port: the port which is connected to VM instance TUNNEL-port: the ovs GRE vport TABLES: multi tables is used SRC_TABLE: This table is firstly used to match packets. by in_port, determine which port the packet comes VM-port or TUNNEL-port. If the packet came from VM-port, set tunnel id based on which network the VM belongs to, and send the packet to the tunnel out table. If the packet came from TUNNEL-port and its tunnel id is known to this switch, send the packet to local out table. Otherwise drop it. TUNNEL_OUT_TABLE: This table looks at tunnel id and dl_dst, send the packet to tunnel ports if necessary. And then, sends the packet to LOCAL_OUT_TABLE. By matching the packet with tunnel_id and dl_dst, determine which tunnel port the packet is send to. LOCAL_OUT_TABLE: This table looks at tunnel id and dl_dst, send the packet to local VM ports if necessary. Otherwise drop the packet. The packet from vm port traverses as SRC_TABLE -> TUNNEL_OUT_TABLE -> LOCAL_OUT_TABLE The packet from tunnel port traverses as SRC_TABLE -> LOCAL_OUT_TABLE The packet from vm port: SRC_TABLE match action in_port(VM) & dl_src set_tunnel & goto TUNNEL_OUT_TABLE in_port(VM) drop (catch-all drop rule) in_port(TUNNEL) & tun_id goto LOCAL_OUT_TABLE in_port(TUNNEL) drop (catch-all drop rule) TUNNEL_OUT_TABLE match action tun_id & dl_dst out tunnel port & goto LOCAL_OUT_TABLE (unicast or broadcast) tun_id goto LOCAL_OUT_TABLE (catch-all rule) LOCAL_OUT_TABLE tun_id & dl_dst output(VM) (unicast or broadcast) tun_id drop (catch-all drop rule) NOTE: adding/deleting flow entries should be done carefully in certain order such that packet in event should not be triggered. """ _CONTEXTS = { 'network': network.Network, 'dpset': dpset.DPSet, 'tunnels': tunnels.Tunnels, } DEFAULT_COOKIE = 0 # cookie isn't used. Just set 0 # Tables SRC_TABLE = 0 TUNNEL_OUT_TABLE = 1 LOCAL_OUT_TABLE = 2 FLOW_TABLES = [SRC_TABLE, TUNNEL_OUT_TABLE, LOCAL_OUT_TABLE] # Priorities. The only inequality is important. # '/ 2' is used just for easy looking instead of '- 1'. # 0x7ffff vs 0x4000 TABLE_DEFAULT_PRPIRITY = 32768 # = ofproto.OFP_DEFAULT_PRIORITY # SRC_TABLE for VM-port SRC_PRI_MAC = TABLE_DEFAULT_PRPIRITY SRC_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 2 # SRC_TABLE for TUNNEL-port SRC_PRI_TUNNEL_PASS = TABLE_DEFAULT_PRPIRITY SRC_PRI_TUNNEL_DROP = TABLE_DEFAULT_PRPIRITY / 2 # TUNNEL_OUT_TABLE TUNNEL_OUT_PRI_MAC = TABLE_DEFAULT_PRPIRITY TUNNEL_OUT_PRI_BROADCAST = TABLE_DEFAULT_PRPIRITY / 2 TUNNEL_OUT_PRI_PASS = TABLE_DEFAULT_PRPIRITY / 4 TUNNEL_OUT_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 8 # LOCAL_OUT_TABLE LOCAL_OUT_PRI_MAC = TABLE_DEFAULT_PRPIRITY LOCAL_OUT_PRI_BROADCAST = TABLE_DEFAULT_PRPIRITY / 2 LOCAL_OUT_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 4 def __init__(self, *args, **kwargs): super(GRETunnel, self).__init__(*args, **kwargs) self.nw = kwargs['network'] self.dpset = kwargs['dpset'] self.tunnels = kwargs['tunnels'] self.port_set = PortSet(**kwargs) map(lambda ev_cls: self.port_set.register_observer(ev_cls, self.name), [dpset.EventDP, PortSet.EventTunnelKeyDel, PortSet.EventVMPort, PortSet.EventTunnelPort, ofp_event.EventOFPPacketIn]) def start(self): super(GRETunnel, self).start() self.port_set.start() def stop(self): app_mgr = app_manager.get_instance() app_mgr.uninstantiate(self.port_set) self.port_set = None super(GRETunnel, self).stop() # TODO: track active vm/tunnel ports @handler.set_ev_handler(dpset.EventDP) def dp_handler(self, ev): if not ev.enter: return # enable nicira extension # TODO:XXX error handling dp = ev.dp ofproto = dp.ofproto dp.send_nxt_set_flow_format(ofproto.NXFF_NXM) flow_mod_table_id = dp.ofproto_parser.NXTFlowModTableId(dp, 1) dp.send_msg(flow_mod_table_id) dp.send_barrier() # delete all flows in all tables # current controller.handlers takes care of only table = 0 for table in self.FLOW_TABLES: rule = cls_rule() self.send_flow_del(dp, rule, table, ofproto.OFPFC_DELETE, None, None) dp.send_barrier() @staticmethod def _make_command(table, command): return table << 8 | command def send_flow_mod(self, dp, rule, table, command, priority, actions): command = self._make_command(table, command) dp.send_flow_mod(rule=rule, cookie=self.DEFAULT_COOKIE, command=command, idle_timeout=0, hard_timeout=0, priority=priority, actions=actions) def send_flow_del(self, dp, rule, table, command, priority, out_port): command = self._make_command(table, command) dp.send_flow_mod(rule=rule, cookie=self.DEFAULT_COOKIE, command=command, idle_timeout=0, hard_timeout=0, priority=priority, out_port=out_port) def _list_tunnel_port(self, dp, remote_dpids): dpid = dp.id tunnel_ports = [] for other_dpid in remote_dpids: if other_dpid == dpid: continue other_dp = self.dpset.get(other_dpid) if other_dp is None: continue try: port_no = self.tunnels.get_port(dpid, other_dpid) except ryu_exc.PortNotFound: continue if not self._link_is_up(dp, port_no): continue tunnel_ports.append(port_no) return tunnel_ports def _link_is_up(self, dp, port_no): return _link_is_up(self.dpset, dp, port_no) def _port_is_active(self, network_id, dp, nw_port): return (nw_port.network_id == network_id and nw_port.mac_address is not None and self._link_is_up(dp, nw_port.port_no)) def _tunnel_port_with_mac(self, remote_dp, dpid, network_id, port_no, mac_address): tunnel_ports = [] ports = self.nw.get_ports_with_mac(network_id, mac_address).copy() ports.discard((dpid, port_no)) assert len(ports) <= 1 for port in ports: try: tunnel_port_no = self.tunnels.get_port(remote_dp.id, port.dpid) except ryu_exc.PortNotFound: pass else: if self._link_is_up(remote_dp, tunnel_port_no): tunnel_ports.append(tunnel_port_no) assert len(tunnel_ports) <= 1 return tunnel_ports def _vm_port_add(self, ev): dpid = ev.dpid dp = self.dpset.get(dpid) assert dp is not None ofproto = dp.ofproto ofproto_parser = dp.ofproto_parser mac_address = ev.mac_address network_id = ev.network_id tunnel_key = ev.tunnel_key remote_dpids = self.nw.get_dpids(network_id) remote_dpids.remove(dpid) # LOCAL_OUT_TABLE: unicast # live-migration: there can be two ports with same mac_address ports = self.nw.get_ports(dpid, network_id, mac_address) assert ev.port_no in [port.port_no for port in ports] rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address) actions = [ofproto_parser.OFPActionOutput(port.port_no) for port in ports if self._link_is_up(dp, port.port_no)] self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_ADD, self.LOCAL_OUT_PRI_MAC, actions) # LOCAL_OUT_TABLE: broad cast rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST) actions = [] for port in self.nw.get_ports(dpid): if not self._port_is_active(network_id, dp, port): continue actions.append(ofproto_parser.OFPActionOutput(port.port_no)) first_instance = (len(actions) == 1) assert actions if first_instance: command = ofproto.OFPFC_ADD else: command = ofproto.OFPFC_MODIFY_STRICT self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, command, self.LOCAL_OUT_PRI_BROADCAST, actions) # LOCAL_OUT_TABLE: multicast TODO:XXX # LOCAL_OUT_TABLE: catch-all drop if first_instance: rule = cls_rule(tun_id=tunnel_key) self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_ADD, self.LOCAL_OUT_PRI_DROP, []) # TUNNEL_OUT_TABLE: unicast mac_to_ports = collections.defaultdict(set) for remote_dpid in remote_dpids: remote_dp = self.dpset.get(remote_dpid) if remote_dp is None: continue try: tunnel_port_no = self.tunnels.get_port(dpid, remote_dpid) except ryu_exc.PortNotFound: continue if not self._link_is_up(dp, tunnel_port_no): continue for port in self.nw.get_ports(remote_dpid): if not self._port_is_active(network_id, remote_dp, port): continue # TUNNEL_OUT_TABLE: unicast # live-migration: there can be more than one tunnel-ports that # have a given mac address mac_to_ports[port.mac_address].add(tunnel_port_no) if first_instance: # SRC_TABLE: TUNNEL-port: resubmit to LOAL_OUT_TABLE rule = cls_rule(in_port=tunnel_port_no, tun_id=tunnel_key) resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = [resubmit_table] self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD, self.SRC_PRI_TUNNEL_PASS, actions) # TUNNEL_OUT_TABLE: unicast for remote_mac_address, tunnel_ports in mac_to_ports.items(): rule = cls_rule(tun_id=tunnel_key, dl_dst=remote_mac_address) outputs = [ofproto_parser.OFPActionOutput(tunnel_port_no) for tunnel_port_no in tunnel_ports] resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = outputs + [resubmit_table] self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC, actions) if first_instance: # TUNNEL_OUT_TABLE: catch-all(resubmit to LOCAL_OUT_TABLE) rule = cls_rule(tun_id=tunnel_key) resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = [resubmit_table] self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_PASS, actions) # TUNNEL_OUT_TABLE: broadcast rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST) actions = [ofproto_parser.OFPActionOutput(tunnel_port_no) for tunnel_port_no in self._list_tunnel_port(dp, remote_dpids)] resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions.append(resubmit_table) self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_BROADCAST, actions) # TUNNEL_OUT_TABLE: multicast TODO:XXX # SRC_TABLE: VM-port unicast dp.send_barrier() rule = cls_rule(in_port=ev.port_no, dl_src=mac_address) set_tunnel = ofproto_parser.NXActionSetTunnel(tunnel_key) resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.TUNNEL_OUT_TABLE) actions = [set_tunnel, resubmit_table] self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD, self.SRC_PRI_MAC, actions) # SRC_TABLE: VM-port catch-call drop rule = cls_rule(in_port=ev.port_no) self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD, self.SRC_PRI_DROP, []) # remote dp for remote_dpid in remote_dpids: remote_dp = self.dpset.get(remote_dpid) if remote_dp is None: continue try: tunnel_port_no = self.tunnels.get_port(remote_dpid, dpid) except ryu_exc.PortNotFound: continue if not self._link_is_up(remote_dp, tunnel_port_no): continue remote_ofproto = remote_dp.ofproto remote_ofproto_parser = remote_dp.ofproto_parser # TUNNEL_OUT_TABLE: unicast # live-migration: there can be another port that has # same mac address tunnel_ports = self._tunnel_port_with_mac(remote_dp, dpid, network_id, ev.port_no, mac_address) tunnel_ports.append(tunnel_port_no) rule = cls_rule(tun_id=ev.tunnel_key, dl_dst=mac_address) outputs = [remote_ofproto_parser.OFPActionOutput(port_no) for port_no in tunnel_ports] resubmit_table = remote_ofproto_parser.NXActionResubmitTable( in_port=remote_ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = outputs + [resubmit_table] self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE, remote_ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC, actions) if not first_instance: continue # SRC_TABLE: TUNNEL-port rule = cls_rule(in_port=tunnel_port_no, tun_id=ev.tunnel_key) resubmit_table = remote_ofproto_parser.NXActionResubmitTable( in_port=remote_ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = [resubmit_table] self.send_flow_mod(remote_dp, rule, self.SRC_TABLE, remote_ofproto.OFPFC_ADD, self.SRC_PRI_TUNNEL_PASS, actions) # TUNNEL_OUT_TABLE: broadcast rule = cls_rule(tun_id=ev.tunnel_key, dl_dst=mac.BROADCAST) tunnel_ports = self._list_tunnel_port(remote_dp, remote_dpids) if tunnel_port_no not in tunnel_ports: tunnel_ports.append(tunnel_port_no) actions = [remote_ofproto_parser.OFPActionOutput(port_no) for port_no in tunnel_ports] if len(actions) == 1: command = remote_dp.ofproto.OFPFC_ADD else: command = remote_dp.ofproto.OFPFC_MODIFY_STRICT resubmit_table = remote_ofproto_parser.NXActionResubmitTable( in_port=remote_ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions.append(resubmit_table) self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE, command, self.TUNNEL_OUT_PRI_BROADCAST, actions) # TUNNEL_OUT_TABLE: multicast TODO:XXX def _vm_port_del(self, ev): dpid = ev.dpid dp = self.dpset.get(dpid) assert dp is not None ofproto = dp.ofproto ofproto_parser = dp.ofproto_parser mac_address = ev.mac_address network_id = ev.network_id tunnel_key = ev.tunnel_key local_ports = [] for port in self.nw.get_ports(dpid): if port.port_no == ev.port_no: continue if not self._port_is_active(network_id, dp, port): continue local_ports.append(port.port_no) last_instance = not local_ports # SRC_TABLE: VM-port unicast and catch-call rule = cls_rule(in_port=ev.port_no) self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_DELETE, ofproto.OFP_DEFAULT_PRIORITY, []) # priority is ignored if last_instance: # SRC_TABLE: TUNNEL-port: all tunnel matching rule = cls_rule(tun_id=tunnel_key) self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_DELETE, ofproto.OFP_DEFAULT_PRIORITY, []) # priority is ignored # TUNNEL_OUT_TABLE: (tun_id & dl_dst) and tun_id rule = cls_rule(tun_id=tunnel_key) self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, ofproto.OFPFC_DELETE, ofproto.OFP_DEFAULT_PRIORITY, []) # priority is ignored # LOCAL_OUT: tun_id catch-all drop rule rule = cls_rule(tun_id=tunnel_key) self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_DELETE, ofproto.OFP_DEFAULT_PRIORITY, []) # priority is ignored else: # LOCAL_OUT_TABLE: unicast # live-migration: there can be two ports with same mac_address ports = self.nw.get_ports(dpid, network_id, mac_address) port_nos = [port.port_no for port in ports if (port.port_no != ev.port_no and self._link_is_up(dp, port.port_no))] rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address) if port_nos: assert len(ports) == 1 actions = [ofproto_parser.OFPActionOutput(port_no) for port_no in port_nos] self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_MODIFY_STRICT, self.LOCAL_OUT_PRI_MAC, actions) else: self.send_flow_del(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_DELETE_STRICT, self.LOCAL_OUT_PRI_MAC, ev.port_no) # LOCAL_OUT_TABLE: broadcast rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST) actions = [ofproto_parser.OFPActionOutput(port_no) for port_no in local_ports] self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_MODIFY_STRICT, self.LOCAL_OUT_PRI_BROADCAST, actions) # LOCAL_OUT_TABLE: multicast TODO:XXX # remote dp remote_dpids = self.nw.get_dpids(ev.network_id) if dpid in remote_dpids: remote_dpids.remove(dpid) for remote_dpid in remote_dpids: remote_dp = self.dpset.get(remote_dpid) if remote_dp is None: continue try: tunnel_port_no = self.tunnels.get_port(remote_dpid, dpid) except ryu_exc.PortNotFound: continue if not self._link_is_up(remote_dp, tunnel_port_no): continue remote_ofproto = remote_dp.ofproto remote_ofproto_parser = remote_dp.ofproto_parser if last_instance: # SRC_TABLE: TUNNEL-port rule = cls_rule(in_port=tunnel_port_no, tun_id=tunnel_key) self.send_flow_del(remote_dp, rule, self.SRC_TABLE, remote_ofproto.OFPFC_DELETE_STRICT, self.SRC_PRI_TUNNEL_PASS, None) # SRC_TABLE: TUNNEL-port catch-call drop rule rule = cls_rule(in_port=tunnel_port_no) self.send_flow_del(remote_dp, rule, self.SRC_TABLE, remote_ofproto.OFPFC_DELETE_STRICT, self.SRC_PRI_TUNNEL_DROP, None) # TUNNEL_OUT_TABLE: broadcast # tunnel_ports.remove(tunnel_port_no) rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST) tunnel_ports = self._list_tunnel_port(remote_dp, remote_dpids) assert tunnel_port_no not in tunnel_ports actions = [remote_ofproto_parser.OFPActionOutput(port_no) for port_no in tunnel_ports] if not actions: command = remote_dp.ofproto.OFPFC_DELETE_STRICT else: command = remote_dp.ofproto.OFPFC_MODIFY_STRICT resubmit_table = \ remote_ofproto_parser.NXActionResubmitTable( in_port=remote_ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions.append(resubmit_table) self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE, command, self.TUNNEL_OUT_PRI_BROADCAST, actions) # TUNNEL_OUT_TABLE: unicast # live-migration: there can be more than one (dpid, port_no) # with a given mac address tunnel_ports = self._tunnel_port_with_mac(remote_dp, dpid, network_id, ev.port_no, mac_address) rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address) if tunnel_ports: outputs = [remote_ofproto_parser.OFPActionOutput(port_no) for port_no in tunnel_ports] resubmit_table = remote_ofproto_parser.NXActionResubmitTable( in_port=remote_ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = outputs + [resubmit_table] self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE, remote_ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC, actions) else: self.send_flow_del(remote_dp, rule, self.TUNNEL_OUT_TABLE, remote_ofproto.OFPFC_DELETE_STRICT, self.TUNNEL_OUT_PRI_MAC, tunnel_port_no) # TODO:XXX multicast def _get_vm_ports(self, dpid): ports = collections.defaultdict(list) for port in self.nw.get_ports(dpid): if port.network_id in RESERVED_NETWORK_IDS: continue ports[port.network_id].append(port) return ports def _tunnel_port_add(self, ev): dpid = ev.dpid dp = self.dpset.get(dpid) ofproto = dp.ofproto ofproto_parser = dp.ofproto_parser remote_dpid = ev.remote_dpid local_ports = self._get_vm_ports(dpid) remote_ports = self._get_vm_ports(remote_dpid) # SRC_TABLE: TUNNEL-port catch-call drop rule # ingress flow from this tunnel port: remote -> tunnel port # drop if unknown tunnel_key rule = cls_rule(in_port=ev.port_no) self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD, self.SRC_PRI_TUNNEL_DROP, []) # SRC_TABLE: TUNNEL-port: pass if known tunnel_key for network_id in local_ports: try: tunnel_key = self.tunnels.get_key(network_id) except tunnels.TunnelKeyNotFound: continue if network_id not in remote_ports: continue rule = cls_rule(in_port=ev.port_no, tun_id=tunnel_key) resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = [resubmit_table] self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD, self.SRC_PRI_TUNNEL_PASS, actions) # egress flow into this tunnel port: vm port -> tunnel port -> remote for network_id in local_ports: try: tunnel_key = self.tunnels.get_key(network_id) except tunnels.TunnelKeyNotFound: continue ports = remote_ports.get(network_id) if ports is None: continue # TUNNEL_OUT_TABLE: unicast for port in ports: if port.mac_address is None: continue rule = cls_rule(tun_id=tunnel_key, dl_dst=port.mac_address) output = ofproto_parser.OFPActionOutput(ev.port_no) resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions = [output, resubmit_table] self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC, actions) # TUNNEL_OUT_TABLE: broadcast remote_dpids = self.nw.get_dpids(network_id) remote_dpids.remove(dpid) rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST) tunnel_ports = self._list_tunnel_port(dp, remote_dpids) if ev.port_no not in tunnel_ports: tunnel_ports.append(ev.port_no) actions = [ofproto_parser.OFPActionOutput(port_no) for port_no in tunnel_ports] resubmit_table = ofproto_parser.NXActionResubmitTable( in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE) actions.append(resubmit_table) if len(tunnel_ports) == 1: command = ofproto.OFPFC_ADD else: command = ofproto.OFPFC_MODIFY_STRICT self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE, command, self.TUNNEL_OUT_PRI_BROADCAST, actions) # TUNNEL_OUT_TABLE: multicast TODO:XXX def _tunnel_port_del(self, ev): # almost nothing to do because all flow related to this tunnel port # should be handled by self._vm_port_del() as tunnel port deletion # follows vm port deletion. # the tunnel port is deleted if and only if no instance of same # tenants resides in both nodes of tunnel end points. self.logger.debug('tunnel_port_del %s', ev) dp = self.dpset.get(ev.dpid) # SRC_TABLE: TUNNEL-port catch-all drop rule rule = cls_rule(in_port=ev.port_no) self.send_flow_mod(dp, rule, self.SRC_TABLE, dp.ofproto.OFPFC_DELETE_STRICT, self.SRC_PRI_TUNNEL_DROP, []) @handler.set_ev_handler(PortSet.EventTunnelKeyDel) def tunnel_key_del_handler(self, ev): self.logger.debug('tunnel_key_del ev %s', ev) @handler.set_ev_handler(PortSet.EventVMPort) def vm_port_handler(self, ev): self.logger.debug('vm_port ev %s', ev) if ev.add_del: self._vm_port_add(ev) else: self._vm_port_del(ev) @handler.set_ev_handler(PortSet.EventTunnelPort) def tunnel_port_handler(self, ev): self.logger.debug('tunnel_port ev %s', ev) if ev.add_del: self._tunnel_port_add(ev) else: self._tunnel_port_del(ev) @handler.set_ev_handler(ofp_event.EventOFPPacketIn) def packet_in_handler(self, ev): # for debug msg = ev.msg self.logger.debug('packet in ev %s msg %s', ev, ev.msg) if msg.buffer_id != msg.datapath.ofproto.OFP_NO_BUFFER: msg.datapath.send_packet_out(msg.buffer_id, msg.in_port, [])