# Copyright (C) 2014 Xinguard, Inc. # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. # # 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. """ Implementation of Bidirectional Forwarding Detection for IPv4 (Single Hop) This module provides a simple way to let Ryu act like a daemon for running IPv4 single hop BFD (RFC5881). Please note that: * Demand mode and echo function are not yet supported. * Mechanism on negotiating L2/L3 addresses for an established session is not yet implemented. * The interoperability of authentication support is not tested. * Configuring a BFD session with too small interval may lead to full of event queue and congestion of Openflow channels. For deploying a low-latency configuration or with a large number of BFD sessions, use standalone BFD daemon instead. """ import logging import six import time import random from ryu.base import app_manager from ryu.controller import event from ryu.controller import ofp_event from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER from ryu.controller.handler import set_ev_cls from ryu.exception import RyuException from ryu.ofproto.ether import ETH_TYPE_IP, ETH_TYPE_ARP from ryu.ofproto import ofproto_v1_3 from ryu.ofproto import inet from ryu.lib import ofctl_v1_3 from ryu.lib import hub from ryu.lib.packet import packet from ryu.lib.packet import ethernet from ryu.lib.packet import ipv4 from ryu.lib.packet import udp from ryu.lib.packet import bfd from ryu.lib.packet import arp from ryu.lib.packet.arp import ARP_REQUEST, ARP_REPLY LOG = logging.getLogger(__name__) UINT16_MAX = (1 << 16) - 1 UINT32_MAX = (1 << 32) - 1 # RFC5881 Section 8 BFD_CONTROL_UDP_PORT = 3784 BFD_ECHO_UDP_PORT = 3785 class BFDSession(object): """BFD Session class. An instance maintains a BFD session. """ def __init__(self, app, my_discr, dpid, ofport, src_mac, src_ip, src_port, dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255", detect_mult=3, desired_min_tx_interval=1000000, required_min_rx_interval=1000000, auth_type=0, auth_keys={}): """ Initialize a BFD session. __init__ takes the corresponding args in this order. .. tabularcolumns:: |l|L| ========================= ============================================ Argument Description ========================= ============================================ app The instance of BFDLib. my_discr My Discriminator. dpid Datapath ID of the BFD interface. ofport Openflow port number of the BFD interface. src_mac Source MAC address of the BFD interface. src_ip Source IPv4 address of the BFD interface. dst_mac (Optional) Destination MAC address of the BFD interface. dst_ip (Optional) Destination IPv4 address of the BFD interface. detect_mult (Optional) Detection time multiplier. desired_min_tx_interval (Optional) Desired Min TX Interval. (in microseconds) required_min_rx_interval (Optional) Required Min RX Interval. (in microseconds) auth_type (Optional) Authentication type. auth_keys (Optional) A dictionary of authentication key chain which key is an integer of *Auth Key ID* and value is a string of *Password* or *Auth Key*. ========================= ============================================ Example:: sess = BFDSession(app=self.bfdlib, my_discr=1, dpid=1, ofport=1, src_mac="01:23:45:67:89:AB", src_ip="192.168.1.1", dst_mac="12:34:56:78:9A:BC", dst_ip="192.168.1.2", detect_mult=3, desired_min_tx_interval=1000000, required_min_rx_interval=1000000, auth_type=bfd.BFD_AUTH_KEYED_SHA1, auth_keys={1: "secret key 1", 2: "secret key 2"}) """ assert not (auth_type and len(auth_keys) == 0) # RyuApp reference to BFDLib self.app = app # RFC5880 Section 6.8.1. # BFD Internal Variables self._session_state = bfd.BFD_STATE_DOWN self._remote_session_state = bfd.BFD_STATE_DOWN self._local_discr = my_discr self._remote_discr = 0 self._local_diag = 0 self._desired_min_tx_interval = 1000000 self._required_min_rx_interval = required_min_rx_interval self._remote_min_rx_interval = -1 # TODO: Demand mode is not yet supported. self._demand_mode = 0 self._remote_demand_mode = 0 self._detect_mult = detect_mult self._auth_type = auth_type self._auth_keys = auth_keys if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5, bfd.BFD_AUTH_METICULOUS_KEYED_MD5, bfd.BFD_AUTH_KEYED_SHA1, bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]: self._rcv_auth_seq = 0 self._xmit_auth_seq = random.randint(0, UINT32_MAX) self._auth_seq_known = 0 # BFD Runtime Variables self._cfg_desired_min_tx_interval = desired_min_tx_interval self._cfg_required_min_echo_rx_interval = 0 self._active_role = True self._detect_time = 0 self._xmit_period = None self._update_xmit_period() self._is_polling = True self._pending_final = False # _enable_send indicates the switch of the periodic transmission of # BFD Control packets. self._enable_send = True self._lock = None # L2/L3/L4 Header fields self.src_mac = src_mac self.dst_mac = dst_mac self.src_ip = src_ip self.dst_ip = dst_ip self.ipv4_id = random.randint(0, UINT16_MAX) self.src_port = src_port self.dst_port = BFD_CONTROL_UDP_PORT if dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255": self._remote_addr_config = False else: self._remote_addr_config = True # Switch and port associated to this BFD session. self.dpid = dpid self.datapath = None self.ofport = ofport # Spawn a periodic transmission loop for BFD Control packets. hub.spawn(self._send_loop) LOG.info("[BFD][%s][INIT] BFD Session initialized.", hex(self._local_discr)) @property def my_discr(self): """ Returns My Discriminator of the BFD session. """ return self._local_discr @property def your_discr(self): """ Returns Your Discriminator of the BFD session. """ return self._remote_discr def set_remote_addr(self, dst_mac, dst_ip): """ Configure remote ethernet and IP addresses. """ self.dst_mac = dst_mac self.dst_ip = dst_ip if not (dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255"): self._remote_addr_config = True LOG.info("[BFD][%s][REMOTE] Remote address configured: %s, %s.", hex(self._local_discr), self.dst_ip, self.dst_mac) def recv(self, bfd_pkt): """ BFD packet receiver. """ LOG.debug("[BFD][%s][RECV] BFD Control received: %s", hex(self._local_discr), six.binary_type(bfd_pkt)) self._remote_discr = bfd_pkt.my_discr self._remote_state = bfd_pkt.state self._remote_demand_mode = bfd_pkt.flags & bfd.BFD_FLAG_DEMAND if self._remote_min_rx_interval != bfd_pkt.required_min_rx_interval: self._remote_min_rx_interval = bfd_pkt.required_min_rx_interval # Update transmit interval (RFC5880 Section 6.8.2.) self._update_xmit_period() # TODO: Echo function (RFC5880 Page 35) if bfd_pkt.flags & bfd.BFD_FLAG_FINAL and self._is_polling: self._is_polling = False # Check and update the session state (RFC5880 Page 35) if self._session_state == bfd.BFD_STATE_ADMIN_DOWN: return if bfd_pkt.state == bfd.BFD_STATE_ADMIN_DOWN: if self._session_state != bfd.BFD_STATE_DOWN: self._set_state(bfd.BFD_STATE_DOWN, bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN) else: if self._session_state == bfd.BFD_STATE_DOWN: if bfd_pkt.state == bfd.BFD_STATE_DOWN: self._set_state(bfd.BFD_STATE_INIT) elif bfd_pkt.state == bfd.BFD_STATE_INIT: self._set_state(bfd.BFD_STATE_UP) elif self._session_state == bfd.BFD_STATE_INIT: if bfd_pkt.state in [bfd.BFD_STATE_INIT, bfd.BFD_STATE_UP]: self._set_state(bfd.BFD_STATE_UP) else: if bfd_pkt.state == bfd.BFD_STATE_DOWN: self._set_state(bfd.BFD_STATE_DOWN, bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN) # TODO: Demand mode support. if self._remote_demand_mode and \ self._session_state == bfd.BFD_STATE_UP and \ self._remote_session_state == bfd.BFD_STATE_UP: self._enable_send = False if not self._remote_demand_mode or \ self._session_state != bfd.BFD_STATE_UP or \ self._remote_session_state != bfd.BFD_STATE_UP: if not self._enable_send: self._enable_send = True hub.spawn(self._send_loop) # Update the detection time (RFC5880 Section 6.8.4.) if self._detect_time == 0: self._detect_time = bfd_pkt.desired_min_tx_interval * \ bfd_pkt.detect_mult / 1000000.0 # Start the timeout loop. hub.spawn(self._recv_timeout_loop) if bfd_pkt.flags & bfd.BFD_FLAG_POLL: self._pending_final = True self._detect_time = bfd_pkt.desired_min_tx_interval * \ bfd_pkt.detect_mult / 1000000.0 # Update the remote authentication sequence number. if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5, bfd.BFD_AUTH_METICULOUS_KEYED_MD5, bfd.BFD_AUTH_KEYED_SHA1, bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]: self._rcv_auth_seq = bfd_pkt.auth_cls.seq self._auth_seq_known = 1 # Set the lock. if self._lock is not None: self._lock.set() def _set_state(self, new_state, diag=None): """ Set the state of the BFD session. """ old_state = self._session_state LOG.info("[BFD][%s][STATE] State changed from %s to %s.", hex(self._local_discr), bfd.BFD_STATE_NAME[old_state], bfd.BFD_STATE_NAME[new_state]) self._session_state = new_state if new_state == bfd.BFD_STATE_DOWN: if diag is not None: self._local_diag = diag self._desired_min_tx_interval = 1000000 self._is_polling = True self._update_xmit_period() elif new_state == bfd.BFD_STATE_UP: self._desired_min_tx_interval = self._cfg_desired_min_tx_interval self._is_polling = True self._update_xmit_period() self.app.send_event_to_observers( EventBFDSessionStateChanged(self, old_state, new_state)) def _recv_timeout_loop(self): """ A loop to check timeout of receiving remote BFD packet. """ while self._detect_time: last_wait = time.time() self._lock = hub.Event() self._lock.wait(timeout=self._detect_time) if self._lock.is_set(): # Authentication variable check (RFC5880 Section 6.8.1.) if getattr(self, "_auth_seq_known", 0): if last_wait > time.time() + 2 * self._detect_time: self._auth_seq_known = 0 else: # Check Detection Time expiration (RFC5880 section 6.8.4.) LOG.info("[BFD][%s][RECV] BFD Session timed out.", hex(self._local_discr)) if self._session_state not in [bfd.BFD_STATE_DOWN, bfd.BFD_STATE_ADMIN_DOWN]: self._set_state(bfd.BFD_STATE_DOWN, bfd.BFD_DIAG_CTRL_DETECT_TIME_EXPIRED) # Authentication variable check (RFC5880 Section 6.8.1.) if getattr(self, "_auth_seq_known", 0): self._auth_seq_known = 0 def _update_xmit_period(self): """ Update transmission period of the BFD session. """ # RFC5880 Section 6.8.7. if self._desired_min_tx_interval > self._remote_min_rx_interval: xmit_period = self._desired_min_tx_interval else: xmit_period = self._remote_min_rx_interval # This updates the transmission period of BFD Control packets. # (RFC5880 Section 6.8.2 & 6.8.3.) if self._detect_mult == 1: xmit_period *= random.randint(75, 90) / 100.0 else: xmit_period *= random.randint(75, 100) / 100.0 self._xmit_period = xmit_period / 1000000.0 LOG.info("[BFD][%s][XMIT] Transmission period changed to %f", hex(self._local_discr), self._xmit_period) def _send_loop(self): """ A loop to proceed periodic BFD packet transmission. """ while self._enable_send: hub.sleep(self._xmit_period) # Send BFD packet. (RFC5880 Section 6.8.7.) if self._remote_discr == 0 and not self._active_role: continue if self._remote_min_rx_interval == 0: continue if self._remote_demand_mode and \ self._session_state == bfd.BFD_STATE_UP and \ self._remote_session_state == bfd.BFD_STATE_UP and \ not self._is_polling: continue self._send() def _send(self): """ BFD packet sender. """ # If the switch was not connected to controller, exit. if self.datapath is None: return # BFD Flags Setup flags = 0 if self._pending_final: flags |= bfd.BFD_FLAG_FINAL self._pending_final = False self._is_polling = False if self._is_polling: flags |= bfd.BFD_FLAG_POLL # Authentication Section auth_cls = None if self._auth_type: auth_key_id = list(self._auth_keys.keys())[ random.randint(0, len(list(self._auth_keys.keys())) - 1)] auth_key = self._auth_keys[auth_key_id] if self._auth_type == bfd.BFD_AUTH_SIMPLE_PASS: auth_cls = bfd.SimplePassword(auth_key_id=auth_key_id, password=auth_key) if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5, bfd.BFD_AUTH_METICULOUS_KEYED_MD5, bfd.BFD_AUTH_KEYED_SHA1, bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]: if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5, bfd.BFD_AUTH_KEYED_SHA1]: if random.randint(0, 1): self._xmit_auth_seq = \ (self._xmit_auth_seq + 1) & UINT32_MAX else: self._xmit_auth_seq = \ (self._xmit_auth_seq + 1) & UINT32_MAX auth_cls = bfd.bfd._auth_parsers[self._auth_type]( auth_key_id=auth_key_id, seq=self._xmit_auth_seq, auth_key=auth_key) if auth_cls is not None: flags |= bfd.BFD_FLAG_AUTH_PRESENT if self._demand_mode and \ self._session_state == bfd.BFD_STATE_UP and \ self._remote_session_state == bfd.BFD_STATE_UP: flags |= bfd.BFD_FLAG_DEMAND ver = 1 diag = self._local_diag state = self._session_state detect_mult = self._detect_mult my_discr = self._local_discr your_discr = self._remote_discr desired_min_tx_interval = self._desired_min_tx_interval required_min_rx_interval = self._required_min_rx_interval required_min_echo_rx_interval = self._cfg_required_min_echo_rx_interval # Prepare for Ethernet/IP/UDP header fields src_mac = self.src_mac dst_mac = self.dst_mac src_ip = self.src_ip dst_ip = self.dst_ip self.ipv4_id = (self.ipv4_id + 1) & UINT16_MAX ipv4_id = self.ipv4_id src_port = self.src_port dst_port = self.dst_port # Construct BFD Control packet data = BFDPacket.bfd_packet( src_mac=src_mac, dst_mac=dst_mac, src_ip=src_ip, dst_ip=dst_ip, ipv4_id=ipv4_id, src_port=src_port, dst_port=dst_port, diag=diag, state=state, flags=flags, detect_mult=detect_mult, my_discr=my_discr, your_discr=your_discr, desired_min_tx_interval=desired_min_tx_interval, required_min_rx_interval=required_min_rx_interval, required_min_echo_rx_interval=required_min_echo_rx_interval, auth_cls=auth_cls) # Prepare for a datapath datapath = self.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser actions = [parser.OFPActionOutput(self.ofport)] out = parser.OFPPacketOut(datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=data) datapath.send_msg(out) LOG.debug("[BFD][%s][SEND] BFD Control sent.", hex(self._local_discr)) class BFDPacket(object): """ BFDPacket class for parsing raw BFD packet, and generating BFD packet with Ethernet, IPv4, and UDP headers. """ class BFDUnknownFormat(RyuException): message = '%(msg)s' @staticmethod def bfd_packet(src_mac, dst_mac, src_ip, dst_ip, ipv4_id, src_port, dst_port, diag=0, state=0, flags=0, detect_mult=0, my_discr=0, your_discr=0, desired_min_tx_interval=0, required_min_rx_interval=0, required_min_echo_rx_interval=0, auth_cls=None): """ Generate BFD packet with Ethernet/IPv4/UDP encapsulated. """ # Generate ethernet header first. pkt = packet.Packet() eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_IP) pkt.add_protocol(eth_pkt) # IPv4 encapsulation # set ToS to 192 (Network control/CS6) # set TTL to 255 (RFC5881 Section 5.) ipv4_pkt = ipv4.ipv4(proto=inet.IPPROTO_UDP, src=src_ip, dst=dst_ip, tos=192, identification=ipv4_id, ttl=255) pkt.add_protocol(ipv4_pkt) # UDP encapsulation udp_pkt = udp.udp(src_port=src_port, dst_port=dst_port) pkt.add_protocol(udp_pkt) # BFD payload bfd_pkt = bfd.bfd( ver=1, diag=diag, state=state, flags=flags, detect_mult=detect_mult, my_discr=my_discr, your_discr=your_discr, desired_min_tx_interval=desired_min_tx_interval, required_min_rx_interval=required_min_rx_interval, required_min_echo_rx_interval=required_min_echo_rx_interval, auth_cls=auth_cls) pkt.add_protocol(bfd_pkt) pkt.serialize() return pkt.data @staticmethod def bfd_parse(data): """ Parse raw packet and return BFD class from packet library. """ pkt = packet.Packet(data) i = iter(pkt) eth_pkt = i.next() assert type(eth_pkt) == ethernet.ethernet ipv4_pkt = i.next() assert type(ipv4_pkt) == ipv4.ipv4 udp_pkt = i.next() assert type(udp_pkt) == udp.udp udp_payload = i.next() return bfd.bfd.parser(udp_payload)[0] class ARPPacket(object): """ ARPPacket class for parsing raw ARP packet, and generating ARP packet with Ethernet header. """ class ARPUnknownFormat(RyuException): message = '%(msg)s' @staticmethod def arp_packet(opcode, src_mac, src_ip, dst_mac, dst_ip): """ Generate ARP packet with ethernet encapsulated. """ # Generate ethernet header first. pkt = packet.Packet() eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_ARP) pkt.add_protocol(eth_pkt) # Use IPv4 ARP wrapper from packet library directly. arp_pkt = arp.arp_ip(opcode, src_mac, src_ip, dst_mac, dst_ip) pkt.add_protocol(arp_pkt) pkt.serialize() return pkt.data @staticmethod def arp_parse(data): """ Parse ARP packet, return ARP class from packet library. """ # Iteratize pkt pkt = packet.Packet(data) i = iter(pkt) eth_pkt = i.next() # Ensure it's an ethernet frame. assert type(eth_pkt) == ethernet.ethernet arp_pkt = i.next() if type(arp_pkt) != arp.arp: raise ARPPacket.ARPUnknownFormat() if arp_pkt.opcode not in (ARP_REQUEST, ARP_REPLY): raise ARPPacket.ARPUnknownFormat( msg='unsupported opcode %d' % arp_pkt.opcode) if arp_pkt.proto != ETH_TYPE_IP: raise ARPPacket.ARPUnknownFormat( msg='unsupported arp ethtype 0x%04x' % arp_pkt.proto) return arp_pkt class EventBFDSessionStateChanged(event.EventBase): """ An event class that notifies the state change of a BFD session. """ def __init__(self, session, old_state, new_state): super(EventBFDSessionStateChanged, self).__init__() self.session = session self.old_state = old_state self.new_state = new_state class BFDLib(app_manager.RyuApp): """ BFD daemon library. Add this library as a context in your app and use ``add_bfd_session`` function to establish a BFD session. Example:: from ryu.base import app_manager from ryu.controller.handler import set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib import bfdlib from ryu.lib.packet import bfd class Foo(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _CONTEXTS = { 'bfdlib': bfdlib.BFDLib } def __init__(self, *args, **kwargs): super(Foo, self).__init__(*args, **kwargs) self.bfdlib = kwargs['bfdlib'] self.my_discr = \ self.bfdlib.add_bfd_session(dpid=1, ofport=1, src_mac="00:23:45:67:89:AB", src_ip="192.168.1.1") @set_ev_cls(bfdlib.EventBFDSessionStateChanged) def bfd_state_handler(self, ev): if ev.session.my_discr != self.my_discr: return if ev.new_state == bfd.BFD_STATE_DOWN: print "BFD Session=%d is DOWN!" % ev.session.my_discr elif ev.new_state == bfd.BFD_STATE_UP: print "BFD Session=%d is UP!" % ev.session.my_discr """ OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] _EVENTS = [EventBFDSessionStateChanged] def __init__(self, *args, **kwargs): super(BFDLib, self).__init__(*args, **kwargs) # BFD Session Dictionary # key: My Discriminator # value: BFDSession object self.session = {} def close(self): pass @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): datapath = ev.msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser # Update datapath object in BFD sessions for s in self.session.values(): if s.dpid == datapath.id: s.datapath = datapath # Install default flows for capturing ARP & BFD packets. match = parser.OFPMatch(eth_type=ETH_TYPE_ARP) actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0xFFFF, match, actions) match = parser.OFPMatch(eth_type=ETH_TYPE_IP, ip_proto=inet.IPPROTO_UDP, udp_dst=3784) actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)] self.add_flow(datapath, 0xFFFF, match, actions) def add_flow(self, datapath, priority, match, actions): ofproto = datapath.ofproto parser = datapath.ofproto_parser inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] mod = parser.OFPFlowMod(datapath=datapath, priority=priority, match=match, instructions=inst) datapath.send_msg(mod) # Packet-In Handler, only for BFD packets. @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): msg = ev.msg datapath = msg.datapath ofproto = datapath.ofproto parser = datapath.ofproto_parser in_port = msg.match['in_port'] pkt = packet.Packet(msg.data) # If there's someone asked for an IP address associated # with a BFD session, generate an ARP reply for it. if arp.arp in pkt: arp_pkt = ARPPacket.arp_parse(msg.data) if arp_pkt.opcode == ARP_REQUEST: for s in self.session.values(): if s.dpid == datapath.id and \ s.ofport == in_port and \ s.src_ip == arp_pkt.dst_ip: ans = ARPPacket.arp_packet( ARP_REPLY, s.src_mac, s.src_ip, arp_pkt.src_mac, arp_pkt.src_ip) actions = [parser.OFPActionOutput(in_port)] out = parser.OFPPacketOut( datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER, in_port=ofproto.OFPP_CONTROLLER, actions=actions, data=ans) datapath.send_msg(out) return return # Check whether it's BFD packet or not. if ipv4.ipv4 not in pkt or udp.udp not in pkt: return udp_hdr = pkt.get_protocols(udp.udp)[0] if udp_hdr.dst_port != BFD_CONTROL_UDP_PORT: return # Parse BFD packet here. self.recv_bfd_pkt(datapath, in_port, msg.data) def add_bfd_session(self, dpid, ofport, src_mac, src_ip, dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255", auth_type=0, auth_keys={}): """ Establish a new BFD session and return My Discriminator of new session. Configure the BFD session with the following arguments. ================ ====================================================== Argument Description ================ ====================================================== dpid Datapath ID of the BFD interface. ofport Openflow port number of the BFD interface. src_mac Source MAC address of the BFD interface. src_ip Source IPv4 address of the BFD interface. dst_mac (Optional) Destination MAC address of the BFD interface. dst_ip (Optional) Destination IPv4 address of the BFD interface. auth_type (Optional) Authentication type. auth_keys (Optional) A dictionary of authentication key chain which key is an integer of *Auth Key ID* and value is a string of *Password* or *Auth Key*. ================ ====================================================== Example:: add_bfd_session(dpid=1, ofport=1, src_mac="01:23:45:67:89:AB", src_ip="192.168.1.1", dst_mac="12:34:56:78:9A:BC", dst_ip="192.168.1.2", auth_type=bfd.BFD_AUTH_KEYED_SHA1, auth_keys={1: "secret key 1", 2: "secret key 2"}) """ # Generate a unique discriminator while True: # Generate My Discriminator my_discr = random.randint(1, UINT32_MAX) # Generate an UDP destination port according to RFC5881 Section 4. src_port = random.randint(49152, 65535) # Ensure generated discriminator and UDP port are unique. if my_discr in self.session: continue unique_flag = True for s in self.session.values(): if s.your_discr == my_discr or s.src_port == src_port: unique_flag = False break if unique_flag: break sess = BFDSession(app=self, my_discr=my_discr, dpid=dpid, ofport=ofport, src_mac=src_mac, src_ip=src_ip, src_port=src_port, dst_mac=dst_mac, dst_ip=dst_ip, auth_type=auth_type, auth_keys=auth_keys) self.session[my_discr] = sess return my_discr def recv_bfd_pkt(self, datapath, in_port, data): pkt = packet.Packet(data) eth = pkt.get_protocols(ethernet.ethernet)[0] if eth.ethertype != ETH_TYPE_IP: return ip_pkt = pkt.get_protocols(ipv4.ipv4)[0] # Discard it if TTL != 255 for single hop bfd. (RFC5881 Section 5.) if ip_pkt.ttl != 255: return # Parse BFD packet here. bfd_pkt = BFDPacket.bfd_parse(data) if not isinstance(bfd_pkt, bfd.bfd): return # BFD sanity checks # RFC 5880 Section 6.8.6. if bfd_pkt.ver != 1: return if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT: if bfd_pkt.length < 26: return else: if bfd_pkt.length < 24: return if bfd_pkt.detect_mult == 0: return if bfd_pkt.flags & bfd.BFD_FLAG_MULTIPOINT: return if bfd_pkt.my_discr == 0: return if bfd_pkt.your_discr != 0 and bfd_pkt.your_discr not in self.session: return if bfd_pkt.your_discr == 0 and \ bfd_pkt.state not in [bfd.BFD_STATE_ADMIN_DOWN, bfd.BFD_STATE_DOWN]: return sess_my_discr = None if bfd_pkt.your_discr == 0: # Select session (Page 34) for s in self.session.values(): if s.dpid == datapath.id and s.ofport == in_port: sess_my_discr = s.my_discr break # BFD Session not found. if sess_my_discr is None: return else: sess_my_discr = bfd_pkt.your_discr sess = self.session[sess_my_discr] if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT and sess._auth_type == 0: return if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT == 0 and \ sess._auth_type != 0: return # Authenticate the session (Section 6.7.) if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT: if sess._auth_type == 0: return if bfd_pkt.auth_cls.auth_type != sess._auth_type: return # Check authentication sequence number to defend replay attack. if sess._auth_type in [bfd.BFD_AUTH_KEYED_MD5, bfd.BFD_AUTH_METICULOUS_KEYED_MD5, bfd.BFD_AUTH_KEYED_SHA1, bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]: if sess._auth_seq_known: if bfd_pkt.auth_cls.seq < sess._rcv_auth_seq: return if sess._auth_type in [bfd.BFD_AUTH_METICULOUS_KEYED_MD5, bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]: if bfd_pkt.auth_cls.seq <= sess._rcv_auth_seq: return if bfd_pkt.auth_cls.seq > sess._rcv_auth_seq \ + 3 * sess._detect_mult: return if not bfd_pkt.authenticate(sess._auth_keys): LOG.debug("[BFD][%s][AUTH] BFD Control authentication failed.", hex(sess._local_discr)) return # Sanity check passed, proceed. if sess is not None: # Check whether L2/L3 addresses were configured or not. # TODO: L2/L3 addresses negotiation for an established session. if not sess._remote_addr_config: sess.set_remote_addr(eth.src, ip_pkt.src) # Proceed to session update. sess.recv(bfd_pkt)