backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / lib / bfdlib.py
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 """
18 Implementation of Bidirectional Forwarding Detection for IPv4 (Single Hop)
19
20 This module provides a simple way to let Ryu act like a daemon for running
21 IPv4 single hop BFD (RFC5881).
22
23 Please 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
36 import logging
37 import time
38 import random
39
40 import six
41
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
59
60 LOG = logging.getLogger(__name__)
61
62 UINT16_MAX = (1 << 16) - 1
63 UINT32_MAX = (1 << 32) - 1
64
65 # RFC5881 Section 8
66 BFD_CONTROL_UDP_PORT = 3784
67 BFD_ECHO_UDP_PORT = 3785
68
69
70 class 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
514 class 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
586 class 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
639 class 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
651 class 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)