diff options
-rw-r--r-- | ryu/lib/bfdlib.py | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/ryu/lib/bfdlib.py b/ryu/lib/bfdlib.py new file mode 100644 index 00000000..21bfd6fa --- /dev/null +++ b/ryu/lib/bfdlib.py @@ -0,0 +1,961 @@ +# 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 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), str(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 = self._auth_keys.keys()[ + random.randint(0, len(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 + # ToS sets to 192 (Network control/CS6) + ipv4_pkt = ipv4.ipv4(proto=inet.IPPROTO_UDP, src=src_ip, dst=dst_ip, + tos=192, identification=ipv4_id) + 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] + + # 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) |