1 # Copyright (C) 2014 Xinguard, Inc.
2 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 Implementation of Bidirectional Forwarding Detection for IPv4 (Single Hop)
20 This module provides a simple way to let Ryu act like a daemon for running
21 IPv4 single hop BFD (RFC5881).
25 * Demand mode and echo function are not yet supported.
26 * Mechanism on negotiating L2/L3 addresses for an established
27 session is not yet implemented.
28 * The interoperability of authentication support is not tested.
29 * Configuring a BFD session with too small interval may lead to
30 full of event queue and congestion of Openflow channels.
31 For deploying a low-latency configuration or with a large number
32 of BFD sessions, use standalone BFD daemon instead.
42 from ryu.base import app_manager
43 from ryu.controller import event
44 from ryu.controller import ofp_event
45 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
46 from ryu.controller.handler import set_ev_cls
47 from ryu.exception import RyuException
48 from ryu.ofproto.ether import ETH_TYPE_IP, ETH_TYPE_ARP
49 from ryu.ofproto import ofproto_v1_3
50 from ryu.ofproto import inet
51 from ryu.lib import hub
52 from ryu.lib.packet import packet
53 from ryu.lib.packet import ethernet
54 from ryu.lib.packet import ipv4
55 from ryu.lib.packet import udp
56 from ryu.lib.packet import bfd
57 from ryu.lib.packet import arp
58 from ryu.lib.packet.arp import ARP_REQUEST, ARP_REPLY
60 LOG = logging.getLogger(__name__)
62 UINT16_MAX = (1 << 16) - 1
63 UINT32_MAX = (1 << 32) - 1
66 BFD_CONTROL_UDP_PORT = 3784
67 BFD_ECHO_UDP_PORT = 3785
70 class BFDSession(object):
73 An instance maintains a BFD session.
76 def __init__(self, app, my_discr, dpid, ofport,
77 src_mac, src_ip, src_port,
78 dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255",
80 desired_min_tx_interval=1000000,
81 required_min_rx_interval=1000000,
82 auth_type=0, auth_keys=None):
84 Initialize a BFD session.
86 __init__ takes the corresponding args in this order.
88 .. tabularcolumns:: |l|L|
90 ========================= ============================================
92 ========================= ============================================
93 app The instance of BFDLib.
94 my_discr My Discriminator.
95 dpid Datapath ID of the BFD interface.
96 ofport Openflow port number of the BFD interface.
97 src_mac Source MAC address of the BFD interface.
98 src_ip Source IPv4 address of the BFD interface.
99 dst_mac (Optional) Destination MAC address of the
101 dst_ip (Optional) Destination IPv4 address of the
103 detect_mult (Optional) Detection time multiplier.
104 desired_min_tx_interval (Optional) Desired Min TX Interval.
106 required_min_rx_interval (Optional) Required Min RX Interval.
108 auth_type (Optional) Authentication type.
109 auth_keys (Optional) A dictionary of authentication
110 key chain which key is an integer of
111 *Auth Key ID* and value is a string of
112 *Password* or *Auth Key*.
113 ========================= ============================================
117 sess = BFDSession(app=self.bfdlib,
121 src_mac="01:23:45:67:89:AB",
122 src_ip="192.168.1.1",
123 dst_mac="12:34:56:78:9A:BC",
124 dst_ip="192.168.1.2",
126 desired_min_tx_interval=1000000,
127 required_min_rx_interval=1000000,
128 auth_type=bfd.BFD_AUTH_KEYED_SHA1,
129 auth_keys={1: "secret key 1",
132 auth_keys = auth_keys if auth_keys else {}
133 assert not (auth_type and len(auth_keys) == 0)
135 # RyuApp reference to BFDLib
138 # RFC5880 Section 6.8.1.
139 # BFD Internal Variables
140 self._session_state = bfd.BFD_STATE_DOWN
141 self._remote_session_state = bfd.BFD_STATE_DOWN
142 self._local_discr = my_discr
143 self._remote_discr = 0
145 self._desired_min_tx_interval = 1000000
146 self._required_min_rx_interval = required_min_rx_interval
147 self._remote_min_rx_interval = -1
148 # TODO: Demand mode is not yet supported.
149 self._demand_mode = 0
150 self._remote_demand_mode = 0
151 self._detect_mult = detect_mult
152 self._auth_type = auth_type
153 self._auth_keys = auth_keys
155 if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
156 bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
157 bfd.BFD_AUTH_KEYED_SHA1,
158 bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
159 self._rcv_auth_seq = 0
160 self._xmit_auth_seq = random.randint(0, UINT32_MAX)
161 self._auth_seq_known = 0
163 # BFD Runtime Variables
164 self._cfg_desired_min_tx_interval = desired_min_tx_interval
165 self._cfg_required_min_echo_rx_interval = 0
166 self._active_role = True
167 self._detect_time = 0
168 self._xmit_period = None
169 self._update_xmit_period()
170 self._is_polling = True
171 self._pending_final = False
172 # _enable_send indicates the switch of the periodic transmission of
173 # BFD Control packets.
174 self._enable_send = True
177 # L2/L3/L4 Header fields
178 self.src_mac = src_mac
179 self.dst_mac = dst_mac
182 self.ipv4_id = random.randint(0, UINT16_MAX)
183 self.src_port = src_port
184 self.dst_port = BFD_CONTROL_UDP_PORT
186 if dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255":
187 self._remote_addr_config = False
189 self._remote_addr_config = True
191 # Switch and port associated to this BFD session.
196 # Spawn a periodic transmission loop for BFD Control packets.
197 hub.spawn(self._send_loop)
199 LOG.info("[BFD][%s][INIT] BFD Session initialized.",
200 hex(self._local_discr))
205 Returns My Discriminator of the BFD session.
207 return self._local_discr
210 def your_discr(self):
212 Returns Your Discriminator of the BFD session.
214 return self._remote_discr
216 def set_remote_addr(self, dst_mac, dst_ip):
218 Configure remote ethernet and IP addresses.
220 self.dst_mac = dst_mac
223 if not (dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255"):
224 self._remote_addr_config = True
226 LOG.info("[BFD][%s][REMOTE] Remote address configured: %s, %s.",
227 hex(self._local_discr), self.dst_ip, self.dst_mac)
229 def recv(self, bfd_pkt):
233 LOG.debug("[BFD][%s][RECV] BFD Control received: %s",
234 hex(self._local_discr), six.binary_type(bfd_pkt))
235 self._remote_discr = bfd_pkt.my_discr
236 self._remote_state = bfd_pkt.state
237 self._remote_demand_mode = bfd_pkt.flags & bfd.BFD_FLAG_DEMAND
239 if self._remote_min_rx_interval != bfd_pkt.required_min_rx_interval:
240 self._remote_min_rx_interval = bfd_pkt.required_min_rx_interval
241 # Update transmit interval (RFC5880 Section 6.8.2.)
242 self._update_xmit_period()
244 # TODO: Echo function (RFC5880 Page 35)
246 if bfd_pkt.flags & bfd.BFD_FLAG_FINAL and self._is_polling:
247 self._is_polling = False
249 # Check and update the session state (RFC5880 Page 35)
250 if self._session_state == bfd.BFD_STATE_ADMIN_DOWN:
253 if bfd_pkt.state == bfd.BFD_STATE_ADMIN_DOWN:
254 if self._session_state != bfd.BFD_STATE_DOWN:
255 self._set_state(bfd.BFD_STATE_DOWN,
256 bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN)
258 if self._session_state == bfd.BFD_STATE_DOWN:
259 if bfd_pkt.state == bfd.BFD_STATE_DOWN:
260 self._set_state(bfd.BFD_STATE_INIT)
261 elif bfd_pkt.state == bfd.BFD_STATE_INIT:
262 self._set_state(bfd.BFD_STATE_UP)
264 elif self._session_state == bfd.BFD_STATE_INIT:
265 if bfd_pkt.state in [bfd.BFD_STATE_INIT, bfd.BFD_STATE_UP]:
266 self._set_state(bfd.BFD_STATE_UP)
269 if bfd_pkt.state == bfd.BFD_STATE_DOWN:
270 self._set_state(bfd.BFD_STATE_DOWN,
271 bfd.BFD_DIAG_NEIG_SIG_SESS_DOWN)
273 # TODO: Demand mode support.
275 if self._remote_demand_mode and \
276 self._session_state == bfd.BFD_STATE_UP and \
277 self._remote_session_state == bfd.BFD_STATE_UP:
278 self._enable_send = False
280 if not self._remote_demand_mode or \
281 self._session_state != bfd.BFD_STATE_UP or \
282 self._remote_session_state != bfd.BFD_STATE_UP:
283 if not self._enable_send:
284 self._enable_send = True
285 hub.spawn(self._send_loop)
287 # Update the detection time (RFC5880 Section 6.8.4.)
288 if self._detect_time == 0:
289 self._detect_time = bfd_pkt.desired_min_tx_interval * \
290 bfd_pkt.detect_mult / 1000000.0
291 # Start the timeout loop.
292 hub.spawn(self._recv_timeout_loop)
294 if bfd_pkt.flags & bfd.BFD_FLAG_POLL:
295 self._pending_final = True
296 self._detect_time = bfd_pkt.desired_min_tx_interval * \
297 bfd_pkt.detect_mult / 1000000.0
299 # Update the remote authentication sequence number.
300 if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
301 bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
302 bfd.BFD_AUTH_KEYED_SHA1,
303 bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
304 self._rcv_auth_seq = bfd_pkt.auth_cls.seq
305 self._auth_seq_known = 1
308 if self._lock is not None:
311 def _set_state(self, new_state, diag=None):
313 Set the state of the BFD session.
315 old_state = self._session_state
317 LOG.info("[BFD][%s][STATE] State changed from %s to %s.",
318 hex(self._local_discr),
319 bfd.BFD_STATE_NAME[old_state],
320 bfd.BFD_STATE_NAME[new_state])
321 self._session_state = new_state
323 if new_state == bfd.BFD_STATE_DOWN:
325 self._local_diag = diag
326 self._desired_min_tx_interval = 1000000
327 self._is_polling = True
328 self._update_xmit_period()
329 elif new_state == bfd.BFD_STATE_UP:
330 self._desired_min_tx_interval = self._cfg_desired_min_tx_interval
331 self._is_polling = True
332 self._update_xmit_period()
334 self.app.send_event_to_observers(
335 EventBFDSessionStateChanged(self, old_state, new_state))
337 def _recv_timeout_loop(self):
339 A loop to check timeout of receiving remote BFD packet.
341 while self._detect_time:
342 last_wait = time.time()
343 self._lock = hub.Event()
345 self._lock.wait(timeout=self._detect_time)
347 if self._lock.is_set():
348 # Authentication variable check (RFC5880 Section 6.8.1.)
349 if getattr(self, "_auth_seq_known", 0):
350 if last_wait > time.time() + 2 * self._detect_time:
351 self._auth_seq_known = 0
354 # Check Detection Time expiration (RFC5880 section 6.8.4.)
355 LOG.info("[BFD][%s][RECV] BFD Session timed out.",
356 hex(self._local_discr))
357 if self._session_state not in [bfd.BFD_STATE_DOWN,
358 bfd.BFD_STATE_ADMIN_DOWN]:
359 self._set_state(bfd.BFD_STATE_DOWN,
360 bfd.BFD_DIAG_CTRL_DETECT_TIME_EXPIRED)
362 # Authentication variable check (RFC5880 Section 6.8.1.)
363 if getattr(self, "_auth_seq_known", 0):
364 self._auth_seq_known = 0
366 def _update_xmit_period(self):
368 Update transmission period of the BFD session.
370 # RFC5880 Section 6.8.7.
371 if self._desired_min_tx_interval > self._remote_min_rx_interval:
372 xmit_period = self._desired_min_tx_interval
374 xmit_period = self._remote_min_rx_interval
376 # This updates the transmission period of BFD Control packets.
377 # (RFC5880 Section 6.8.2 & 6.8.3.)
378 if self._detect_mult == 1:
379 xmit_period *= random.randint(75, 90) / 100.0
381 xmit_period *= random.randint(75, 100) / 100.0
383 self._xmit_period = xmit_period / 1000000.0
384 LOG.info("[BFD][%s][XMIT] Transmission period changed to %f",
385 hex(self._local_discr), self._xmit_period)
387 def _send_loop(self):
389 A loop to proceed periodic BFD packet transmission.
391 while self._enable_send:
392 hub.sleep(self._xmit_period)
394 # Send BFD packet. (RFC5880 Section 6.8.7.)
396 if self._remote_discr == 0 and not self._active_role:
399 if self._remote_min_rx_interval == 0:
402 if self._remote_demand_mode and \
403 self._session_state == bfd.BFD_STATE_UP and \
404 self._remote_session_state == bfd.BFD_STATE_UP and \
405 not self._is_polling:
414 # If the switch was not connected to controller, exit.
415 if self.datapath is None:
421 if self._pending_final:
422 flags |= bfd.BFD_FLAG_FINAL
423 self._pending_final = False
424 self._is_polling = False
427 flags |= bfd.BFD_FLAG_POLL
429 # Authentication Section
432 auth_key_id = list(self._auth_keys.keys())[
433 random.randint(0, len(list(self._auth_keys.keys())) - 1)]
434 auth_key = self._auth_keys[auth_key_id]
436 if self._auth_type == bfd.BFD_AUTH_SIMPLE_PASS:
437 auth_cls = bfd.SimplePassword(auth_key_id=auth_key_id,
440 if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
441 bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
442 bfd.BFD_AUTH_KEYED_SHA1,
443 bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
444 if self._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
445 bfd.BFD_AUTH_KEYED_SHA1]:
446 if random.randint(0, 1):
447 self._xmit_auth_seq = \
448 (self._xmit_auth_seq + 1) & UINT32_MAX
450 self._xmit_auth_seq = \
451 (self._xmit_auth_seq + 1) & UINT32_MAX
453 auth_cls = bfd.bfd._auth_parsers[self._auth_type](
454 auth_key_id=auth_key_id,
455 seq=self._xmit_auth_seq,
458 if auth_cls is not None:
459 flags |= bfd.BFD_FLAG_AUTH_PRESENT
461 if self._demand_mode and \
462 self._session_state == bfd.BFD_STATE_UP and \
463 self._remote_session_state == bfd.BFD_STATE_UP:
464 flags |= bfd.BFD_FLAG_DEMAND
466 diag = self._local_diag
467 state = self._session_state
468 detect_mult = self._detect_mult
469 my_discr = self._local_discr
470 your_discr = self._remote_discr
471 desired_min_tx_interval = self._desired_min_tx_interval
472 required_min_rx_interval = self._required_min_rx_interval
473 required_min_echo_rx_interval = self._cfg_required_min_echo_rx_interval
475 # Prepare for Ethernet/IP/UDP header fields
476 src_mac = self.src_mac
477 dst_mac = self.dst_mac
480 self.ipv4_id = (self.ipv4_id + 1) & UINT16_MAX
481 ipv4_id = self.ipv4_id
482 src_port = self.src_port
483 dst_port = self.dst_port
485 # Construct BFD Control packet
486 data = BFDPacket.bfd_packet(
487 src_mac=src_mac, dst_mac=dst_mac,
488 src_ip=src_ip, dst_ip=dst_ip, ipv4_id=ipv4_id,
489 src_port=src_port, dst_port=dst_port,
490 diag=diag, state=state, flags=flags, detect_mult=detect_mult,
491 my_discr=my_discr, your_discr=your_discr,
492 desired_min_tx_interval=desired_min_tx_interval,
493 required_min_rx_interval=required_min_rx_interval,
494 required_min_echo_rx_interval=required_min_echo_rx_interval,
497 # Prepare for a datapath
498 datapath = self.datapath
499 ofproto = datapath.ofproto
500 parser = datapath.ofproto_parser
502 actions = [parser.OFPActionOutput(self.ofport)]
504 out = parser.OFPPacketOut(datapath=datapath,
505 buffer_id=ofproto.OFP_NO_BUFFER,
506 in_port=ofproto.OFPP_CONTROLLER,
510 datapath.send_msg(out)
511 LOG.debug("[BFD][%s][SEND] BFD Control sent.", hex(self._local_discr))
514 class BFDPacket(object):
516 BFDPacket class for parsing raw BFD packet, and generating BFD packet with
517 Ethernet, IPv4, and UDP headers.
520 class BFDUnknownFormat(RyuException):
524 def bfd_packet(src_mac, dst_mac, src_ip, dst_ip, ipv4_id,
526 diag=0, state=0, flags=0, detect_mult=0,
527 my_discr=0, your_discr=0, desired_min_tx_interval=0,
528 required_min_rx_interval=0,
529 required_min_echo_rx_interval=0,
532 Generate BFD packet with Ethernet/IPv4/UDP encapsulated.
534 # Generate ethernet header first.
535 pkt = packet.Packet()
536 eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_IP)
537 pkt.add_protocol(eth_pkt)
540 # set ToS to 192 (Network control/CS6)
541 # set TTL to 255 (RFC5881 Section 5.)
542 ipv4_pkt = ipv4.ipv4(proto=inet.IPPROTO_UDP, src=src_ip, dst=dst_ip,
543 tos=192, identification=ipv4_id, ttl=255)
544 pkt.add_protocol(ipv4_pkt)
547 udp_pkt = udp.udp(src_port=src_port, dst_port=dst_port)
548 pkt.add_protocol(udp_pkt)
552 ver=1, diag=diag, state=state, flags=flags,
553 detect_mult=detect_mult,
554 my_discr=my_discr, your_discr=your_discr,
555 desired_min_tx_interval=desired_min_tx_interval,
556 required_min_rx_interval=required_min_rx_interval,
557 required_min_echo_rx_interval=required_min_echo_rx_interval,
559 pkt.add_protocol(bfd_pkt)
567 Parse raw packet and return BFD class from packet library.
569 pkt = packet.Packet(data)
573 assert isinstance(eth_pkt, ethernet.ethernet)
576 assert isinstance(ipv4_pkt, ipv4.ipv4)
579 assert isinstance(udp_pkt, udp.udp)
581 udp_payload = next(i)
583 return bfd.bfd.parser(udp_payload)[0]
586 class ARPPacket(object):
588 ARPPacket class for parsing raw ARP packet, and generating ARP packet with
592 class ARPUnknownFormat(RyuException):
596 def arp_packet(opcode, src_mac, src_ip, dst_mac, dst_ip):
598 Generate ARP packet with ethernet encapsulated.
600 # Generate ethernet header first.
601 pkt = packet.Packet()
602 eth_pkt = ethernet.ethernet(dst_mac, src_mac, ETH_TYPE_ARP)
603 pkt.add_protocol(eth_pkt)
605 # Use IPv4 ARP wrapper from packet library directly.
606 arp_pkt = arp.arp_ip(opcode, src_mac, src_ip, dst_mac, dst_ip)
607 pkt.add_protocol(arp_pkt)
615 Parse ARP packet, return ARP class from packet library.
618 pkt = packet.Packet(data)
621 # Ensure it's an ethernet frame.
622 assert isinstance(eth_pkt, ethernet.ethernet)
625 if not isinstance(arp_pkt, arp.arp):
626 raise ARPPacket.ARPUnknownFormat()
628 if arp_pkt.opcode not in (ARP_REQUEST, ARP_REPLY):
629 raise ARPPacket.ARPUnknownFormat(
630 msg='unsupported opcode %d' % arp_pkt.opcode)
632 if arp_pkt.proto != ETH_TYPE_IP:
633 raise ARPPacket.ARPUnknownFormat(
634 msg='unsupported arp ethtype 0x%04x' % arp_pkt.proto)
639 class EventBFDSessionStateChanged(event.EventBase):
641 An event class that notifies the state change of a BFD session.
644 def __init__(self, session, old_state, new_state):
645 super(EventBFDSessionStateChanged, self).__init__()
646 self.session = session
647 self.old_state = old_state
648 self.new_state = new_state
651 class BFDLib(app_manager.RyuApp):
655 Add this library as a context in your app and use ``add_bfd_session``
656 function to establish a BFD session.
660 from ryu.base import app_manager
661 from ryu.controller.handler import set_ev_cls
662 from ryu.ofproto import ofproto_v1_3
663 from ryu.lib import bfdlib
664 from ryu.lib.packet import bfd
666 class Foo(app_manager.RyuApp):
667 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
670 'bfdlib': bfdlib.BFDLib
673 def __init__(self, *args, **kwargs):
674 super(Foo, self).__init__(*args, **kwargs)
675 self.bfdlib = kwargs['bfdlib']
677 self.bfdlib.add_bfd_session(dpid=1,
679 src_mac="00:23:45:67:89:AB",
680 src_ip="192.168.1.1")
682 @set_ev_cls(bfdlib.EventBFDSessionStateChanged)
683 def bfd_state_handler(self, ev):
684 if ev.session.my_discr != self.my_discr:
687 if ev.new_state == bfd.BFD_STATE_DOWN:
688 print "BFD Session=%d is DOWN!" % ev.session.my_discr
689 elif ev.new_state == bfd.BFD_STATE_UP:
690 print "BFD Session=%d is UP!" % ev.session.my_discr
692 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
694 _EVENTS = [EventBFDSessionStateChanged]
696 def __init__(self, *args, **kwargs):
697 super(BFDLib, self).__init__(*args, **kwargs)
699 # BFD Session Dictionary
700 # key: My Discriminator
701 # value: BFDSession object
707 @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
708 def switch_features_handler(self, ev):
709 datapath = ev.msg.datapath
710 ofproto = datapath.ofproto
711 parser = datapath.ofproto_parser
713 # Update datapath object in BFD sessions
714 for s in self.session.values():
715 if s.dpid == datapath.id:
716 s.datapath = datapath
718 # Install default flows for capturing ARP & BFD packets.
719 match = parser.OFPMatch(eth_type=ETH_TYPE_ARP)
720 actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
721 ofproto.OFPCML_NO_BUFFER)]
722 self.add_flow(datapath, 0xFFFF, match, actions)
724 match = parser.OFPMatch(eth_type=ETH_TYPE_IP,
725 ip_proto=inet.IPPROTO_UDP,
727 actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
728 ofproto.OFPCML_NO_BUFFER)]
729 self.add_flow(datapath, 0xFFFF, match, actions)
731 def add_flow(self, datapath, priority, match, actions):
732 ofproto = datapath.ofproto
733 parser = datapath.ofproto_parser
735 inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
738 mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
739 match=match, instructions=inst)
740 datapath.send_msg(mod)
742 # Packet-In Handler, only for BFD packets.
743 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
744 def _packet_in_handler(self, ev):
746 datapath = msg.datapath
747 ofproto = datapath.ofproto
748 parser = datapath.ofproto_parser
749 in_port = msg.match['in_port']
751 pkt = packet.Packet(msg.data)
753 # If there's someone asked for an IP address associated
754 # with a BFD session, generate an ARP reply for it.
756 arp_pkt = ARPPacket.arp_parse(msg.data)
757 if arp_pkt.opcode == ARP_REQUEST:
758 for s in self.session.values():
759 if s.dpid == datapath.id and \
760 s.ofport == in_port and \
761 s.src_ip == arp_pkt.dst_ip:
763 ans = ARPPacket.arp_packet(
766 arp_pkt.src_mac, arp_pkt.src_ip)
768 actions = [parser.OFPActionOutput(in_port)]
769 out = parser.OFPPacketOut(
771 buffer_id=ofproto.OFP_NO_BUFFER,
772 in_port=ofproto.OFPP_CONTROLLER,
773 actions=actions, data=ans)
775 datapath.send_msg(out)
779 # Check whether it's BFD packet or not.
780 if ipv4.ipv4 not in pkt or udp.udp not in pkt:
783 udp_hdr = pkt.get_protocols(udp.udp)[0]
784 if udp_hdr.dst_port != BFD_CONTROL_UDP_PORT:
787 # Parse BFD packet here.
788 self.recv_bfd_pkt(datapath, in_port, msg.data)
790 def add_bfd_session(self, dpid, ofport, src_mac, src_ip,
791 dst_mac="FF:FF:FF:FF:FF:FF", dst_ip="255.255.255.255",
792 auth_type=0, auth_keys=None):
794 Establish a new BFD session and return My Discriminator of new session.
796 Configure the BFD session with the following arguments.
798 ================ ======================================================
800 ================ ======================================================
801 dpid Datapath ID of the BFD interface.
802 ofport Openflow port number of the BFD interface.
803 src_mac Source MAC address of the BFD interface.
804 src_ip Source IPv4 address of the BFD interface.
805 dst_mac (Optional) Destination MAC address of the BFD
807 dst_ip (Optional) Destination IPv4 address of the BFD
809 auth_type (Optional) Authentication type.
810 auth_keys (Optional) A dictionary of authentication key chain
811 which key is an integer of *Auth Key ID* and value
812 is a string of *Password* or *Auth Key*.
813 ================ ======================================================
817 add_bfd_session(dpid=1,
819 src_mac="01:23:45:67:89:AB",
820 src_ip="192.168.1.1",
821 dst_mac="12:34:56:78:9A:BC",
822 dst_ip="192.168.1.2",
823 auth_type=bfd.BFD_AUTH_KEYED_SHA1,
824 auth_keys={1: "secret key 1",
827 auth_keys = auth_keys if auth_keys else {}
828 # Generate a unique discriminator
830 # Generate My Discriminator
831 my_discr = random.randint(1, UINT32_MAX)
833 # Generate an UDP destination port according to RFC5881 Section 4.
834 src_port = random.randint(49152, 65535)
836 # Ensure generated discriminator and UDP port are unique.
837 if my_discr in self.session:
842 for s in self.session.values():
843 if s.your_discr == my_discr or s.src_port == src_port:
850 sess = BFDSession(app=self, my_discr=my_discr,
851 dpid=dpid, ofport=ofport,
852 src_mac=src_mac, src_ip=src_ip, src_port=src_port,
853 dst_mac=dst_mac, dst_ip=dst_ip,
854 auth_type=auth_type, auth_keys=auth_keys)
856 self.session[my_discr] = sess
860 def recv_bfd_pkt(self, datapath, in_port, data):
861 pkt = packet.Packet(data)
862 eth = pkt.get_protocols(ethernet.ethernet)[0]
864 if eth.ethertype != ETH_TYPE_IP:
867 ip_pkt = pkt.get_protocols(ipv4.ipv4)[0]
869 # Discard it if TTL != 255 for single hop bfd. (RFC5881 Section 5.)
870 if ip_pkt.ttl != 255:
873 # Parse BFD packet here.
874 bfd_pkt = BFDPacket.bfd_parse(data)
876 if not isinstance(bfd_pkt, bfd.bfd):
880 # RFC 5880 Section 6.8.6.
884 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT:
885 if bfd_pkt.length < 26:
888 if bfd_pkt.length < 24:
891 if bfd_pkt.detect_mult == 0:
894 if bfd_pkt.flags & bfd.BFD_FLAG_MULTIPOINT:
897 if bfd_pkt.my_discr == 0:
900 if bfd_pkt.your_discr != 0 and bfd_pkt.your_discr not in self.session:
903 if bfd_pkt.your_discr == 0 and \
904 bfd_pkt.state not in [bfd.BFD_STATE_ADMIN_DOWN,
910 if bfd_pkt.your_discr == 0:
911 # Select session (Page 34)
912 for s in self.session.values():
913 if s.dpid == datapath.id and s.ofport == in_port:
914 sess_my_discr = s.my_discr
917 # BFD Session not found.
918 if sess_my_discr is None:
921 sess_my_discr = bfd_pkt.your_discr
923 sess = self.session[sess_my_discr]
925 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT and sess._auth_type == 0:
928 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT == 0 and \
929 sess._auth_type != 0:
932 # Authenticate the session (Section 6.7.)
933 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT:
934 if sess._auth_type == 0:
937 if bfd_pkt.auth_cls.auth_type != sess._auth_type:
940 # Check authentication sequence number to defend replay attack.
941 if sess._auth_type in [bfd.BFD_AUTH_KEYED_MD5,
942 bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
943 bfd.BFD_AUTH_KEYED_SHA1,
944 bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
945 if sess._auth_seq_known:
946 if bfd_pkt.auth_cls.seq < sess._rcv_auth_seq:
949 if sess._auth_type in [bfd.BFD_AUTH_METICULOUS_KEYED_MD5,
950 bfd.BFD_AUTH_METICULOUS_KEYED_SHA1]:
951 if bfd_pkt.auth_cls.seq <= sess._rcv_auth_seq:
954 if bfd_pkt.auth_cls.seq > sess._rcv_auth_seq \
955 + 3 * sess._detect_mult:
958 if not bfd_pkt.authenticate(sess._auth_keys):
959 LOG.debug("[BFD][%s][AUTH] BFD Control authentication failed.",
960 hex(sess._local_discr))
963 # Sanity check passed, proceed.
965 # Check whether L2/L3 addresses were configured or not.
966 # TODO: L2/L3 addresses negotiation for an established session.
967 if not sess._remote_addr_config:
968 sess.set_remote_addr(eth.src, ip_pkt.src)
969 # Proceed to session update.