diff options
-rw-r--r-- | ryu/app/gre_tunnel.py | 908 |
1 files changed, 908 insertions, 0 deletions
diff --git a/ryu/app/gre_tunnel.py b/ryu/app/gre_tunnel.py new file mode 100644 index 00000000..2f677416 --- /dev/null +++ b/ryu/app/gre_tunnel.py @@ -0,0 +1,908 @@ +# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp> +# +# 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 collections import defaultdict + +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 (dispatcher, + dpset, + event, + handler, + handler_utils, + network, + ofp_event, + tunnels) +from ryu.ofproto import nx_match +from ryu.lib import dpid as dpid_lib +from ryu.lib import mac + + +LOG = logging.getLogger(__name__) + + +def _is_reserved_port(ofproto, port_no): + return port_no > ofproto.OFPP_MAX + + +# 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(EventTunnelKeyDel, self).__init__() + self.tunnel_key = tunnel_key + + +class EventPortBase(event.EventBase): + def __init__(self, dpid, port_no): + super(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(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 %s port_no %d ' + 'network_id %s tunnel_key %s mac %s add_del %s>' % + (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(EventTunnelPort, self).__init__(dpid, port_no) + self.remote_dpid = remote_dpid + self.add_del = add_del + + def __str__(self): + return ('EventTunnelPort<dpid %s port_no %d remote_dpid %s ' + 'add_del %s>' % + (dpid_lib.dpid_to_str(self.dpid), self.port_no, + dpid_lib.dpid_to_str(self.remote_dpid), self.add_del)) + + +QUEUE_NAME_PORT_SET_EV = 'port_set_event' +DISPATCHER_NAME_PORT_SET_EV = 'port_set_event' +PORT_SET_EV_DISPATCHER = dispatcher.EventDispatcher( + DISPATCHER_NAME_PORT_SET_EV) + + +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(handler_utils.QueueSerializer): + _EV_CLSES = ( + (dpset.EventDP, dpset.DPSET_EV_DISPATCHER), + (ofp_event.EventOFPPacketIn, handler.MAIN_DISPATCHER), + ) + + def __init__(self, **kwargs): + self.nw = kwargs['network'] + self.tunnels = kwargs['tunnels'] + self.dpset = kwargs['dpset'] + + super(PortSet, self).__init__(QUEUE_NAME_PORT_SET_EV, + PORT_SET_EV_DISPATCHER, self._EV_CLSES) + + 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._ev_q.queue(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._ev_q.queue(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): + 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._ev_q.queue(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, + network.NETWORK_TENANT_EV_DISPATCHER) + 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 + for (dpid, port_no) in self.nw.list_ports(network_id): + self._vm_port_mac_handler(dpid, port_no, network_id, False) + self._tunnel_key_del(tunnel_key) + + @handler.set_ev_cls(network.EventNetworkPort, + network.NETWORK_TENANT_EV_DISPATCHER) + 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, + network.NETWORK_TENANT_EV_DISPATCHER) + 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, + tunnels.TUNNEL_EV_DISPATCHER) + def tunnel_key_add_handler(self, ev): + for (dpid, port_no) in self.nw.list_ports(ev.network_id): + self._vm_port_mac_handler(dpid, port_no, ev.network_id, True) + + @handler.set_ev_cls(tunnels.EventTunnelKeyDel, + tunnels.TUNNEL_EV_DISPATCHER) + def tunnel_key_del_handler(self, ev): + network_id = ev.network_id + for (dpid, port_no) in self.nw.list_ports(network_id): + self._vm_port_mac_handler(dpid, port_no, network_id, False) + if self.nw.has_networks(network_id): + self._tunnel_key_del(ev.tunnel_key) + + @handler.set_ev_cls(tunnels.EventTunnelPort, tunnels.TUNNEL_EV_DISPATCHER) + def tunnel_port_handler(self, ev): + self._port_handler(ev.dpid, ev.port_no, ev.add_del) + + @handler.set_ev_cls(dpset.EventDP, dpset.DPSET_EV_DISPATCHER) + def dp_handler(self, ev): + enter_leave = ev.enter_leave + if not enter_leave: + # TODO:XXX + # What to do on datapath disconnection? + LOG.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, dpset.DPSET_EV_DISPATCHER) + def port_add_handler(self, ev): + self._port_handler(ev.dp.id, ev.port.port_no, True) + + @handler.set_ev_cls(dpset.EventPortDelete, dpset.DPSET_EV_DISPATCHER) + def port_del_handler(self, ev): + self._port_handler(ev.dp.id, ev.port.port_no, False) + + @handler.set_ev_cls(dpset.EventPortModify, dpset.DPSET_EV_DISPATCHER) + 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)) + + +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 + macth 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 = { + 'netowrk': 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) + + # TODO: track active vm/tunnel ports + + @handler.set_ev_cls(dpset.EventDP, PORT_SET_EV_DISPATCHER) + def dp_handler(self, ev): + if not ev.enter_leave: + 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 _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 + rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address) + actions = [ofproto_parser.OFPActionOutput(ev.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 (port.network_id != network_id or port.mac_address is None): + continue + if not self._link_is_up(dp, port.port_no): + 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 + 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 port.network_id != network_id or port.mac_address is None: + continue + if not self._link_is_up(remote_dp, port.port_no): + continue + # TUNNEL_OUT_TABLE: unicast + rule = cls_rule(tun_id=tunnel_key, dl_dst=port.mac_address) + output = ofproto_parser.OFPActionOutput(tunnel_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) + + 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) + + 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 + rule = cls_rule(tun_id=ev.tunnel_key, dl_dst=mac_address) + output = remote_ofproto_parser.OFPActionOutput(tunnel_port_no) + resubmit_table = remote_ofproto_parser.NXActionResubmitTable( + in_port=remote_ofproto.OFPP_IN_PORT, + table=self.LOCAL_OUT_TABLE) + actions = [output, resubmit_table] + self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE, + remote_ofproto.OFPFC_ADD, + self.TUNNEL_OUT_PRI_MAC, actions) + + if first_instance: + # 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) + else: + continue + + # 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 (port.network_id != network_id or port.mac_address is None): + continue + if not self._link_is_up(dp, port.port_no): + 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 + rule = cls_rule(tun_id=tunnel_key, dl_src=mac_address) + 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) + 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, tun_id=tunnel_key) + 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 + rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address) + 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 = 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. + LOG.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_cls(EventTunnelKeyDel, PORT_SET_EV_DISPATCHER) + def tunnel_key_del_handler(self, ev): + LOG.debug('tunnel_key_del ev %s', ev) + + @handler.set_ev_cls(EventVMPort, PORT_SET_EV_DISPATCHER) + def vm_port_handler(self, ev): + LOG.debug('vm_port ev %s', ev) + if ev.add_del: + self._vm_port_add(ev) + else: + self._vm_port_del(ev) + + @handler.set_ev_cls(EventTunnelPort, PORT_SET_EV_DISPATCHER) + def tunnel_port_handler(self, ev): + LOG.debug('tunnel_port ev %s', ev) + if ev.add_del: + self._tunnel_port_add(ev) + else: + self._tunnel_port_del(ev) + + @handler.set_ev_cls(ofp_event.EventOFPPacketIn, PORT_SET_EV_DISPATCHER) + def packet_in_handler(self, ev): + # for debug + msg = ev.msg + LOG.debug('packet in ev %s msg %s', ev, ev.msg) + if msg.buffer_id != 0xffffffff: # TODO:XXX use constant instead of -1 + msg.datapath.send_packet_out(msg.buffer_id, msg.in_port, []) |