# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation. # Copyright (C) 2011, 2012 Isaku Yamahata # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. 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 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) @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=ofproto.OFP_NO_BUFFER, 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): self.logger.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 self.logger.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 = [] self.logger.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): self.logger.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): # self.logger.debug('packet in ev %s msg %s', ev, ev.msg) msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto 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: self.logger.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=ofproto.OFPFC_DELETE, idle_timeout=0, hard_timeout=0, priority=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? self.logger.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 self.logger.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) # self.logger.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