# 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. """ Flow table updater for OpenStack integration. Despite of the name, this isn't GRE specific. """ 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, [])