ff5e007b0d
calling send_delete_all_flows function in the core (for all applications) is a bad idea since some applications don't want such (e.g. failover). send_delete_all_flows function doesn't work for OF1.2. nxm code needs the specific code for each OF version. I really don't want to do so. So let's not call this function in the core. At this point, it's impossible to write applications that can work with any OF versions (incluing nxm). Let applications do whatever they want. We should remove OF protocol version dependent functions (send_delete_all_flows, send_flow_mod, and send_flow_del). Also trying to enable NXM blindly is very bad for buggy hardware switches. But they are not called in the core code so I leave them alone for now. Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
354 lines
14 KiB
Python
354 lines
14 KiB
Python
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
|
|
# Copyright (C) 2011, 2012 Isaku Yamahata <yamahata at valinux co 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
|
|
import struct
|
|
|
|
from ryu.app.rest_nw_id import NW_ID_UNKNOWN, NW_ID_EXTERNAL
|
|
from ryu.base import app_manager
|
|
from ryu.exception import MacAddressDuplicated
|
|
from ryu.exception import PortUnknown
|
|
from ryu.controller import dpset
|
|
from ryu.controller import mac_to_network
|
|
from ryu.controller import mac_to_port
|
|
from ryu.controller import network
|
|
from ryu.controller import ofp_event
|
|
from ryu.controller.handler import MAIN_DISPATCHER
|
|
from ryu.controller.handler import CONFIG_DISPATCHER
|
|
from ryu.controller.handler import set_ev_cls
|
|
from ryu.ofproto import nx_match
|
|
from ryu.lib.mac import haddr_to_str
|
|
from ryu.lib import mac
|
|
|
|
|
|
LOG = logging.getLogger('ryu.app.simple_isolation')
|
|
|
|
|
|
class SimpleIsolation(app_manager.RyuApp):
|
|
_CONTEXTS = {
|
|
'network': network.Network,
|
|
'dpset': dpset.DPSet,
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SimpleIsolation, self).__init__(*args, **kwargs)
|
|
self.nw = kwargs['network']
|
|
self.dpset = kwargs['dpset']
|
|
self.mac2port = mac_to_port.MacToPortTable()
|
|
self.mac2net = mac_to_network.MacToNetwork(self.nw)
|
|
|
|
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
def switch_features_handler(self, ev):
|
|
msg = ev.msg
|
|
datapath = msg.datapath
|
|
|
|
datapath.send_delete_all_flows()
|
|
datapath.send_barrier()
|
|
|
|
self.mac2port.dpid_add(ev.msg.datapath_id)
|
|
self.nw.add_datapath(ev.msg)
|
|
|
|
@set_ev_cls(ofp_event.EventOFPBarrierReply)
|
|
def barrier_reply_handler(self, ev):
|
|
LOG.debug('barrier reply ev %s msg %s', ev, ev.msg)
|
|
|
|
@staticmethod
|
|
def _modflow_and_send_packet(msg, src, dst, actions):
|
|
datapath = msg.datapath
|
|
ofproto = datapath.ofproto
|
|
|
|
#
|
|
# install flow and then send packet
|
|
#
|
|
rule = nx_match.ClsRule()
|
|
rule.set_in_port(msg.in_port)
|
|
rule.set_dl_dst(dst)
|
|
rule.set_dl_src(src)
|
|
datapath.send_flow_mod(
|
|
rule=rule, cookie=0, command=datapath.ofproto.OFPFC_ADD,
|
|
idle_timeout=0, hard_timeout=0,
|
|
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
|
buffer_id=0xffffffff, out_port=ofproto.OFPP_NONE,
|
|
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
|
|
|
|
datapath.send_packet_out(msg.buffer_id, msg.in_port, actions)
|
|
|
|
def _forward_to_nw_id(self, msg, src, dst, nw_id, out_port):
|
|
assert out_port is not None
|
|
datapath = msg.datapath
|
|
|
|
if not self.nw.same_network(datapath.id, nw_id, out_port,
|
|
NW_ID_EXTERNAL):
|
|
LOG.debug('packet is blocked src %s dst %s '
|
|
'from %d to %d on datapath %d',
|
|
haddr_to_str(src), haddr_to_str(dst),
|
|
msg.in_port, out_port, datapath.id)
|
|
return
|
|
|
|
LOG.debug("learned dpid %s in_port %d out_port %d src %s dst %s",
|
|
datapath.id, msg.in_port, out_port,
|
|
haddr_to_str(src), haddr_to_str(dst))
|
|
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
self._modflow_and_send_packet(msg, src, dst, actions)
|
|
|
|
def _flood_to_nw_id(self, msg, src, dst, nw_id):
|
|
datapath = msg.datapath
|
|
actions = []
|
|
LOG.debug("dpid %s in_port %d src %s dst %s ports %s",
|
|
datapath.id, msg.in_port,
|
|
haddr_to_str(src), haddr_to_str(dst),
|
|
self.nw.dpids.get(datapath.id, {}).items())
|
|
for port_no in self.nw.filter_ports(datapath.id, msg.in_port,
|
|
nw_id, NW_ID_EXTERNAL):
|
|
LOG.debug("port_no %s", port_no)
|
|
actions.append(datapath.ofproto_parser.OFPActionOutput(port_no))
|
|
self._modflow_and_send_packet(msg, src, dst, actions)
|
|
|
|
def _learned_mac_or_flood_to_nw_id(self, msg, src, dst,
|
|
dst_nw_id, out_port):
|
|
if out_port is not None:
|
|
self._forward_to_nw_id(msg, src, dst, dst_nw_id, out_port)
|
|
else:
|
|
self._flood_to_nw_id(msg, src, dst, dst_nw_id)
|
|
|
|
def _modflow_and_drop_packet(self, msg, src, dst):
|
|
self._modflow_and_send_packet(msg, src, dst, [])
|
|
|
|
def _drop_packet(self, msg):
|
|
datapath = msg.datapath
|
|
datapath.send_packet_out(msg.buffer_id, msg.in_port, [])
|
|
|
|
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
def packet_in_handler(self, ev):
|
|
# LOG.debug('packet in ev %s msg %s', ev, ev.msg)
|
|
msg = ev.msg
|
|
datapath = msg.datapath
|
|
|
|
dst, src, _eth_type = struct.unpack_from('!6s6sH', buffer(msg.data), 0)
|
|
|
|
try:
|
|
port_nw_id = self.nw.get_network(datapath.id, msg.in_port)
|
|
except PortUnknown:
|
|
port_nw_id = NW_ID_UNKNOWN
|
|
|
|
if port_nw_id != NW_ID_UNKNOWN:
|
|
# Here it is assumed that the
|
|
# (port <-> network id)/(mac <-> network id) relationship
|
|
# is stable once the port is created. The port will be destroyed
|
|
# before assigning new network id to the given port.
|
|
# This is correct nova-network/nova-compute.
|
|
try:
|
|
# allow external -> known nw id change
|
|
self.mac2net.add_mac(src, port_nw_id, NW_ID_EXTERNAL)
|
|
except MacAddressDuplicated:
|
|
LOG.warn('mac address %s is already in use.'
|
|
' So (dpid %s, port %s) can not use it',
|
|
haddr_to_str(src), datapath.id, msg.in_port)
|
|
#
|
|
# should we install drop action pro-actively for future?
|
|
#
|
|
self._drop_packet(msg)
|
|
return
|
|
|
|
old_port = self.mac2port.port_add(datapath.id, msg.in_port, src)
|
|
if old_port is not None and old_port != msg.in_port:
|
|
# We really overwrite already learned mac address.
|
|
# So discard already installed stale flow entry which conflicts
|
|
# new port.
|
|
rule = nx_match.ClsRule()
|
|
rule.set_dl_dst(src)
|
|
datapath.send_flow_mod(rule=rule, cookie=0,
|
|
command=datapath.ofproto.OFPFC_DELETE, idle_timeout=0,
|
|
hard_timeout=0, priority=datapath.ofproto.OFP_DEFAULT_PRIORITY,
|
|
out_port=old_port)
|
|
|
|
# to make sure the old flow entries are purged.
|
|
datapath.send_barrier()
|
|
|
|
src_nw_id = self.mac2net.get_network(src, NW_ID_UNKNOWN)
|
|
dst_nw_id = self.mac2net.get_network(dst, NW_ID_UNKNOWN)
|
|
|
|
# we handle multicast packet as same as broadcast
|
|
broadcast = (dst == mac.BROADCAST) or mac.is_multicast(dst)
|
|
out_port = self.mac2port.port_get(datapath.id, dst)
|
|
|
|
#
|
|
# there are several combinations:
|
|
# in_port: known nw_id, external, unknown nw,
|
|
# src mac: known nw_id, external, unknown nw,
|
|
# dst mac: known nw_id, external, unknown nw, and broadcast/multicast
|
|
# where known nw_id: is quantum network id
|
|
# external: means that these ports are connected to outside
|
|
# unknown nw: means that we don't know this port is bounded to
|
|
# specific nw_id or external
|
|
# broadcast: the destination mac address is broadcast address
|
|
# (or multicast address)
|
|
#
|
|
# Can the following logic be refined/shortened?
|
|
#
|
|
|
|
# When NW_ID_UNKNOWN is found, registering ports might be delayed.
|
|
# So just drop only this packet and not install flow entry.
|
|
# It is expected that when next packet arrives, the port is registers
|
|
# with some network id
|
|
|
|
if port_nw_id != NW_ID_EXTERNAL and port_nw_id != NW_ID_UNKNOWN:
|
|
if broadcast:
|
|
# flood to all ports of external or src_nw_id
|
|
self._flood_to_nw_id(msg, src, dst, src_nw_id)
|
|
elif src_nw_id == NW_ID_EXTERNAL:
|
|
self._modflow_and_drop_packet(msg, src, dst)
|
|
return
|
|
elif src_nw_id == NW_ID_UNKNOWN:
|
|
self._drop_packet(msg)
|
|
return
|
|
else:
|
|
# src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN:
|
|
#
|
|
# try learned mac check if the port is net_id
|
|
# or
|
|
# flood to all ports of external or src_nw_id
|
|
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
|
src_nw_id, out_port)
|
|
|
|
elif port_nw_id == NW_ID_EXTERNAL:
|
|
if src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN:
|
|
if broadcast:
|
|
# flood to all ports of external or src_nw_id
|
|
self._flood_to_nw_id(msg, src, dst, src_nw_id)
|
|
elif (dst_nw_id != NW_ID_EXTERNAL and
|
|
dst_nw_id != NW_ID_UNKNOWN):
|
|
if src_nw_id == dst_nw_id:
|
|
# try learned mac
|
|
# check if the port is external or same net_id
|
|
# or
|
|
# flood to all ports of external or src_nw_id
|
|
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
|
src_nw_id,
|
|
out_port)
|
|
else:
|
|
# should not occur?
|
|
LOG.debug("should this case happen?")
|
|
self._drop_packet(msg)
|
|
elif dst_nw_id == NW_ID_EXTERNAL:
|
|
# try learned mac
|
|
# or
|
|
# flood to all ports of external or src_nw_id
|
|
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
|
src_nw_id, out_port)
|
|
else:
|
|
assert dst_nw_id == NW_ID_UNKNOWN
|
|
LOG.debug("Unknown dst_nw_id")
|
|
self._drop_packet(msg)
|
|
elif src_nw_id == NW_ID_EXTERNAL:
|
|
self._modflow_and_drop_packet(msg, src, dst)
|
|
else:
|
|
# should not occur?
|
|
assert src_nw_id == NW_ID_UNKNOWN
|
|
self._drop_packet(msg)
|
|
else:
|
|
# drop packets
|
|
assert port_nw_id == NW_ID_UNKNOWN
|
|
self._drop_packet(msg)
|
|
# LOG.debug("Unknown port_nw_id")
|
|
|
|
def _port_add(self, ev):
|
|
#
|
|
# delete flows entries that matches with
|
|
# dl_dst == broadcast/multicast
|
|
# and dl_src = network id if network id of this port is known
|
|
# to send broadcast packet to this newly added port.
|
|
#
|
|
# Openflow v1.0 doesn't support masked match of dl_dst,
|
|
# so delete all flow entries. It's inefficient, though.
|
|
#
|
|
msg = ev.msg
|
|
datapath = msg.datapath
|
|
|
|
datapath.send_delete_all_flows()
|
|
datapath.send_barrier()
|
|
self.nw.port_added(datapath, msg.desc.port_no)
|
|
|
|
def _port_del(self, ev):
|
|
# free mac addresses associated to this VM port,
|
|
# and delete related flow entries for later reuse of mac address
|
|
|
|
dps_needs_barrier = set()
|
|
|
|
msg = ev.msg
|
|
datapath = msg.datapath
|
|
datapath_id = datapath.id
|
|
port_no = msg.desc.port_no
|
|
|
|
rule = nx_match.ClsRule()
|
|
rule.set_in_port(port_no)
|
|
datapath.send_flow_del(rule=rule, cookie=0)
|
|
|
|
rule = nx_match.ClsRule()
|
|
datapath.send_flow_del(rule=rule, cookie=0, out_port=port_no)
|
|
dps_needs_barrier.add(datapath)
|
|
|
|
try:
|
|
port_nw_id = self.nw.get_network(datapath_id, port_no)
|
|
except PortUnknown:
|
|
# race condition between rest api delete port
|
|
# and openflow port deletion ofp_event
|
|
pass
|
|
else:
|
|
if port_nw_id in (NW_ID_UNKNOWN, NW_ID_EXTERNAL):
|
|
datapath.send_barrier()
|
|
return
|
|
|
|
for mac_ in self.mac2port.mac_list(datapath_id, port_no):
|
|
for (_dpid, dp) in self.dpset.get_all():
|
|
if self.mac2port.port_get(dp.id, mac_) is None:
|
|
continue
|
|
|
|
rule = nx_match.ClsRule()
|
|
rule.set_dl_src(mac_)
|
|
dp.send_flow_del(rule=rule, cookie=0)
|
|
|
|
rule = nx_match.ClsRule()
|
|
rule.set_dl_dst(mac_)
|
|
dp.send_flow_del(rule=rule, cookie=0)
|
|
dps_needs_barrier.add(dp)
|
|
|
|
self.mac2port.mac_del(dp.id, mac_)
|
|
|
|
self.mac2net.del_mac(mac_)
|
|
|
|
self.nw.port_deleted(datapath.id, port_no)
|
|
|
|
for dp in dps_needs_barrier:
|
|
dp.send_barrier()
|
|
|
|
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
|
|
def port_status_handler(self, ev):
|
|
msg = ev.msg
|
|
reason = msg.reason
|
|
ofproto = msg.datapath.ofproto
|
|
|
|
if reason == ofproto.OFPPR_ADD:
|
|
self._port_add(ev)
|
|
elif reason == ofproto.OFPPR_DELETE:
|
|
self._port_del(ev)
|
|
else:
|
|
assert reason == ofproto.OFPPR_MODIFY
|
|
|
|
@set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER)
|
|
def barrier_replay_handler(self, ev):
|
|
pass
|