1# Copyright (C) 2014 Xinguard, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12# implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""
17BFD Control packet parser/serializer
18
19[RFC 5880] BFD Control packet format::
20
21    0                   1                   2                   3
22    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
23   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24   |Vers |  Diag   |Sta|P|F|C|A|D|M|  Detect Mult  |    Length     |
25   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26   |                       My Discriminator                        |
27   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28   |                      Your Discriminator                       |
29   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30   |                    Desired Min TX Interval                    |
31   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32   |                   Required Min RX Interval                    |
33   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
34   |                 Required Min Echo RX Interval                 |
35   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
36
37An optional Authentication Section MAY be present in the following
38format of types:
39
401. Format of Simple Password Authentication Section::
41
42        0                   1                   2                   3
43        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
44       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
45       |   Auth Type   |   Auth Len    |  Auth Key ID  |  Password...  |
46       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
47       |                              ...                              |
48       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
49
502. Format of Keyed MD5 and Meticulous Keyed MD5 Authentication Section::
51
52        0                   1                   2                   3
53        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
54       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55       |   Auth Type   |   Auth Len    |  Auth Key ID  |   Reserved    |
56       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
57       |                        Sequence Number                        |
58       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
59       |                      Auth Key/Digest...                       |
60       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61       |                              ...                              |
62       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63
643. Format of Keyed SHA1 and Meticulous Keyed SHA1 Authentication Section::
65
66        0                   1                   2                   3
67        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
68       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
69       |   Auth Type   |   Auth Len    |  Auth Key ID  |   Reserved    |
70       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71       |                        Sequence Number                        |
72       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73       |                       Auth Key/Hash...                        |
74       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
75       |                              ...                              |
76       +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77"""
78import binascii
79import hashlib
80import random
81import six
82import struct
83
84from . import packet_base
85from ryu.lib import addrconv
86from ryu.lib import stringify
87
88BFD_STATE_ADMIN_DOWN = 0
89BFD_STATE_DOWN = 1
90BFD_STATE_INIT = 2
91BFD_STATE_UP = 3
92
93BFD_STATE_NAME = {0: "AdminDown",
94                  1: "Down",
95                  2: "Init",
96                  3: "Up"}
97
98BFD_FLAG_POLL = 1 << 5
99BFD_FLAG_FINAL = 1 << 4
100BFD_FLAG_CTRL_PLANE_INDEP = 1 << 3
101BFD_FLAG_AUTH_PRESENT = 1 << 2
102BFD_FLAG_DEMAND = 1 << 1
103BFD_FLAG_MULTIPOINT = 1
104
105BFD_DIAG_NO_DIAG = 0
106BFD_DIAG_CTRL_DETECT_TIME_EXPIRED = 1
107BFD_DIAG_ECHO_FUNC_FAILED = 2
108BFD_DIAG_NEIG_SIG_SESS_DOWN = 3
109BFD_DIAG_FWD_PLANE_RESET = 4
110BFD_DIAG_PATH_DOWN = 5
111BFD_DIAG_CONCAT_PATH_DOWN = 6
112BFD_DIAG_ADMIN_DOWN = 7
113BFD_DIAG_REV_CONCAT_PATH_DOWN = 8
114
115BFD_DIAG_CODE_NAME = {0: "No Diagnostic",
116                      1: "Control Detection Time Expired",
117                      2: "Echo Function Failed",
118                      3: "Neighbor Signaled Session Down",
119                      4: "Forwarding Plane Reset",
120                      5: "Path Down",
121                      6: "Concatenated Path Down",
122                      7: "Administratively Down",
123                      8: "Reverse Concatenated Path Down"}
124
125BFD_AUTH_RESERVED = 0
126BFD_AUTH_SIMPLE_PASS = 1
127BFD_AUTH_KEYED_MD5 = 2
128BFD_AUTH_METICULOUS_KEYED_MD5 = 3
129BFD_AUTH_KEYED_SHA1 = 4
130BFD_AUTH_METICULOUS_KEYED_SHA1 = 5
131
132BFD_AUTH_TYPE_NAME = {0: "Reserved",
133                      1: "Simple Password",
134                      2: "Keyed MD5",
135                      3: "Meticulous Keyed MD5",
136                      4: "Keyed SHA1",
137                      5: "Meticulous Keyed SHA1"}
138
139
140class bfd(packet_base.PacketBase):
141    """BFD (RFC 5880) Control packet encoder/decoder class.
142
143    The serialized packet would looks like the ones described
144    in the following sections.
145
146    * RFC 5880 Generic BFD Control Packet Format
147
148    An instance has the following attributes at least.
149    Most of them are same to the on-wire counterparts but in host byte order.
150
151    __init__ takes the corresponding args in this order.
152
153    .. tabularcolumns:: |l|L|
154
155    ============================== ============================================
156    Attribute                      Description
157    ============================== ============================================
158    ver                            The version number of the protocol.
159                                   This class implements protocol version 1.
160    diag                           A diagnostic code specifying the local
161                                   system's reason for the last change in
162                                   session state.
163    state                          The current BFD session state as seen by
164                                   the transmitting system.
165    flags                          Bitmap of the following flags.
166
167                                   | BFD_FLAG_POLL
168                                   | BFD_FLAG_FINAL
169                                   | BFD_FLAG_CTRL_PLANE_INDEP
170                                   | BFD_FLAG_AUTH_PRESENT
171                                   | BFD_FLAG_DEMAND
172                                   | BFD_FLAG_MULTIPOINT
173    detect_mult                    Detection time multiplier.
174    my_discr                       My Discriminator.
175    your_discr                     Your Discriminator.
176    desired_min_tx_interval        Desired Min TX Interval. (in microseconds)
177    required_min_rx_interval       Required Min RX Interval. (in microseconds)
178    required_min_echo_rx_interval  Required Min Echo RX Interval.
179                                   (in microseconds)
180    auth_cls                       (Optional) Authentication Section instance.
181                                   It's defined only when the Authentication
182                                   Present (A) bit is set in flags.
183                                   Assign an instance of the following classes:
184                                   ``SimplePassword``, ``KeyedMD5``,
185                                   ``MeticulousKeyedMD5``, ``KeyedSHA1``, and
186                                   ``MeticulousKeyedSHA1``.
187    length                         (Optional) Length of the BFD Control packet,
188                                   in bytes.
189    ============================== ============================================
190    """
191
192    _PACK_STR = '!BBBBIIIII'
193    _PACK_STR_LEN = struct.calcsize(_PACK_STR)
194
195    _TYPE = {
196        'ascii': []
197    }
198
199    _auth_parsers = {}
200
201    def __init__(self, ver=1, diag=0, state=0, flags=0, detect_mult=0,
202                 my_discr=0, your_discr=0, desired_min_tx_interval=0,
203                 required_min_rx_interval=0, required_min_echo_rx_interval=0,
204                 auth_cls=None, length=None):
205        super(bfd, self).__init__()
206
207        self.ver = ver
208        self.diag = diag
209        self.state = state
210        self.flags = flags
211        self.detect_mult = detect_mult
212        self.my_discr = my_discr
213        self.your_discr = your_discr
214        self.desired_min_tx_interval = desired_min_tx_interval
215        self.required_min_rx_interval = required_min_rx_interval
216        self.required_min_echo_rx_interval = required_min_echo_rx_interval
217        self.auth_cls = auth_cls
218        if isinstance(length, int):
219            self.length = length
220        else:
221            self.length = len(self)
222
223    def __len__(self):
224        if self.flags & BFD_FLAG_AUTH_PRESENT and self.auth_cls is not None:
225            return self._PACK_STR_LEN + len(self.auth_cls)
226        else:
227            return self._PACK_STR_LEN
228
229    @classmethod
230    def parser(cls, buf):
231        (diag, flags, detect_mult, length, my_discr, your_discr,
232         desired_min_tx_interval, required_min_rx_interval,
233         required_min_echo_rx_interval) = \
234            struct.unpack_from(cls._PACK_STR, buf[:cls._PACK_STR_LEN])
235
236        ver = diag >> 5
237        diag = diag & 0x1f
238        state = flags >> 6
239        flags = flags & 0x3f
240
241        if flags & BFD_FLAG_AUTH_PRESENT:
242            auth_type = six.indexbytes(buf, cls._PACK_STR_LEN)
243            auth_cls = cls._auth_parsers[auth_type].\
244                parser(buf[cls._PACK_STR_LEN:])[0]
245        else:
246            auth_cls = None
247
248        msg = cls(ver, diag, state, flags, detect_mult,
249                  my_discr, your_discr, desired_min_tx_interval,
250                  required_min_rx_interval, required_min_echo_rx_interval,
251                  auth_cls)
252
253        return msg, None, None
254
255    def serialize(self, payload, prev):
256        if self.flags & BFD_FLAG_AUTH_PRESENT and self.auth_cls is not None:
257            return self.pack() + \
258                self.auth_cls.serialize(payload=None, prev=self)
259        else:
260            return self.pack()
261
262    def pack(self):
263        """
264        Encode a BFD Control packet without authentication section.
265        """
266        diag = (self.ver << 5) + self.diag
267        flags = (self.state << 6) + self.flags
268        length = len(self)
269
270        return struct.pack(self._PACK_STR, diag, flags, self.detect_mult,
271                           length, self.my_discr, self.your_discr,
272                           self.desired_min_tx_interval,
273                           self.required_min_rx_interval,
274                           self.required_min_echo_rx_interval)
275
276    def authenticate(self, *args, **kwargs):
277        """Authenticate this packet.
278
279        Returns a boolean indicates whether the packet can be authenticated
280        or not.
281
282        Returns ``False`` if the Authentication Present (A) is not set in the
283        flag of this packet.
284
285        Returns ``False`` if the Authentication Section for this packet is not
286        present.
287
288        For the description of the arguemnts of this method, refer to the
289        authentication method of the Authentication Section classes.
290        """
291        if not self.flags & BFD_FLAG_AUTH_PRESENT or \
292                not issubclass(self.auth_cls.__class__, BFDAuth):
293            return False
294
295        return self.auth_cls.authenticate(self, *args, **kwargs)
296
297    @classmethod
298    def set_auth_parser(cls, auth_cls):
299        cls._auth_parsers[auth_cls.auth_type] = auth_cls
300
301    @classmethod
302    def register_auth_type(cls, auth_type):
303        def _set_type(auth_cls):
304            auth_cls.set_type(auth_cls, auth_type)
305            cls.set_auth_parser(auth_cls)
306            return auth_cls
307        return _set_type
308
309
310class BFDAuth(stringify.StringifyMixin):
311    """Base class of BFD (RFC 5880) Authentication Section
312
313    An instance has the following attributes at least.
314    Most of them are same to the on-wire counterparts but in host byte order.
315
316    .. tabularcolumns:: |l|L|
317
318    =========== ============================================
319    Attribute   Description
320    =========== ============================================
321    auth_type   The authentication type in use.
322    auth_len    The length, in bytes, of the authentication
323                section, including the ``auth_type`` and
324                ``auth_len`` fields.
325    =========== ============================================
326    """
327    _PACK_HDR_STR = '!BB'
328    _PACK_HDR_STR_LEN = struct.calcsize(_PACK_HDR_STR)
329
330    auth_type = None
331
332    def __init__(self, auth_len=None):
333        super(BFDAuth, self).__init__()
334        if isinstance(auth_len, int):
335            self.auth_len = auth_len
336        else:
337            self.auth_len = len(self)
338
339    @staticmethod
340    def set_type(subcls, auth_type):
341        assert issubclass(subcls, BFDAuth)
342        subcls.auth_type = auth_type
343
344    @classmethod
345    def parser_hdr(cls, buf):
346        """
347        Parser for common part of authentication section.
348        """
349        return struct.unpack_from(cls._PACK_HDR_STR,
350                                  buf[:cls._PACK_HDR_STR_LEN])
351
352    def serialize_hdr(self):
353        """
354        Serialization function for common part of authentication section.
355        """
356        return struct.pack(self._PACK_HDR_STR, self.auth_type, self.auth_len)
357
358
359@bfd.register_auth_type(BFD_AUTH_SIMPLE_PASS)
360class SimplePassword(BFDAuth):
361    """ BFD (RFC 5880) Simple Password Authentication Section class
362
363    An instance has the following attributes.
364    Most of them are same to the on-wire counterparts but in host byte order.
365
366    .. tabularcolumns:: |l|L|
367
368    =========== ============================================
369    Attribute   Description
370    =========== ============================================
371    auth_type   (Fixed) The authentication type in use.
372    auth_key_id The authentication Key ID in use.
373    password    The simple password in use on this session.
374                The password is a binary string, and MUST be
375                from 1 to 16 bytes in length.
376    auth_len    The length, in bytes, of the authentication
377                section, including the ``auth_type`` and
378                ``auth_len`` fields.
379    =========== ============================================
380    """
381    _PACK_STR = '!B'
382    _PACK_STR_LEN = struct.calcsize(_PACK_STR)
383
384    def __init__(self, auth_key_id, password, auth_len=None):
385        assert len(password) >= 1 and len(password) <= 16
386        self.auth_key_id = auth_key_id
387        self.password = password
388        super(SimplePassword, self).__init__(auth_len)
389
390    def __len__(self):
391        return self._PACK_HDR_STR_LEN + self._PACK_STR_LEN + len(self.password)
392
393    @classmethod
394    def parser(cls, buf):
395        (auth_type, auth_len) = cls.parser_hdr(buf)
396        assert auth_type == cls.auth_type
397
398        auth_key_id = six.indexbytes(buf, cls._PACK_HDR_STR_LEN)
399
400        password = buf[cls._PACK_HDR_STR_LEN + cls._PACK_STR_LEN:auth_len]
401
402        msg = cls(auth_key_id, password, auth_len)
403
404        return msg, None, None
405
406    def serialize(self, payload, prev):
407        """Encode a Simple Password Authentication Section.
408
409        ``payload`` is the rest of the packet which will immediately follow
410        this section.
411
412        ``prev`` is a ``bfd`` instance for the BFD Control header. It's not
413        necessary for encoding only the Simple Password section.
414        """
415        return self.serialize_hdr() + \
416            struct.pack(self._PACK_STR, self.auth_key_id) + self.password
417
418    def authenticate(self, prev=None, auth_keys=None):
419        """Authenticate the password for this packet.
420
421        This method can be invoked only when ``self.password`` is defined.
422
423        Returns a boolean indicates whether the password can be authenticated
424        or not.
425
426        ``prev`` is a ``bfd`` instance for the BFD Control header. It's not
427        necessary for authenticating the Simple Password.
428
429        ``auth_keys`` is a dictionary of authentication key chain which
430        key is an integer of *Auth Key ID* and value is a string of *Password*.
431        """
432        auth_keys = auth_keys if auth_keys else {}
433        assert isinstance(prev, bfd)
434        if self.auth_key_id in auth_keys and \
435                self.password == auth_keys[self.auth_key_id]:
436            return True
437        else:
438            return False
439
440
441@bfd.register_auth_type(BFD_AUTH_KEYED_MD5)
442class KeyedMD5(BFDAuth):
443    """ BFD (RFC 5880) Keyed MD5 Authentication Section class
444
445    An instance has the following attributes.
446    Most of them are same to the on-wire counterparts but in host byte order.
447
448    .. tabularcolumns:: |l|L|
449
450    =========== =================================================
451    Attribute   Description
452    =========== =================================================
453    auth_type   (Fixed) The authentication type in use.
454    auth_key_id The authentication Key ID in use.
455    seq         The sequence number for this packet.
456                This value is incremented occasionally.
457    auth_key    The shared MD5 key for this packet.
458    digest      (Optional) The 16-byte MD5 digest for the packet.
459    auth_len    (Fixed) The length of the authentication section
460                is 24 bytes.
461    =========== =================================================
462    """
463    _PACK_STR = '!BBL16s'
464    _PACK_STR_LEN = struct.calcsize(_PACK_STR)
465
466    def __init__(self, auth_key_id, seq, auth_key=None, digest=None,
467                 auth_len=None):
468        self.auth_key_id = auth_key_id
469        self.seq = seq
470        self.auth_key = auth_key
471        self.digest = digest
472        super(KeyedMD5, self).__init__(auth_len)
473
474    def __len__(self):
475        # Defined in RFC5880 Section 4.3.
476        return 24
477
478    @classmethod
479    def parser(cls, buf):
480        (auth_type, auth_len) = cls.parser_hdr(buf)
481        assert auth_type == cls.auth_type
482        assert auth_len == 24
483
484        (auth_key_id, reserved, seq, digest) = \
485            struct.unpack_from(cls._PACK_STR, buf[cls._PACK_HDR_STR_LEN:])
486        assert reserved == 0
487
488        msg = cls(auth_key_id=auth_key_id, seq=seq, auth_key=None,
489                  digest=digest)
490
491        return msg, None, None
492
493    def serialize(self, payload, prev):
494        """Encode a Keyed MD5 Authentication Section.
495
496        This method is used only when encoding an BFD Control packet.
497
498        ``payload`` is the rest of the packet which will immediately follow
499        this section.
500
501        ``prev`` is a ``bfd`` instance for the BFD Control header which this
502        authentication section belongs to. It's necessary to be assigned
503        because an MD5 digest must be calculated over the entire BFD Control
504        packet.
505        """
506        assert self.auth_key is not None and len(self.auth_key) <= 16
507        assert isinstance(prev, bfd)
508
509        bfd_bin = prev.pack()
510        auth_hdr_bin = self.serialize_hdr()
511        auth_data_bin = struct.pack(self._PACK_STR, self.auth_key_id, 0,
512                                    self.seq, self.auth_key +
513                                    (b'\x00' * (len(self.auth_key) - 16)))
514
515        h = hashlib.md5()
516        h.update(bfd_bin + auth_hdr_bin + auth_data_bin)
517        self.digest = h.digest()
518
519        return auth_hdr_bin + struct.pack(self._PACK_STR, self.auth_key_id, 0,
520                                          self.seq, self.digest)
521
522    def authenticate(self, prev, auth_keys=None):
523        """Authenticate the MD5 digest for this packet.
524
525        This method can be invoked only when ``self.digest`` is defined.
526
527        Returns a boolean indicates whether the digest can be authenticated
528        by the correspondent Auth Key or not.
529
530        ``prev`` is a ``bfd`` instance for the BFD Control header which this
531        authentication section belongs to. It's necessary to be assigned
532        because an MD5 digest must be calculated over the entire BFD Control
533        packet.
534
535        ``auth_keys`` is a dictionary of authentication key chain which
536        key is an integer of *Auth Key ID* and value is a string of *Auth Key*.
537        """
538        auth_keys = auth_keys if auth_keys else {}
539        assert isinstance(prev, bfd)
540
541        if self.digest is None:
542            return False
543
544        if self.auth_key_id not in auth_keys:
545            return False
546
547        auth_key = auth_keys[self.auth_key_id]
548
549        bfd_bin = prev.pack()
550        auth_hdr_bin = self.serialize_hdr()
551        auth_data_bin = struct.pack(self._PACK_STR, self.auth_key_id, 0,
552                                    self.seq, auth_key +
553                                    (b'\x00' * (len(auth_key) - 16)))
554
555        h = hashlib.md5()
556        h.update(bfd_bin + auth_hdr_bin + auth_data_bin)
557
558        if self.digest == h.digest():
559            return True
560        else:
561            return False
562
563
564@bfd.register_auth_type(BFD_AUTH_METICULOUS_KEYED_MD5)
565class MeticulousKeyedMD5(KeyedMD5):
566    """ BFD (RFC 5880) Meticulous Keyed MD5 Authentication Section class
567
568    All methods of this class are inherited from ``KeyedMD5``.
569
570    An instance has the following attributes.
571    Most of them are same to the on-wire counterparts but in host byte order.
572
573    .. tabularcolumns:: |l|L|
574
575    =========== =================================================
576    Attribute   Description
577    =========== =================================================
578    auth_type   (Fixed) The authentication type in use.
579    auth_key_id The authentication Key ID in use.
580    seq         The sequence number for this packet.
581                This value is incremented for each
582                successive packet transmitted for a session.
583    auth_key    The shared MD5 key for this packet.
584    digest      (Optional) The 16-byte MD5 digest for the packet.
585    auth_len    (Fixed) The length of the authentication section
586                is 24 bytes.
587    =========== =================================================
588    """
589    pass
590
591
592@bfd.register_auth_type(BFD_AUTH_KEYED_SHA1)
593class KeyedSHA1(BFDAuth):
594    """ BFD (RFC 5880) Keyed SHA1 Authentication Section class
595
596    An instance has the following attributes.
597    Most of them are same to the on-wire counterparts but in host byte order.
598
599    .. tabularcolumns:: |l|L|
600
601    =========== ================================================
602    Attribute   Description
603    =========== ================================================
604    auth_type   (Fixed) The authentication type in use.
605    auth_key_id The authentication Key ID in use.
606    seq         The sequence number for this packet.
607                This value is incremented occasionally.
608    auth_key    The shared SHA1 key for this packet.
609    auth_hash   (Optional) The 20-byte SHA1 hash for the packet.
610    auth_len    (Fixed) The length of the authentication section
611                is 28 bytes.
612    =========== ================================================
613    """
614    _PACK_STR = '!BBL20s'
615    _PACK_STR_LEN = struct.calcsize(_PACK_STR)
616
617    def __init__(self, auth_key_id, seq, auth_key=None, auth_hash=None,
618                 auth_len=None):
619        self.auth_key_id = auth_key_id
620        self.seq = seq
621        self.auth_key = auth_key
622        self.auth_hash = auth_hash
623        super(KeyedSHA1, self).__init__(auth_len)
624
625    def __len__(self):
626        # Defined in RFC5880 Section 4.4.
627        return 28
628
629    @classmethod
630    def parser(cls, buf):
631        (auth_type, auth_len) = cls.parser_hdr(buf)
632        assert auth_type == cls.auth_type
633        assert auth_len == 28
634
635        (auth_key_id, reserved, seq, auth_hash) = \
636            struct.unpack_from(cls._PACK_STR, buf[cls._PACK_HDR_STR_LEN:])
637        assert reserved == 0
638
639        msg = cls(auth_key_id=auth_key_id, seq=seq, auth_key=None,
640                  auth_hash=auth_hash)
641
642        return msg, None, None
643
644    def serialize(self, payload, prev):
645        """Encode a Keyed SHA1 Authentication Section.
646
647        This method is used only when encoding an BFD Control packet.
648
649        ``payload`` is the rest of the packet which will immediately follow
650        this section.
651
652        ``prev`` is a ``bfd`` instance for the BFD Control header which this
653        authentication section belongs to. It's necessary to be assigned
654        because an SHA1 hash must be calculated over the entire BFD Control
655        packet.
656        """
657        assert self.auth_key is not None and len(self.auth_key) <= 20
658        assert isinstance(prev, bfd)
659
660        bfd_bin = prev.pack()
661        auth_hdr_bin = self.serialize_hdr()
662        auth_data_bin = struct.pack(self._PACK_STR, self.auth_key_id, 0,
663                                    self.seq, self.auth_key +
664                                    (b'\x00' * (len(self.auth_key) - 20)))
665
666        h = hashlib.sha1()
667        h.update(bfd_bin + auth_hdr_bin + auth_data_bin)
668        self.auth_hash = h.digest()
669
670        return auth_hdr_bin + struct.pack(self._PACK_STR, self.auth_key_id, 0,
671                                          self.seq, self.auth_hash)
672
673    def authenticate(self, prev, auth_keys=None):
674        """Authenticate the SHA1 hash for this packet.
675
676        This method can be invoked only when ``self.auth_hash`` is defined.
677
678        Returns a boolean indicates whether the hash can be authenticated
679        by the correspondent Auth Key or not.
680
681        ``prev`` is a ``bfd`` instance for the BFD Control header which this
682        authentication section belongs to. It's necessary to be assigned
683        because an SHA1 hash must be calculated over the entire BFD Control
684        packet.
685
686        ``auth_keys`` is a dictionary of authentication key chain which
687        key is an integer of *Auth Key ID* and value is a string of *Auth Key*.
688        """
689        auth_keys = auth_keys if auth_keys else {}
690        assert isinstance(prev, bfd)
691
692        if self.auth_hash is None:
693            return False
694
695        if self.auth_key_id not in auth_keys:
696            return False
697
698        auth_key = auth_keys[self.auth_key_id]
699
700        bfd_bin = prev.pack()
701        auth_hdr_bin = self.serialize_hdr()
702        auth_data_bin = struct.pack(self._PACK_STR, self.auth_key_id, 0,
703                                    self.seq, auth_key +
704                                    (b'\x00' * (len(auth_key) - 20)))
705
706        h = hashlib.sha1()
707        h.update(bfd_bin + auth_hdr_bin + auth_data_bin)
708
709        if self.auth_hash == h.digest():
710            return True
711        else:
712            return False
713
714
715@bfd.register_auth_type(BFD_AUTH_METICULOUS_KEYED_SHA1)
716class MeticulousKeyedSHA1(KeyedSHA1):
717    """ BFD (RFC 5880) Meticulous Keyed SHA1 Authentication Section class
718
719    All methods of this class are inherited from ``KeyedSHA1``.
720
721    An instance has the following attributes.
722    Most of them are same to the on-wire counterparts but in host byte order.
723
724    .. tabularcolumns:: |l|L|
725
726    =========== ================================================
727    Attribute   Description
728    =========== ================================================
729    auth_type   (Fixed) The authentication type in use.
730    auth_key_id The authentication Key ID in use.
731    seq         The sequence number for this packet.
732                This value is incremented for each
733                successive packet transmitted for a session.
734    auth_key    The shared SHA1 key for this packet.
735    auth_hash   (Optional) The 20-byte SHA1 hash for the packet.
736    auth_len    (Fixed) The length of the authentication section
737                is 28 bytes.
738    =========== ================================================
739    """
740    pass
741
742
743bfd.set_classes(bfd._auth_parsers)
744