os-ken/ryu/app/gre_tunnel.py
Yoshihiro Kaneko 2eb59a09ff doc: add components page
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>
2014-05-27 20:24:50 +09:00

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, [])