1# Copyright (C) 2014 Xinguard, Inc. 2# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. 3# 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 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 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 13# implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Implementation of Bidirectional Forwarding Detection for IPv4 (Single Hop) 19 20This module provides a simple way to let Ryu act like a daemon for running 21IPv4 single hop BFD (RFC5881). 22 23Please note that: 24 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. 33""" 34 35 36import logging 37import time 38import random 39 40import six 41 42from ryu.base import app_manager 43from ryu.controller import event 44from ryu.controller import ofp_event 45from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER 46from ryu.controller.handler import set_ev_cls 47from ryu.exception import RyuException 48from ryu.ofproto.ether import ETH_TYPE_IP, ETH_TYPE_ARP 49from ryu.ofproto import ofproto_v1_3 50from ryu.ofproto import inet 51from ryu.lib import hub 52from ryu.lib.packet import packet 53from ryu.lib.packet import ethernet 54from ryu.lib.packet import ipv4 55from ryu.lib.packet import udp 56from ryu.lib.packet import bfd 57from ryu.lib.packet import arp 58from ryu.lib.packet.arp import ARP_REQUEST, ARP_REPLY 59 60LOG = logging.getLogger(__name__) 61 62UINT16_MAX = (1 << 16) - 1 63UINT32_MAX = (1 << 32) - 1 64 65# RFC5881 Section 8 66BFD_CONTROL_UDP_PORT = 3784 67BFD_ECHO_UDP_PORT = 3785 68 69 70class BFDSession(object): 71 """BFD Session class. 72 73 An instance maintains a BFD session. 74 """ 75 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", 79 detect_mult=3, 80 desired_min_tx_interval=1000000, 81 required_min_rx_interval=1000000, 82 auth_type=0, auth_keys=None): 83 """ 84 Initialize a BFD session. 85 86 __init__ takes the corresponding args in this order. 87 88 .. tabularcolumns:: |l|L| 89 90 ========================= ============================================ 91 Argument Description 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 100 BFD interface. 101 dst_ip (Optional) Destination IPv4 address of the 102 BFD interface. 103 detect_mult (Optional) Detection time multiplier. 104 desired_min_tx_interval (Optional) Desired Min TX Interval. 105 (in microseconds) 106 required_min_rx_interval (Optional) Required Min RX Interval. 107 (in microseconds) 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 ========================= ============================================ 114 115 Example:: 116 117 sess = BFDSession(app=self.bfdlib, 118 my_discr=1, 119 dpid=1, 120 ofport=1, 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", 125 detect_mult=3, 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", 130 2: "secret key 2"}) 131 """ 132 auth_keys = auth_keys if auth_keys else {} 133 assert not (auth_type and len(auth_keys) == 0) 134 135 # RyuApp reference to BFDLib 136 self.app = app 137 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 144 self._local_diag = 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 154 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 162 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 175 self._lock = None 176 177 # L2/L3/L4 Header fields 178 self.src_mac = src_mac 179 self.dst_mac = dst_mac 180 self.src_ip = src_ip 181 self.dst_ip = dst_ip 182 self.ipv4_id = random.randint(0, UINT16_MAX) 183 self.src_port = src_port 184 self.dst_port = BFD_CONTROL_UDP_PORT 185 186 if dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255": 187 self._remote_addr_config = False 188 else: 189 self._remote_addr_config = True 190 191 # Switch and port associated to this BFD session. 192 self.dpid = dpid 193 self.datapath = None 194 self.ofport = ofport 195 196 # Spawn a periodic transmission loop for BFD Control packets. 197 hub.spawn(self._send_loop) 198 199 LOG.info("[BFD][%s][INIT] BFD Session initialized.", 200 hex(self._local_discr)) 201 202 @property 203 def my_discr(self): 204 """ 205 Returns My Discriminator of the BFD session. 206 """ 207 return self._local_discr 208 209 @property 210 def your_discr(self): 211 """ 212 Returns Your Discriminator of the BFD session. 213 """ 214 return self._remote_discr 215 216 def set_remote_addr(self, dst_mac, dst_ip): 217 """ 218 Configure remote ethernet and IP addresses. 219 """ 220 self.dst_mac = dst_mac 221 self.dst_ip = dst_ip 222 223 if not (dst_mac == "FF:FF:FF:FF:FF:FF" or dst_ip == "255.255.255.255"): 224 self._remote_addr_config = True 225 226 LOG.info("[BFD][%s][REMOTE] Remote address configured: %s, %s.", 227 hex(self._local_discr), self.dst_ip, self.dst_mac) 228 229 def recv(self, bfd_pkt): 230 """ 231 BFD packet receiver. 232 """ 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 238 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() 243 244 # TODO: Echo function (RFC5880 Page 35) 245 246 if bfd_pkt.flags & bfd.BFD_FLAG_FINAL and self._is_polling: 247 self._is_polling = False 248 249 # Check and update the session state (RFC5880 Page 35) 250 if self._session_state == bfd.BFD_STATE_ADMIN_DOWN: 251 return 252 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) 257 else: 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) 263 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) 267 268 else: 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) 272 273 # TODO: Demand mode support. 274 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 279 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) 286 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) 293 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 298 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 306 307 # Set the lock. 308 if self._lock is not None: 309 self._lock.set() 310 311 def _set_state(self, new_state, diag=None): 312 """ 313 Set the state of the BFD session. 314 """ 315 old_state = self._session_state 316 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 322 323 if new_state == bfd.BFD_STATE_DOWN: 324 if diag is not None: 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() 333 334 self.app.send_event_to_observers( 335 EventBFDSessionStateChanged(self, old_state, new_state)) 336 337 def _recv_timeout_loop(self): 338 """ 339 A loop to check timeout of receiving remote BFD packet. 340 """ 341 while self._detect_time: 342 last_wait = time.time() 343 self._lock = hub.Event() 344 345 self._lock.wait(timeout=self._detect_time) 346 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 352 353 else: 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) 361 362 # Authentication variable check (RFC5880 Section 6.8.1.) 363 if getattr(self, "_auth_seq_known", 0): 364 self._auth_seq_known = 0 365 366 def _update_xmit_period(self): 367 """ 368 Update transmission period of the BFD session. 369 """ 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 373 else: 374 xmit_period = self._remote_min_rx_interval 375 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 380 else: 381 xmit_period *= random.randint(75, 100) / 100.0 382 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) 386 387 def _send_loop(self): 388 """ 389 A loop to proceed periodic BFD packet transmission. 390 """ 391 while self._enable_send: 392 hub.sleep(self._xmit_period) 393 394 # Send BFD packet. (RFC5880 Section 6.8.7.) 395 396 if self._remote_discr == 0 and not self._active_role: 397 continue 398 399 if self._remote_min_rx_interval == 0: 400 continue 401 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: 406 continue 407 408 self._send() 409 410 def _send(self): 411 """ 412 BFD packet sender. 413 """ 414 # If the switch was not connected to controller, exit. 415 if self.datapath is None: 416 return 417 418 # BFD Flags Setup 419 flags = 0 420 421 if self._pending_final: 422 flags |= bfd.BFD_FLAG_FINAL 423 self._pending_final = False 424 self._is_polling = False 425 426 if self._is_polling: 427 flags |= bfd.BFD_FLAG_POLL 428 429 # Authentication Section 430 auth_cls = None 431 if self._auth_type: 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] 435 436 if self._auth_type == bfd.BFD_AUTH_SIMPLE_PASS: 437 auth_cls = bfd.SimplePassword(auth_key_id=auth_key_id, 438 password=auth_key) 439 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 449 else: 450 self._xmit_auth_seq = \ 451 (self._xmit_auth_seq + 1) & UINT32_MAX 452 453 auth_cls = bfd.bfd._auth_parsers[self._auth_type]( 454 auth_key_id=auth_key_id, 455 seq=self._xmit_auth_seq, 456 auth_key=auth_key) 457 458 if auth_cls is not None: 459 flags |= bfd.BFD_FLAG_AUTH_PRESENT 460 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 465 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 474 475 # Prepare for Ethernet/IP/UDP header fields 476 src_mac = self.src_mac 477 dst_mac = self.dst_mac 478 src_ip = self.src_ip 479 dst_ip = self.dst_ip 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 484 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, 495 auth_cls=auth_cls) 496 497 # Prepare for a datapath 498 datapath = self.datapath 499 ofproto = datapath.ofproto 500 parser = datapath.ofproto_parser 501 502 actions = [parser.OFPActionOutput(self.ofport)] 503 504 out = parser.OFPPacketOut(datapath=datapath, 505 buffer_id=ofproto.OFP_NO_BUFFER, 506 in_port=ofproto.OFPP_CONTROLLER, 507 actions=actions, 508 data=data) 509 510 datapath.send_msg(out) 511 LOG.debug("[BFD][%s][SEND] BFD Control sent.", hex(self._local_discr)) 512 513 514class BFDPacket(object): 515 """ 516 BFDPacket class for parsing raw BFD packet, and generating BFD packet with 517 Ethernet, IPv4, and UDP headers. 518 """ 519 520 class BFDUnknownFormat(RyuException): 521 message = '%(msg)s' 522 523 @staticmethod 524 def bfd_packet(src_mac, dst_mac, src_ip, dst_ip, ipv4_id, 525 src_port, dst_port, 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, 530 auth_cls=None): 531 """ 532 Generate BFD packet with Ethernet/IPv4/UDP encapsulated. 533 """ 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) 538 539 # IPv4 encapsulation 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) 545 546 # UDP encapsulation 547 udp_pkt = udp.udp(src_port=src_port, dst_port=dst_port) 548 pkt.add_protocol(udp_pkt) 549 550 # BFD payload 551 bfd_pkt = bfd.bfd( 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, 558 auth_cls=auth_cls) 559 pkt.add_protocol(bfd_pkt) 560 561 pkt.serialize() 562 return pkt.data 563 564 @staticmethod 565 def bfd_parse(data): 566 """ 567 Parse raw packet and return BFD class from packet library. 568 """ 569 pkt = packet.Packet(data) 570 i = iter(pkt) 571 eth_pkt = next(i) 572 573 assert isinstance(eth_pkt, ethernet.ethernet) 574 575 ipv4_pkt = next(i) 576 assert isinstance(ipv4_pkt, ipv4.ipv4) 577 578 udp_pkt = next(i) 579 assert isinstance(udp_pkt, udp.udp) 580 581 udp_payload = next(i) 582 583 return bfd.bfd.parser(udp_payload)[0] 584 585 586class ARPPacket(object): 587 """ 588 ARPPacket class for parsing raw ARP packet, and generating ARP packet with 589 Ethernet header. 590 """ 591 592 class ARPUnknownFormat(RyuException): 593 message = '%(msg)s' 594 595 @staticmethod 596 def arp_packet(opcode, src_mac, src_ip, dst_mac, dst_ip): 597 """ 598 Generate ARP packet with ethernet encapsulated. 599 """ 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) 604 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) 608 609 pkt.serialize() 610 return pkt.data 611 612 @staticmethod 613 def arp_parse(data): 614 """ 615 Parse ARP packet, return ARP class from packet library. 616 """ 617 # Iteratize pkt 618 pkt = packet.Packet(data) 619 i = iter(pkt) 620 eth_pkt = next(i) 621 # Ensure it's an ethernet frame. 622 assert isinstance(eth_pkt, ethernet.ethernet) 623 624 arp_pkt = next(i) 625 if not isinstance(arp_pkt, arp.arp): 626 raise ARPPacket.ARPUnknownFormat() 627 628 if arp_pkt.opcode not in (ARP_REQUEST, ARP_REPLY): 629 raise ARPPacket.ARPUnknownFormat( 630 msg='unsupported opcode %d' % arp_pkt.opcode) 631 632 if arp_pkt.proto != ETH_TYPE_IP: 633 raise ARPPacket.ARPUnknownFormat( 634 msg='unsupported arp ethtype 0x%04x' % arp_pkt.proto) 635 636 return arp_pkt 637 638 639class EventBFDSessionStateChanged(event.EventBase): 640 """ 641 An event class that notifies the state change of a BFD session. 642 """ 643 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 649 650 651class BFDLib(app_manager.RyuApp): 652 """ 653 BFD daemon library. 654 655 Add this library as a context in your app and use ``add_bfd_session`` 656 function to establish a BFD session. 657 658 Example:: 659 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 665 666 class Foo(app_manager.RyuApp): 667 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 668 669 _CONTEXTS = { 670 'bfdlib': bfdlib.BFDLib 671 } 672 673 def __init__(self, *args, **kwargs): 674 super(Foo, self).__init__(*args, **kwargs) 675 self.bfdlib = kwargs['bfdlib'] 676 self.my_discr = \ 677 self.bfdlib.add_bfd_session(dpid=1, 678 ofport=1, 679 src_mac="00:23:45:67:89:AB", 680 src_ip="192.168.1.1") 681 682 @set_ev_cls(bfdlib.EventBFDSessionStateChanged) 683 def bfd_state_handler(self, ev): 684 if ev.session.my_discr != self.my_discr: 685 return 686 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 691 """ 692 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 693 694 _EVENTS = [EventBFDSessionStateChanged] 695 696 def __init__(self, *args, **kwargs): 697 super(BFDLib, self).__init__(*args, **kwargs) 698 699 # BFD Session Dictionary 700 # key: My Discriminator 701 # value: BFDSession object 702 self.session = {} 703 704 def close(self): 705 pass 706 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 712 713 # Update datapath object in BFD sessions 714 for s in self.session.values(): 715 if s.dpid == datapath.id: 716 s.datapath = datapath 717 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) 723 724 match = parser.OFPMatch(eth_type=ETH_TYPE_IP, 725 ip_proto=inet.IPPROTO_UDP, 726 udp_dst=3784) 727 actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, 728 ofproto.OFPCML_NO_BUFFER)] 729 self.add_flow(datapath, 0xFFFF, match, actions) 730 731 def add_flow(self, datapath, priority, match, actions): 732 ofproto = datapath.ofproto 733 parser = datapath.ofproto_parser 734 735 inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, 736 actions)] 737 738 mod = parser.OFPFlowMod(datapath=datapath, priority=priority, 739 match=match, instructions=inst) 740 datapath.send_msg(mod) 741 742 # Packet-In Handler, only for BFD packets. 743 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 744 def _packet_in_handler(self, ev): 745 msg = ev.msg 746 datapath = msg.datapath 747 ofproto = datapath.ofproto 748 parser = datapath.ofproto_parser 749 in_port = msg.match['in_port'] 750 751 pkt = packet.Packet(msg.data) 752 753 # If there's someone asked for an IP address associated 754 # with a BFD session, generate an ARP reply for it. 755 if arp.arp in pkt: 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: 762 763 ans = ARPPacket.arp_packet( 764 ARP_REPLY, 765 s.src_mac, s.src_ip, 766 arp_pkt.src_mac, arp_pkt.src_ip) 767 768 actions = [parser.OFPActionOutput(in_port)] 769 out = parser.OFPPacketOut( 770 datapath=datapath, 771 buffer_id=ofproto.OFP_NO_BUFFER, 772 in_port=ofproto.OFPP_CONTROLLER, 773 actions=actions, data=ans) 774 775 datapath.send_msg(out) 776 return 777 return 778 779 # Check whether it's BFD packet or not. 780 if ipv4.ipv4 not in pkt or udp.udp not in pkt: 781 return 782 783 udp_hdr = pkt.get_protocols(udp.udp)[0] 784 if udp_hdr.dst_port != BFD_CONTROL_UDP_PORT: 785 return 786 787 # Parse BFD packet here. 788 self.recv_bfd_pkt(datapath, in_port, msg.data) 789 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): 793 """ 794 Establish a new BFD session and return My Discriminator of new session. 795 796 Configure the BFD session with the following arguments. 797 798 ================ ====================================================== 799 Argument Description 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 806 interface. 807 dst_ip (Optional) Destination IPv4 address of the BFD 808 interface. 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 ================ ====================================================== 814 815 Example:: 816 817 add_bfd_session(dpid=1, 818 ofport=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", 825 2: "secret key 2"}) 826 """ 827 auth_keys = auth_keys if auth_keys else {} 828 # Generate a unique discriminator 829 while True: 830 # Generate My Discriminator 831 my_discr = random.randint(1, UINT32_MAX) 832 833 # Generate an UDP destination port according to RFC5881 Section 4. 834 src_port = random.randint(49152, 65535) 835 836 # Ensure generated discriminator and UDP port are unique. 837 if my_discr in self.session: 838 continue 839 840 unique_flag = True 841 842 for s in self.session.values(): 843 if s.your_discr == my_discr or s.src_port == src_port: 844 unique_flag = False 845 break 846 847 if unique_flag: 848 break 849 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) 855 856 self.session[my_discr] = sess 857 858 return my_discr 859 860 def recv_bfd_pkt(self, datapath, in_port, data): 861 pkt = packet.Packet(data) 862 eth = pkt.get_protocols(ethernet.ethernet)[0] 863 864 if eth.ethertype != ETH_TYPE_IP: 865 return 866 867 ip_pkt = pkt.get_protocols(ipv4.ipv4)[0] 868 869 # Discard it if TTL != 255 for single hop bfd. (RFC5881 Section 5.) 870 if ip_pkt.ttl != 255: 871 return 872 873 # Parse BFD packet here. 874 bfd_pkt = BFDPacket.bfd_parse(data) 875 876 if not isinstance(bfd_pkt, bfd.bfd): 877 return 878 879 # BFD sanity checks 880 # RFC 5880 Section 6.8.6. 881 if bfd_pkt.ver != 1: 882 return 883 884 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT: 885 if bfd_pkt.length < 26: 886 return 887 else: 888 if bfd_pkt.length < 24: 889 return 890 891 if bfd_pkt.detect_mult == 0: 892 return 893 894 if bfd_pkt.flags & bfd.BFD_FLAG_MULTIPOINT: 895 return 896 897 if bfd_pkt.my_discr == 0: 898 return 899 900 if bfd_pkt.your_discr != 0 and bfd_pkt.your_discr not in self.session: 901 return 902 903 if bfd_pkt.your_discr == 0 and \ 904 bfd_pkt.state not in [bfd.BFD_STATE_ADMIN_DOWN, 905 bfd.BFD_STATE_DOWN]: 906 return 907 908 sess_my_discr = None 909 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 915 break 916 917 # BFD Session not found. 918 if sess_my_discr is None: 919 return 920 else: 921 sess_my_discr = bfd_pkt.your_discr 922 923 sess = self.session[sess_my_discr] 924 925 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT and sess._auth_type == 0: 926 return 927 928 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT == 0 and \ 929 sess._auth_type != 0: 930 return 931 932 # Authenticate the session (Section 6.7.) 933 if bfd_pkt.flags & bfd.BFD_FLAG_AUTH_PRESENT: 934 if sess._auth_type == 0: 935 return 936 937 if bfd_pkt.auth_cls.auth_type != sess._auth_type: 938 return 939 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: 947 return 948 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: 952 return 953 954 if bfd_pkt.auth_cls.seq > sess._rcv_auth_seq \ 955 + 3 * sess._detect_mult: 956 return 957 958 if not bfd_pkt.authenticate(sess._auth_keys): 959 LOG.debug("[BFD][%s][AUTH] BFD Control authentication failed.", 960 hex(sess._local_discr)) 961 return 962 963 # Sanity check passed, proceed. 964 if sess is not None: 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. 970 sess.recv(bfd_pkt) 971