2eb59a09ff
port from wiki. dummy quantumclient is necessary to import ryu.app.quantum_adapter by sphinx.ext.autodoc. Signed-off-by: Yoshihiro Kaneko <ykaneko0929@gmail.com>
981 lines
39 KiB
Python
981 lines
39 KiB
Python
# 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.
|
|
|
|
# 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 %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(PortSet.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))
|
|
|
|
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, [])
|