def datapath_connect(self, dp_id, discovered_up_port_nums): """Handle Ryu datapath connection event and provision pipeline. Args: dp_id (int): datapath ID. discovered_up_port_nums (list): datapath ports that are up as ints. Returns: list: OpenFlow messages to send to datapath. """ if self._ignore_dpid(dp_id): return [] self.logger.info('Configuring %s', util.dpid_log(dp_id)) ofmsgs = [] ofmsgs.extend(self._add_default_flows()) changed_ports = set([]) for port_no in discovered_up_port_nums: if valve_of.ignore_port(port_no): continue changed_ports.add(port_no) changed_vlans = self.dp.vlans.iterkeys() changes = ([], changed_ports, [], changed_vlans) ofmsgs.extend(self._apply_config_changes(self.dp, changes)) ofmsgs.extend(self._add_ports_and_vlans(discovered_up_port_nums)) self.dp.running = True return ofmsgs
def port_status_handler(self, ryu_event): """Handle a port status change event. Args: ryu_event (ryu.controller.ofp_event.EventOFPPortStatus): trigger. """ msg = ryu_event.msg ryu_dp = msg.datapath dp_id = ryu_dp.id ofp = msg.datapath.ofproto reason = msg.reason port_no = msg.desc.port_no if dp_id not in self.valves: self.logger.error('port_status_handler: unknown %s', dpid_log(dp_id)) return valve = self.valves[dp_id] flowmods = [] if reason == ofp.OFPPR_ADD: flowmods = valve.port_add(dp_id, port_no) elif reason == ofp.OFPPR_DELETE: flowmods = valve.port_delete(dp_id, port_no) elif reason == ofp.OFPPR_MODIFY: port_down = msg.desc.state & ofp.OFPPS_LINK_DOWN if port_down: flowmods = valve.port_delete(dp_id, port_no) else: flowmods = valve.port_add(dp_id, port_no) else: self.logger.warning('Unhandled port status %s for port %u', reason, port_no) self._send_flow_msgs(ryu_dp, flowmods)
def _packet_in_handler(self, ryu_event): """Handle a packet in event from the dataplane. Args: ryu_event (ryu.controller.event.EventReplyBase): packet in message. """ msg = ryu_event.msg ryu_dp = msg.datapath dp_id = ryu_dp.id if not dp_id in self.valves: self.logger.error('_packet_in_handler: unknown %s', dpid_log(dp_id)) return valve = self.valves[dp_id] valve.ofchannel_log([msg]) pkt = packet.Packet(msg.data) eth_pkt = pkt.get_protocols(ethernet.ethernet)[0] eth_type = eth_pkt.ethertype # Packet ins, can only come when a VLAN header has already been pushed # (ie. when we have progressed past the VLAN table). This gaurantees # a VLAN header will always be present, so we know which VLAN the packet # belongs to. if eth_type == ether.ETH_TYPE_8021Q: # tagged packet vlan_proto = pkt.get_protocols(ryu_vlan.vlan)[0] vlan_vid = vlan_proto.vid else: return in_port = msg.match['in_port'] flowmods = valve.rcv_packet(dp_id, self.valves, in_port, vlan_vid, pkt) self._send_flow_msgs(ryu_dp, flowmods)
def port_status_handler(self, ryu_event): """Handle a port status change event. Args: ryu_event (ryu.controller.ofp_event.EventOFPPortStatus): trigger. """ msg = ryu_event.msg ryu_dp = msg.datapath dp_id = ryu_dp.id ofp = msg.datapath.ofproto reason = msg.reason port_no = msg.desc.port_no if dp_id not in self.valves: self.logger.error( 'port_status_handler: unknown %s', dpid_log(dp_id)) return valve = self.valves[dp_id] flowmods = [] if reason == ofp.OFPPR_ADD: flowmods = valve.port_add(dp_id, port_no) elif reason == ofp.OFPPR_DELETE: flowmods = valve.port_delete(dp_id, port_no) elif reason == ofp.OFPPR_MODIFY: port_down = msg.desc.state & ofp.OFPPS_LINK_DOWN if port_down: flowmods = valve.port_delete(dp_id, port_no) else: flowmods = valve.port_add(dp_id, port_no) else: self.logger.warning('Unhandled port status %s for port %u', reason, port_no) self._send_flow_msgs(ryu_dp, flowmods)
def datapath_connect(self, dp_id, discovered_up_port_nums): """Handle Ryu datapath connection event and provision pipeline. Args: dp_id (int): datapath ID. discovered_up_port_nums (list): datapath ports that are up as ints. Returns: list: OpenFlow messages to send to datapath. """ if self._ignore_dpid(dp_id): return [] self.logger.info('Configuring %s', util.dpid_log(dp_id)) ofmsgs = [] # Add the default meter to ofmsgs, assumes that meter and Controller meter are supported (Jayden Hewer) #ofmsgs.extend(self._add_default_meters()) # The line above is commented out as Lagopus does not support this feature. ofmsgs.extend(self._add_simple_meter()) ofmsgs.extend(self._add_default_flows()) changed_ports = set([]) for port_no in discovered_up_port_nums: if valve_of.ignore_port(port_no): continue changed_ports.add(port_no) changed_vlans = self.dp.vlans.iterkeys() changes = ([], changed_ports, [], changed_vlans) ofmsgs.extend(self._apply_config_changes(self.dp, changes)) ofmsgs.extend(self._add_ports_and_vlans(discovered_up_port_nums)) self.dp.running = True return ofmsgs
def handler_connect_or_disconnect(self, ryu_event): ryu_dp = ryu_event.dp dp_id = ryu_dp.id if dp_id not in self.watchers: self.logger.info('no watcher configured for %s', dpid_log(dp_id)) return if ryu_event.enter: # DP is connecting self.logger.info('%s up', dpid_log(dp_id)) for watcher in self.watchers[dp_id].values(): watcher.start(ryu_dp) else: # DP is disconnecting if ryu_dp.id in self.watchers: for watcher in self.watchers[dp_id].values(): watcher.stop() del self.watchers[dp_id] self.logger.info('%s down', dpid_log(dp_id))
def datapath_disconnect(self, dp_id): """Handle Ryu datapath disconnection event. Args: dp_id (int): datapath ID. """ if not self._ignore_dpid(dp_id): self.dp.running = False self.logger.warning('%s down', util.dpid_log(dp_id))
def handler_reconnect(self, ryu_event): """Handle reconnection of a datapath. Args: ryu_event (ryu.controller.dpset.EventDPReconnected): trigger. """ ryu_dp = ryu_event.dp self.logger.debug('%s reconnected', dpid_log(ryu_dp.id)) self.handler_datapath(ryu_dp)
def handler_connect_or_disconnect(self, ryu_event): """Handle connection or disconnection of a datapath. Args: ryu_event (ryu.controller.dpset.EventDP): trigger. """ ryu_dp = ryu_event.dp dp_id = ryu_dp.id if not ryu_event.enter: if dp_id in self.valves: # Datapath down message self.logger.debug('%s disconnected', dpid_log(dp_id)) self.valves[dp_id].datapath_disconnect(dp_id) else: self.logger.error( 'handler_connect_or_disconnect: unknown %s', dpid_log(dp_id)) return self.logger.debug('%s connected', dpid_log(dp_id)) self.handler_datapath(ryu_dp)
def rcv_packet(self, dp_id, in_port, vlan_vid, pkt): """Handle a packet from the dataplane (eg to re/learn a host). The packet may be sent to us also in response to FAUCET initiating IPv6 neighbor discovery, or ARP, to resolve a nexthop. Args: dp_id (int): datapath ID. in_port (int): port packet was received on. vlan_vid (int): VLAN VID of port packet was received on. pkt (ryu.lib.packet.packet): packet received. Return: list: OpenFlow messages, if any. """ if not self._known_up_dpid_and_port(dp_id, in_port): return [] ofmsgs = [] eth_pkt = valve_packet.parse_pkt(pkt) eth_src = eth_pkt.src eth_dst = eth_pkt.dst vlan = self.dp.vlans[vlan_vid] port = self.dp.ports[in_port] if valve_packet.mac_addr_is_unicast(eth_src): self.logger.debug( 'Packet_in %s src:%s in_port:%d vid:%s', util.dpid_log(dp_id), eth_src, in_port, vlan_vid) ofmsgs.extend(self.control_plane_handler( in_port, vlan, eth_src, eth_dst, pkt)) # ban learning new hosts if max_hosts reached on a VLAN. if (vlan.max_hosts is not None and len(vlan.host_cache) == vlan.max_hosts and eth_src not in vlan.host_cache): ofmsgs.append(self.host_manager.temp_ban_host_learning_on_vlan( vlan)) self.logger.info( 'max hosts %u reached on vlan %u, ' + 'temporarily banning learning on this vlan, ' + 'and not learning %s', vlan.max_hosts, vlan.vid, eth_src) else: # TODO: it would be good to be able to notify an external # system upon re/learning a host. ofmsgs.extend(self.host_manager.learn_host_on_vlan_port( port, vlan, eth_src)) self.logger.info( 'learned %u hosts on vlan %u', len(vlan.host_cache), vlan.vid) return ofmsgs
def handler_connect_or_disconnect(self, ryu_event): """Handle connection or disconnection of a datapath. Args: ryu_event (ryu.controller.dpset.EventDP): trigger. """ ryu_dp = ryu_event.dp dp_id = ryu_dp.id if not ryu_event.enter: if dp_id in self.valves: # Datapath down message self.logger.debug('%s disconnected', dpid_log(dp_id)) self.valves[dp_id].datapath_disconnect(dp_id) else: self.logger.error('handler_connect_or_disconnect: unknown %s', dpid_log(dp_id)) return self.logger.debug('%s connected', dpid_log(dp_id)) self.handler_datapath(ryu_dp)
def _ignore_dpid(self, dp_id): """Return True if this datapath ID is not ours. Args: dp_id (int): datapath ID Returns: bool: True if this datapath ID is not ours. """ if dp_id != self.dp.dp_id: self.logger.error('Unknown %s', util.dpid_log(dp_id)) return True return False
def handler_datapath(self, ryu_dp): """Handle any/all re/dis/connection of a datapath. Args: ryu_dp (ryu.controller.controller.Datapath): datapath. """ dp_id = ryu_dp.id if dp_id in self.valves: discovered_ports = [ p.port_no for p in ryu_dp.ports.values() if p.state == 0] flowmods = self.valves[dp_id].datapath_connect(dp_id, discovered_ports) self._send_flow_msgs(ryu_dp, flowmods) else: self.logger.error('handler_datapath: unknown %s', dpid_log(dp_id))
def _error_handler(self, ryu_event): """Handle an OFPError from a datapath. Args: ryu_event (ryu.controller.ofp_event.EventOFPErrorMsg): trigger """ msg = ryu_event.msg ryu_dp = msg.datapath dp_id = ryu_dp.id if dp_id in self.valves: self.valves[dp_id].ofchannel_log([msg]) self.logger.error('Got OFError: %s', msg) else: self.logger.error('_error_handler: unknown %s', dpid_log(dp_id))
def handler_features(self, ryu_event): """Handle receiving a switch features message from a datapath. Args: ryu_event (ryu.controller.ofp_event.EventOFPStateChange): trigger. """ msg = ryu_event.msg ryu_dp = msg.datapath dp_id = ryu_dp.id if dp_id in self.valves: flowmods = self.valves[dp_id].switch_features(dp_id, msg) self._send_flow_msgs(ryu_dp, flowmods) else: self.logger.error('handler_features: unknown %s', dpid_log(dp_id))
def _send_flow_msgs(self, ryu_dp, flow_msgs): """Send OpenFlow messages to a connected datapath. Args: ryu_db (ryu.controller.controller.Datapath): datapath. flow_msgs (list): OpenFlow messages to send. """ dp_id = ryu_dp.id if dp_id not in self.valves: self.logger.error('send_flow_msgs: unknown %s', dpid_log(dp_id)) return self.valves[dp_id].ofchannel_log(flow_msgs) for flow_msg in flow_msgs: flow_msg.datapath = ryu_dp ryu_dp.send_msg(flow_msg)
def handler_datapath(self, ryu_dp): """Handle any/all re/dis/connection of a datapath. Args: ryu_dp (ryu.controller.controller.Datapath): datapath. """ dp_id = ryu_dp.id if dp_id in self.valves: discovered_up_port_nums = [ port.port_no for port in ryu_dp.ports.values() if port.state == 0] flowmods = self.valves[dp_id].datapath_connect( dp_id, discovered_up_port_nums) self._send_flow_msgs(ryu_dp, flowmods) else: self.logger.error('handler_datapath: unknown %s', dpid_log(dp_id))
def rcv_packet(self, dp_id, valves, in_port, vlan_vid, pkt): """Handle a packet from the dataplane (eg to re/learn a host). The packet may be sent to us also in response to FAUCET initiating IPv6 neighbor discovery, or ARP, to resolve a nexthop. Args: dp_id (int): datapath ID. valves (dict): all datapaths, indexed by datapath ID. in_port (int): port packet was received on. vlan_vid (int): VLAN VID of port packet was received on. pkt (ryu.lib.packet.packet): packet received. Return: list: OpenFlow messages, if any. """ if not self._known_up_dpid_and_port(dp_id, in_port): return [] pkt_meta = self._parse_rcv_packet(in_port, vlan_vid, pkt) ofmsgs = [] if valve_packet.mac_addr_is_unicast(pkt_meta.eth_src): self.logger.debug( 'Packet_in %s src:%s in_port:%d vid:%s', util.dpid_log(dp_id), pkt_meta.eth_src, pkt_meta.port.number, pkt_meta.vlan.vid) ofmsgs.extend(self.control_plane_handler(pkt_meta)) if self._rate_limit_packet_ins(): return ofmsgs ban_vlan_rules = self._vlan_learn_ban_rules(pkt_meta) if ban_vlan_rules: ofmsgs.extend(ban_vlan_rules) return ofmsgs ofmsgs.extend(self._learn_host(valves, dp_id, pkt_meta)) return ofmsgs
def ofchannel_log(self, ofmsgs): """Log OpenFlow messages in text format to debugging log.""" if (self.dp is not None and self.dp.ofchannel_log is not None): if self.ofchannel_logger is None: self.ofchannel_logger = util.get_logger( self.dp.ofchannel_log, self.dp.ofchannel_log, logging.DEBUG, 0) for i, ofmsg in enumerate(ofmsgs, start=1): log_prefix = '%u/%u %s' % (i, len(ofmsgs), util.dpid_log(self.dp.dp_id)) self.ofchannel_logger.debug('%s %s', log_prefix, ofmsg) # TODO: log group operations as well. if valve_of.is_flowmod(ofmsg): match_types = self.debug_match_types(ofmsg) inst_types, action_types = self.debug_instruction_types( ofmsg) self.ofchannel_logger.debug( '%s FlowMod types table: %u match: %s instructions: %s actions: %s', log_prefix, ofmsg.table_id, match_types, inst_types, action_types)
def datapath_connect(self, dp_id, discovered_port_nums): """Handle Ryu datapath connection event and provision pipeline. Args: dp_id (int): datapath ID. discovered_port_nums (list): known datapath ports as ints. Returns: list: OpenFlow messages to send to datapath. """ if self._ignore_dpid(dp_id): return [] if discovered_port_nums is None: discovered_port_nums = [] self.logger.info('Configuring %s', util.dpid_log(dp_id)) ofmsgs = [] ofmsgs.extend(self._add_default_flows()) ofmsgs.extend(self._add_ports_and_vlans(discovered_port_nums)) self.dp.running = True return ofmsgs
def rcv_packet(self, dp_id, valves, in_port, vlan_vid, pkt): """Handle a packet from the dataplane (eg to re/learn a host). The packet may be sent to us also in response to FAUCET initiating IPv6 neighbor discovery, or ARP, to resolve a nexthop. Args: dp_id (int): datapath ID. valves (dict): all datapaths, indexed by datapath ID. in_port (int): port packet was received on. vlan_vid (int): VLAN VID of port packet was received on. pkt (ryu.lib.packet.packet): packet received. Return: list: OpenFlow messages, if any. """ if not self._known_up_dpid_and_port(dp_id, in_port): return [] ofmsgs = [] eth_pkt = valve_packet.parse_pkt(pkt) eth_src = eth_pkt.src eth_dst = eth_pkt.dst vlan = self.dp.vlans[vlan_vid] port = self.dp.ports[in_port] if valve_packet.mac_addr_is_unicast(eth_src): self.logger.debug( 'Packet_in %s src:%s in_port:%d vid:%s', util.dpid_log(dp_id), eth_src, in_port, vlan_vid) ofmsgs.extend(self.control_plane_handler( in_port, vlan, eth_src, eth_dst, pkt)) # Apply learning packet in rate limit. if self.dp.ignore_learn_ins: if int(time.time() * 1e3) % self.dp.ignore_learn_ins == 0: return ofmsgs # ban learning new hosts if max_hosts reached on a VLAN. if (vlan.max_hosts is not None and len(vlan.host_cache) == vlan.max_hosts and eth_src not in vlan.host_cache): ofmsgs.append(self.host_manager.temp_ban_host_learning_on_vlan( vlan)) self.logger.info( 'max hosts %u reached on vlan %u, ' + 'temporarily banning learning on this vlan, ' + 'and not learning %s', vlan.max_hosts, vlan.vid, eth_src) else: if port.stack is None: learn_port = port else: # TODO: simplest possible unicast learning. # We find just one port that is the shortest unicast path to # the destination. We could use other factors (eg we could # load balance over multiple ports based on destination MAC). # TODO: each DP learns independently. An edge DP could # call other valves so they learn immediately without waiting # for packet in. # TODO: edge DPs could use a different forwarding algorithm # (for example, just default switch to a neighbor). host_learned_other_dp = None # Find port that forwards closer to destination DP that # has already learned this host (if any). for other_dpid, other_valve in valves.iteritems(): if other_dpid == dp_id: continue other_dp = other_valve.dp other_dp_host_cache = other_dp.vlans[vlan_vid].host_cache if eth_src in other_dp_host_cache: host = other_dp_host_cache[eth_src] if host.edge: host_learned_other_dp = other_dp break # No edge DP may have learned this host yet. if host_learned_other_dp is None: return ofmsgs learn_port = self.dp.shortest_path_port( host_learned_other_dp.name) self.logger.info( 'host learned via stack port to %s', host_learned_other_dp.name) # TODO: it would be good to be able to notify an external # system upon re/learning a host. ofmsgs.extend(self.host_manager.learn_host_on_vlan_port( learn_port, vlan, eth_src)) self.logger.info( 'learned %u hosts on vlan %u', len(vlan.host_cache), vlan.vid) return ofmsgs
def handler_reconnect(self, ryu_event): ryu_dp = ryu_event.dp self.logger.info('%s reconnected', dpid_log(ryu_dp.id)) for watcher in self.watchers[ryu_dp.id].values(): watcher.start(ryu_dp)