1# Copyright (C) 2013,2014 Nippon Telegraph and Telephone Corporation.
2# Copyright (C) 2013,2014 YAMAMOTO Takashi <yamamoto at valinux co jp>
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"""
18RFC 4271 BGP-4
19"""
20
21# todo
22# - notify data
23# - RFC 4364 BGP/MPLS IP Virtual Private Networks (VPNs)
24
25import abc
26import base64
27import collections
28import copy
29import functools
30import io
31import itertools
32import math
33import re
34import socket
35import struct
36
37import netaddr
38import six
39
40from ryu.lib.stringify import StringifyMixin
41from ryu.lib.packet import afi as addr_family
42from ryu.lib.packet import safi as subaddr_family
43from ryu.lib.packet import packet_base
44from ryu.lib.packet import stream_parser
45from ryu.lib.packet import vxlan
46from ryu.lib.packet import mpls
47from ryu.lib import addrconv
48from ryu.lib import type_desc
49from ryu.lib.type_desc import TypeDisp
50from ryu.lib import ip
51from ryu.lib.pack_utils import msg_pack_into
52from ryu.utils import binary_str
53from ryu.utils import import_module
54
55reduce = six.moves.reduce
56
57TCP_SERVER_PORT = 179
58
59BGP_MSG_OPEN = 1
60BGP_MSG_UPDATE = 2
61BGP_MSG_NOTIFICATION = 3
62BGP_MSG_KEEPALIVE = 4
63BGP_MSG_ROUTE_REFRESH = 5  # RFC 2918
64
65_VERSION = 4
66_MARKER = 16 * b'\xff'
67
68BGP_OPT_CAPABILITY = 2  # RFC 5492
69
70BGP_CAP_MULTIPROTOCOL = 1  # RFC 4760
71BGP_CAP_ROUTE_REFRESH = 2  # RFC 2918
72BGP_CAP_CARRYING_LABEL_INFO = 4  # RFC 3107
73BGP_CAP_GRACEFUL_RESTART = 64  # RFC 4724
74BGP_CAP_FOUR_OCTET_AS_NUMBER = 65  # RFC 4893
75BGP_CAP_ENHANCED_ROUTE_REFRESH = 70  # https://tools.ietf.org/html/\
76# draft-ietf-idr-bgp-enhanced-route-refresh-05
77BGP_CAP_ROUTE_REFRESH_CISCO = 128  # in cisco routers, there are two\
78# route refresh code: one using the capability code of 128 (old),
79# another using the capability code of 2 (new).
80
81BGP_ATTR_FLAG_OPTIONAL = 1 << 7
82BGP_ATTR_FLAG_TRANSITIVE = 1 << 6
83BGP_ATTR_FLAG_PARTIAL = 1 << 5
84BGP_ATTR_FLAG_EXTENDED_LENGTH = 1 << 4
85
86BGP_ATTR_TYPE_ORIGIN = 1  # 0,1,2 (1 byte)
87BGP_ATTR_TYPE_AS_PATH = 2  # a list of AS_SET/AS_SEQUENCE  eg. {1 2 3} 4 5
88BGP_ATTR_TYPE_NEXT_HOP = 3  # an IPv4 address
89BGP_ATTR_TYPE_MULTI_EXIT_DISC = 4  # uint32 metric
90BGP_ATTR_TYPE_LOCAL_PREF = 5  # uint32
91BGP_ATTR_TYPE_ATOMIC_AGGREGATE = 6  # 0 bytes
92BGP_ATTR_TYPE_AGGREGATOR = 7  # AS number and IPv4 address
93BGP_ATTR_TYPE_COMMUNITIES = 8  # RFC 1997
94BGP_ATTR_TYPE_ORIGINATOR_ID = 9  # RFC 4456
95BGP_ATTR_TYPE_CLUSTER_LIST = 10  # RFC 4456
96BGP_ATTR_TYPE_MP_REACH_NLRI = 14  # RFC 4760
97BGP_ATTR_TYPE_MP_UNREACH_NLRI = 15  # RFC 4760
98BGP_ATTR_TYPE_EXTENDED_COMMUNITIES = 16  # RFC 4360
99BGP_ATTR_TYPE_AS4_PATH = 17  # RFC 4893
100BGP_ATTR_TYPE_AS4_AGGREGATOR = 18  # RFC 4893
101BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE = 22  # RFC 6514
102
103BGP_ATTR_ORIGIN_IGP = 0x00
104BGP_ATTR_ORIGIN_EGP = 0x01
105BGP_ATTR_ORIGIN_INCOMPLETE = 0x02
106
107AS_TRANS = 23456  # RFC 4893
108
109# Well known commmunities  (RFC 1997)
110BGP_COMMUNITY_NO_EXPORT = 0xffffff01
111BGP_COMMUNITY_NO_ADVERTISE = 0xffffff02
112BGP_COMMUNITY_NO_EXPORT_SUBCONFED = 0xffffff03
113
114# RFC 4360
115# The low-order octet of Type field (subtype)
116BGP_EXTENDED_COMMUNITY_ROUTE_TARGET = 0x02
117BGP_EXTENDED_COMMUNITY_ROUTE_ORIGIN = 0x03
118
119# NOTIFICATION Error Code and SubCode
120# Note: 0 is a valid SubCode.  (Unspecific)
121
122# NOTIFICATION Error Code  RFC 4271 4.5.
123BGP_ERROR_MESSAGE_HEADER_ERROR = 1
124BGP_ERROR_OPEN_MESSAGE_ERROR = 2
125BGP_ERROR_UPDATE_MESSAGE_ERROR = 3
126BGP_ERROR_HOLD_TIMER_EXPIRED = 4
127BGP_ERROR_FSM_ERROR = 5
128BGP_ERROR_CEASE = 6
129
130# NOTIFICATION Error Subcode for BGP_ERROR_MESSAGE_HEADER_ERROR
131BGP_ERROR_SUB_CONNECTION_NOT_SYNCHRONIZED = 1
132BGP_ERROR_SUB_BAD_MESSAGE_LENGTH = 2  # Data: the erroneous Length field
133BGP_ERROR_SUB_BAD_MESSAGE_TYPE = 3  # Data: the erroneous Type field
134
135# NOTIFICATION Error Subcode for BGP_ERROR_OPEN_MESSAGE_ERROR
136BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER = 1  # Data: 2 octet version number
137BGP_ERROR_SUB_BAD_PEER_AS = 2
138BGP_ERROR_SUB_BAD_BGP_IDENTIFIER = 3
139BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER = 4
140BGP_ERROR_SUB_AUTHENTICATION_FAILURE = 5  # deprecated RFC 1771
141BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME = 6
142
143# NOTIFICATION Error Subcode for BGP_ERROR_UPDATE_MESSAGE_ERROR
144BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST = 1
145BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2  # Data: type of the attr
146BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE = 3  # Data: ditto
147BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR = 4  # Data: the attr (type, len, value)
148BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR = 5  # Data: ditto
149BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE = 6  # Data: ditto
150BGP_ERROR_SUB_ROUTING_LOOP = 7  # deprecated RFC 1771 AS Routing Loop
151BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE = 8  # Data: ditto
152BGP_ERROR_SUB_OPTIONAL_ATTRIBUTE_ERROR = 9  # Data: ditto
153BGP_ERROR_SUB_INVALID_NETWORK_FIELD = 10
154BGP_ERROR_SUB_MALFORMED_AS_PATH = 11
155
156# NOTIFICATION Error Subcode for BGP_ERROR_HOLD_TIMER_EXPIRED
157BGP_ERROR_SUB_HOLD_TIMER_EXPIRED = 1
158
159# NOTIFICATION Error Subcode for BGP_ERROR_FSM_ERROR
160BGP_ERROR_SUB_FSM_ERROR = 1
161
162# NOTIFICATION Error Subcode for BGP_ERROR_CEASE  (RFC 4486)
163BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED = 1  # Data: optional
164BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN = 2
165BGP_ERROR_SUB_PEER_DECONFIGURED = 3
166BGP_ERROR_SUB_ADMINISTRATIVE_RESET = 4
167BGP_ERROR_SUB_CONNECTION_RESET = 5
168BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE = 6
169BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION = 7
170BGP_ERROR_SUB_OUT_OF_RESOURCES = 8
171
172
173class _Value(object):
174    _VALUE_PACK_STR = None
175    _VALUE_FIELDS = ['value']
176
177    @staticmethod
178    def do_init(cls_type, self, kwargs, **extra_kwargs):
179        ourfields = {}
180        for f in cls_type._VALUE_FIELDS:
181            v = kwargs[f]
182            del kwargs[f]
183            ourfields[f] = v
184        kwargs.update(extra_kwargs)
185        super(cls_type, self).__init__(**kwargs)
186        self.__dict__.update(ourfields)
187
188    @classmethod
189    def parse_value(cls, buf):
190        values = struct.unpack_from(cls._VALUE_PACK_STR, six.binary_type(buf))
191        return dict(zip(cls._VALUE_FIELDS, values))
192
193    def serialize_value(self):
194        args = []
195        for f in self._VALUE_FIELDS:
196            args.append(getattr(self, f))
197        return struct.pack(self._VALUE_PACK_STR, *args)
198
199
200class BgpExc(Exception):
201    """Base bgp exception."""
202
203    CODE = 0
204    """BGP error code."""
205
206    SUB_CODE = 0
207    """BGP error sub-code."""
208
209    SEND_ERROR = True
210    """Flag if set indicates Notification message should be sent to peer."""
211
212    def __init__(self, data=''):
213        super(BgpExc, self).__init__()
214        self.data = data
215
216    def __str__(self):
217        return '<%s %r>' % (self.__class__.__name__, self.data)
218
219
220class BadNotification(BgpExc):
221    SEND_ERROR = False
222
223# ============================================================================
224# Message Header Errors
225# ============================================================================
226
227
228class NotSync(BgpExc):
229    CODE = BGP_ERROR_MESSAGE_HEADER_ERROR
230    SUB_CODE = BGP_ERROR_SUB_CONNECTION_NOT_SYNCHRONIZED
231
232
233class BadLen(BgpExc):
234    CODE = BGP_ERROR_MESSAGE_HEADER_ERROR
235    SUB_CODE = BGP_ERROR_SUB_BAD_MESSAGE_LENGTH
236
237    def __init__(self, msg_type_code, message_length):
238        super(BadLen, self).__init__()
239        self.msg_type_code = msg_type_code
240        self.length = message_length
241        self.data = struct.pack('!H', self.length)
242
243    def __str__(self):
244        return '<BadLen %d msgtype=%d>' % (self.length, self.msg_type_code)
245
246
247class BadMsg(BgpExc):
248    """Error to indicate un-recognized message type.
249
250    RFC says: If the Type field of the message header is not recognized, then
251    the Error Subcode MUST be set to Bad Message Type.  The Data field MUST
252    contain the erroneous Type field.
253    """
254    CODE = BGP_ERROR_MESSAGE_HEADER_ERROR
255    SUB_CODE = BGP_ERROR_SUB_BAD_MESSAGE_TYPE
256
257    def __init__(self, msg_type):
258        super(BadMsg, self).__init__()
259        self.msg_type = msg_type
260        self.data = struct.pack('B', msg_type)
261
262    def __str__(self):
263        return '<BadMsg %d>' % (self.msg_type,)
264
265# ============================================================================
266# OPEN Message Errors
267# ============================================================================
268
269
270class MalformedOptionalParam(BgpExc):
271    """If recognized optional parameters are malformed.
272
273    RFC says: If one of the Optional Parameters in the OPEN message is
274    recognized, but is malformed, then the Error Subcode MUST be set to 0
275    (Unspecific).
276    """
277    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
278    SUB_CODE = 0
279
280
281class UnsupportedVersion(BgpExc):
282    """Error to indicate unsupport bgp version number.
283
284    RFC says: If the version number in the Version field of the received OPEN
285    message is not supported, then the Error Subcode MUST be set to Unsupported
286    Version Number.  The Data field is a 2-octet unsigned integer, which
287    indicates the largest, locally-supported version number less than the
288    version the remote BGP peer bid (as indicated in the received OPEN
289    message), or if the smallest, locally-supported version number is greater
290    than the version the remote BGP peer bid, then the smallest, locally-
291    supported version number.
292    """
293    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
294    SUB_CODE = BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER
295
296    def __init__(self, locally_support_version):
297        super(UnsupportedVersion, self).__init__()
298        self.data = struct.pack('H', locally_support_version)
299
300
301class BadPeerAs(BgpExc):
302    """Error to indicate open message has incorrect AS number.
303
304    RFC says: If the Autonomous System field of the OPEN message is
305    unacceptable, then the Error Subcode MUST be set to Bad Peer AS.  The
306    determination of acceptable Autonomous System numbers is configure peer AS.
307    """
308    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
309    SUB_CODE = BGP_ERROR_SUB_BAD_PEER_AS
310
311
312class BadBgpId(BgpExc):
313    """Error to indicate incorrect BGP Identifier.
314
315    RFC says: If the BGP Identifier field of the OPEN message is syntactically
316    incorrect, then the Error Subcode MUST be set to Bad BGP Identifier.
317    Syntactic correctness means that the BGP Identifier field represents a
318    valid unicast IP host address.
319    """
320    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
321    SUB_CODE = BGP_ERROR_SUB_BAD_BGP_IDENTIFIER
322
323
324class UnsupportedOptParam(BgpExc):
325    """Error to indicate unsupported optional parameters.
326
327    RFC says: If one of the Optional Parameters in the OPEN message is not
328    recognized, then the Error Subcode MUST be set to Unsupported Optional
329    Parameters.
330    """
331    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
332    SUB_CODE = BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER
333
334
335class AuthFailure(BgpExc):
336    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
337    SUB_CODE = BGP_ERROR_SUB_AUTHENTICATION_FAILURE
338
339
340class UnacceptableHoldTime(BgpExc):
341    """Error to indicate Unacceptable Hold Time in open message.
342
343    RFC says: If the Hold Time field of the OPEN message is unacceptable, then
344    the Error Subcode MUST be set to Unacceptable Hold Time.
345    """
346    CODE = BGP_ERROR_OPEN_MESSAGE_ERROR
347    SUB_CODE = BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME
348
349# ============================================================================
350# UPDATE message related errors
351# ============================================================================
352
353
354class MalformedAttrList(BgpExc):
355    """Error to indicate UPDATE message is malformed.
356
357    RFC says: Error checking of an UPDATE message begins by examining the path
358    attributes.  If the Withdrawn Routes Length or Total Attribute Length is
359    too large (i.e., if Withdrawn Routes Length + Total Attribute Length + 23
360    exceeds the message Length), then the Error Subcode MUST be set to
361    Malformed Attribute List.
362    """
363    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
364    SUB_CODE = BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST
365
366
367class UnRegWellKnowAttr(BgpExc):
368    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
369    SUB_CODE = BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE
370
371
372class MissingWellKnown(BgpExc):
373    """Error to indicate missing well-known attribute.
374
375    RFC says: If any of the well-known mandatory attributes are not present,
376    then the Error Subcode MUST be set to Missing Well-known Attribute.  The
377    Data field MUST contain the Attribute Type Code of the missing, well-known
378    attribute.
379    """
380    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
381    SUB_CODE = BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE
382
383    def __init__(self, pattr_type_code):
384        super(MissingWellKnown, self).__init__()
385        self.pattr_type_code = pattr_type_code
386        self.data = struct.pack('B', pattr_type_code)
387
388
389class AttrFlagError(BgpExc):
390    """Error to indicate recognized path attributes have incorrect flags.
391
392    RFC says: If any recognized attribute has Attribute Flags that conflict
393    with the Attribute Type Code, then the Error Subcode MUST be set to
394    Attribute Flags Error.  The Data field MUST contain the erroneous attribute
395    (type, length, and value).
396    """
397    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
398    SUB_CODE = BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR
399
400
401class AttrLenError(BgpExc):
402    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
403    SUB_CODE = BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR
404
405
406class InvalidOriginError(BgpExc):
407    """Error indicates undefined Origin attribute value.
408
409    RFC says: If the ORIGIN attribute has an undefined value, then the Error
410    Sub- code MUST be set to Invalid Origin Attribute.  The Data field MUST
411    contain the unrecognized attribute (type, length, and value).
412    """
413    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
414    SUB_CODE = BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE
415
416
417class RoutingLoop(BgpExc):
418    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
419    SUB_CODE = BGP_ERROR_SUB_ROUTING_LOOP
420
421
422class InvalidNextHop(BgpExc):
423    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
424    SUB_CODE = BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE
425
426
427class OptAttrError(BgpExc):
428    """Error indicates Optional Attribute is malformed.
429
430    RFC says: If an optional attribute is recognized, then the value of this
431    attribute MUST be checked.  If an error is detected, the attribute MUST be
432    discarded, and the Error Subcode MUST be set to Optional Attribute Error.
433    The Data field MUST contain the attribute (type, length, and value).
434    """
435    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
436    SUB_CODE = BGP_ERROR_SUB_OPTIONAL_ATTRIBUTE_ERROR
437
438
439class InvalidNetworkField(BgpExc):
440    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
441    SUB_CODE = BGP_ERROR_SUB_INVALID_NETWORK_FIELD
442
443
444class MalformedAsPath(BgpExc):
445    """Error to indicate if AP_PATH attribute is syntactically incorrect.
446
447    RFC says: The AS_PATH attribute is checked for syntactic correctness.  If
448    the path is syntactically incorrect, then the Error Subcode MUST be set to
449    Malformed AS_PATH.
450    """
451    CODE = BGP_ERROR_UPDATE_MESSAGE_ERROR
452    SUB_CODE = BGP_ERROR_SUB_MALFORMED_AS_PATH
453
454
455# ============================================================================
456# Hold Timer Expired
457# ============================================================================
458
459
460class HoldTimerExpired(BgpExc):
461    """Error to indicate Hold Timer expired.
462
463    RFC says: If a system does not receive successive KEEPALIVE, UPDATE, and/or
464    NOTIFICATION messages within the period specified in the Hold Time field of
465    the OPEN message, then the NOTIFICATION message with the Hold Timer Expired
466    Error Code is sent and the BGP connection is closed.
467    """
468    CODE = BGP_ERROR_HOLD_TIMER_EXPIRED
469    SUB_CODE = BGP_ERROR_SUB_HOLD_TIMER_EXPIRED
470
471# ============================================================================
472# Finite State Machine Error
473# ============================================================================
474
475
476class FiniteStateMachineError(BgpExc):
477    """Error to indicate any Finite State Machine Error.
478
479    RFC says: Any error detected by the BGP Finite State Machine (e.g., receipt
480    of an unexpected event) is indicated by sending the NOTIFICATION message
481    with the Error Code Finite State Machine Error.
482    """
483    CODE = BGP_ERROR_FSM_ERROR
484    SUB_CODE = BGP_ERROR_SUB_FSM_ERROR
485
486
487# ============================================================================
488# Cease Errors
489# ============================================================================
490
491class MaxPrefixReached(BgpExc):
492    CODE = BGP_ERROR_CEASE
493    SUB_CODE = BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED
494
495
496class AdminShutdown(BgpExc):
497    """Error to indicate Administrative shutdown.
498
499    RFC says: If a BGP speaker decides to administratively shut down its
500    peering with a neighbor, then the speaker SHOULD send a NOTIFICATION
501    message  with the Error Code Cease and the Error Subcode 'Administrative
502    Shutdown'.
503    """
504    CODE = BGP_ERROR_CEASE
505    SUB_CODE = BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN
506
507
508class PeerDeConfig(BgpExc):
509    CODE = BGP_ERROR_CEASE
510    SUB_CODE = BGP_ERROR_SUB_PEER_DECONFIGURED
511
512
513class AdminReset(BgpExc):
514    CODE = BGP_ERROR_CEASE
515    SUB_CODE = BGP_ERROR_SUB_ADMINISTRATIVE_RESET
516
517
518class ConnRejected(BgpExc):
519    """Error to indicate Connection Rejected.
520
521    RFC says: If a BGP speaker decides to disallow a BGP connection (e.g., the
522    peer is not configured locally) after the speaker accepts a transport
523    protocol connection, then the BGP speaker SHOULD send a NOTIFICATION
524    message with the Error Code Cease and the Error Subcode "Connection
525    Rejected".
526    """
527    CODE = BGP_ERROR_CEASE
528    SUB_CODE = BGP_ERROR_SUB_CONNECTION_RESET
529
530
531class OtherConfChange(BgpExc):
532    CODE = BGP_ERROR_CEASE
533    SUB_CODE = BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE
534
535
536class CollisionResolution(BgpExc):
537    """Error to indicate Connection Collision Resolution.
538
539    RFC says: If a BGP speaker decides to send a NOTIFICATION message with the
540    Error Code Cease as a result of the collision resolution procedure (as
541    described in [BGP-4]), then the subcode SHOULD be set to "Connection
542    Collision Resolution".
543    """
544    CODE = BGP_ERROR_CEASE
545    SUB_CODE = BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION
546
547
548class OutOfResource(BgpExc):
549    CODE = BGP_ERROR_CEASE
550    SUB_CODE = BGP_ERROR_SUB_OUT_OF_RESOURCES
551
552
553@functools.total_ordering
554class RouteFamily(StringifyMixin):
555    def __init__(self, afi, safi):
556        self.afi = afi
557        self.safi = safi
558
559    def __lt__(self, other):
560        return (self.afi, self.safi) < (other.afi, other.safi)
561
562    def __eq__(self, other):
563        return (self.afi, self.safi) == (other.afi, other.safi)
564
565    def __hash__(self):
566        return hash((self.afi, self.safi))
567
568
569# Route Family Singleton
570RF_IPv4_UC = RouteFamily(addr_family.IP, subaddr_family.UNICAST)
571RF_IPv6_UC = RouteFamily(addr_family.IP6, subaddr_family.UNICAST)
572RF_IPv4_VPN = RouteFamily(addr_family.IP, subaddr_family.MPLS_VPN)
573RF_IPv6_VPN = RouteFamily(addr_family.IP6, subaddr_family.MPLS_VPN)
574RF_IPv4_MPLS = RouteFamily(addr_family.IP, subaddr_family.MPLS_LABEL)
575RF_IPv6_MPLS = RouteFamily(addr_family.IP6, subaddr_family.MPLS_LABEL)
576RF_L2_EVPN = RouteFamily(addr_family.L2VPN, subaddr_family.EVPN)
577RF_IPv4_FLOWSPEC = RouteFamily(addr_family.IP, subaddr_family.IP_FLOWSPEC)
578RF_IPv6_FLOWSPEC = RouteFamily(addr_family.IP6, subaddr_family.IP_FLOWSPEC)
579RF_VPNv4_FLOWSPEC = RouteFamily(addr_family.IP, subaddr_family.VPN_FLOWSPEC)
580RF_VPNv6_FLOWSPEC = RouteFamily(addr_family.IP6, subaddr_family.VPN_FLOWSPEC)
581RF_L2VPN_FLOWSPEC = RouteFamily(
582    addr_family.L2VPN, subaddr_family.VPN_FLOWSPEC)
583RF_RTC_UC = RouteFamily(addr_family.IP,
584                        subaddr_family.ROUTE_TARGET_CONSTRAINTS)
585
586_rf_map = {
587    (addr_family.IP, subaddr_family.UNICAST): RF_IPv4_UC,
588    (addr_family.IP6, subaddr_family.UNICAST): RF_IPv6_UC,
589    (addr_family.IP, subaddr_family.MPLS_VPN): RF_IPv4_VPN,
590    (addr_family.IP6, subaddr_family.MPLS_VPN): RF_IPv6_VPN,
591    (addr_family.IP, subaddr_family.MPLS_LABEL): RF_IPv4_MPLS,
592    (addr_family.IP6, subaddr_family.MPLS_LABEL): RF_IPv6_MPLS,
593    (addr_family.L2VPN, subaddr_family.EVPN): RF_L2_EVPN,
594    (addr_family.IP, subaddr_family.IP_FLOWSPEC): RF_IPv4_FLOWSPEC,
595    (addr_family.IP6, subaddr_family.IP_FLOWSPEC): RF_IPv6_FLOWSPEC,
596    (addr_family.IP, subaddr_family.VPN_FLOWSPEC): RF_VPNv4_FLOWSPEC,
597    (addr_family.IP6, subaddr_family.VPN_FLOWSPEC): RF_VPNv6_FLOWSPEC,
598    (addr_family.L2VPN, subaddr_family.VPN_FLOWSPEC): RF_L2VPN_FLOWSPEC,
599    (addr_family.IP, subaddr_family.ROUTE_TARGET_CONSTRAINTS): RF_RTC_UC
600}
601
602
603def get_rf(afi, safi):
604    return _rf_map[(afi, safi)]
605
606
607def pad(binary, len_):
608    assert len(binary) <= len_
609    return binary + b'\0' * (len_ - len(binary))
610
611
612class _RouteDistinguisher(StringifyMixin, TypeDisp, _Value):
613    _PACK_STR = '!H'
614    TWO_OCTET_AS = 0
615    IPV4_ADDRESS = 1
616    FOUR_OCTET_AS = 2
617
618    def __init__(self, admin=0, assigned=0, type_=None):
619        if type_ is None:
620            type_ = self._rev_lookup_type(self.__class__)
621        self.type = type_
622        self.admin = admin
623        self.assigned = assigned
624
625    @classmethod
626    def parser(cls, buf):
627        assert len(buf) == 8
628        (type_,) = struct.unpack_from(cls._PACK_STR, six.binary_type(buf))
629        rest = buf[struct.calcsize(cls._PACK_STR):]
630        subcls = cls._lookup_type(type_)
631        return subcls(**subcls.parse_value(rest))
632
633    @classmethod
634    def from_str(cls, str_):
635        assert isinstance(str_, str)
636
637        first, second = str_.split(':')
638        if '.' in first:
639            type_ = cls.IPV4_ADDRESS
640        elif int(first) > (1 << 16):
641            type_ = cls.FOUR_OCTET_AS
642            first = int(first)
643        else:
644            type_ = cls.TWO_OCTET_AS
645            first = int(first)
646        subcls = cls._lookup_type(type_)
647        return subcls(admin=first, assigned=int(second))
648
649    def serialize(self):
650        value = self.serialize_value()
651        buf = bytearray()
652        msg_pack_into(self._PACK_STR, buf, 0, self.type)
653        return six.binary_type(buf + value)
654
655    @property
656    def formatted_str(self):
657        return "%s:%s" % (self.admin, self.assigned)
658
659
660@_RouteDistinguisher.register_type(_RouteDistinguisher.TWO_OCTET_AS)
661class BGPTwoOctetAsRD(_RouteDistinguisher):
662    _VALUE_PACK_STR = '!HI'
663    _VALUE_FIELDS = ['admin', 'assigned']
664
665    def __init__(self, **kwargs):
666        super(BGPTwoOctetAsRD, self).__init__()
667        self.do_init(BGPTwoOctetAsRD, self, kwargs)
668
669
670@_RouteDistinguisher.register_type(_RouteDistinguisher.IPV4_ADDRESS)
671class BGPIPv4AddressRD(_RouteDistinguisher):
672    _VALUE_PACK_STR = '!4sH'
673    _VALUE_FIELDS = ['admin', 'assigned']
674    _TYPE = {
675        'ascii': [
676            'admin'
677        ]
678    }
679
680    def __init__(self, **kwargs):
681        super(BGPIPv4AddressRD, self).__init__()
682        self.do_init(BGPIPv4AddressRD, self, kwargs)
683
684    @classmethod
685    def parse_value(cls, buf):
686        d_ = super(BGPIPv4AddressRD, cls).parse_value(buf)
687        d_['admin'] = addrconv.ipv4.bin_to_text(d_['admin'])
688        return d_
689
690    def serialize_value(self):
691        args = []
692        for f in self._VALUE_FIELDS:
693            v = getattr(self, f)
694            if f == 'admin':
695                v = bytes(addrconv.ipv4.text_to_bin(v))
696            args.append(v)
697        buf = bytearray()
698        msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args)
699        return buf
700
701
702@_RouteDistinguisher.register_type(_RouteDistinguisher.FOUR_OCTET_AS)
703class BGPFourOctetAsRD(_RouteDistinguisher):
704    _VALUE_PACK_STR = '!IH'
705    _VALUE_FIELDS = ['admin', 'assigned']
706
707    def __init__(self, **kwargs):
708        super(BGPFourOctetAsRD, self).__init__()
709        self.do_init(BGPFourOctetAsRD, self, kwargs)
710
711
712@six.add_metaclass(abc.ABCMeta)
713class _AddrPrefix(StringifyMixin):
714    _PACK_STR = '!B'  # length
715
716    def __init__(self, length, addr, prefixes=None, **kwargs):
717        # length is on-wire bit length of prefixes+addr.
718        assert prefixes != ()
719        if isinstance(addr, tuple):
720            # for _AddrPrefix.parser
721            # also for _VPNAddrPrefix.__init__ etc
722            (addr,) = addr
723        self.length = length
724        if prefixes:
725            addr = prefixes + (addr,)
726        self.addr = addr
727
728    @classmethod
729    @abc.abstractmethod
730    def _to_bin(cls, addr):
731        pass
732
733    @classmethod
734    @abc.abstractmethod
735    def _from_bin(cls, addr):
736        pass
737
738    @classmethod
739    def parser(cls, buf):
740        (length, ) = struct.unpack_from(cls._PACK_STR, six.binary_type(buf))
741        rest = buf[struct.calcsize(cls._PACK_STR):]
742        byte_length = (length + 7) // 8
743        addr = cls._from_bin(rest[:byte_length])
744        rest = rest[byte_length:]
745        return cls(length=length, addr=addr), rest
746
747    def serialize(self):
748        # fixup
749        byte_length = (self.length + 7) // 8
750        bin_addr = self._to_bin(self.addr)
751        if (self.length % 8) == 0:
752            bin_addr = bin_addr[:byte_length]
753        else:
754            # clear trailing bits in the last octet.
755            # rfc doesn't require this.
756            mask = 0xff00 >> (self.length % 8)
757            last_byte = six.int2byte(
758                six.indexbytes(bin_addr, byte_length - 1) & mask)
759            bin_addr = bin_addr[:byte_length - 1] + last_byte
760        self.addr = self._from_bin(bin_addr)
761
762        buf = bytearray()
763        msg_pack_into(self._PACK_STR, buf, 0, self.length)
764        return buf + bytes(bin_addr)
765
766
767class _BinAddrPrefix(_AddrPrefix):
768    @classmethod
769    def _to_bin(cls, addr):
770        return addr
771
772    @classmethod
773    def _from_bin(cls, addr):
774        return addr
775
776
777class _LabelledAddrPrefix(_AddrPrefix):
778    _LABEL_PACK_STR = '!3B'
779    # RFC3107
780    # 3. Carrying Label Mapping Information
781    # The label information carried (as part of NLRI) in the Withdrawn
782    # Routes field should be set to 0x800000.  (Of course, terminating the
783    # BGP session also withdraws all the previously advertised routes.)
784    #
785    _WITHDRAW_LABEL = 0x800000
786
787    def __init__(self, length, addr, labels=None, **kwargs):
788        labels = labels if labels else []
789        assert isinstance(labels, list)
790        is_tuple = isinstance(addr, tuple)
791        if is_tuple:
792            # for _AddrPrefix.parser
793            assert not labels
794            labels = addr[0]
795            addr = addr[1:]
796        else:
797            length += struct.calcsize(self._LABEL_PACK_STR) * 8 * len(labels)
798        assert length > struct.calcsize(self._LABEL_PACK_STR) * 8 * len(labels)
799        prefixes = (labels,)
800        super(_LabelledAddrPrefix, self).__init__(prefixes=prefixes,
801                                                  length=length,
802                                                  addr=addr,
803                                                  **kwargs)
804
805    @classmethod
806    def _label_to_bin(cls, label):
807        buf = bytearray()
808        msg_pack_into(cls._LABEL_PACK_STR, buf, 0,
809                      (label & 0xff0000) >> 16,
810                      (label & 0x00ff00) >> 8,
811                      (label & 0x0000ff) >> 0)
812        return six.binary_type(buf)
813
814    @classmethod
815    def _label_from_bin(cls, label):
816        (b1, b2, b3) = struct.unpack_from(cls._LABEL_PACK_STR,
817                                          six.binary_type(label))
818        rest = label[struct.calcsize(cls._LABEL_PACK_STR):]
819        return (b1 << 16) | (b2 << 8) | b3, rest
820
821    @classmethod
822    def _to_bin(cls, addr):
823        labels = addr[0]
824        rest = addr[1:]
825        labels = [x << 4 for x in labels]
826        if labels and labels[-1] != cls._WITHDRAW_LABEL:
827            labels[-1] |= 1  # bottom of stack
828        bin_labels = list(cls._label_to_bin(l) for l in labels)
829        return bytes(reduce(lambda x, y: x + y, bin_labels,
830                            bytearray()) + cls._prefix_to_bin(rest))
831
832    @classmethod
833    def _has_no_label(cls, bin_):
834        try:
835            length = len(bin_)
836            labels = []
837            while True:
838                (label, bin_) = cls._label_from_bin(bin_)
839                labels.append(label)
840                if label & 1 or label == cls._WITHDRAW_LABEL:
841                    break
842            assert length > struct.calcsize(cls._LABEL_PACK_STR) * len(labels)
843        except struct.error:
844            return True
845        except AssertionError:
846            return True
847        return False
848
849    @classmethod
850    def _from_bin(cls, addr):
851        rest = addr
852        labels = []
853
854        if cls._has_no_label(rest):
855            return ([],) + cls._prefix_from_bin(rest)
856
857        while True:
858            (label, rest) = cls._label_from_bin(rest)
859            labels.append(label >> 4)
860            if label & 1 or label == cls._WITHDRAW_LABEL:
861                break
862        return (labels,) + cls._prefix_from_bin(rest)
863
864
865class _UnlabelledAddrPrefix(_AddrPrefix):
866    @classmethod
867    def _to_bin(cls, addr):
868        return cls._prefix_to_bin((addr,))
869
870    @classmethod
871    def _from_bin(cls, binaddr):
872        (addr,) = cls._prefix_from_bin(binaddr)
873        return addr
874
875
876class _IPAddrPrefix(_AddrPrefix):
877    @staticmethod
878    def _prefix_to_bin(addr):
879        (addr,) = addr
880        return addrconv.ipv4.text_to_bin(addr)
881
882    @staticmethod
883    def _prefix_from_bin(addr):
884        return addrconv.ipv4.bin_to_text(pad(addr, 4)),
885
886
887class _IP6AddrPrefix(_AddrPrefix):
888    @staticmethod
889    def _prefix_to_bin(addr):
890        (addr,) = addr
891        return addrconv.ipv6.text_to_bin(addr)
892
893    @staticmethod
894    def _prefix_from_bin(addr):
895        return addrconv.ipv6.bin_to_text(pad(addr, 16)),
896
897
898class _VPNAddrPrefix(_AddrPrefix):
899    _RD_PACK_STR = '!Q'
900
901    def __init__(self, length, addr, prefixes=(), route_dist=0):
902        if isinstance(addr, tuple):
903            # for _AddrPrefix.parser
904            assert not route_dist
905            assert length > struct.calcsize(self._RD_PACK_STR) * 8
906            route_dist = addr[0]
907            addr = addr[1:]
908        else:
909            length += struct.calcsize(self._RD_PACK_STR) * 8
910
911        if isinstance(route_dist, str):
912            route_dist = _RouteDistinguisher.from_str(route_dist)
913
914        prefixes = prefixes + (route_dist,)
915        super(_VPNAddrPrefix, self).__init__(prefixes=prefixes,
916                                             length=length,
917                                             addr=addr)
918
919    @classmethod
920    def _prefix_to_bin(cls, addr):
921        rd = addr[0]
922        rest = addr[1:]
923        binrd = rd.serialize()
924        return binrd + super(_VPNAddrPrefix, cls)._prefix_to_bin(rest)
925
926    @classmethod
927    def _prefix_from_bin(cls, binaddr):
928        binrd = binaddr[:8]
929        binrest = binaddr[8:]
930        rd = _RouteDistinguisher.parser(binrd)
931        return (rd,) + super(_VPNAddrPrefix, cls)._prefix_from_bin(binrest)
932
933
934class IPAddrPrefix(_UnlabelledAddrPrefix, _IPAddrPrefix):
935    ROUTE_FAMILY = RF_IPv4_UC
936    _TYPE = {
937        'ascii': [
938            'addr'
939        ]
940    }
941
942    @property
943    def prefix(self):
944        return self.addr + '/{0}'.format(self.length)
945
946    @property
947    def formatted_nlri_str(self):
948        return self.prefix
949
950
951class IP6AddrPrefix(_UnlabelledAddrPrefix, _IP6AddrPrefix):
952    ROUTE_FAMILY = RF_IPv6_UC
953    _TYPE = {
954        'ascii': [
955            'addr'
956        ]
957    }
958
959    @property
960    def prefix(self):
961        return self.addr + '/{0}'.format(self.length)
962
963    @property
964    def formatted_nlri_str(self):
965        return self.prefix
966
967
968class LabelledIPAddrPrefix(_LabelledAddrPrefix, _IPAddrPrefix):
969    ROUTE_FAMILY = RF_IPv4_MPLS
970
971
972class LabelledIP6AddrPrefix(_LabelledAddrPrefix, _IP6AddrPrefix):
973    ROUTE_FAMILY = RF_IPv6_MPLS
974
975
976class LabelledVPNIPAddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix,
977                              _IPAddrPrefix):
978    ROUTE_FAMILY = RF_IPv4_VPN
979
980    @property
981    def prefix(self):
982        masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \
983            - struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2])
984        return self.addr[-1] + '/{0}'.format(masklen)
985
986    @property
987    def route_dist(self):
988        return self.addr[-2].formatted_str
989
990    @property
991    def label_list(self):
992        return self.addr[0]
993
994    @property
995    def formatted_nlri_str(self):
996        return "%s:%s" % (self.route_dist, self.prefix)
997
998
999class LabelledVPNIP6AddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix,
1000                               _IP6AddrPrefix):
1001    ROUTE_FAMILY = RF_IPv6_VPN
1002
1003    @property
1004    def prefix(self):
1005        masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \
1006            - struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2])
1007        return self.addr[-1] + '/{0}'.format(masklen)
1008
1009    @property
1010    def route_dist(self):
1011        return self.addr[-2].formatted_str
1012
1013    @property
1014    def label_list(self):
1015        return self.addr[0]
1016
1017    @property
1018    def formatted_nlri_str(self):
1019        return "%s:%s" % (self.route_dist, self.prefix)
1020
1021
1022class EvpnEsi(StringifyMixin, TypeDisp, _Value):
1023    """
1024    Ethernet Segment Identifier
1025
1026    The supported ESI Types:
1027
1028     - ``EvpnEsi.ARBITRARY`` indicates EvpnArbitraryEsi.
1029
1030     - ``EvpnEsi.LACP`` indicates EvpnLACPEsi.
1031
1032     - ``EvpnEsi.L2_BRIDGE`` indicates EvpnL2BridgeEsi.
1033
1034     - ``EvpnEsi.MAC_BASED`` indicates EvpnMacBasedEsi.
1035
1036     - ``EvpnEsi.ROUTER_ID`` indicates EvpnRouterIDEsi.
1037
1038     - ``EvpnEsi.AS_BASED`` indicates EvpnASBasedEsi.
1039    """
1040    _PACK_STR = "!B"  # ESI Type
1041    _ESI_LEN = 10
1042
1043    ARBITRARY = 0x00
1044    LACP = 0x01
1045    L2_BRIDGE = 0x02
1046    MAC_BASED = 0x03
1047    ROUTER_ID = 0x04
1048    AS_BASED = 0x05
1049    MAX = 0xff  # Reserved
1050
1051    _TYPE_NAME = None  # must be defined in subclass
1052
1053    def __init__(self, type_=None):
1054        if type_ is None:
1055            type_ = self._rev_lookup_type(self.__class__)
1056        self.type = type_
1057
1058    @classmethod
1059    def parser(cls, buf):
1060        (esi_type,) = struct.unpack_from(
1061            cls._PACK_STR, six.binary_type(buf))
1062        subcls = cls._lookup_type(esi_type)
1063        return subcls(**subcls.parse_value(buf[1:cls._ESI_LEN]))
1064
1065    def serialize(self):
1066        buf = bytearray()
1067        msg_pack_into(EvpnEsi._PACK_STR, buf, 0, self.type)
1068        return six.binary_type(buf + self.serialize_value())
1069
1070    @property
1071    def formatted_str(self):
1072        return '%s(%s)' % (
1073            self._TYPE_NAME,
1074            ','.join(str(getattr(self, v)) for v in self._VALUE_FIELDS))
1075
1076
1077@EvpnEsi.register_unknown_type()
1078class EvpnUnknownEsi(EvpnEsi):
1079    """
1080    ESI value for unknown type
1081    """
1082    _TYPE_NAME = 'unknown'
1083    _VALUE_PACK_STR = '!9s'
1084    _VALUE_FIELDS = ['value']
1085
1086    def __init__(self, value, type_=None):
1087        super(EvpnUnknownEsi, self).__init__(type_)
1088        self.value = value
1089
1090    @property
1091    def formatted_str(self):
1092        return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value))
1093
1094
1095@EvpnEsi.register_type(EvpnEsi.ARBITRARY)
1096class EvpnArbitraryEsi(EvpnEsi):
1097    """
1098    Arbitrary 9-octet ESI value
1099
1100    This type indicates an arbitrary 9-octet ESI value,
1101    which is managed and configured by the operator.
1102    """
1103    _TYPE_NAME = 'arbitrary'
1104    _VALUE_PACK_STR = '!9s'
1105    _VALUE_FIELDS = ['value']
1106
1107    def __init__(self, value, type_=None):
1108        super(EvpnArbitraryEsi, self).__init__(type_)
1109        self.value = value
1110
1111    @property
1112    def formatted_str(self):
1113        return '%s(%s)' % (self._TYPE_NAME, binary_str(self.value))
1114
1115
1116@EvpnEsi.register_type(EvpnEsi.LACP)
1117class EvpnLACPEsi(EvpnEsi):
1118    """
1119    ESI value for LACP
1120
1121    When IEEE 802.1AX LACP is used between the PEs and CEs,
1122    this ESI type indicates an auto-generated ESI value
1123    determined from LACP.
1124    """
1125    _TYPE_NAME = 'lacp'
1126    _VALUE_PACK_STR = '!6sHx'
1127    _VALUE_FIELDS = ['mac_addr', 'port_key']
1128    _TYPE = {
1129        'ascii': [
1130            'mac_addr'
1131        ]
1132    }
1133
1134    def __init__(self, mac_addr, port_key, type_=None):
1135        super(EvpnLACPEsi, self).__init__(type_)
1136        self.mac_addr = mac_addr
1137        self.port_key = port_key
1138
1139    @classmethod
1140    def parse_value(cls, buf):
1141        (mac_addr, port_key) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
1142        return {
1143            'mac_addr': addrconv.mac.bin_to_text(mac_addr),
1144            'port_key': port_key,
1145        }
1146
1147    def serialize_value(self):
1148        return struct.pack(
1149            self._VALUE_PACK_STR,
1150            addrconv.mac.text_to_bin(self.mac_addr), self.port_key)
1151
1152
1153@EvpnEsi.register_type(EvpnEsi.L2_BRIDGE)
1154class EvpnL2BridgeEsi(EvpnEsi):
1155    """
1156    ESI value for Layer 2 Bridge
1157
1158    This type is used in the case of indirectly connected hosts
1159    via a bridged LAN between the CEs and the PEs.
1160    The ESI Value is auto-generated and determined based
1161    on the Layer 2 bridge protocol.
1162    """
1163    _TYPE_NAME = 'l2_bridge'
1164    _VALUE_PACK_STR = '!6sHx'
1165    _VALUE_FIELDS = ['mac_addr', 'priority']
1166    _TYPE = {
1167        'ascii': [
1168            'mac_addr'
1169        ]
1170    }
1171
1172    def __init__(self, mac_addr, priority, type_=None):
1173        super(EvpnL2BridgeEsi, self).__init__(type_)
1174        self.mac_addr = mac_addr
1175        self.priority = priority
1176
1177    @classmethod
1178    def parse_value(cls, buf):
1179        (mac_addr, priority) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
1180        return {
1181            'mac_addr': addrconv.mac.bin_to_text(mac_addr),
1182            'priority': priority,
1183        }
1184
1185    def serialize_value(self):
1186        return struct.pack(
1187            self._VALUE_PACK_STR,
1188            addrconv.mac.text_to_bin(self.mac_addr), self.priority)
1189
1190
1191@EvpnEsi.register_type(EvpnEsi.MAC_BASED)
1192class EvpnMacBasedEsi(EvpnEsi):
1193    """
1194    MAC-based ESI Value
1195
1196    This type indicates a MAC-based ESI Value that
1197    can be auto-generated or configured by the operator.
1198    """
1199    _TYPE_NAME = 'mac_based'
1200    _VALUE_PACK_STR = '!6s3s'
1201    _VALUE_FIELDS = ['mac_addr', 'local_disc']
1202    _TYPE = {
1203        'ascii': [
1204            'mac_addr'
1205        ]
1206    }
1207
1208    def __init__(self, mac_addr, local_disc, type_=None):
1209        super(EvpnMacBasedEsi, self).__init__(type_)
1210        self.mac_addr = mac_addr
1211        self.local_disc = local_disc
1212
1213    @classmethod
1214    def parse_value(cls, buf):
1215        (mac_addr, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
1216        return {
1217            'mac_addr': addrconv.mac.bin_to_text(mac_addr),
1218            'local_disc': type_desc.Int3.to_user(local_disc),
1219        }
1220
1221    def serialize_value(self):
1222        return struct.pack(
1223            self._VALUE_PACK_STR,
1224            addrconv.mac.text_to_bin(self.mac_addr),
1225            type_desc.Int3.from_user(self.local_disc))
1226
1227
1228@EvpnEsi.register_type(EvpnEsi.ROUTER_ID)
1229class EvpnRouterIDEsi(EvpnEsi):
1230    """
1231    Router-ID ESI Value
1232
1233    This type indicates a router-ID ESI Value that
1234    can be auto-generated or configured by the operator.
1235    """
1236    _TYPE_NAME = 'router_id'
1237    _VALUE_PACK_STR = '!4sIx'
1238    _VALUE_FIELDS = ['router_id', 'local_disc']
1239    _TYPE = {
1240        'ascii': [
1241            'router_id'
1242        ]
1243    }
1244
1245    def __init__(self, router_id, local_disc, type_=None):
1246        super(EvpnRouterIDEsi, self).__init__(type_)
1247        self.router_id = router_id
1248        self.local_disc = local_disc
1249
1250    @classmethod
1251    def parse_value(cls, buf):
1252        (router_id, local_disc) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
1253        return {
1254            'router_id': addrconv.ipv4.bin_to_text(router_id),
1255            'local_disc': local_disc,
1256        }
1257
1258    def serialize_value(self):
1259        return struct.pack(
1260            self._VALUE_PACK_STR,
1261            addrconv.ipv4.text_to_bin(self.router_id), self.local_disc)
1262
1263
1264@EvpnEsi.register_type(EvpnEsi.AS_BASED)
1265class EvpnASBasedEsi(EvpnEsi):
1266    """
1267    AS based ESI value
1268
1269    This type indicates an Autonomous System(AS)-based
1270    ESI Value that can be auto-generated or configured by
1271    the operator.
1272    """
1273    _TYPE_NAME = 'as_based'
1274    _VALUE_PACK_STR = '!IIx'
1275    _VALUE_FIELDS = ['as_number', 'local_disc']
1276
1277    def __init__(self, as_number, local_disc, type_=None):
1278        super(EvpnASBasedEsi, self).__init__(type_)
1279        self.as_number = as_number
1280        self.local_disc = local_disc
1281
1282
1283class EvpnNLRI(StringifyMixin, TypeDisp):
1284    """
1285    BGP Network Layer Reachability Information (NLRI) for EVPN
1286    """
1287    ROUTE_FAMILY = RF_L2_EVPN
1288
1289    # EVPN NLRI:
1290    # +-----------------------------------+
1291    # |    Route Type (1 octet)           |
1292    # +-----------------------------------+
1293    # |     Length (1 octet)              |
1294    # +-----------------------------------+
1295    # | Route Type specific (variable)    |
1296    # +-----------------------------------+
1297    _PACK_STR = "!BB"
1298    _PACK_STR_SIZE = struct.calcsize(_PACK_STR)
1299
1300    ETHERNET_AUTO_DISCOVERY = 0x01
1301    MAC_IP_ADVERTISEMENT = 0x02
1302    INCLUSIVE_MULTICAST_ETHERNET_TAG = 0x03
1303    ETHERNET_SEGMENT = 0x04
1304    IP_PREFIX_ROUTE = 0x05
1305
1306    ROUTE_TYPE_NAME = None  # must be defined in subclass
1307
1308    # Reserved value for Ethernet Tag ID.
1309    MAX_ET = 0xFFFFFFFF
1310
1311    # Dictionary of ROUTE_TYPE_NAME to subclass.
1312    # e.g.)
1313    #   _NAMES = {'eth_ad': EvpnEthernetAutoDiscoveryNLRI, ...}
1314    _NAMES = {}
1315
1316    # List of the fields considered to be part of the prefix in the NLRI.
1317    # This list should be defined in subclasses to format NLRI string
1318    # representation.
1319    NLRI_PREFIX_FIELDS = []
1320
1321    def __init__(self, type_=None, length=None):
1322        if type_ is None:
1323            type_ = self._rev_lookup_type(self.__class__)
1324        self.type = type_
1325        self.length = length
1326        self.route_dist = None  # should be initialized in subclass
1327
1328    @classmethod
1329    def register_type(cls, type_):
1330        cls._TYPES = cls._TYPES.copy()
1331        cls._NAMES = cls._NAMES.copy()
1332
1333        def _register_type(subcls):
1334            cls._TYPES[type_] = subcls
1335            cls._NAMES[subcls.ROUTE_TYPE_NAME] = subcls
1336            cls._REV_TYPES = None
1337            return subcls
1338
1339        return _register_type
1340
1341    @classmethod
1342    def _lookup_type_name(cls, type_name):
1343        try:
1344            return cls._NAMES[type_name]
1345        except KeyError:
1346            return EvpnUnknownNLRI
1347
1348    @classmethod
1349    def parser(cls, buf):
1350        (route_type, length) = struct.unpack_from(
1351            cls._PACK_STR, six.binary_type(buf))
1352        offset = cls._PACK_STR_SIZE + length
1353        subcls = cls._lookup_type(route_type)
1354        values = subcls.parse_value(buf[cls._PACK_STR_SIZE:offset])
1355        return subcls(type_=route_type, length=length,
1356                      **values), buf[offset:]
1357
1358    def serialize_value(self):
1359        # Overrided in subclass
1360        return b''
1361
1362    def serialize(self):
1363        value_bin = self.serialize_value()
1364        # fixup
1365        self.length = len(value_bin)
1366        return struct.pack(EvpnNLRI._PACK_STR,
1367                           self.type, self.length) + value_bin
1368
1369    @staticmethod
1370    def _rd_from_bin(buf):
1371        return _RouteDistinguisher.parser(buf[:8]), buf[8:]
1372
1373    @staticmethod
1374    def _rd_to_bin(rd):
1375        return six.binary_type(rd.serialize())
1376
1377    @staticmethod
1378    def _esi_from_bin(buf):
1379        return EvpnEsi.parser(buf[:10]), buf[10:]
1380
1381    @staticmethod
1382    def _esi_to_bin(esi):
1383        return esi.serialize()
1384
1385    @staticmethod
1386    def _ethernet_tag_id_from_bin(buf):
1387        return type_desc.Int4.to_user(six.binary_type(buf[:4])), buf[4:]
1388
1389    @staticmethod
1390    def _ethernet_tag_id_to_bin(tag_id):
1391        return type_desc.Int4.from_user(tag_id)
1392
1393    @staticmethod
1394    def _mac_addr_len_from_bin(buf):
1395        return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:]
1396
1397    @staticmethod
1398    def _mac_addr_len_to_bin(mac_len):
1399        return type_desc.Int1.from_user(mac_len)
1400
1401    @staticmethod
1402    def _mac_addr_from_bin(buf, mac_len):
1403        mac_len //= 8
1404        return addrconv.mac.bin_to_text(buf[:mac_len]), buf[mac_len:]
1405
1406    @staticmethod
1407    def _mac_addr_to_bin(mac_addr):
1408        return addrconv.mac.text_to_bin(mac_addr)
1409
1410    @staticmethod
1411    def _ip_addr_len_from_bin(buf):
1412        return type_desc.Int1.to_user(six.binary_type(buf[:1])), buf[1:]
1413
1414    @staticmethod
1415    def _ip_addr_len_to_bin(ip_len):
1416        return type_desc.Int1.from_user(ip_len)
1417
1418    @staticmethod
1419    def _ip_addr_from_bin(buf, ip_len):
1420        return ip.bin_to_text(buf[:ip_len]), buf[ip_len:]
1421
1422    @staticmethod
1423    def _ip_addr_to_bin(ip_addr):
1424        return ip.text_to_bin(ip_addr)
1425
1426    @staticmethod
1427    def _mpls_label_from_bin(buf):
1428        mpls_label, is_bos = mpls.label_from_bin(buf)
1429        rest = buf[3:]
1430        return mpls_label, rest, is_bos
1431
1432    @staticmethod
1433    def _mpls_label_to_bin(label, is_bos=True):
1434        return mpls.label_to_bin(label, is_bos=is_bos)
1435
1436    @staticmethod
1437    def _vni_from_bin(buf):
1438        return vxlan.vni_from_bin(six.binary_type(buf[:3])), buf[3:]
1439
1440    @staticmethod
1441    def _vni_to_bin(vni):
1442        return vxlan.vni_to_bin(vni)
1443
1444    @property
1445    def prefix(self):
1446        def _format(i):
1447            pairs = []
1448            for k in i.NLRI_PREFIX_FIELDS:
1449                v = getattr(i, k)
1450                if k == 'esi':
1451                    pairs.append('%s:%s' % (k, v.formatted_str))
1452                else:
1453                    pairs.append('%s:%s' % (k, v))
1454            return ','.join(pairs)
1455
1456        return '%s(%s)' % (self.ROUTE_TYPE_NAME, _format(self))
1457
1458    @property
1459    def formatted_nlri_str(self):
1460        return '%s:%s' % (self.route_dist, self.prefix)
1461
1462
1463@EvpnNLRI.register_unknown_type()
1464class EvpnUnknownNLRI(EvpnNLRI):
1465    """
1466    Unknown route type specific EVPN NLRI
1467    """
1468    ROUTE_TYPE_NAME = 'unknown'
1469    NLRI_PREFIX_FIELDS = ['value']
1470
1471    def __init__(self, value, type_, length=None):
1472        super(EvpnUnknownNLRI, self).__init__(type_, length)
1473        self.value = value
1474
1475    @classmethod
1476    def parse_value(cls, buf):
1477        return {
1478            'value': buf
1479        }
1480
1481    def serialize_value(self):
1482        return self.value
1483
1484    @property
1485    def formatted_nlri_str(self):
1486        return '%s(%s)' % (self.ROUTE_TYPE_NAME, binary_str(self.value))
1487
1488
1489@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_AUTO_DISCOVERY)
1490class EvpnEthernetAutoDiscoveryNLRI(EvpnNLRI):
1491    """
1492    Ethernet A-D route type specific EVPN NLRI
1493    """
1494    ROUTE_TYPE_NAME = 'eth_ad'
1495
1496    # +---------------------------------------+
1497    # |  Route Distinguisher (RD) (8 octets)  |
1498    # +---------------------------------------+
1499    # |Ethernet Segment Identifier (10 octets)|
1500    # +---------------------------------------+
1501    # |  Ethernet Tag ID (4 octets)           |
1502    # +---------------------------------------+
1503    # |  MPLS Label (3 octets)                |
1504    # +---------------------------------------+
1505    _PACK_STR = "!8s10sI3s"
1506    NLRI_PREFIX_FIELDS = ['esi', 'ethernet_tag_id']
1507    _TYPE = {
1508        'ascii': [
1509            'route_dist',
1510        ]
1511    }
1512
1513    def __init__(self, route_dist, esi, ethernet_tag_id,
1514                 mpls_label=None, vni=None, label=None,
1515                 type_=None, length=None):
1516        super(EvpnEthernetAutoDiscoveryNLRI, self).__init__(type_, length)
1517        self.route_dist = route_dist
1518        self.esi = esi
1519        self.ethernet_tag_id = ethernet_tag_id
1520        if label:
1521            # If binary type label field value is specified, stores it
1522            # and decodes as MPLS label and VNI.
1523            self._label = label
1524            self._mpls_label, _, _ = self._mpls_label_from_bin(label)
1525            self._vni, _ = self._vni_from_bin(label)
1526        else:
1527            # If either MPLS label or VNI is specified, stores it
1528            # and encodes into binary type label field value.
1529            self._label = self._serialize_label(mpls_label, vni)
1530            self._mpls_label = mpls_label
1531            self._vni = vni
1532
1533    def _serialize_label(self, mpls_label, vni):
1534        if mpls_label:
1535            return self._mpls_label_to_bin(mpls_label, is_bos=True)
1536        elif vni:
1537            return self._vni_to_bin(vni)
1538        else:
1539            return b'\x00' * 3
1540
1541    @classmethod
1542    def parse_value(cls, buf):
1543        route_dist, rest = cls._rd_from_bin(buf)
1544        esi, rest = cls._esi_from_bin(rest)
1545        ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
1546
1547        return {
1548            'route_dist': route_dist.formatted_str,
1549            'esi': esi,
1550            'ethernet_tag_id': ethernet_tag_id,
1551            'label': rest,
1552        }
1553
1554    def serialize_value(self):
1555        route_dist = _RouteDistinguisher.from_str(self.route_dist)
1556        return struct.pack(
1557            self._PACK_STR, route_dist.serialize(), self.esi.serialize(),
1558            self.ethernet_tag_id, self._label)
1559
1560    @property
1561    def mpls_label(self):
1562        return self._mpls_label
1563
1564    @mpls_label.setter
1565    def mpls_label(self, mpls_label):
1566        self._label = self._mpls_label_to_bin(mpls_label, is_bos=True)
1567        self._mpls_label = mpls_label
1568        self._vni = None  # disables VNI
1569
1570    @property
1571    def vni(self):
1572        return self._vni
1573
1574    @vni.setter
1575    def vni(self, vni):
1576        self._label = self._vni_to_bin(vni)
1577        self._mpls_label = None  # disables MPLS label
1578        self._vni = vni
1579
1580    @property
1581    def label_list(self):
1582        return [self.mpls_label]
1583
1584
1585@EvpnNLRI.register_type(EvpnNLRI.MAC_IP_ADVERTISEMENT)
1586class EvpnMacIPAdvertisementNLRI(EvpnNLRI):
1587    """
1588    MAC/IP Advertisement route type specific EVPN NLRI
1589    """
1590    ROUTE_TYPE_NAME = 'mac_ip_adv'
1591
1592    # +---------------------------------------+
1593    # |  RD (8 octets)                        |
1594    # +---------------------------------------+
1595    # |Ethernet Segment Identifier (10 octets)|
1596    # +---------------------------------------+
1597    # |  Ethernet Tag ID (4 octets)           |
1598    # +---------------------------------------+
1599    # |  MAC Address Length (1 octet)         |
1600    # +---------------------------------------+
1601    # |  MAC Address (6 octets)               |
1602    # +---------------------------------------+
1603    # |  IP Address Length (1 octet)          |
1604    # +---------------------------------------+
1605    # |  IP Address (0, 4, or 16 octets)      |
1606    # +---------------------------------------+
1607    # |  MPLS Label1 (3 octets)               |
1608    # +---------------------------------------+
1609    # |  MPLS Label2 (0 or 3 octets)          |
1610    # +---------------------------------------+
1611    _PACK_STR = "!8s10sIB6sB%ds%ds"
1612    # Note: mac_addr_len and ip_addr_len are omitted for readability.
1613    NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'mac_addr', 'ip_addr']
1614    _TYPE = {
1615        'ascii': [
1616            'route_dist',
1617            'mac_addr',
1618            'ip_addr',
1619        ]
1620    }
1621
1622    def __init__(self, route_dist, ethernet_tag_id, mac_addr, ip_addr,
1623                 esi=None, mpls_labels=None, vni=None, labels=None,
1624                 mac_addr_len=None, ip_addr_len=None,
1625                 type_=None, length=None):
1626        super(EvpnMacIPAdvertisementNLRI, self).__init__(type_, length)
1627        self.route_dist = route_dist
1628        self.esi = esi
1629        self.ethernet_tag_id = ethernet_tag_id
1630        self.mac_addr_len = mac_addr_len
1631        self.mac_addr = mac_addr
1632        self.ip_addr_len = ip_addr_len
1633        self.ip_addr = ip_addr
1634        if labels:
1635            # If binary type labels field value is specified, stores it
1636            # and decodes as MPLS labels and VNI.
1637            self._mpls_labels, self._vni = self._parse_labels(labels)
1638            self._labels = labels
1639        else:
1640            # If either MPLS labels or VNI is specified, stores it
1641            # and encodes into binary type labels field value.
1642            self._labels = self._serialize_labels(mpls_labels, vni)
1643            self._mpls_labels = mpls_labels
1644            self._vni = vni
1645
1646    def _parse_labels(self, labels):
1647        mpls_label1, rest, is_bos = self._mpls_label_from_bin(labels)
1648        mpls_labels = [mpls_label1]
1649        if rest and not is_bos:
1650            mpls_label2, rest, _ = self._mpls_label_from_bin(rest)
1651            mpls_labels.append(mpls_label2)
1652        vni, _ = self._vni_from_bin(labels)
1653        return mpls_labels, vni
1654
1655    def _serialize_labels(self, mpls_labels, vni):
1656        if mpls_labels:
1657            return self._serialize_mpls_labels(mpls_labels)
1658        elif vni:
1659            return self._vni_to_bin(vni)
1660        else:
1661            return b'\x00' * 3
1662
1663    def _serialize_mpls_labels(self, mpls_labels):
1664        if len(mpls_labels) == 1:
1665            return self._mpls_label_to_bin(mpls_labels[0], is_bos=True)
1666        elif len(mpls_labels) == 2:
1667            return (self._mpls_label_to_bin(mpls_labels[0], is_bos=False) +
1668                    self._mpls_label_to_bin(mpls_labels[1], is_bos=True))
1669        else:
1670            return b'\x00' * 3
1671
1672    @classmethod
1673    def parse_value(cls, buf):
1674        route_dist, rest = cls._rd_from_bin(buf)
1675        esi, rest = cls._esi_from_bin(rest)
1676        ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
1677        mac_addr_len, rest = cls._mac_addr_len_from_bin(rest)
1678        mac_addr, rest = cls._mac_addr_from_bin(rest, mac_addr_len)
1679        ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
1680        if ip_addr_len != 0:
1681            ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
1682        else:
1683            ip_addr = None
1684
1685        return {
1686            'route_dist': route_dist.formatted_str,
1687            'esi': esi,
1688            'ethernet_tag_id': ethernet_tag_id,
1689            'mac_addr_len': mac_addr_len,
1690            'mac_addr': mac_addr,
1691            'ip_addr_len': ip_addr_len,
1692            'ip_addr': ip_addr,
1693            'labels': rest,
1694        }
1695
1696    def serialize_value(self):
1697        route_dist = _RouteDistinguisher.from_str(self.route_dist)
1698        mac_addr = self._mac_addr_to_bin(self.mac_addr)
1699        self.mac_addr_len = len(mac_addr) * 8  # fixup
1700        if self.ip_addr:
1701            ip_addr = self._ip_addr_to_bin(self.ip_addr)
1702        else:
1703            ip_addr = b''
1704        ip_addr_len = len(ip_addr)
1705        self.ip_addr_len = ip_addr_len * 8  # fixup
1706
1707        return struct.pack(
1708            self._PACK_STR % (ip_addr_len, len(self._labels)),
1709            route_dist.serialize(), self.esi.serialize(),
1710            self.ethernet_tag_id,
1711            self.mac_addr_len, mac_addr,
1712            self.ip_addr_len, ip_addr,
1713            self._labels)
1714
1715    @property
1716    def mpls_labels(self):
1717        return self._mpls_labels
1718
1719    @mpls_labels.setter
1720    def mpls_labels(self, mpls_labels):
1721        self._labels = self._serialize_mpls_labels(mpls_labels)
1722        self._mpls_labels = mpls_labels
1723        self._vni = None  # disables VNI
1724
1725    @property
1726    def vni(self):
1727        return self._vni
1728
1729    @vni.setter
1730    def vni(self, vni):
1731        self._labels = self._vni_to_bin(vni)
1732        self._mpls_labels = None  # disables MPLS labels
1733        self._vni = vni
1734
1735    @property
1736    def label_list(self):
1737        return self.mpls_labels
1738
1739
1740@EvpnNLRI.register_type(EvpnNLRI.INCLUSIVE_MULTICAST_ETHERNET_TAG)
1741class EvpnInclusiveMulticastEthernetTagNLRI(EvpnNLRI):
1742    """
1743    Inclusive Multicast Ethernet Tag route type specific EVPN NLRI
1744    """
1745    ROUTE_TYPE_NAME = 'multicast_etag'
1746
1747    # +---------------------------------------+
1748    # |  RD (8 octets)                        |
1749    # +---------------------------------------+
1750    # |  Ethernet Tag ID (4 octets)           |
1751    # +---------------------------------------+
1752    # |  IP Address Length (1 octet)          |
1753    # +---------------------------------------+
1754    # |  Originating Router's IP Address      |
1755    # |          (4 or 16 octets)             |
1756    # +---------------------------------------+
1757    _PACK_STR = '!8sIB%ds'
1758    NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_addr']
1759    _TYPE = {
1760        'ascii': [
1761            'route_dist',
1762            'ip_addr',
1763        ]
1764    }
1765
1766    def __init__(self, route_dist, ethernet_tag_id, ip_addr,
1767                 ip_addr_len=None, type_=None, length=None):
1768        super(EvpnInclusiveMulticastEthernetTagNLRI,
1769              self).__init__(type_, length)
1770        self.route_dist = route_dist
1771        self.ethernet_tag_id = ethernet_tag_id
1772        self.ip_addr_len = ip_addr_len
1773        self.ip_addr = ip_addr
1774
1775    @classmethod
1776    def parse_value(cls, buf):
1777        route_dist, rest = cls._rd_from_bin(buf)
1778        ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
1779        ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
1780        ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
1781
1782        return {
1783            'route_dist': route_dist.formatted_str,
1784            'ethernet_tag_id': ethernet_tag_id,
1785            'ip_addr_len': ip_addr_len,
1786            'ip_addr': ip_addr,
1787        }
1788
1789    def serialize_value(self):
1790        route_dist = _RouteDistinguisher.from_str(self.route_dist)
1791        ip_addr = self._ip_addr_to_bin(self.ip_addr)
1792        self.ip_addr_len = len(ip_addr) * 8  # fixup
1793
1794        return struct.pack(
1795            self._PACK_STR % len(ip_addr),
1796            route_dist.serialize(), self.ethernet_tag_id,
1797            self.ip_addr_len, ip_addr)
1798
1799
1800@EvpnNLRI.register_type(EvpnNLRI.ETHERNET_SEGMENT)
1801class EvpnEthernetSegmentNLRI(EvpnNLRI):
1802    """
1803    Ethernet Segment route type specific EVPN NLRI
1804    """
1805    ROUTE_TYPE_NAME = 'eth_seg'
1806
1807    # +---------------------------------------+
1808    # |  RD (8 octets)                        |
1809    # +---------------------------------------+
1810    # |Ethernet Segment Identifier (10 octets)|
1811    # +---------------------------------------+
1812    # |  IP Address Length (1 octet)          |
1813    # +---------------------------------------+
1814    # |  Originating Router's IP Address      |
1815    # |          (4 or 16 octets)             |
1816    # +---------------------------------------+
1817    _PACK_STR = '!8s10sB%ds'
1818    NLRI_PREFIX_FIELDS = ['esi', 'ip_addr']
1819    _TYPE = {
1820        'ascii': [
1821            'route_dist',
1822            'ip_addr',
1823        ]
1824    }
1825
1826    def __init__(self, route_dist, esi, ip_addr, ip_addr_len=None,
1827                 type_=None, length=None):
1828        super(EvpnEthernetSegmentNLRI, self).__init__(type_, length)
1829        self.route_dist = route_dist
1830        self.esi = esi
1831        self.ip_addr_len = ip_addr_len
1832        self.ip_addr = ip_addr
1833
1834    @classmethod
1835    def parse_value(cls, buf):
1836        route_dist, rest = cls._rd_from_bin(buf)
1837        esi, rest = cls._esi_from_bin(rest)
1838        ip_addr_len, rest = cls._ip_addr_len_from_bin(rest)
1839        ip_addr, rest = cls._ip_addr_from_bin(rest, ip_addr_len // 8)
1840
1841        return {
1842            'route_dist': route_dist.formatted_str,
1843            'esi': esi,
1844            'ip_addr_len': ip_addr_len,
1845            'ip_addr': ip_addr,
1846        }
1847
1848    def serialize_value(self):
1849        route_dist = _RouteDistinguisher.from_str(self.route_dist)
1850        ip_addr = self._ip_addr_to_bin(self.ip_addr)
1851        # fixup
1852        self.ip_addr_len = len(ip_addr) * 8
1853
1854        return struct.pack(
1855            self._PACK_STR % len(ip_addr),
1856            route_dist.serialize(), self.esi.serialize(),
1857            self.ip_addr_len, ip_addr)
1858
1859
1860@EvpnNLRI.register_type(EvpnNLRI.IP_PREFIX_ROUTE)
1861class EvpnIpPrefixNLRI(EvpnNLRI):
1862    """
1863    IP Prefix advertisement route NLRI
1864    """
1865    ROUTE_TYPE_NAME = 'ip_prefix'
1866
1867    # +---------------------------------------+
1868    # |      RD   (8 octets)                  |
1869    # +---------------------------------------+
1870    # |Ethernet Segment Identifier (10 octets)|
1871    # +---------------------------------------+
1872    # |  Ethernet Tag ID (4 octets)           |
1873    # +---------------------------------------+
1874    # |  IP Prefix Length (1 octet)           |
1875    # +---------------------------------------+
1876    # |  IP Prefix (4 or 16 octets)           |
1877    # +---------------------------------------+
1878    # |  GW IP Address (4 or 16 octets)       |
1879    # +---------------------------------------+
1880    # |  MPLS Label (3 octets)                |
1881    # +---------------------------------------+
1882    _PACK_STR = '!8s10sIB%ds%ds3s'
1883    NLRI_PREFIX_FIELDS = ['ethernet_tag_id', 'ip_prefix']
1884    _TYPE = {
1885        'ascii': [
1886            'route_dist',
1887            'ip_prefix',
1888            'gw_ip_addr'
1889        ]
1890    }
1891    _LABEL_LEN = 3
1892
1893    def __init__(self, route_dist, ethernet_tag_id, ip_prefix,
1894                 esi=None, gw_ip_addr=None,
1895                 mpls_label=None, vni=None, label=None,
1896                 type_=None, length=None):
1897        super(EvpnIpPrefixNLRI, self).__init__(type_, length)
1898        self.route_dist = route_dist
1899        self.esi = esi
1900        self.ethernet_tag_id = ethernet_tag_id
1901        self._ip_prefix = None
1902        self._ip_prefix_len = None
1903        self.ip_prefix = ip_prefix
1904
1905        if gw_ip_addr is None:
1906            if ':' not in self._ip_prefix:
1907                self.gw_ip_addr = '0.0.0.0'
1908            else:
1909                self.gw_ip_addr = '::'
1910        else:
1911            self.gw_ip_addr = gw_ip_addr
1912
1913        if label:
1914            # If binary type label field value is specified, stores it
1915            # and decodes as MPLS label and VNI.
1916            self._label = label
1917            self._mpls_label, _, _ = self._mpls_label_from_bin(label)
1918            self._vni, _ = self._vni_from_bin(label)
1919        else:
1920            # If either MPLS label or VNI is specified, stores it
1921            # and encodes into binary type label field value.
1922            self._label = self._serialize_label(mpls_label, vni)
1923            self._mpls_label = mpls_label
1924            self._vni = vni
1925
1926    def _serialize_label(self, mpls_label, vni):
1927        if mpls_label:
1928            return self._mpls_label_to_bin(mpls_label, is_bos=True)
1929        elif vni:
1930            return vxlan.vni_to_bin(vni)
1931        else:
1932            return b'\x00' * 3
1933
1934    @classmethod
1935    def parse_value(cls, buf):
1936        route_dist, rest = cls._rd_from_bin(buf)
1937        esi, rest = cls._esi_from_bin(rest)
1938        ethernet_tag_id, rest = cls._ethernet_tag_id_from_bin(rest)
1939        ip_prefix_len, rest = cls._ip_addr_len_from_bin(rest)
1940        _len = (len(rest) - cls._LABEL_LEN) // 2
1941        ip_prefix, rest = cls._ip_addr_from_bin(rest, _len)
1942        gw_ip_addr, rest = cls._ip_addr_from_bin(rest, _len)
1943
1944        return {
1945            'route_dist': route_dist.formatted_str,
1946            'esi': esi,
1947            'ethernet_tag_id': ethernet_tag_id,
1948            'ip_prefix': '%s/%s' % (ip_prefix, ip_prefix_len),
1949            'gw_ip_addr': gw_ip_addr,
1950            'label': rest,
1951        }
1952
1953    def serialize_value(self):
1954        route_dist = _RouteDistinguisher.from_str(self.route_dist)
1955        ip_prefix = self._ip_addr_to_bin(self._ip_prefix)
1956        gw_ip_addr = self._ip_addr_to_bin(self.gw_ip_addr)
1957
1958        return struct.pack(
1959            self._PACK_STR % (len(ip_prefix), len(gw_ip_addr)),
1960            route_dist.serialize(), self.esi.serialize(),
1961            self.ethernet_tag_id, self._ip_prefix_len, ip_prefix,
1962            gw_ip_addr, self._label)
1963
1964    @property
1965    def ip_prefix(self):
1966        return '%s/%s' % (self._ip_prefix, self._ip_prefix_len)
1967
1968    @ip_prefix.setter
1969    def ip_prefix(self, ip_prefix):
1970        self._ip_prefix, ip_prefix_len = ip_prefix.split('/')
1971        self._ip_prefix_len = int(ip_prefix_len)
1972
1973    @property
1974    def mpls_label(self):
1975        return self._mpls_label
1976
1977    @mpls_label.setter
1978    def mpls_label(self, mpls_label):
1979        self._label = self._mpls_label_to_bin(mpls_label, is_bos=True)
1980        self._mpls_label = mpls_label
1981        self._vni = None  # disables VNI
1982
1983    @property
1984    def vni(self):
1985        return self._vni
1986
1987    @vni.setter
1988    def vni(self, vni):
1989        self._label = self._vni_to_bin(vni)
1990        self._mpls_label = None  # disables MPLS label
1991        self._vni = vni
1992
1993    @property
1994    def label_list(self):
1995        return [self.mpls_label]
1996
1997
1998class _FlowSpecNLRIBase(StringifyMixin, TypeDisp):
1999    """
2000    Base class for Flow Specification NLRI
2001    """
2002
2003    # flow-spec NLRI:
2004    # +-----------------------------------+
2005    # |    length (0xnn or 0xfn nn)       |
2006    # +-----------------------------------+
2007    # |     NLRI value  (variable)        |
2008    # +-----------------------------------+
2009    ROUTE_FAMILY = None
2010    _LENGTH_SHORT_FMT = '!B'
2011    LENGTH_SHORT_SIZE = struct.calcsize(_LENGTH_SHORT_FMT)
2012    _LENGTH_LONG_FMT = '!H'
2013    LENGTH_LONG_SIZE = struct.calcsize(_LENGTH_LONG_FMT)
2014    _LENGTH_THRESHOLD = 0xf000
2015    FLOWSPEC_FAMILY = ''
2016
2017    def __init__(self, length=0, rules=None):
2018        self.length = length
2019        rules = rules or []
2020        for r in rules:
2021            assert isinstance(r, _FlowSpecComponentBase)
2022        self.rules = rules
2023
2024    @classmethod
2025    def parser(cls, buf):
2026        (length,) = struct.unpack_from(
2027            cls._LENGTH_LONG_FMT, six.binary_type(buf))
2028
2029        if length < cls._LENGTH_THRESHOLD:
2030            length >>= 8
2031            offset = cls.LENGTH_SHORT_SIZE
2032        else:
2033            offset = cls.LENGTH_LONG_SIZE
2034
2035        kwargs = {'length': length}
2036        rest = buf[offset:offset + length]
2037
2038        if cls.ROUTE_FAMILY.safi == subaddr_family.VPN_FLOWSPEC:
2039            route_dist = _RouteDistinguisher.parser(rest[:8])
2040            kwargs['route_dist'] = route_dist.formatted_str
2041            rest = rest[8:]
2042
2043        rules = []
2044
2045        while rest:
2046            subcls, rest = _FlowSpecComponentBase.parse_header(
2047                rest, cls.ROUTE_FAMILY.afi)
2048
2049            while rest:
2050                rule, rest = subcls.parse_body(rest)
2051                rules.append(rule)
2052
2053                if (not isinstance(rule, _FlowSpecOperatorBase) or
2054                        rule.operator & rule.END_OF_LIST):
2055                    break
2056
2057        kwargs['rules'] = rules
2058
2059        return cls(**kwargs), rest
2060
2061    def serialize(self):
2062        rules_bin = b''
2063
2064        if self.ROUTE_FAMILY.safi == subaddr_family.VPN_FLOWSPEC:
2065            route_dist = _RouteDistinguisher.from_str(self.route_dist)
2066            rules_bin += route_dist.serialize()
2067
2068        self.rules.sort(key=lambda x: x.type)
2069        for _, rules in itertools.groupby(self.rules, key=lambda x: x.type):
2070            rules = list(rules)
2071            rules_bin += rules[0].serialize_header()
2072
2073            if isinstance(rules[-1], _FlowSpecOperatorBase):
2074                rules[-1].operator |= rules[-1].END_OF_LIST
2075
2076            for r in rules:
2077                rules_bin += r.serialize_body()
2078
2079        self.length = len(rules_bin)
2080
2081        if self.length < self._LENGTH_THRESHOLD:
2082            buf = struct.pack(self._LENGTH_SHORT_FMT, self.length)
2083        else:
2084            buf = struct.pack(self._LENGTH_LONG_FMT, self.length)
2085
2086        return buf + rules_bin
2087
2088    @classmethod
2089    def _from_user(cls, **kwargs):
2090        rules = []
2091        for k, v in kwargs.items():
2092            subcls = _FlowSpecComponentBase.lookup_type_name(
2093                k, cls.ROUTE_FAMILY.afi)
2094            rule = subcls.from_str(str(v))
2095            rules.extend(rule)
2096        rules.sort(key=lambda x: x.type)
2097        return cls(rules=rules)
2098
2099    @property
2100    def prefix(self):
2101        def _format(i):
2102            pairs = []
2103            i.rules.sort(key=lambda x: x.type)
2104            previous_type = None
2105            for r in i.rules:
2106                if r.type == previous_type:
2107                    if r.to_str()[0] != '&':
2108                        pairs[-1] += '|'
2109                    pairs[-1] += r.to_str()
2110                else:
2111                    pairs.append('%s:%s' % (r.COMPONENT_NAME, r.to_str()))
2112                previous_type = r.type
2113
2114            return ','.join(pairs)
2115
2116        return '%s(%s)' % (self.FLOWSPEC_FAMILY, _format(self))
2117
2118    @property
2119    def formatted_nlri_str(self):
2120        return self.prefix
2121
2122
2123class FlowSpecIPv4NLRI(_FlowSpecNLRIBase):
2124    """
2125    Flow Specification NLRI class for IPv4 [RFC 5575]
2126    """
2127    ROUTE_FAMILY = RF_IPv4_FLOWSPEC
2128    FLOWSPEC_FAMILY = 'ipv4fs'
2129
2130    @classmethod
2131    def from_user(cls, **kwargs):
2132        """
2133        Utility method for creating a NLRI instance.
2134
2135        This function returns a NLRI instance from human readable format value.
2136
2137        :param kwargs: The following arguments are available.
2138
2139        =========== ============= ========= ==============================
2140        Argument    Value         Operator  Description
2141        =========== ============= ========= ==============================
2142        dst_prefix  IPv4 Prefix   Nothing   Destination Prefix.
2143        src_prefix  IPv4 Prefix   Nothing   Source Prefix.
2144        ip_proto    Integer       Numeric   IP Protocol.
2145        port        Integer       Numeric   Port number.
2146        dst_port    Integer       Numeric   Destination port number.
2147        src_port    Integer       Numeric   Source port number.
2148        icmp_type   Integer       Numeric   ICMP type.
2149        icmp_code   Integer       Numeric   ICMP code.
2150        tcp_flags   Fixed string  Bitmask   TCP flags.
2151                                            Supported values are
2152                                            ``CWR``, ``ECN``, ``URGENT``,
2153                                            ``ACK``, ``PUSH``, ``RST``,
2154                                            ``SYN`` and ``FIN``.
2155        packet_len  Integer       Numeric   Packet length.
2156        dscp        Integer       Numeric   Differentiated Services
2157                                            Code Point.
2158        fragment    Fixed string  Bitmask   Fragment.
2159                                            Supported values are
2160                                            ``DF`` (Don't fragment),
2161                                            ``ISF`` (Is a fragment),
2162                                            ``FF`` (First fragment) and
2163                                            ``LF`` (Last fragment)
2164        =========== ============= ========= ==============================
2165
2166        Example::
2167
2168            >>> msg = bgp.FlowSpecIPv4NLRI.from_user(
2169            ...     dst_prefix='10.0.0.0/24',
2170            ...     src_prefix='20.0.0.1/24',
2171            ...     ip_proto=6,
2172            ...     port='80 | 8000',
2173            ...     dst_port='>9000 & <9050',
2174            ...     src_port='>=8500 & <=9000',
2175            ...     icmp_type=0,
2176            ...     icmp_code=6,
2177            ...     tcp_flags='SYN+ACK & !=URGENT',
2178            ...     packet_len=1000,
2179            ...     dscp='22 | 24',
2180            ...     fragment='LF | ==FF')
2181            >>>
2182
2183        You can specify conditions with the following keywords.
2184
2185        The following keywords can be used when the operator type is Numeric.
2186
2187        ========== ============================================================
2188        Keyword    Description
2189        ========== ============================================================
2190        <          Less than comparison between data and value.
2191        <=         Less than or equal to comparison between data and value.
2192        >          Greater than comparison between data and value.
2193        >=         Greater than or equal to comparison between data and value.
2194        ==         Equality between data and value.
2195                   This operator can be omitted.
2196        ========== ============================================================
2197
2198        The following keywords can be used when the operator type is Bitmask.
2199
2200        ========== ================================================
2201        Keyword    Description
2202        ========== ================================================
2203        !=         Not equal operation.
2204        ==         Exact match operation if specified.
2205                   Otherwise partial match operation.
2206        `+`        Used for the summation of bitmask values.
2207                   (e.g., SYN+ACK)
2208        ========== ================================================
2209
2210        You can combine the multiple conditions with the following operators.
2211
2212        ========== =======================================
2213        Keyword    Description
2214        ========== =======================================
2215        `|`        Logical OR operation
2216        &          Logical AND operation
2217        ========== =======================================
2218
2219        :return: A instance of FlowSpecVPNv4NLRI.
2220        """
2221        return cls._from_user(**kwargs)
2222
2223
2224class FlowSpecVPNv4NLRI(_FlowSpecNLRIBase):
2225    """
2226    Flow Specification NLRI class for VPNv4 [RFC 5575]
2227    """
2228
2229    # flow-spec NLRI:
2230    # +-----------------------------------+
2231    # |    length (0xnn or 0xfn nn)       |
2232    # +-----------------------------------+
2233    # |     RD   (8 octets)               |
2234    # +-----------------------------------+
2235    # |     NLRI value  (variable)        |
2236    # +-----------------------------------+
2237    ROUTE_FAMILY = RF_VPNv4_FLOWSPEC
2238    FLOWSPEC_FAMILY = 'vpnv4fs'
2239
2240    def __init__(self, length=0, route_dist=None, rules=None):
2241        super(FlowSpecVPNv4NLRI, self).__init__(length, rules)
2242        assert route_dist is not None
2243        self.route_dist = route_dist
2244
2245    @classmethod
2246    def _from_user(cls, route_dist, **kwargs):
2247        rules = []
2248        for k, v in kwargs.items():
2249            subcls = _FlowSpecComponentBase.lookup_type_name(
2250                k, cls.ROUTE_FAMILY.afi)
2251            rule = subcls.from_str(str(v))
2252            rules.extend(rule)
2253        rules.sort(key=lambda x: x.type)
2254        return cls(route_dist=route_dist, rules=rules)
2255
2256    @classmethod
2257    def from_user(cls, route_dist, **kwargs):
2258        """
2259        Utility method for creating a NLRI instance.
2260
2261        This function returns a NLRI instance from human readable format value.
2262
2263        :param route_dist: Route Distinguisher.
2264        :param kwargs: See :py:mod:`ryu.lib.packet.bgp.FlowSpecIPv4NLRI`
2265
2266        Example::
2267
2268            >>> msg = bgp.FlowSpecIPv4NLRI.from_user(
2269            ...     route_dist='65000:1000',
2270            ...     dst_prefix='10.0.0.0/24',
2271            ...     src_prefix='20.0.0.1/24',
2272            ...     ip_proto=6,
2273            ...     port='80 | 8000',
2274            ...     dst_port='>9000 & <9050',
2275            ...     src_port='>=8500 & <=9000',
2276            ...     icmp_type=0,
2277            ...     icmp_code=6,
2278            ...     tcp_flags='SYN+ACK & !=URGENT',
2279            ...     packet_len=1000,
2280            ...     dscp='22 | 24',
2281            ...     fragment='LF | ==FF')
2282            >>>
2283        """
2284        return cls._from_user(route_dist, **kwargs)
2285
2286    @property
2287    def formatted_nlri_str(self):
2288        return '%s:%s' % (self.route_dist, self.prefix)
2289
2290
2291class FlowSpecIPv6NLRI(_FlowSpecNLRIBase):
2292    """
2293    Flow Specification NLRI class for IPv6 [RFC draft-ietf-idr-flow-spec-v6-08]
2294    """
2295    ROUTE_FAMILY = RF_IPv6_FLOWSPEC
2296    FLOWSPEC_FAMILY = 'ipv6fs'
2297
2298    @classmethod
2299    def from_user(cls, **kwargs):
2300        """
2301        Utility method for creating a NLRI instance.
2302
2303        This function returns a NLRI instance from human readable format value.
2304
2305        :param kwargs: The following arguments are available.
2306
2307        =========== ============= ========= ==============================
2308        Argument    Value         Operator  Description
2309        =========== ============= ========= ==============================
2310        dst_prefix  IPv6 Prefix   Nothing   Destination Prefix.
2311        src_prefix  IPv6 Prefix   Nothing   Source Prefix.
2312        next_header Integer       Numeric   Next Header.
2313        port        Integer       Numeric   Port number.
2314        dst_port    Integer       Numeric   Destination port number.
2315        src_port    Integer       Numeric   Source port number.
2316        icmp_type   Integer       Numeric   ICMP type.
2317        icmp_code   Integer       Numeric   ICMP code.
2318        tcp_flags   Fixed string  Bitmask   TCP flags.
2319                                            Supported values are
2320                                            ``CWR``, ``ECN``, ``URGENT``,
2321                                            ``ACK``, ``PUSH``, ``RST``,
2322                                            ``SYN`` and ``FIN``.
2323        packet_len  Integer       Numeric   Packet length.
2324        dscp        Integer       Numeric   Differentiated Services
2325                                            Code Point.
2326        fragment    Fixed string  Bitmask   Fragment.
2327                                            Supported values are
2328                                            ``ISF`` (Is a fragment),
2329                                            ``FF`` (First fragment) and
2330                                            ``LF`` (Last fragment)
2331        flow_label   Intefer      Numeric   Flow Label.
2332        =========== ============= ========= ==============================
2333
2334        .. Note::
2335
2336            For ``dst_prefix`` and ``src_prefix``, you can give "offset" value
2337            like this: ``2001::2/128/32``. At this case, ``offset`` is 32.
2338            ``offset`` can be omitted, then ``offset`` is treated as 0.
2339        """
2340        return cls._from_user(**kwargs)
2341
2342
2343class FlowSpecVPNv6NLRI(_FlowSpecNLRIBase):
2344    """
2345    Flow Specification NLRI class for VPNv6 [draft-ietf-idr-flow-spec-v6-08]
2346    """
2347
2348    # flow-spec NLRI:
2349    # +-----------------------------------+
2350    # |    length (0xnn or 0xfn nn)       |
2351    # +-----------------------------------+
2352    # |     RD   (8 octets)               |
2353    # +-----------------------------------+
2354    # |     NLRI value  (variable)        |
2355    # +-----------------------------------+
2356    ROUTE_FAMILY = RF_VPNv6_FLOWSPEC
2357    FLOWSPEC_FAMILY = 'vpnv6fs'
2358
2359    def __init__(self, length=0, route_dist=None, rules=None):
2360        super(FlowSpecVPNv6NLRI, self).__init__(length, rules)
2361        assert route_dist is not None
2362        self.route_dist = route_dist
2363
2364    @classmethod
2365    def _from_user(cls, route_dist, **kwargs):
2366        rules = []
2367        for k, v in kwargs.items():
2368            subcls = _FlowSpecComponentBase.lookup_type_name(
2369                k, cls.ROUTE_FAMILY.afi)
2370            rule = subcls.from_str(str(v))
2371            rules.extend(rule)
2372        rules.sort(key=lambda x: x.type)
2373        return cls(route_dist=route_dist, rules=rules)
2374
2375    @classmethod
2376    def from_user(cls, route_dist, **kwargs):
2377        """
2378        Utility method for creating a NLRI instance.
2379
2380        This function returns a NLRI instance from human readable format value.
2381
2382        :param route_dist: Route Distinguisher.
2383        :param kwargs: See :py:mod:`ryu.lib.packet.bgp.FlowSpecIPv6NLRI`
2384        """
2385        return cls._from_user(route_dist, **kwargs)
2386
2387    @property
2388    def formatted_nlri_str(self):
2389        return '%s:%s' % (self.route_dist, self.prefix)
2390
2391
2392class FlowSpecL2VPNNLRI(_FlowSpecNLRIBase):
2393    """
2394    Flow Specification NLRI class for L2VPN [draft-ietf-idr-flowspec-l2vpn-05]
2395    """
2396
2397    # flow-spec NLRI:
2398    # +-----------------------------------+
2399    # |    length (0xnn or 0xfn nn)       |
2400    # +-----------------------------------+
2401    # |     RD   (8 octets)               |
2402    # +-----------------------------------+
2403    # |     NLRI value  (variable)        |
2404    # +-----------------------------------+
2405    ROUTE_FAMILY = RF_L2VPN_FLOWSPEC
2406    FLOWSPEC_FAMILY = 'l2vpnfs'
2407
2408    def __init__(self, length=0, route_dist=None, rules=None):
2409        super(FlowSpecL2VPNNLRI, self).__init__(length, rules)
2410        assert route_dist is not None
2411        self.route_dist = route_dist
2412
2413    @classmethod
2414    def _from_user(cls, route_dist, **kwargs):
2415        rules = []
2416        for k, v in kwargs.items():
2417            subcls = _FlowSpecComponentBase.lookup_type_name(
2418                k, cls.ROUTE_FAMILY.afi)
2419            rule = subcls.from_str(str(v))
2420            rules.extend(rule)
2421        rules.sort(key=lambda x: x.type)
2422        return cls(route_dist=route_dist, rules=rules)
2423
2424    @classmethod
2425    def from_user(cls, route_dist, **kwargs):
2426        """
2427        Utility method for creating a L2VPN NLRI instance.
2428
2429        This function returns a L2VPN NLRI instance
2430        from human readable format value.
2431
2432        :param kwargs: The following arguments are available.
2433
2434        ============== ============= ========= ==============================
2435        Argument       Value         Operator  Description
2436        ============== ============= ========= ==============================
2437        ether_type     Integer       Numeric   Ethernet Type.
2438        src_mac        Mac Address   Nothing   Source Mac address.
2439        dst_mac        Mac Address   Nothing   Destination Mac address.
2440        llc_ssap       Integer       Numeric   Source Service Access Point
2441                                               in LLC.
2442        llc_dsap       Integer       Numeric   Destination Service Access
2443                                               Point in LLC.
2444        llc_control    Integer       Numeric   Control field in LLC.
2445        snap           Integer       Numeric   Sub-Network Access Protocol
2446                                               field.
2447        vlan_id        Integer       Numeric   VLAN ID.
2448        vlan_cos       Integer       Numeric   VLAN COS field.
2449        inner_vlan_id  Integer       Numeric   Inner VLAN ID.
2450        inner_vlan_cos Integer       Numeric   Inner VLAN COS field.
2451        ============== ============= ========= ==============================
2452        """
2453        return cls._from_user(route_dist, **kwargs)
2454
2455    @property
2456    def formatted_nlri_str(self):
2457        return '%s:%s' % (self.route_dist, self.prefix)
2458
2459
2460class _FlowSpecComponentBase(StringifyMixin, TypeDisp):
2461    """
2462    Base class for Flow Specification NLRI component
2463    """
2464    COMPONENT_NAME = None
2465
2466    _BASE_STR = '!B'
2467    _BASE_STR_SIZE = struct.calcsize(_BASE_STR)
2468
2469    # Dictionary of COMPONENT_NAME to subclass.
2470    # e.g.)
2471    #   _NAMES = {'dst_prefix': FlowSpecDestPrefix, ...}
2472    _NAMES = {}
2473
2474    def __init__(self, type_=None):
2475        if type_ is None:
2476            type_, _ = self._rev_lookup_type(self.__class__)
2477        self.type = type_
2478
2479    @classmethod
2480    def register_type(cls, type_, afi):
2481        cls._TYPES = cls._TYPES.copy()
2482        cls._NAMES = cls._NAMES.copy()
2483
2484        def _register_type(subcls):
2485            cls._TYPES[(type_, afi)] = subcls
2486            cls._NAMES[(subcls.COMPONENT_NAME, afi)] = subcls
2487            cls._REV_TYPES = None
2488            return subcls
2489
2490        return _register_type
2491
2492    @classmethod
2493    def lookup_type_name(cls, type_name, afi):
2494        return cls._NAMES[(type_name, afi)]
2495
2496    @classmethod
2497    def _lookup_type(cls, type_, afi):
2498        try:
2499            return cls._TYPES[(type_, afi)]
2500        except KeyError:
2501            return cls._UNKNOWN_TYPE
2502
2503    @classmethod
2504    def parse_header(cls, rest, afi):
2505        (type_,) = struct.unpack_from(
2506            cls._BASE_STR, six.binary_type(rest))
2507        rest = rest[cls._BASE_STR_SIZE:]
2508        return cls._lookup_type(type_, afi), rest
2509
2510    def serialize_header(self):
2511        return struct.pack(self._BASE_STR, self.type)
2512
2513
2514class _FlowSpecIPv4Component(_FlowSpecComponentBase):
2515    """
2516    Base class for Flow Specification for IPv4 NLRI component
2517    """
2518    TYPE_DESTINATION_PREFIX = 0x01
2519    TYPE_SOURCE_PREFIX = 0x02
2520    TYPE_PROTOCOL = 0x03
2521    TYPE_PORT = 0x04
2522    TYPE_DESTINATION_PORT = 0x05
2523    TYPE_SOURCE_PORT = 0x06
2524    TYPE_ICMP = 0x07
2525    TYPE_ICMP_CODE = 0x08
2526    TYPE_TCP_FLAGS = 0x09
2527    TYPE_PACKET_LENGTH = 0x0a
2528    TYPE_DIFFSERV_CODE_POINT = 0x0b
2529    TYPE_FRAGMENT = 0x0c
2530
2531
2532class _FlowSpecIPv6Component(_FlowSpecComponentBase):
2533    """
2534    Base class for Flow Specification for IPv6 NLRI component
2535    """
2536    TYPE_DESTINATION_PREFIX = 0x01
2537    TYPE_SOURCE_PREFIX = 0x02
2538    TYPE_NEXT_HEADER = 0x03
2539    TYPE_PORT = 0x04
2540    TYPE_DESTINATION_PORT = 0x05
2541    TYPE_SOURCE_PORT = 0x06
2542    TYPE_ICMP = 0x07
2543    TYPE_ICMP_CODE = 0x08
2544    TYPE_TCP_FLAGS = 0x09
2545    TYPE_PACKET_LENGTH = 0x0a
2546    TYPE_DIFFSERV_CODE_POINT = 0x0b
2547    TYPE_FRAGMENT = 0x0c
2548    TYPE_FLOW_LABEL = 0x0d
2549
2550
2551class _FlowSpecL2VPNComponent(_FlowSpecComponentBase):
2552    """
2553    Base class for Flow Specification for L2VPN NLRI component
2554    """
2555    TYPE_ETHER_TYPE = 0x0e
2556    TYPE_SOURCE_MAC = 0x0f
2557    TYPE_DESTINATION_MAC = 0x10
2558    TYPE_LLC_DSAP = 0x11
2559    TYPE_LLC_SSAP = 0x12
2560    TYPE_LLC_CONTROL = 0x13
2561    TYPE_SNAP = 0x14
2562    TYPE_VLAN_ID = 0x15
2563    TYPE_VLAN_COS = 0x16
2564    TYPE_INNER_VLAN_ID = 0x17
2565    TYPE_INNER_VLAN_COS = 0x18
2566
2567
2568@_FlowSpecComponentBase.register_unknown_type()
2569class FlowSpecComponentUnknown(_FlowSpecComponentBase):
2570    """
2571    Unknown component type for Flow Specification NLRI component
2572    """
2573
2574    def __init__(self, buf, type_=None):
2575        super(FlowSpecComponentUnknown, self).__init__(type_)
2576        self.buf = buf
2577
2578    @classmethod
2579    def parse_body(cls, buf):
2580        return cls(buf), None
2581
2582    def serialize_body(self):
2583        return self.buf
2584
2585
2586class _FlowSpecPrefixBase(_FlowSpecIPv4Component, IPAddrPrefix):
2587    """
2588    Prefix base class for Flow Specification NLRI component
2589    """
2590
2591    def __init__(self, length, addr, type_=None):
2592        super(_FlowSpecPrefixBase, self).__init__(type_)
2593        self.length = length
2594        prefix = "%s/%s" % (addr, length)
2595        self.addr = str(netaddr.ip.IPNetwork(prefix).network)
2596
2597    @classmethod
2598    def parse_body(cls, buf):
2599        return cls.parser(buf)
2600
2601    def serialize_body(self):
2602        return self.serialize()
2603
2604    @classmethod
2605    def from_str(cls, value):
2606        rule = []
2607        addr, length = value.split('/')
2608        rule.append(cls(int(length), addr))
2609        return rule
2610
2611    @property
2612    def value(self):
2613        return "%s/%s" % (self.addr, self.length)
2614
2615    def to_str(self):
2616        return self.value
2617
2618
2619class _FlowSpecIPv6PrefixBase(_FlowSpecIPv6Component, IP6AddrPrefix):
2620    """
2621    Prefix base class for Flow Specification NLRI component
2622    """
2623    _PACK_STR = '!BB'  # length, offset
2624
2625    def __init__(self, length, addr, offset=0, type_=None):
2626        super(_FlowSpecIPv6PrefixBase, self).__init__(type_)
2627        self.length = length
2628        self.offset = offset
2629        prefix = "%s/%s" % (addr, length)
2630        self.addr = str(netaddr.ip.IPNetwork(prefix).network)
2631
2632    @classmethod
2633    def parser(cls, buf):
2634        (length, offset) = struct.unpack_from(
2635            cls._PACK_STR, six.binary_type(buf))
2636        rest = buf[struct.calcsize(cls._PACK_STR):]
2637        byte_length = (length + 7) // 8
2638        addr = cls._from_bin(rest[:byte_length])
2639        rest = rest[byte_length:]
2640        return cls(length=length, offset=offset, addr=addr), rest
2641
2642    @classmethod
2643    def parse_body(cls, buf):
2644        return cls.parser(buf)
2645
2646    def serialize(self):
2647        byte_length = (self.length + 7) // 8
2648        bin_addr = self._to_bin(self.addr)[:byte_length]
2649        buf = bytearray()
2650        msg_pack_into(self._PACK_STR, buf, 0, self.length, self.offset)
2651        return buf + bin_addr
2652
2653    def serialize_body(self):
2654        return self.serialize()
2655
2656    @classmethod
2657    def from_str(cls, value):
2658        rule = []
2659        values = value.split('/')
2660        if len(values) == 3:
2661            rule.append(cls(int(values[1]), values[0], offset=int(values[2])))
2662        else:
2663            rule.append(cls(int(values[1]), values[0]))
2664        return rule
2665
2666    @property
2667    def value(self):
2668        return "%s/%s/%s" % (self.addr, self.length, self.offset)
2669
2670    def to_str(self):
2671        return self.value
2672
2673
2674class _FlowSpecL2VPNPrefixBase(_FlowSpecL2VPNComponent):
2675    """
2676    Prefix base class for Flow Specification NLRI component
2677    """
2678    _PACK_STR = "!B6s"
2679
2680    def __init__(self, length, addr, type_=None):
2681        super(_FlowSpecL2VPNPrefixBase, self).__init__(type_)
2682        self.length = length
2683        self.addr = addr.lower()
2684
2685    @classmethod
2686    def parse_body(cls, buf):
2687        (length, addr) = struct.unpack_from(
2688            cls._PACK_STR, six.binary_type(buf))
2689        rest = buf[struct.calcsize(cls._PACK_STR):]
2690        addr = addrconv.mac.bin_to_text(addr)
2691        return cls(length=length, addr=addr), rest
2692
2693    def serialize(self):
2694        addr = addrconv.mac.text_to_bin(self.addr)
2695        return struct.pack(self._PACK_STR, self.length, addr)
2696
2697    def serialize_body(self):
2698        return self.serialize()
2699
2700    @classmethod
2701    def from_str(cls, value):
2702        return [cls(len(value.split(':')), value)]
2703
2704    @property
2705    def value(self):
2706        return self.addr
2707
2708    def to_str(self):
2709        return self.value
2710
2711
2712@_FlowSpecComponentBase.register_type(
2713    _FlowSpecIPv4Component.TYPE_DESTINATION_PREFIX, addr_family.IP)
2714class FlowSpecDestPrefix(_FlowSpecPrefixBase):
2715    """
2716    Destination Prefix for Flow Specification NLRI component
2717    """
2718    COMPONENT_NAME = 'dst_prefix'
2719
2720
2721@_FlowSpecComponentBase.register_type(
2722    _FlowSpecIPv4Component.TYPE_SOURCE_PREFIX, addr_family.IP)
2723class FlowSpecSrcPrefix(_FlowSpecPrefixBase):
2724    """
2725    Source Prefix for Flow Specification NLRI component
2726    """
2727    COMPONENT_NAME = 'src_prefix'
2728
2729
2730@_FlowSpecComponentBase.register_type(
2731    _FlowSpecIPv6Component.TYPE_DESTINATION_PREFIX, addr_family.IP6)
2732class FlowSpecIPv6DestPrefix(_FlowSpecIPv6PrefixBase):
2733    """
2734    IPv6 destination Prefix for Flow Specification NLRI component
2735    """
2736    COMPONENT_NAME = 'dst_prefix'
2737
2738
2739@_FlowSpecComponentBase.register_type(
2740    _FlowSpecIPv6Component.TYPE_SOURCE_PREFIX, addr_family.IP6)
2741class FlowSpecIPv6SrcPrefix(_FlowSpecIPv6PrefixBase):
2742    """
2743    IPv6 source Prefix for Flow Specification NLRI component
2744    """
2745    COMPONENT_NAME = 'src_prefix'
2746
2747
2748class _FlowSpecOperatorBase(_FlowSpecComponentBase):
2749    """Operator base class for Flow Specification NLRI component
2750
2751    ===================== ===============================================
2752    Attribute             Description
2753    ===================== ===============================================
2754    operator              Match conditions.
2755    value                 Value of component.
2756    ===================== ===============================================
2757    """
2758    _OPE_PACK_STR = '!B'
2759    _OPE_PACK_STR_SIZE = struct.calcsize(_OPE_PACK_STR)
2760    _VAL_PACK_STR = '!%ds'
2761
2762    END_OF_LIST = 1 << 7     # END OF LIST bit
2763    AND = 1 << 6             # AND bit
2764    OR = 0                   # OR
2765    _LENGTH_BIT_MASK = 0x30  # The mask for length of the value
2766
2767    _logical_conditions = {
2768        "|": OR,
2769        "&": AND,
2770    }
2771    _comparison_conditions = {}
2772
2773    def __init__(self, operator, value, type_=None):
2774        super(_FlowSpecOperatorBase, self).__init__(type_)
2775        self.operator = operator
2776        self.value = value
2777
2778    @classmethod
2779    def parse_body(cls, rest):
2780        (operator,) = struct.unpack_from(cls._OPE_PACK_STR,
2781                                         six.binary_type(rest))
2782        rest = rest[cls._OPE_PACK_STR_SIZE:]
2783        length = 1 << ((operator & cls._LENGTH_BIT_MASK) >> 4)
2784        value_type = type_desc.IntDescr(length)
2785        value = value_type.to_user(rest)
2786        rest = rest[length:]
2787
2788        return cls(operator, value), rest
2789
2790    def serialize_body(self):
2791        byte_length = (self.value.bit_length() + 7) // 8 or 1
2792        length = int(math.ceil(math.log(byte_length, 2)))
2793        self.operator |= length << 4
2794        buf = struct.pack(self._OPE_PACK_STR, self.operator)
2795        value_type = type_desc.IntDescr(1 << length)
2796        buf += struct.pack(self._VAL_PACK_STR % (1 << length),
2797                           value_type.from_user(self.value))
2798
2799        return buf
2800
2801    @classmethod
2802    def from_str(cls, val):
2803        operator = 0
2804        rules = []
2805
2806        # e.g.)
2807        # value = '80 | ==90|>=8000&<=9000 | <100 & >110'
2808        # elements = ['80', '|', '==', '90', '|', '>=', '8000', '&',
2809        #             '<=', '9000', '|', '<', '100', '&', '>', '110']
2810        elements = [v.strip() for v in re.split(
2811            r'([0-9]+)|([A-Z]+)|(\|&\+)|([!=<>]+)', val) if v and v.strip()]
2812
2813        elms_iter = iter(elements)
2814
2815        for elm in elms_iter:
2816            if elm in cls._logical_conditions:
2817                # ['&', '|']
2818                operator |= cls._logical_conditions[elm]
2819                continue
2820            elif elm in cls._comparison_conditions:
2821                # ['=', '<', '>', '<=', '>=' ] or ['=', '!=']
2822                operator |= cls._comparison_conditions[elm]
2823                continue
2824            elif elm == '+':
2825                # If keyword "+" is used, add the value to the previous rule.
2826                #  e.g.) 'SYN+ACK' or '!=SYN+ACK'
2827                rules[-1].value |= cls._to_value(next(elms_iter))
2828                continue
2829
2830            value = cls._to_value(elm)
2831
2832            operator = cls.normalize_operator(operator)
2833
2834            rules.append(cls(operator, value))
2835            operator = 0
2836
2837        return rules
2838
2839    @classmethod
2840    def _to_value(cls, value):
2841        return value
2842
2843    @classmethod
2844    def normalize_operator(cls, operator):
2845        return operator
2846
2847
2848class _FlowSpecNumeric(_FlowSpecOperatorBase):
2849    """
2850    Numeric operator class for Flow Specification NLRI component
2851    """
2852    # Numeric operator format
2853    #  0   1   2   3   4   5   6   7
2854    # +---+---+---+---+---+---+---+---+
2855    # | e | a |  len  | 0 |lt |gt |eq |
2856    # +---+---+---+---+---+---+---+---+
2857
2858    LT = 1 << 2  # Less than comparison bit
2859    GT = 1 << 1  # Greater than comparison bit
2860    EQ = 1 << 0  # Equality bit
2861
2862    _comparison_conditions = {
2863        '==': EQ,
2864        '<': LT,
2865        '>': GT,
2866        '<=': LT | EQ,
2867        '>=': GT | EQ
2868    }
2869
2870    @classmethod
2871    def _to_value(cls, value):
2872        try:
2873            return int(str(value), 0)
2874        except ValueError:
2875            raise ValueError('Invalid params: %s="%s"' % (
2876                cls.COMPONENT_NAME, value))
2877
2878    def to_str(self):
2879        string = ""
2880        if self.operator & self.AND:
2881            string += "&"
2882
2883        operator = self.operator & (self.LT | self.GT | self.EQ)
2884        for k, v in self._comparison_conditions.items():
2885            if operator == v:
2886                string += k
2887
2888        string += str(self.value)
2889
2890        return string
2891
2892    @classmethod
2893    def normalize_operator(cls, operator):
2894        if operator & (cls.LT | cls.GT | cls.EQ):
2895            return operator
2896        else:
2897            return operator | cls.EQ
2898
2899
2900class _FlowSpecBitmask(_FlowSpecOperatorBase):
2901    """
2902    Bitmask operator class for Flow Specification NLRI component
2903    """
2904    # Bitmask operator format
2905    #  0   1   2   3   4   5   6   7
2906    # +---+---+---+---+---+---+---+---+
2907    # | e | a |  len  | 0 | 0 |not| m |
2908    # +---+---+---+---+---+---+---+---+
2909
2910    NOT = 1 << 1    # NOT bit
2911    MATCH = 1 << 0  # MATCH bit
2912
2913    _comparison_conditions = {
2914        '!=': NOT,
2915        '==': MATCH,
2916    }
2917
2918    _bitmask_flags = {}
2919
2920    @classmethod
2921    def _to_value(cls, value):
2922        try:
2923            return cls.__dict__[value]
2924        except KeyError:
2925            raise ValueError('Invalid params: %s="%s"' % (
2926                cls.COMPONENT_NAME, value))
2927
2928    def to_str(self):
2929        string = ""
2930        if self.operator & self.AND:
2931            string += "&"
2932
2933        operator = self.operator & (self.NOT | self.MATCH)
2934        for k, v in self._comparison_conditions.items():
2935            if operator == v:
2936                string += k
2937
2938        plus = ""
2939        for k, v in self._bitmask_flags.items():
2940            if self.value & k:
2941                string += plus + v
2942                plus = "+"
2943
2944        return string
2945
2946
2947@_FlowSpecComponentBase.register_type(
2948    _FlowSpecIPv4Component.TYPE_PROTOCOL, addr_family.IP)
2949class FlowSpecIPProtocol(_FlowSpecNumeric):
2950    """IP Protocol for Flow Specification NLRI component
2951
2952    Set the IP protocol number at value.
2953    """
2954    COMPONENT_NAME = 'ip_proto'
2955
2956
2957@_FlowSpecComponentBase.register_type(
2958    _FlowSpecIPv6Component.TYPE_NEXT_HEADER, addr_family.IP6)
2959class FlowSpecNextHeader(_FlowSpecNumeric):
2960    """Next Header value in IPv6 packets
2961
2962    Set the IP protocol number at value
2963    """
2964    COMPONENT_NAME = 'next_header'
2965
2966
2967@_FlowSpecComponentBase.register_type(
2968    _FlowSpecIPv4Component.TYPE_PORT, addr_family.IP)
2969@_FlowSpecComponentBase.register_type(
2970    _FlowSpecIPv6Component.TYPE_PORT, addr_family.IP6)
2971class FlowSpecPort(_FlowSpecNumeric):
2972    """Port number for Flow Specification NLRI component
2973
2974    Set the source or destination TCP/UDP ports at value.
2975    """
2976    COMPONENT_NAME = 'port'
2977
2978
2979@_FlowSpecComponentBase.register_type(
2980    _FlowSpecIPv4Component.TYPE_DESTINATION_PORT, addr_family.IP)
2981@_FlowSpecComponentBase.register_type(
2982    _FlowSpecIPv6Component.TYPE_DESTINATION_PORT, addr_family.IP6)
2983class FlowSpecDestPort(_FlowSpecNumeric):
2984    """Destination port number for Flow Specification NLRI component
2985
2986    Set the destination port of a TCP or UDP packet at value.
2987    """
2988    COMPONENT_NAME = 'dst_port'
2989
2990
2991@_FlowSpecComponentBase.register_type(
2992    _FlowSpecIPv4Component.TYPE_SOURCE_PORT, addr_family.IP)
2993@_FlowSpecComponentBase.register_type(
2994    _FlowSpecIPv6Component.TYPE_SOURCE_PORT, addr_family.IP6)
2995class FlowSpecSrcPort(_FlowSpecNumeric):
2996    """Source port number for Flow Specification NLRI component
2997
2998    Set the source port of a TCP or UDP packet at value.
2999    """
3000    COMPONENT_NAME = 'src_port'
3001
3002
3003@_FlowSpecComponentBase.register_type(
3004    _FlowSpecIPv4Component.TYPE_ICMP, addr_family.IP)
3005@_FlowSpecComponentBase.register_type(
3006    _FlowSpecIPv6Component.TYPE_ICMP, addr_family.IP6)
3007class FlowSpecIcmpType(_FlowSpecNumeric):
3008    """ICMP type for Flow Specification NLRI component
3009
3010    Set the type field of an ICMP packet at value.
3011    """
3012    COMPONENT_NAME = 'icmp_type'
3013
3014
3015@_FlowSpecComponentBase.register_type(
3016    _FlowSpecIPv4Component.TYPE_ICMP_CODE, addr_family.IP)
3017@_FlowSpecComponentBase.register_type(
3018    _FlowSpecIPv6Component.TYPE_ICMP_CODE, addr_family.IP6)
3019class FlowSpecIcmpCode(_FlowSpecNumeric):
3020    """ICMP code Flow Specification NLRI component
3021
3022    Set the code field of an ICMP packet at value.
3023    """
3024    COMPONENT_NAME = 'icmp_code'
3025
3026
3027@_FlowSpecComponentBase.register_type(
3028    _FlowSpecIPv4Component.TYPE_TCP_FLAGS, addr_family.IP)
3029@_FlowSpecComponentBase.register_type(
3030    _FlowSpecIPv6Component.TYPE_TCP_FLAGS, addr_family.IP6)
3031class FlowSpecTCPFlags(_FlowSpecBitmask):
3032    """TCP flags for Flow Specification NLRI component
3033
3034    Supported TCP flags are CWR, ECN, URGENT, ACK, PUSH, RST, SYN and FIN.
3035    """
3036    COMPONENT_NAME = 'tcp_flags'
3037
3038    # bitmask format
3039    #  0    1    2    3    4    5    6    7
3040    # +----+----+----+----+----+----+----+----+
3041    # |CWR |ECN |URG |ACK |PSH |RST |SYN |FIN |
3042    # +----+----+----+----+----+----+----+----+
3043
3044    CWR = 1 << 7
3045    ECN = 1 << 6
3046    URGENT = 1 << 5
3047    ACK = 1 << 4
3048    PUSH = 1 << 3
3049    RST = 1 << 2
3050    SYN = 1 << 1
3051    FIN = 1 << 0
3052
3053    _bitmask_flags = collections.OrderedDict()
3054    _bitmask_flags[SYN] = 'SYN'
3055    _bitmask_flags[ACK] = 'ACK'
3056    _bitmask_flags[FIN] = 'FIN'
3057    _bitmask_flags[RST] = 'RST'
3058    _bitmask_flags[PUSH] = 'PUSH'
3059    _bitmask_flags[URGENT] = 'URGENT'
3060    _bitmask_flags[ECN] = 'ECN'
3061    _bitmask_flags[CWR] = 'CWR'
3062
3063
3064@_FlowSpecComponentBase.register_type(
3065    _FlowSpecIPv4Component.TYPE_PACKET_LENGTH, addr_family.IP)
3066@_FlowSpecComponentBase.register_type(
3067    _FlowSpecIPv6Component.TYPE_PACKET_LENGTH, addr_family.IP6)
3068class FlowSpecPacketLen(_FlowSpecNumeric):
3069    """Packet length for Flow Specification NLRI component
3070
3071    Set the total IP packet length at value.
3072    """
3073    COMPONENT_NAME = 'packet_len'
3074
3075
3076@_FlowSpecComponentBase.register_type(
3077    _FlowSpecIPv4Component.TYPE_DIFFSERV_CODE_POINT, addr_family.IP)
3078@_FlowSpecComponentBase.register_type(
3079    _FlowSpecIPv6Component.TYPE_DIFFSERV_CODE_POINT, addr_family.IP6)
3080class FlowSpecDSCP(_FlowSpecNumeric):
3081    """Diffserv Code Point for Flow Specification NLRI component
3082
3083    Set the 6-bit DSCP field at value. [RFC2474]
3084    """
3085    COMPONENT_NAME = 'dscp'
3086
3087
3088@_FlowSpecComponentBase.register_type(
3089    _FlowSpecIPv4Component.TYPE_FRAGMENT, addr_family.IP)
3090class FlowSpecFragment(_FlowSpecBitmask):
3091    """Fragment for Flow Specification NLRI component
3092
3093    Set the bitmask for operand format at value.
3094    The following values are supported.
3095
3096    ========== ===============================================
3097    Attribute  Description
3098    ========== ===============================================
3099    LF         Last fragment
3100    FF         First fragment
3101    ISF        Is a fragment
3102    DF         Don't fragment
3103    ========== ===============================================
3104    """
3105    COMPONENT_NAME = 'fragment'
3106
3107    # bitmask format
3108    #  0   1   2   3   4   5   6   7
3109    # +---+---+---+---+---+---+---+---+
3110    # |   Reserved    |LF |FF |IsF|DF |
3111    # +---+---+---+---+---+---+---+---+
3112
3113    LF = 1 << 3
3114    FF = 1 << 2
3115    ISF = 1 << 1
3116    DF = 1 << 0
3117
3118    _bitmask_flags = collections.OrderedDict()
3119    _bitmask_flags[LF] = 'LF'
3120    _bitmask_flags[FF] = 'FF'
3121    _bitmask_flags[ISF] = 'ISF'
3122    _bitmask_flags[DF] = 'DF'
3123
3124
3125@_FlowSpecComponentBase.register_type(
3126    _FlowSpecIPv6Component.TYPE_FRAGMENT, addr_family.IP6)
3127class FlowSpecIPv6Fragment(_FlowSpecBitmask):
3128    """Fragment for Flow Specification for IPv6 NLRI component
3129
3130    ========== ===============================================
3131    Attribute  Description
3132    ========== ===============================================
3133    LF         Last fragment
3134    FF         First fragment
3135    ISF        Is a fragment
3136    ========== ===============================================
3137    """
3138    COMPONENT_NAME = 'fragment'
3139
3140    # bitmask format
3141    #  0   1   2   3   4   5   6   7
3142    # +---+---+---+---+---+---+---+---+
3143    # |   Reserved    |LF |FF |IsF| 0 |
3144    # +---+---+---+---+---+---+---+---+
3145
3146    LF = 1 << 3
3147    FF = 1 << 2
3148    ISF = 1 << 1
3149
3150    _bitmask_flags = collections.OrderedDict()
3151    _bitmask_flags[LF] = 'LF'
3152    _bitmask_flags[FF] = 'FF'
3153    _bitmask_flags[ISF] = 'ISF'
3154
3155
3156@_FlowSpecComponentBase.register_type(
3157    _FlowSpecL2VPNComponent.TYPE_ETHER_TYPE, addr_family.L2VPN)
3158class FlowSpecEtherType(_FlowSpecNumeric):
3159    """Ethernet Type field in an Ethernet frame.
3160
3161    Set the 2 byte value of an Ethernet Type field at value.
3162    """
3163    COMPONENT_NAME = 'ether_type'
3164
3165
3166@_FlowSpecComponentBase.register_type(
3167    _FlowSpecL2VPNComponent.TYPE_SOURCE_MAC, addr_family.L2VPN)
3168class FlowSpecSourceMac(_FlowSpecL2VPNPrefixBase):
3169    """Source Mac Address.
3170
3171    Set the Mac Address at value.
3172    """
3173    COMPONENT_NAME = 'src_mac'
3174
3175
3176@_FlowSpecComponentBase.register_type(
3177    _FlowSpecL2VPNComponent.TYPE_DESTINATION_MAC, addr_family.L2VPN)
3178class FlowSpecDestinationMac(_FlowSpecL2VPNPrefixBase):
3179    """Destination Mac Address.
3180
3181    Set the Mac Address at value.
3182    """
3183    COMPONENT_NAME = 'dst_mac'
3184
3185
3186@_FlowSpecComponentBase.register_type(
3187    _FlowSpecL2VPNComponent.TYPE_LLC_DSAP, addr_family.L2VPN)
3188class FlowSpecLLCDSAP(_FlowSpecNumeric):
3189    """Destination SAP field in LLC header in an Ethernet frame.
3190
3191    Set the 2 byte value of an Destination SAP at value.
3192    """
3193    COMPONENT_NAME = 'llc_dsap'
3194
3195
3196@_FlowSpecComponentBase.register_type(
3197    _FlowSpecL2VPNComponent.TYPE_LLC_SSAP, addr_family.L2VPN)
3198class FlowSpecLLCSSAP(_FlowSpecNumeric):
3199    """Source SAP field in LLC header in an Ethernet frame.
3200
3201    Set the 2 byte value of an Source SAP at value.
3202    """
3203    COMPONENT_NAME = 'llc_ssap'
3204
3205
3206@_FlowSpecComponentBase.register_type(
3207    _FlowSpecL2VPNComponent.TYPE_LLC_CONTROL, addr_family.L2VPN)
3208class FlowSpecLLCControl(_FlowSpecNumeric):
3209    """Control field in LLC header in an Ethernet frame.
3210
3211    Set the Contorol field at value.
3212    """
3213    COMPONENT_NAME = 'llc_control'
3214
3215
3216@_FlowSpecComponentBase.register_type(
3217    _FlowSpecL2VPNComponent.TYPE_SNAP, addr_family.L2VPN)
3218class FlowSpecSNAP(_FlowSpecNumeric):
3219    """Sub-Network Access Protocol field in an Ethernet frame.
3220
3221    Set the 5 byte SNAP field at value.
3222    """
3223    COMPONENT_NAME = 'snap'
3224
3225
3226@_FlowSpecComponentBase.register_type(
3227    _FlowSpecL2VPNComponent.TYPE_VLAN_ID, addr_family.L2VPN)
3228class FlowSpecVLANID(_FlowSpecNumeric):
3229    """VLAN ID.
3230
3231    Set VLAN ID at value.
3232    """
3233    COMPONENT_NAME = 'vlan_id'
3234
3235
3236@_FlowSpecComponentBase.register_type(
3237    _FlowSpecL2VPNComponent.TYPE_VLAN_COS, addr_family.L2VPN)
3238class FlowSpecVLANCoS(_FlowSpecNumeric):
3239    """VLAN CoS Fields in an Ethernet frame.
3240
3241    Set the 3 bit CoS field at value.
3242    """
3243    COMPONENT_NAME = 'vlan_cos'
3244
3245
3246@_FlowSpecComponentBase.register_type(
3247    _FlowSpecL2VPNComponent.TYPE_INNER_VLAN_ID, addr_family.L2VPN)
3248class FlowSpecInnerVLANID(_FlowSpecNumeric):
3249    """Inner VLAN ID.
3250
3251    Set VLAN ID at value.
3252    """
3253    COMPONENT_NAME = 'inner_vlan_id'
3254
3255
3256@_FlowSpecComponentBase.register_type(
3257    _FlowSpecL2VPNComponent.TYPE_INNER_VLAN_COS, addr_family.L2VPN)
3258class FlowSpecInnerVLANCoS(_FlowSpecNumeric):
3259    """VLAN CoS Fields in an Inner Ethernet frame.
3260
3261    Set the 3 bit CoS field at value..
3262    """
3263    COMPONENT_NAME = 'inner_vlan_cos'
3264
3265
3266@_FlowSpecComponentBase.register_type(
3267    _FlowSpecIPv6Component.TYPE_FLOW_LABEL, addr_family.IP6)
3268class FlowSpecIPv6FlowLabel(_FlowSpecNumeric):
3269    COMPONENT_NAME = 'flow_label'
3270
3271
3272@functools.total_ordering
3273class RouteTargetMembershipNLRI(StringifyMixin):
3274    """Route Target Membership NLRI.
3275
3276    Route Target membership NLRI is advertised in BGP UPDATE messages using
3277    the MP_REACH_NLRI and MP_UNREACH_NLRI attributes.
3278    """
3279
3280    ROUTE_FAMILY = RF_RTC_UC
3281    DEFAULT_AS = '0:0'
3282    DEFAULT_RT = '0:0'
3283
3284    def __init__(self, origin_as, route_target):
3285        # If given is not default_as and default_rt
3286        if not (origin_as is self.DEFAULT_AS and
3287                route_target is self.DEFAULT_RT):
3288            # We validate them
3289            if (not self._is_valid_asn(origin_as) or
3290                    not self._is_valid_ext_comm_attr(route_target)):
3291                raise ValueError('Invalid params.')
3292        self.origin_as = origin_as
3293        self.route_target = route_target
3294
3295    def _is_valid_asn(self, asn):
3296        """Returns True if the given AS number is Two or Four Octet."""
3297        if isinstance(asn, six.integer_types) and 0 <= asn <= 0xffffffff:
3298            return True
3299        else:
3300            return False
3301
3302    def _is_valid_ext_comm_attr(self, attr):
3303        """Validates *attr* as string representation of RT or SOO.
3304
3305        Returns True if *attr* is as per our convention of RT or SOO, else
3306        False. Our convention is to represent RT/SOO is a string with format:
3307        *global_admin_part:local_admin_path*
3308        """
3309        is_valid = True
3310
3311        if not isinstance(attr, str):
3312            is_valid = False
3313        else:
3314            first, second = attr.split(':')
3315            try:
3316                if '.' in first:
3317                    socket.inet_aton(first)
3318                else:
3319                    int(first)
3320                    int(second)
3321            except (ValueError, socket.error):
3322                is_valid = False
3323
3324        return is_valid
3325
3326    @property
3327    def formatted_nlri_str(self):
3328        return "%s:%s" % (self.origin_as, self.route_target)
3329
3330    def is_default_rtnlri(self):
3331        if (self._origin_as is self.DEFAULT_AS and
3332                self._route_target is self.DEFAULT_RT):
3333            return True
3334        return False
3335
3336    def __lt__(self, other):
3337        return ((self.origin_as, self.route_target) <
3338                (other.origin_as, other.route_target))
3339
3340    def __eq__(self, other):
3341        return ((self.origin_as, self.route_target) ==
3342                (other.origin_as, other.route_target))
3343
3344    def __hash__(self):
3345        return hash((self.origin_as, self.route_target))
3346
3347    @classmethod
3348    def parser(cls, buf):
3349        idx = 0
3350
3351        # Extract origin AS.
3352        origin_as, = struct.unpack_from('!I', buf, idx)
3353        idx += 4
3354
3355        # Extract route target.
3356        route_target = _ExtendedCommunity(buf[idx:])
3357        return cls(origin_as, route_target)
3358
3359    def serialize(self):
3360        rt_nlri = b''
3361        if not self.is_default_rtnlri():
3362            rt_nlri += struct.pack('!I', self.origin_as)
3363            # Encode route target
3364            rt_nlri += self.route_target.serialize()
3365
3366        # RT Nlri is 12 octets
3367        return struct.pack('B', (8 * 12)) + rt_nlri
3368
3369
3370def _addr_class_key(route_family):
3371    return route_family.afi, route_family.safi
3372
3373
3374_ADDR_CLASSES = {
3375    _addr_class_key(RF_IPv4_UC): IPAddrPrefix,
3376    _addr_class_key(RF_IPv6_UC): IP6AddrPrefix,
3377    _addr_class_key(RF_IPv4_MPLS): LabelledIPAddrPrefix,
3378    _addr_class_key(RF_IPv6_MPLS): LabelledIP6AddrPrefix,
3379    _addr_class_key(RF_IPv4_VPN): LabelledVPNIPAddrPrefix,
3380    _addr_class_key(RF_IPv6_VPN): LabelledVPNIP6AddrPrefix,
3381    _addr_class_key(RF_L2_EVPN): EvpnNLRI,
3382    _addr_class_key(RF_IPv4_FLOWSPEC): FlowSpecIPv4NLRI,
3383    _addr_class_key(RF_IPv6_FLOWSPEC): FlowSpecIPv6NLRI,
3384    _addr_class_key(RF_VPNv4_FLOWSPEC): FlowSpecVPNv4NLRI,
3385    _addr_class_key(RF_VPNv6_FLOWSPEC): FlowSpecVPNv6NLRI,
3386    _addr_class_key(RF_L2VPN_FLOWSPEC): FlowSpecL2VPNNLRI,
3387    _addr_class_key(RF_RTC_UC): RouteTargetMembershipNLRI,
3388}
3389
3390
3391def _get_addr_class(afi, safi):
3392    try:
3393        return _ADDR_CLASSES[(afi, safi)]
3394    except KeyError:
3395        return _BinAddrPrefix
3396
3397
3398class _OptParam(StringifyMixin, TypeDisp, _Value):
3399    _PACK_STR = '!BB'  # type, length
3400
3401    def __init__(self, type_, value=None, length=None):
3402        if type_ is None:
3403            type_ = self._rev_lookup_type(self.__class__)
3404        self.type = type_
3405        self.length = length
3406        if value is not None:
3407            self.value = value
3408
3409    @classmethod
3410    def parser(cls, buf):
3411        (type_, length) = struct.unpack_from(cls._PACK_STR,
3412                                             six.binary_type(buf))
3413        rest = buf[struct.calcsize(cls._PACK_STR):]
3414        value = bytes(rest[:length])
3415        rest = rest[length:]
3416        subcls = cls._lookup_type(type_)
3417        caps = subcls.parse_value(value)
3418        if not isinstance(caps, list):
3419            caps = [subcls(type_=type_, length=length, **caps[0])]
3420        return caps, rest
3421
3422    def serialize(self):
3423        # fixup
3424        value = self.serialize_value()
3425        self.length = len(value)
3426
3427        buf = bytearray()
3428        msg_pack_into(self._PACK_STR, buf, 0, self.type, self.length)
3429        return buf + value
3430
3431
3432@_OptParam.register_unknown_type()
3433class BGPOptParamUnknown(_OptParam):
3434    @classmethod
3435    def parse_value(cls, buf):
3436        return {
3437            'value': buf
3438        }, cls
3439
3440    def serialize_value(self):
3441        return self.value
3442
3443
3444@_OptParam.register_type(BGP_OPT_CAPABILITY)
3445class _OptParamCapability(_OptParam, TypeDisp):
3446    _CAP_HDR_PACK_STR = '!BB'
3447
3448    def __init__(self, cap_code=None, cap_value=None, cap_length=None,
3449                 type_=None, length=None):
3450        super(_OptParamCapability, self).__init__(type_=BGP_OPT_CAPABILITY,
3451                                                  length=length)
3452        if cap_code is None:
3453            cap_code = self._rev_lookup_type(self.__class__)
3454        self.cap_code = cap_code
3455        if cap_value is not None:
3456            self.cap_value = cap_value
3457        if cap_length is not None:
3458            self.cap_length = cap_length
3459
3460    @classmethod
3461    def parse_value(cls, buf):
3462        caps = []
3463        while len(buf) > 0:
3464            (code, length) = struct.unpack_from(cls._CAP_HDR_PACK_STR,
3465                                                six.binary_type(buf))
3466            value = buf[struct.calcsize(cls._CAP_HDR_PACK_STR):]
3467            buf = buf[length + 2:]
3468            kwargs = {
3469                'cap_code': code,
3470                'cap_length': length,
3471            }
3472            subcls = cls._lookup_type(code)
3473            kwargs.update(subcls.parse_cap_value(value))
3474            caps.append(subcls(type_=BGP_OPT_CAPABILITY, length=length + 2,
3475                               **kwargs))
3476        return caps
3477
3478    def serialize_value(self):
3479        # fixup
3480        cap_value = self.serialize_cap_value()
3481        self.cap_length = len(cap_value)
3482
3483        buf = bytearray()
3484        msg_pack_into(self._CAP_HDR_PACK_STR, buf, 0, self.cap_code,
3485                      self.cap_length)
3486        return buf + cap_value
3487
3488
3489class _OptParamEmptyCapability(_OptParamCapability):
3490    @classmethod
3491    def parse_cap_value(cls, buf):
3492        return {}
3493
3494    def serialize_cap_value(self):
3495        return bytearray()
3496
3497
3498@_OptParamCapability.register_unknown_type()
3499class BGPOptParamCapabilityUnknown(_OptParamCapability):
3500    @classmethod
3501    def parse_cap_value(cls, buf):
3502        return {'cap_value': buf}
3503
3504    def serialize_cap_value(self):
3505        return self.cap_value
3506
3507
3508@_OptParamCapability.register_type(BGP_CAP_ROUTE_REFRESH)
3509class BGPOptParamCapabilityRouteRefresh(_OptParamEmptyCapability):
3510    pass
3511
3512
3513@_OptParamCapability.register_type(BGP_CAP_ROUTE_REFRESH_CISCO)
3514class BGPOptParamCapabilityCiscoRouteRefresh(_OptParamEmptyCapability):
3515    pass
3516
3517
3518@_OptParamCapability.register_type(BGP_CAP_ENHANCED_ROUTE_REFRESH)
3519class BGPOptParamCapabilityEnhancedRouteRefresh(_OptParamEmptyCapability):
3520    pass
3521
3522
3523@_OptParamCapability.register_type(BGP_CAP_GRACEFUL_RESTART)
3524class BGPOptParamCapabilityGracefulRestart(_OptParamCapability):
3525    _CAP_PACK_STR = "!H"
3526
3527    def __init__(self, flags, time, tuples, **kwargs):
3528        super(BGPOptParamCapabilityGracefulRestart, self).__init__(**kwargs)
3529        self.flags = flags
3530        self.time = time
3531        self.tuples = tuples
3532
3533    @classmethod
3534    def parse_cap_value(cls, buf):
3535        (restart, ) = struct.unpack_from(cls._CAP_PACK_STR,
3536                                         six.binary_type(buf))
3537        buf = buf[2:]
3538        l = []
3539        while len(buf) >= 4:
3540            l.append(struct.unpack_from("!HBB", buf))
3541            buf = buf[4:]
3542        return {'flags': restart >> 12, 'time': restart & 0xfff, 'tuples': l}
3543
3544    def serialize_cap_value(self):
3545        buf = bytearray()
3546        msg_pack_into(self._CAP_PACK_STR, buf, 0, self.flags << 12 | self.time)
3547        offset = 2
3548        for i in self.tuples:
3549            afi, safi, flags = i
3550            msg_pack_into("!HBB", buf, offset, afi, safi, flags)
3551            offset += 4
3552        return buf
3553
3554
3555@_OptParamCapability.register_type(BGP_CAP_FOUR_OCTET_AS_NUMBER)
3556class BGPOptParamCapabilityFourOctetAsNumber(_OptParamCapability):
3557    _CAP_PACK_STR = '!I'
3558
3559    def __init__(self, as_number, **kwargs):
3560        super(BGPOptParamCapabilityFourOctetAsNumber, self).__init__(**kwargs)
3561        self.as_number = as_number
3562
3563    @classmethod
3564    def parse_cap_value(cls, buf):
3565        (as_number, ) = struct.unpack_from(cls._CAP_PACK_STR,
3566                                           six.binary_type(buf))
3567        return {'as_number': as_number}
3568
3569    def serialize_cap_value(self):
3570        buf = bytearray()
3571        msg_pack_into(self._CAP_PACK_STR, buf, 0, self.as_number)
3572        return buf
3573
3574
3575@_OptParamCapability.register_type(BGP_CAP_MULTIPROTOCOL)
3576class BGPOptParamCapabilityMultiprotocol(_OptParamCapability):
3577    _CAP_PACK_STR = '!HBB'  # afi, reserved, safi
3578
3579    def __init__(self, afi, safi, reserved=0, **kwargs):
3580        super(BGPOptParamCapabilityMultiprotocol, self).__init__(**kwargs)
3581        self.afi = afi
3582        self.reserved = reserved
3583        self.safi = safi
3584
3585    @classmethod
3586    def parse_cap_value(cls, buf):
3587        (afi, reserved, safi,) = struct.unpack_from(cls._CAP_PACK_STR,
3588                                                    six.binary_type(buf))
3589        return {
3590            'afi': afi,
3591            'reserved': reserved,
3592            'safi': safi,
3593        }
3594
3595    def serialize_cap_value(self):
3596        # fixup
3597        self.reserved = 0
3598
3599        buf = bytearray()
3600        msg_pack_into(self._CAP_PACK_STR, buf, 0,
3601                      self.afi, self.reserved, self.safi)
3602        return buf
3603
3604
3605@_OptParamCapability.register_type(BGP_CAP_CARRYING_LABEL_INFO)
3606class BGPOptParamCapabilityCarryingLabelInfo(_OptParamEmptyCapability):
3607    pass
3608
3609
3610class BGPWithdrawnRoute(IPAddrPrefix):
3611    pass
3612
3613
3614class _PathAttribute(StringifyMixin, TypeDisp, _Value):
3615    _PACK_STR = '!BB'  # flags, type
3616    _PACK_STR_LEN = '!B'  # length
3617    _PACK_STR_EXT_LEN = '!H'  # length w/ BGP_ATTR_FLAG_EXTENDED_LENGTH
3618    _ATTR_FLAGS = None
3619
3620    def __init__(self, value=None, flags=0, type_=None, length=None):
3621        if type_ is None:
3622            type_ = self._rev_lookup_type(self.__class__)
3623        self.flags = flags
3624        self.type = type_
3625        self.length = length
3626        if value is not None:
3627            self.value = value
3628
3629    @classmethod
3630    def parser(cls, buf):
3631        (flags, type_) = struct.unpack_from(cls._PACK_STR,
3632                                            six.binary_type(buf))
3633        rest = buf[struct.calcsize(cls._PACK_STR):]
3634        if (flags & BGP_ATTR_FLAG_EXTENDED_LENGTH) != 0:
3635            len_pack_str = cls._PACK_STR_EXT_LEN
3636        else:
3637            len_pack_str = cls._PACK_STR_LEN
3638        (length,) = struct.unpack_from(len_pack_str, six.binary_type(rest))
3639        rest = rest[struct.calcsize(len_pack_str):]
3640        value = bytes(rest[:length])
3641        rest = rest[length:]
3642        subcls = cls._lookup_type(type_)
3643        return subcls(flags=flags, type_=type_, length=length,
3644                      **subcls.parse_value(value)), rest
3645
3646    def serialize(self):
3647        # fixup
3648        if self._ATTR_FLAGS is not None:
3649            self.flags = (
3650                self.flags
3651                & ~(BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE)
3652                | self._ATTR_FLAGS)
3653        value = self.serialize_value()
3654        self.length = len(value)
3655        if self.flags & BGP_ATTR_FLAG_EXTENDED_LENGTH:
3656            len_pack_str = self._PACK_STR_EXT_LEN
3657        elif self.length > 255:
3658            self.flags |= BGP_ATTR_FLAG_EXTENDED_LENGTH
3659            len_pack_str = self._PACK_STR_EXT_LEN
3660        else:
3661            self.flags &= ~BGP_ATTR_FLAG_EXTENDED_LENGTH
3662            len_pack_str = self._PACK_STR_LEN
3663
3664        buf = bytearray()
3665        msg_pack_into(self._PACK_STR, buf, 0, self.flags, self.type)
3666        msg_pack_into(len_pack_str, buf, len(buf), self.length)
3667        return buf + value
3668
3669
3670@_PathAttribute.register_unknown_type()
3671class BGPPathAttributeUnknown(_PathAttribute):
3672    @classmethod
3673    def parse_value(cls, buf):
3674        return {
3675            'value': buf
3676        }
3677
3678    def serialize_value(self):
3679        return self.value
3680
3681
3682class _PathAttributeUint32(_PathAttribute):
3683    _VALUE_PACK_STR = '!I'
3684
3685
3686@_PathAttribute.register_type(BGP_ATTR_TYPE_ORIGIN)
3687class BGPPathAttributeOrigin(_PathAttribute):
3688    _VALUE_PACK_STR = '!B'
3689    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
3690
3691
3692class _BGPPathAttributeAsPathCommon(_PathAttribute):
3693    _AS_SET = 1
3694    _AS_SEQUENCE = 2
3695    _SEG_HDR_PACK_STR = '!BB'
3696    _AS_PACK_STR = None
3697    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
3698
3699    def __init__(self, value, as_pack_str=None, flags=0, type_=None,
3700                 length=None):
3701        super(_BGPPathAttributeAsPathCommon, self).__init__(value=value,
3702                                                            flags=flags,
3703                                                            type_=type_,
3704                                                            length=length)
3705        if as_pack_str:
3706            self._AS_PACK_STR = as_pack_str
3707
3708    @property
3709    def path_seg_list(self):
3710        return copy.deepcopy(self.value)
3711
3712    def get_as_path_len(self):
3713        count = 0
3714        for seg in self.value:
3715            if isinstance(seg, list):
3716                # Segment type 2 stored in list and all AS counted.
3717                count += len(seg)
3718            else:
3719                # Segment type 1 stored in set and count as one.
3720                count += 1
3721
3722        return count
3723
3724    def has_local_as(self, local_as, max_count=0):
3725        """Check if *local_as* is already present on path list."""
3726        _count = 0
3727        for as_path_seg in self.value:
3728            _count += list(as_path_seg).count(local_as)
3729        return _count > max_count
3730
3731    def has_matching_leftmost(self, remote_as):
3732        """Check if leftmost AS matches *remote_as*."""
3733        if not self.value or not remote_as:
3734            return False
3735
3736        leftmost_seg = self.path_seg_list[0]
3737        if leftmost_seg and leftmost_seg[0] == remote_as:
3738            return True
3739
3740        return False
3741
3742    @classmethod
3743    def _is_valid_16bit_as_path(cls, buf):
3744
3745        two_byte_as_size = struct.calcsize('!H')
3746
3747        while buf:
3748            (type_, num_as) = struct.unpack_from(cls._SEG_HDR_PACK_STR,
3749                                                 six.binary_type(buf))
3750
3751            if type_ is not cls._AS_SET and type_ is not cls._AS_SEQUENCE:
3752                return False
3753
3754            buf = buf[struct.calcsize(cls._SEG_HDR_PACK_STR):]
3755
3756            if len(buf) < num_as * two_byte_as_size:
3757                return False
3758
3759            buf = buf[num_as * two_byte_as_size:]
3760
3761        return True
3762
3763    @classmethod
3764    def parse_value(cls, buf):
3765        result = []
3766
3767        if cls._is_valid_16bit_as_path(buf):
3768            as_pack_str = '!H'
3769        else:
3770            as_pack_str = '!I'
3771
3772        while buf:
3773            (type_, num_as) = struct.unpack_from(cls._SEG_HDR_PACK_STR,
3774                                                 six.binary_type(buf))
3775            buf = buf[struct.calcsize(cls._SEG_HDR_PACK_STR):]
3776            l = []
3777            for _ in range(0, num_as):
3778                (as_number,) = struct.unpack_from(as_pack_str,
3779                                                  six.binary_type(buf))
3780                buf = buf[struct.calcsize(as_pack_str):]
3781                l.append(as_number)
3782            if type_ == cls._AS_SET:
3783                result.append(set(l))
3784            elif type_ == cls._AS_SEQUENCE:
3785                result.append(l)
3786            else:
3787                # protocol error
3788                raise struct.error('Unsupported segment type: %s' % type_)
3789        return {
3790            'value': result,
3791            'as_pack_str': as_pack_str,
3792        }
3793
3794    def serialize_value(self):
3795        buf = bytearray()
3796        offset = 0
3797        for e in self.value:
3798            if isinstance(e, set):
3799                type_ = self._AS_SET
3800            elif isinstance(e, list):
3801                type_ = self._AS_SEQUENCE
3802            else:
3803                raise struct.error(
3804                    'Element of %s.value must be of type set or list' %
3805                    self.__class__.__name__)
3806            l = list(e)
3807            num_as = len(l)
3808            if num_as == 0:
3809                continue
3810            msg_pack_into(self._SEG_HDR_PACK_STR, buf, offset, type_, num_as)
3811            offset += struct.calcsize(self._SEG_HDR_PACK_STR)
3812            for i in l:
3813                msg_pack_into(self._AS_PACK_STR, buf, offset, i)
3814                offset += struct.calcsize(self._AS_PACK_STR)
3815        return buf
3816
3817
3818@_PathAttribute.register_type(BGP_ATTR_TYPE_AS_PATH)
3819class BGPPathAttributeAsPath(_BGPPathAttributeAsPathCommon):
3820    # XXX depends on negotiated capability, AS numbers can be 32 bit.
3821    # while wireshark seems to attempt auto-detect, it seems that
3822    # there's no way to detect it reliably.  for example, the
3823    # following byte sequence can be interpreted in two ways.
3824    #   01 02 99 88 77 66 02 01 55 44
3825    #   AS_SET num=2 9988 7766 AS_SEQUENCE num=1 5544
3826    #   AS_SET num=2 99887766 02015544
3827    # we first check whether AS path can be parsed in 16bit format and if
3828    # it fails, we try to parse as 32bit
3829    _AS_PACK_STR = '!H'
3830
3831
3832@_PathAttribute.register_type(BGP_ATTR_TYPE_AS4_PATH)
3833class BGPPathAttributeAs4Path(_BGPPathAttributeAsPathCommon):
3834    _AS_PACK_STR = '!I'
3835    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL
3836
3837    @classmethod
3838    def _is_valid_16bit_as_path(cls, buf):
3839        return False
3840
3841
3842@_PathAttribute.register_type(BGP_ATTR_TYPE_NEXT_HOP)
3843class BGPPathAttributeNextHop(_PathAttribute):
3844    _VALUE_PACK_STR = '!4s'
3845    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
3846    _TYPE = {
3847        'ascii': [
3848            'value'
3849        ]
3850    }
3851
3852    @classmethod
3853    def parse_value(cls, buf):
3854        (ip_addr,) = struct.unpack_from(cls._VALUE_PACK_STR,
3855                                        six.binary_type(buf))
3856        return {
3857            'value': addrconv.ipv4.bin_to_text(ip_addr),
3858        }
3859
3860    def serialize_value(self):
3861        buf = bytearray()
3862        msg_pack_into(self._VALUE_PACK_STR, buf, 0,
3863                      addrconv.ipv4.text_to_bin(self.value))
3864        return buf
3865
3866
3867@_PathAttribute.register_type(BGP_ATTR_TYPE_MULTI_EXIT_DISC)
3868class BGPPathAttributeMultiExitDisc(_PathAttributeUint32):
3869    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
3870
3871
3872@_PathAttribute.register_type(BGP_ATTR_TYPE_LOCAL_PREF)
3873class BGPPathAttributeLocalPref(_PathAttributeUint32):
3874    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
3875
3876
3877@_PathAttribute.register_type(BGP_ATTR_TYPE_ATOMIC_AGGREGATE)
3878class BGPPathAttributeAtomicAggregate(_PathAttribute):
3879    _ATTR_FLAGS = BGP_ATTR_FLAG_TRANSITIVE
3880
3881    @classmethod
3882    def parse_value(cls, buf):
3883        return {}
3884
3885    def serialize_value(self):
3886        return b''
3887
3888
3889class _BGPPathAttributeAggregatorCommon(_PathAttribute):
3890    _VALUE_PACK_STR = None
3891    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
3892    _TYPE = {
3893        'ascii': [
3894            'addr'
3895        ]
3896    }
3897
3898    def __init__(self, as_number, addr, flags=0, type_=None, length=None):
3899        super(_BGPPathAttributeAggregatorCommon, self).__init__(flags=flags,
3900                                                                type_=type_,
3901                                                                length=length)
3902        self.as_number = as_number
3903        self.addr = addr
3904
3905    @classmethod
3906    def parse_value(cls, buf):
3907        (as_number, addr) = struct.unpack_from(cls._VALUE_PACK_STR,
3908                                               six.binary_type(buf))
3909        return {
3910            'as_number': as_number,
3911            'addr': addrconv.ipv4.bin_to_text(addr),
3912        }
3913
3914    def serialize_value(self):
3915        buf = bytearray()
3916        msg_pack_into(self._VALUE_PACK_STR, buf, 0, self.as_number,
3917                      addrconv.ipv4.text_to_bin(self.addr))
3918        return buf
3919
3920
3921@_PathAttribute.register_type(BGP_ATTR_TYPE_AGGREGATOR)
3922class BGPPathAttributeAggregator(_BGPPathAttributeAggregatorCommon):
3923    # Note: AS numbers can be Two-Octet or Four-Octet.
3924    # This class would detect it by the value length field.
3925    # For example,
3926    # - if the value field length is 6 (='!H4s'), AS number should
3927    #   be Two-Octet.
3928    # - else if the length is 8 (='!I4s'), AS number should be Four-Octet.
3929    _TWO_OCTET_VALUE_PACK_STR = '!H4s'
3930    _FOUR_OCTET_VALUE_PACK_STR = '!I4s'
3931    _VALUE_PACK_STR = _TWO_OCTET_VALUE_PACK_STR  # Two-Octet by default
3932    _FOUR_OCTET_VALUE_SIZE = struct.calcsize(_FOUR_OCTET_VALUE_PACK_STR)
3933
3934    @classmethod
3935    def parse_value(cls, buf):
3936        if len(buf) == cls._FOUR_OCTET_VALUE_SIZE:
3937            cls._VALUE_PACK_STR = cls._FOUR_OCTET_VALUE_PACK_STR
3938        return super(BGPPathAttributeAggregator, cls).parse_value(buf)
3939
3940    def serialize_value(self):
3941        if self.as_number > 0xffff:
3942            self._VALUE_PACK_STR = self._FOUR_OCTET_VALUE_PACK_STR
3943        return super(BGPPathAttributeAggregator, self).serialize_value()
3944
3945
3946@_PathAttribute.register_type(BGP_ATTR_TYPE_AS4_AGGREGATOR)
3947class BGPPathAttributeAs4Aggregator(_BGPPathAttributeAggregatorCommon):
3948    _VALUE_PACK_STR = '!I4s'
3949
3950
3951@_PathAttribute.register_type(BGP_ATTR_TYPE_COMMUNITIES)
3952class BGPPathAttributeCommunities(_PathAttribute):
3953    _VALUE_PACK_STR = '!I'
3954    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
3955
3956    # String constants of well-known-communities
3957    NO_EXPORT = int('0xFFFFFF01', 16)
3958    NO_ADVERTISE = int('0xFFFFFF02', 16)
3959    NO_EXPORT_SUBCONFED = int('0xFFFFFF03', 16)
3960    WELL_KNOW_COMMUNITIES = (NO_EXPORT, NO_ADVERTISE, NO_EXPORT_SUBCONFED)
3961
3962    def __init__(self, communities,
3963                 flags=0, type_=None, length=None):
3964        super(BGPPathAttributeCommunities, self).__init__(flags=flags,
3965                                                          type_=type_,
3966                                                          length=length)
3967        self.communities = communities
3968
3969    @classmethod
3970    def parse_value(cls, buf):
3971        rest = buf
3972        communities = []
3973        elem_size = struct.calcsize(cls._VALUE_PACK_STR)
3974        while len(rest) >= elem_size:
3975            (comm, ) = struct.unpack_from(cls._VALUE_PACK_STR,
3976                                          six.binary_type(rest))
3977            communities.append(comm)
3978            rest = rest[elem_size:]
3979        return {
3980            'communities': communities,
3981        }
3982
3983    def serialize_value(self):
3984        buf = bytearray()
3985        for comm in self.communities:
3986            bincomm = bytearray()
3987            msg_pack_into(self._VALUE_PACK_STR, bincomm, 0, comm)
3988            buf += bincomm
3989        return buf
3990
3991    @staticmethod
3992    def is_no_export(comm_attr):
3993        """Returns True if given value matches well-known community NO_EXPORT
3994         attribute value.
3995         """
3996        return comm_attr == BGPPathAttributeCommunities.NO_EXPORT
3997
3998    @staticmethod
3999    def is_no_advertise(comm_attr):
4000        """Returns True if given value matches well-known community
4001        NO_ADVERTISE attribute value.
4002        """
4003        return comm_attr == BGPPathAttributeCommunities.NO_ADVERTISE
4004
4005    @staticmethod
4006    def is_no_export_subconfed(comm_attr):
4007        """Returns True if given value matches well-known community
4008         NO_EXPORT_SUBCONFED attribute value.
4009         """
4010        return comm_attr == BGPPathAttributeCommunities.NO_EXPORT_SUBCONFED
4011
4012    def has_comm_attr(self, attr):
4013        """Returns True if given community attribute is present."""
4014
4015        for comm_attr in self.communities:
4016            if comm_attr == attr:
4017                return True
4018
4019        return False
4020
4021
4022@_PathAttribute.register_type(BGP_ATTR_TYPE_ORIGINATOR_ID)
4023class BGPPathAttributeOriginatorId(_PathAttribute):
4024    # ORIGINATOR_ID is a new optional, non-transitive BGP attribute of Type
4025    # code 9. This attribute is 4 bytes long and it will be created by an
4026    # RR in reflecting a route.
4027    _VALUE_PACK_STR = '!4s'
4028    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
4029    _TYPE = {
4030        'asciilist': [
4031            'value'
4032        ]
4033    }
4034
4035    @classmethod
4036    def parse_value(cls, buf):
4037        (originator_id,) = struct.unpack_from(cls._VALUE_PACK_STR,
4038                                              six.binary_type(buf))
4039        return {
4040            'value': addrconv.ipv4.bin_to_text(originator_id),
4041        }
4042
4043    def serialize_value(self):
4044        buf = bytearray()
4045        msg_pack_into(self._VALUE_PACK_STR, buf, 0,
4046                      addrconv.ipv4.text_to_bin(self.value))
4047        return buf
4048
4049
4050@_PathAttribute.register_type(BGP_ATTR_TYPE_CLUSTER_LIST)
4051class BGPPathAttributeClusterList(_PathAttribute):
4052    # CLUSTER_LIST is a new, optional, non-transitive BGP attribute of Type
4053    # code 10. It is a sequence of CLUSTER_ID values representing the
4054    # reflection path that the route has passed.
4055    _VALUE_PACK_STR = '!4s'
4056    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
4057    _TYPE = {
4058        'ascii': [
4059            'value'
4060        ]
4061    }
4062
4063    @classmethod
4064    def parse_value(cls, buf):
4065        rest = buf
4066        cluster_list = []
4067        elem_size = struct.calcsize(cls._VALUE_PACK_STR)
4068        while len(rest) >= elem_size:
4069            (cluster_id, ) = struct.unpack_from(
4070                cls._VALUE_PACK_STR, six.binary_type(rest))
4071            cluster_list.append(addrconv.ipv4.bin_to_text(cluster_id))
4072            rest = rest[elem_size:]
4073        return {
4074            'value': cluster_list,
4075        }
4076
4077    def serialize_value(self):
4078        buf = bytearray()
4079        offset = 0
4080        for cluster_id in self.value:
4081            msg_pack_into(
4082                self._VALUE_PACK_STR,
4083                buf,
4084                offset,
4085                addrconv.ipv4.text_to_bin(cluster_id))
4086            offset += struct.calcsize(self._VALUE_PACK_STR)
4087        return buf
4088
4089
4090# Extended Communities
4091# RFC 4360
4092# RFC 5668
4093# IANA registry:
4094# https://www.iana.org/assignments/bgp-extended-communities/
4095# bgp-extended-communities.xml
4096#
4097# type
4098# high  low
4099# 00    sub-type    Two-Octet AS Specific Extended Community (transitive)
4100# 40    sub-type    Two-Octet AS Specific Extended Community
4101#                   payload:
4102#                     2 byte Global Administrator (AS number)
4103#                     4 byte Local Administrator (defined by sub-type)
4104# 01    sub-type    IPv4 Address Specific Extended Community (transitive)
4105# 41    sub-type    IPv4 Address Specific Extended Community
4106#                   payload:
4107#                     4 byte Global Administrator (IPv4 address)
4108#                     2 byte Local Administrator (defined by sub-type)
4109# 03    sub-type    Opaque Extended Community (transitive)
4110# 43    sub-type    Opaque Extended Community
4111#                   payload:
4112#                     6 byte opaque value (defined by sub-type)
4113#
4114# 00    02          Route Target Community (two-octet AS specific)
4115# 01    02          Route Target Community (IPv4 address specific)
4116# 02    02          Route Target Community (four-octet AS specific, RFC 5668)
4117# 00    03          Route Origin Community (two-octet AS specific)
4118# 01    03          Route Origin Community (IPv4 address specific)
4119# 02    03          Route Origin Community (four-octet AS specific, RFC 5668)
4120# 06    sub-type    Ethernet VPN Extended Community (RFC 7432)
4121# 80    sub-type    Flow Specification Extended Community (RFC 5575)
4122
4123@_PathAttribute.register_type(BGP_ATTR_TYPE_EXTENDED_COMMUNITIES)
4124class BGPPathAttributeExtendedCommunities(_PathAttribute):
4125    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
4126    _class_prefixes = ['BGP']
4127
4128    def __init__(self, communities,
4129                 flags=0, type_=None, length=None):
4130        super(BGPPathAttributeExtendedCommunities,
4131              self).__init__(flags=flags,
4132                             type_=type_,
4133                             length=length)
4134        self.communities = communities
4135
4136    @classmethod
4137    def parse_value(cls, buf):
4138        rest = buf
4139        communities = []
4140        while rest:
4141            comm, rest = _ExtendedCommunity.parse(rest)
4142            communities.append(comm)
4143        return {
4144            'communities': communities,
4145        }
4146
4147    def serialize_value(self):
4148        buf = bytearray()
4149        for comm in self.communities:
4150            buf += comm.serialize()
4151        return buf
4152
4153    def _community_list(self, subtype):
4154        _list = []
4155        for comm in (c for c in self.communities
4156                     if hasattr(c, "subtype") and c.subtype == subtype):
4157            if comm.type == 0 or comm.type == 2:
4158                _list.append('%d:%d' % (comm.as_number,
4159                                        comm.local_administrator))
4160            elif comm.type == 1:
4161                _list.append('%s:%d' % (comm.ipv4_address,
4162                                        comm.local_administrator))
4163        return _list
4164
4165    @property
4166    def rt_list(self):
4167        return self._community_list(2)
4168
4169    @property
4170    def soo_list(self):
4171        return self._community_list(3)
4172
4173
4174class _ExtendedCommunity(StringifyMixin, TypeDisp, _Value):
4175    _PACK_STR = '!B7s'  # type high (+ type low), value
4176    _PACK_STR_SIZE = struct.calcsize(_PACK_STR)
4177    _SUBTYPE_PACK_STR = '!B'  # subtype
4178    IANA_AUTHORITY = 0x80
4179    TRANSITIVE = 0x40
4180    _TYPE_HIGH_MASK = ~TRANSITIVE
4181
4182    TWO_OCTET_AS_SPECIFIC = 0x00
4183    IPV4_ADDRESS_SPECIFIC = 0x01
4184    FOUR_OCTET_AS_SPECIFIC = 0x02
4185    OPAQUE = 0x03
4186    SUBTYPE_ENCAPSULATION = 0x0c
4187    ENCAPSULATION = (OPAQUE, SUBTYPE_ENCAPSULATION)
4188    EVPN = 0x06
4189    SUBTYPE_EVPN_MAC_MOBILITY = 0x00
4190    SUBTYPE_EVPN_ESI_LABEL = 0x01
4191    SUBTYPE_EVPN_ES_IMPORT_RT = 0x02
4192    EVPN_MAC_MOBILITY = (EVPN, SUBTYPE_EVPN_MAC_MOBILITY)
4193    EVPN_ESI_LABEL = (EVPN, SUBTYPE_EVPN_ESI_LABEL)
4194    EVPN_ES_IMPORT_RT = (EVPN, SUBTYPE_EVPN_ES_IMPORT_RT)
4195    FLOWSPEC = 0x80
4196    FLOWSPEC_L2VPN = 0x08
4197    SUBTYPE_FLOWSPEC_TRAFFIC_RATE = 0x06
4198    SUBTYPE_FLOWSPEC_TRAFFIC_ACTION = 0x07
4199    SUBTYPE_FLOWSPEC_REDIRECT = 0x08
4200    SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING = 0x09
4201    SUBTYPE_FLOWSPEC_VLAN_ACTION = 0x0a
4202    SUBTYPE_FLOWSPEC_TPID_ACTION = 0x0b
4203    FLOWSPEC_TRAFFIC_RATE = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_RATE)
4204    FLOWSPEC_TRAFFIC_ACTION = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_ACTION)
4205    FLOWSPEC_REDIRECT = (FLOWSPEC, SUBTYPE_FLOWSPEC_REDIRECT)
4206    FLOWSPEC_TRAFFIC_REMARKING = (FLOWSPEC, SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING)
4207    FLOWSPEC_VLAN_ACTION = (FLOWSPEC_L2VPN, SUBTYPE_FLOWSPEC_VLAN_ACTION)
4208    FLOWSPEC_TPID_ACTION = (FLOWSPEC_L2VPN, SUBTYPE_FLOWSPEC_TPID_ACTION)
4209
4210    def __init__(self, type_=None):
4211        if type_ is None:
4212            type_ = self._rev_lookup_type(self.__class__)
4213            if isinstance(type_, (tuple, list)):
4214                type_ = type_[0]
4215        self.type = type_
4216
4217    @classmethod
4218    def parse_subtype(cls, buf):
4219        (subtype,) = struct.unpack_from(cls._SUBTYPE_PACK_STR, buf)
4220        return subtype
4221
4222    @classmethod
4223    def parse(cls, buf):
4224        (type_, value) = struct.unpack_from(cls._PACK_STR, buf)
4225        rest = buf[cls._PACK_STR_SIZE:]
4226        type_low = type_ & cls._TYPE_HIGH_MASK
4227        subtype = cls.parse_subtype(value)
4228        subcls = cls._lookup_type((type_low, subtype))
4229        if subcls == cls._UNKNOWN_TYPE:
4230            subcls = cls._lookup_type(type_low)
4231        return subcls(type_=type_, **subcls.parse_value(value)), rest
4232
4233    def serialize(self):
4234        return struct.pack(self._PACK_STR, self.type,
4235                           self.serialize_value())
4236
4237
4238@_ExtendedCommunity.register_type(_ExtendedCommunity.TWO_OCTET_AS_SPECIFIC)
4239class BGPTwoOctetAsSpecificExtendedCommunity(_ExtendedCommunity):
4240    _VALUE_PACK_STR = '!BHI'  # sub type, as number, local adm
4241    _VALUE_FIELDS = ['subtype', 'as_number', 'local_administrator']
4242
4243    def __init__(self, **kwargs):
4244        super(BGPTwoOctetAsSpecificExtendedCommunity, self).__init__()
4245        self.do_init(BGPTwoOctetAsSpecificExtendedCommunity, self, kwargs)
4246
4247
4248@_ExtendedCommunity.register_type(_ExtendedCommunity.IPV4_ADDRESS_SPECIFIC)
4249class BGPIPv4AddressSpecificExtendedCommunity(_ExtendedCommunity):
4250    _VALUE_PACK_STR = '!B4sH'  # sub type, IPv4 address, local adm
4251    _VALUE_FIELDS = ['subtype', 'ipv4_address', 'local_administrator']
4252    _TYPE = {
4253        'ascii': [
4254            'ipv4_address'
4255        ]
4256    }
4257
4258    def __init__(self, **kwargs):
4259        super(BGPIPv4AddressSpecificExtendedCommunity, self).__init__()
4260        self.do_init(BGPIPv4AddressSpecificExtendedCommunity, self, kwargs)
4261
4262    @classmethod
4263    def parse_value(cls, buf):
4264        d_ = super(BGPIPv4AddressSpecificExtendedCommunity,
4265                   cls).parse_value(buf)
4266        d_['ipv4_address'] = addrconv.ipv4.bin_to_text(d_['ipv4_address'])
4267        return d_
4268
4269    def serialize_value(self):
4270        return struct.pack(self._VALUE_PACK_STR, self.subtype,
4271                           addrconv.ipv4.text_to_bin(self.ipv4_address),
4272                           self.local_administrator)
4273
4274
4275@_ExtendedCommunity.register_type(_ExtendedCommunity.FOUR_OCTET_AS_SPECIFIC)
4276class BGPFourOctetAsSpecificExtendedCommunity(_ExtendedCommunity):
4277    _VALUE_PACK_STR = '!BIH'  # sub type, as number, local adm
4278    _VALUE_FIELDS = ['subtype', 'as_number', 'local_administrator']
4279
4280    def __init__(self, **kwargs):
4281        super(BGPFourOctetAsSpecificExtendedCommunity, self).__init__()
4282        self.do_init(BGPFourOctetAsSpecificExtendedCommunity, self, kwargs)
4283
4284
4285@_ExtendedCommunity.register_type(_ExtendedCommunity.OPAQUE)
4286class BGPOpaqueExtendedCommunity(_ExtendedCommunity):
4287    _VALUE_PACK_STR = '!B6s'
4288    _VALUE_FIELDS = ['subtype', 'opaque']
4289
4290    def __init__(self, **kwargs):
4291        super(BGPOpaqueExtendedCommunity, self).__init__()
4292        self.do_init(BGPOpaqueExtendedCommunity, self, kwargs)
4293
4294
4295@_ExtendedCommunity.register_type(_ExtendedCommunity.ENCAPSULATION)
4296class BGPEncapsulationExtendedCommunity(_ExtendedCommunity):
4297    _VALUE_PACK_STR = '!B4xH'
4298    _VALUE_FIELDS = ['subtype', 'tunnel_type']
4299
4300    # BGP Tunnel Encapsulation Attribute Tunnel Types
4301    # http://www.iana.org/assignments/bgp-parameters/bgp-parameters.xhtml#tunnel-types
4302    TUNNEL_TYPE_L2TPV3 = 1
4303    TUNNEL_TYPE_GRE = 2
4304    TUNNEL_TYPE_IP_IN_IP = 7
4305    TUNNEL_TYPE_VXLAN = 8
4306    TUNNEL_TYPE_NVGRE = 9
4307    TUNNEL_TYPE_MPLS = 10
4308    TUNNEL_TYPE_MPLS_IN_GRE = 11
4309    TUNNEL_TYPE_VXLAN_GRE = 12
4310    TUNNEL_TYPE_MPLS_IN_UDP = 13
4311
4312    def __init__(self, **kwargs):
4313        super(BGPEncapsulationExtendedCommunity, self).__init__()
4314        self.do_init(BGPEncapsulationExtendedCommunity, self, kwargs)
4315
4316    @classmethod
4317    def from_str(cls, tunnel_type):
4318        """
4319        Returns an instance identified with the given `tunnel_type`.
4320
4321        `tunnel_type` should be a str type value and corresponding to
4322        BGP Tunnel Encapsulation Attribute Tunnel Type constants name
4323        omitting `TUNNEL_TYPE_` prefix.
4324
4325        Example:
4326            - `gre` means TUNNEL_TYPE_GRE
4327            - `vxlan` means TUNNEL_TYPE_VXLAN
4328
4329        And raises AttributeError when the corresponding Tunnel Type
4330        is not found to the given `tunnel_type`.
4331        """
4332        return cls(subtype=_ExtendedCommunity.SUBTYPE_ENCAPSULATION,
4333                   tunnel_type=getattr(cls,
4334                                       'TUNNEL_TYPE_' + tunnel_type.upper()))
4335
4336
4337@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_MAC_MOBILITY)
4338class BGPEvpnMacMobilityExtendedCommunity(_ExtendedCommunity):
4339    """
4340    MAC Mobility Extended Community
4341    """
4342    #  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
4343    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4344    # | Type=0x06     | Sub-Type=0x00 |Flags(1 octet)|  Reserved=0    |
4345    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4346    # |                       Sequence Number                         |
4347    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4348    _VALUE_PACK_STR = '!BBxI'
4349    _VALUE_FIELDS = ['subtype', 'flags', 'sequence_number']
4350
4351    def __init__(self, **kwargs):
4352        super(BGPEvpnMacMobilityExtendedCommunity, self).__init__()
4353        self.do_init(BGPEvpnMacMobilityExtendedCommunity, self, kwargs)
4354
4355
4356@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ESI_LABEL)
4357class BGPEvpnEsiLabelExtendedCommunity(_ExtendedCommunity):
4358    """
4359    ESI Label Extended Community
4360    """
4361    #  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
4362    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4363    # | Type=0x06     | Sub-Type=0x01 | Flags(1 octet)|  Reserved=0   |
4364    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4365    # |  Reserved=0   |          ESI Label                            |
4366    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4367    _VALUE_PACK_STR = '!BB2x3s'
4368    _VALUE_FIELDS = ['subtype', 'flags']
4369
4370    # Classification for Flags.
4371    SINGLE_ACTIVE_BIT = 1 << 0
4372
4373    def __init__(self, label=None, mpls_label=None, vni=None, **kwargs):
4374        super(BGPEvpnEsiLabelExtendedCommunity, self).__init__()
4375        self.do_init(BGPEvpnEsiLabelExtendedCommunity, self, kwargs)
4376
4377        if label:
4378            # If binary type label field value is specified, stores it
4379            # and decodes as MPLS label and VNI.
4380            self._label = label
4381            self._mpls_label, _ = mpls.label_from_bin(label)
4382            self._vni = vxlan.vni_from_bin(label)
4383        else:
4384            # If either MPLS label or VNI is specified, stores it
4385            # and encodes into binary type label field value.
4386            self._label = self._serialize_label(mpls_label, vni)
4387            self._mpls_label = mpls_label
4388            self._vni = vni
4389
4390    def _serialize_label(self, mpls_label, vni):
4391        if mpls_label:
4392            return mpls.label_to_bin(mpls_label, is_bos=True)
4393        elif vni:
4394            return vxlan.vni_to_bin(vni)
4395        else:
4396            return b'\x00' * 3
4397
4398    @classmethod
4399    def parse_value(cls, buf):
4400        (subtype, flags,
4401         label) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
4402        return {
4403            'subtype': subtype,
4404            'flags': flags,
4405            'label': label,
4406        }
4407
4408    def serialize_value(self):
4409        return struct.pack(self._VALUE_PACK_STR, self.subtype, self.flags,
4410                           self._label)
4411
4412    @property
4413    def mpls_label(self):
4414        return self._mpls_label
4415
4416    @mpls_label.setter
4417    def mpls_label(self, mpls_label):
4418        self._label = mpls.label_to_bin(mpls_label, is_bos=True)
4419        self._mpls_label = mpls_label
4420        self._vni = None  # disables VNI
4421
4422    @property
4423    def vni(self):
4424        return self._vni
4425
4426    @vni.setter
4427    def vni(self, vni):
4428        self._label = vxlan.vni_to_bin(vni)
4429        self._mpls_label = None  # disables ESI label
4430        self._vni = vni
4431
4432
4433@_ExtendedCommunity.register_type(_ExtendedCommunity.EVPN_ES_IMPORT_RT)
4434class BGPEvpnEsImportRTExtendedCommunity(_ExtendedCommunity):
4435    """
4436    ES-Import Route Target Extended Community
4437    """
4438    #  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
4439    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4440    # | Type=0x06     | Sub-Type=0x02 |          ES-Import            |
4441    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4442    # |                     ES-Import Cont'd                          |
4443    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4444    _VALUE_PACK_STR = '!B6s'
4445    _VALUE_FIELDS = ['subtype', 'es_import']
4446    _TYPE = {
4447        'ascii': [
4448            'es_import'
4449        ]
4450    }
4451
4452    def __init__(self, **kwargs):
4453        super(BGPEvpnEsImportRTExtendedCommunity, self).__init__()
4454        self.do_init(BGPEvpnEsImportRTExtendedCommunity, self, kwargs)
4455
4456    @classmethod
4457    def parse_value(cls, buf):
4458        (subtype, es_import) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
4459        return {
4460            'subtype': subtype,
4461            'es_import': addrconv.mac.bin_to_text(es_import),
4462        }
4463
4464    def serialize_value(self):
4465        return struct.pack(self._VALUE_PACK_STR, self.subtype,
4466                           addrconv.mac.text_to_bin(self.es_import))
4467
4468
4469@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_TRAFFIC_RATE)
4470class BGPFlowSpecTrafficRateCommunity(_ExtendedCommunity):
4471    """
4472    Flow Specification Traffic Filtering Actions for Traffic Rate.
4473
4474    ========================== ===============================================
4475    Attribute                  Description
4476    ========================== ===============================================
4477    as_number                  Autonomous System number.
4478    rate_info                  rate information.
4479    ========================== ===============================================
4480    """
4481    # 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
4482    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4483    # | Type=0x80     | Sub-Type=0x06 |           AS number           |
4484    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4485    # |                      Rate information                         |
4486    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4487    _VALUE_PACK_STR = '!BHf'
4488    _VALUE_FIELDS = ['subtype', 'as_number', 'rate_info']
4489    ACTION_NAME = 'traffic_rate'
4490
4491    def __init__(self, **kwargs):
4492        super(BGPFlowSpecTrafficRateCommunity, self).__init__()
4493        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_RATE
4494        self.do_init(BGPFlowSpecTrafficRateCommunity, self, kwargs)
4495
4496    @classmethod
4497    def parse_value(cls, buf):
4498        (subtype, as_number,
4499         rate_info) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
4500        return {
4501            'subtype': subtype,
4502            'as_number': as_number,
4503            'rate_info': rate_info,
4504        }
4505
4506    def serialize_value(self):
4507        return struct.pack(self._VALUE_PACK_STR, self.subtype,
4508                           self.as_number, self.rate_info)
4509
4510
4511@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_TRAFFIC_ACTION)
4512class BGPFlowSpecTrafficActionCommunity(_ExtendedCommunity):
4513    """
4514    Flow Specification Traffic Filtering Actions for Traffic Action.
4515
4516    ========================== ===============================================
4517    Attribute                  Description
4518    ========================== ===============================================
4519    action                     Apply action.
4520                               The supported action are
4521                               ``SAMPLE`` and ``TERMINAL``.
4522    ========================== ===============================================
4523    """
4524    # 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
4525    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4526    # | Type=0x80     | Sub-Type=0x07 |          Traffic-action       |
4527    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4528    # |                     Traffic-action Cont'd                     |
4529    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4530
4531    # Traffic-action format
4532    #  40  41  42  43  44  45  46  47
4533    # +---+---+---+---+---+---+---+---+
4534    # |        reserved       | S | T |
4535    # +---+---+---+---+---+---+---+---+
4536
4537    _VALUE_PACK_STR = '!B5xB'
4538    _VALUE_FIELDS = ['subtype', 'action']
4539    ACTION_NAME = 'traffic_action'
4540    SAMPLE = 1 << 1
4541    TERMINAL = 1 << 0
4542
4543    def __init__(self, **kwargs):
4544        super(BGPFlowSpecTrafficActionCommunity, self).__init__()
4545        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_ACTION
4546        self.do_init(BGPFlowSpecTrafficActionCommunity, self, kwargs)
4547
4548
4549@_ExtendedCommunity.register_type(_ExtendedCommunity.FLOWSPEC_REDIRECT)
4550class BGPFlowSpecRedirectCommunity(BGPTwoOctetAsSpecificExtendedCommunity):
4551    """
4552    Flow Specification Traffic Filtering Actions for Redirect.
4553
4554    ========================== ===============================================
4555    Attribute                  Description
4556    ========================== ===============================================
4557    as_number                  Autonomous System number.
4558    local_administrator        Local Administrator.
4559    ========================== ===============================================
4560    """
4561    ACTION_NAME = 'redirect'
4562
4563    def __init__(self, **kwargs):
4564        super(BGPTwoOctetAsSpecificExtendedCommunity, self).__init__()
4565        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_REDIRECT
4566        self.do_init(BGPTwoOctetAsSpecificExtendedCommunity, self, kwargs)
4567
4568
4569@_ExtendedCommunity.register_type(
4570    _ExtendedCommunity.FLOWSPEC_TRAFFIC_REMARKING)
4571class BGPFlowSpecTrafficMarkingCommunity(_ExtendedCommunity):
4572    """
4573    Flow Specification Traffic Filtering Actions for Traffic Marking.
4574
4575    ========================== ===============================================
4576    Attribute                  Description
4577    ========================== ===============================================
4578    dscp                       Differentiated Services Code Point.
4579    ========================== ===============================================
4580    """
4581    # 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
4582    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4583    # | Type=0x80     | Sub-Type=0x09 |           Reserved=0          |
4584    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4585    # |                    Reserved=0                 |      Dscp     |
4586    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4587    _VALUE_PACK_STR = '!B5xB'
4588    _VALUE_FIELDS = ['subtype', 'dscp']
4589    ACTION_NAME = 'traffic_marking'
4590
4591    def __init__(self, **kwargs):
4592        super(BGPFlowSpecTrafficMarkingCommunity, self).__init__()
4593        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TRAFFIC_REMARKING
4594        self.do_init(BGPFlowSpecTrafficMarkingCommunity, self, kwargs)
4595
4596    @classmethod
4597    def parse_value(cls, buf):
4598        (subtype, dscp) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
4599        return {
4600            'subtype': subtype,
4601            'dscp': dscp,
4602        }
4603
4604    def serialize_value(self):
4605        return struct.pack(self._VALUE_PACK_STR, self.subtype, self.dscp)
4606
4607
4608# TODO
4609# Implement "Redirect-IPv6" [draft-ietf-idr-flow-spec-v6-08]
4610
4611
4612@_ExtendedCommunity.register_type(
4613    _ExtendedCommunity.FLOWSPEC_VLAN_ACTION)
4614class BGPFlowSpecVlanActionCommunity(_ExtendedCommunity):
4615    """
4616    Flow Specification Vlan Actions.
4617
4618    ========= ===============================================
4619    Attribute Description
4620    ========= ===============================================
4621    actions_1 Bit representation of actions.
4622              Supported actions are
4623              ``POP``, ``PUSH``, ``SWAP``, ``REWRITE_INNER``, ``REWRITE_OUTER``.
4624    actions_2 Same as ``actions_1``.
4625    vlan_1    VLAN ID used by ``actions_1``.
4626    cos_1     Class of Service used by ``actions_1``.
4627    vlan_2    VLAN ID used by ``actions_2``.
4628    cos_2     Class of Service used by ``actions_2``.
4629    ========= ===============================================
4630    """
4631    # 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
4632    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4633    # | Type=0x08     | Sub-Type=0x0a |PO1|PU1|SW1|RT1|RO1|...|PO2|...|
4634    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4635    # |       VLAN ID1      |  COS1 |0|       VLAN ID2      |  COS2 |0|
4636    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4637    _VALUE_PACK_STR = '!BBBHH'
4638    _VALUE_FIELDS = [
4639        'subtype',
4640        'actions_1',
4641        'actions_2',
4642        'vlan_1',
4643        'vlan_2',
4644        'cos_1',
4645        'cos_2']
4646    ACTION_NAME = 'vlan_action'
4647    _COS_MASK = 0x07
4648
4649    POP = 1 << 7
4650    PUSH = 1 << 6
4651    SWAP = 1 << 5
4652    REWRITE_INNER = 1 << 4
4653    REWRITE_OUTER = 1 << 3
4654
4655    def __init__(self, **kwargs):
4656        super(BGPFlowSpecVlanActionCommunity, self).__init__()
4657        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_VLAN_ACTION
4658        self.do_init(BGPFlowSpecVlanActionCommunity, self, kwargs)
4659
4660    @classmethod
4661    def parse_value(cls, buf):
4662        (subtype, actions_1, actions_2,
4663         vlan_cos_1, vlan_cos_2) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
4664
4665        return {
4666            'subtype': subtype,
4667            'actions_1': actions_1,
4668            'vlan_1': int(vlan_cos_1 >> 4),
4669            'cos_1': int((vlan_cos_1 >> 1) & cls._COS_MASK),
4670            'actions_2': actions_2,
4671            'vlan_2': int(vlan_cos_2 >> 4),
4672            'cos_2': int((vlan_cos_2 >> 1) & cls._COS_MASK)
4673        }
4674
4675    def serialize_value(self):
4676        return struct.pack(
4677            self._VALUE_PACK_STR,
4678            self.subtype,
4679            self.actions_1,
4680            self.actions_2,
4681            (self.vlan_1 << 4) + (self.cos_1 << 1),
4682            (self.vlan_2 << 4) + (self.cos_2 << 1),
4683        )
4684
4685
4686@_ExtendedCommunity.register_type(
4687    _ExtendedCommunity.FLOWSPEC_TPID_ACTION)
4688class BGPFlowSpecTPIDActionCommunity(_ExtendedCommunity):
4689    """
4690    Flow Specification TPID Actions.
4691
4692    ========= =========================================================
4693    Attribute Description
4694    ========= =========================================================
4695    actions   Bit representation of actions.
4696              Supported actions are
4697              ``TI(inner TPID action)`` and ``TO(outer TPID action)``.
4698    tpid_1    TPID used by ``TI``.
4699    tpid_2    TPID used by ``TO``.
4700    ========= =========================================================
4701    """
4702    # 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
4703    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4704    # | Type=0x08     | Sub-Type=0x0b   |TI|TO|      Reserved=0       |
4705    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4706    # |             TPID1               |             TPID2           |
4707    # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4708    _VALUE_PACK_STR = '!BHHH'
4709    _VALUE_FIELDS = ['subtype', 'actions', 'tpid_1', 'tpid_2']
4710    ACTION_NAME = 'tpid_action'
4711
4712    TI = 1 << 15
4713    TO = 1 << 14
4714
4715    def __init__(self, **kwargs):
4716        super(BGPFlowSpecTPIDActionCommunity, self).__init__()
4717        kwargs['subtype'] = self.SUBTYPE_FLOWSPEC_TPID_ACTION
4718        self.do_init(BGPFlowSpecTPIDActionCommunity, self, kwargs)
4719
4720    @classmethod
4721    def parse_value(cls, buf):
4722        (subtype, actions, tpid_1, tpid_2) = struct.unpack_from(
4723            cls._VALUE_PACK_STR, buf)
4724
4725        return {
4726            'subtype': subtype,
4727            'actions': actions,
4728            'tpid_1': tpid_1,
4729            'tpid_2': tpid_2,
4730        }
4731
4732    def serialize_value(self):
4733        return struct.pack(
4734            self._VALUE_PACK_STR,
4735            self.subtype,
4736            self.actions,
4737            self.tpid_1,
4738            self.tpid_2,
4739        )
4740
4741
4742@_ExtendedCommunity.register_unknown_type()
4743class BGPUnknownExtendedCommunity(_ExtendedCommunity):
4744    _VALUE_PACK_STR = '!7s'  # opaque value
4745
4746    def __init__(self, type_, **kwargs):
4747        super(BGPUnknownExtendedCommunity, self).__init__(type_=type_)
4748        self.do_init(BGPUnknownExtendedCommunity, self, kwargs, type_=type_)
4749
4750
4751@_PathAttribute.register_type(BGP_ATTR_TYPE_MP_REACH_NLRI)
4752class BGPPathAttributeMpReachNLRI(_PathAttribute):
4753    _VALUE_PACK_STR = '!HBB'  # afi, safi, next_hop_len
4754    _VALUE_PACK_SIZE = struct.calcsize(_VALUE_PACK_STR)
4755    _RD_LENGTH = 8
4756    _RESERVED_LENGTH = 1
4757    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
4758    _class_suffixes = ['AddrPrefix']
4759    _opt_attributes = ['next_hop']
4760    _TYPE = {
4761        'ascii': [
4762            'next_hop'
4763        ]
4764    }
4765
4766    def __init__(self, afi, safi, next_hop, nlri,
4767                 flags=0, type_=None, length=None):
4768        super(BGPPathAttributeMpReachNLRI, self).__init__(
4769            flags=flags, type_=type_, length=length)
4770        self.afi = afi
4771        self.safi = safi
4772        if not isinstance(next_hop, (list, tuple)):
4773            next_hop = [next_hop]
4774        for n in next_hop:
4775            if not ip.valid_ipv4(n) and not ip.valid_ipv6(n):
4776                raise ValueError('Invalid address for next_hop: %s' % n)
4777        # Note: For the backward compatibility, stores the first next_hop
4778        # address and all next_hop addresses separately.
4779        if next_hop:
4780            self._next_hop = next_hop[0]
4781        else:
4782            self._next_hop = None
4783        self._next_hop_list = next_hop
4784        self.nlri = nlri
4785        addr_cls = _get_addr_class(afi, safi)
4786        for i in nlri:
4787            if not isinstance(i, addr_cls):
4788                raise ValueError('Invalid NRLI class for afi=%d and safi=%d'
4789                                 % (self.afi, self.safi))
4790
4791    @staticmethod
4792    def split_bin_with_len(buf, unit_len):
4793        f = io.BytesIO(buf)
4794        return [f.read(unit_len) for _ in range(0, len(buf), unit_len)]
4795
4796    @classmethod
4797    def parse_next_hop_ipv4(cls, buf, unit_len):
4798        next_hop = []
4799        for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
4800            next_hop.append(addrconv.ipv4.bin_to_text(next_hop_bin[-4:]))
4801        return next_hop
4802
4803    @classmethod
4804    def parse_next_hop_ipv6(cls, buf, unit_len):
4805        next_hop = []
4806        for next_hop_bin in cls.split_bin_with_len(buf, unit_len):
4807            next_hop.append(addrconv.ipv6.bin_to_text(next_hop_bin[-16:]))
4808        return next_hop
4809
4810    @classmethod
4811    def parse_value(cls, buf):
4812        (afi, safi, next_hop_len,) = struct.unpack_from(
4813            cls._VALUE_PACK_STR, six.binary_type(buf))
4814        rest = buf[cls._VALUE_PACK_SIZE:]
4815
4816        next_hop_bin = rest[:next_hop_len]
4817        rest = rest[next_hop_len:]
4818        reserved = rest[:cls._RESERVED_LENGTH]
4819        assert reserved == b'\0'
4820
4821        nlri_bin = rest[cls._RESERVED_LENGTH:]
4822        addr_cls = _get_addr_class(afi, safi)
4823        nlri = []
4824        while nlri_bin:
4825            n, nlri_bin = addr_cls.parser(nlri_bin)
4826            nlri.append(n)
4827
4828        rf = RouteFamily(afi, safi)
4829        if rf == RF_IPv4_VPN:
4830            next_hop = cls.parse_next_hop_ipv4(next_hop_bin,
4831                                               cls._RD_LENGTH + 4)
4832            next_hop_len -= cls._RD_LENGTH * len(next_hop)
4833        elif rf == RF_IPv6_VPN:
4834            next_hop = cls.parse_next_hop_ipv6(next_hop_bin,
4835                                               cls._RD_LENGTH + 16)
4836            next_hop_len -= cls._RD_LENGTH * len(next_hop)
4837        elif (afi == addr_family.IP
4838              or (rf == RF_L2_EVPN and next_hop_len < 16)):
4839            next_hop = cls.parse_next_hop_ipv4(next_hop_bin, 4)
4840        elif (afi == addr_family.IP6
4841              or (rf == RF_L2_EVPN and next_hop_len >= 16)):
4842            next_hop = cls.parse_next_hop_ipv6(next_hop_bin, 16)
4843        elif rf == RF_L2VPN_FLOWSPEC:
4844            next_hop = []
4845        else:
4846            raise ValueError('Invalid address family: afi=%d, safi=%d'
4847                             % (afi, safi))
4848
4849        return {
4850            'afi': afi,
4851            'safi': safi,
4852            'next_hop': next_hop,
4853            'nlri': nlri,
4854        }
4855
4856    def serialize_next_hop(self):
4857        buf = bytearray()
4858        for next_hop in self.next_hop_list:
4859            if self.afi == addr_family.IP6:
4860                next_hop = str(netaddr.IPAddress(next_hop).ipv6())
4861            next_hop_bin = ip.text_to_bin(next_hop)
4862            if RouteFamily(self.afi, self.safi) in (RF_IPv4_VPN, RF_IPv6_VPN):
4863                # Empty label stack(RD=0:0) + IP address
4864                next_hop_bin = b'\x00' * self._RD_LENGTH + next_hop_bin
4865            buf += next_hop_bin
4866
4867        return buf
4868
4869    def serialize_value(self):
4870        next_hop_bin = self.serialize_next_hop()
4871
4872        # fixup
4873        next_hop_len = len(next_hop_bin)
4874
4875        buf = bytearray()
4876        msg_pack_into(self._VALUE_PACK_STR, buf, 0,
4877                      self.afi, self.safi, next_hop_len)
4878        buf += next_hop_bin
4879        buf += b'\0'  # reserved
4880
4881        nlri_bin = bytearray()
4882        for n in self.nlri:
4883            nlri_bin += n.serialize()
4884        buf += nlri_bin
4885
4886        return buf
4887
4888    @property
4889    def next_hop(self):
4890        return self._next_hop
4891
4892    @next_hop.setter
4893    def next_hop(self, addr):
4894        if not ip.valid_ipv4(addr) and not ip.valid_ipv6(addr):
4895            raise ValueError('Invalid address for next_hop: %s' % addr)
4896        self._next_hop = addr
4897        self.next_hop_list[0] = addr
4898
4899    @property
4900    def next_hop_list(self):
4901        return self._next_hop_list
4902
4903    @next_hop_list.setter
4904    def next_hop_list(self, addr_list):
4905        if not isinstance(addr_list, (list, tuple)):
4906            addr_list = [addr_list]
4907        for addr in addr_list:
4908            if not ip.valid_ipv4(addr) and not ip.valid_ipv6(addr):
4909                raise ValueError('Invalid address for next_hop: %s' % addr)
4910        self._next_hop = addr_list[0]
4911        self._next_hop_list = addr_list
4912
4913    @property
4914    def route_family(self):
4915        return _rf_map[(self.afi, self.safi)]
4916
4917
4918@_PathAttribute.register_type(BGP_ATTR_TYPE_MP_UNREACH_NLRI)
4919class BGPPathAttributeMpUnreachNLRI(_PathAttribute):
4920    _VALUE_PACK_STR = '!HB'  # afi, safi
4921    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL
4922    _class_suffixes = ['AddrPrefix']
4923
4924    def __init__(self, afi, safi, withdrawn_routes,
4925                 flags=0, type_=None, length=None):
4926        super(BGPPathAttributeMpUnreachNLRI, self).__init__(
4927            flags=flags, type_=type_, length=length)
4928        self.afi = afi
4929        self.safi = safi
4930        self.withdrawn_routes = withdrawn_routes
4931        addr_cls = _get_addr_class(afi, safi)
4932        for i in withdrawn_routes:
4933            if not isinstance(i, addr_cls):
4934                raise ValueError('Invalid NRLI class for afi=%d and safi=%d'
4935                                 % (self.afi, self.safi))
4936
4937    @classmethod
4938    def parse_value(cls, buf):
4939        (afi, safi,) = struct.unpack_from(
4940            cls._VALUE_PACK_STR, six.binary_type(buf))
4941
4942        nlri_bin = buf[struct.calcsize(cls._VALUE_PACK_STR):]
4943        addr_cls = _get_addr_class(afi, safi)
4944        nlri = []
4945        while nlri_bin:
4946            n, nlri_bin = addr_cls.parser(nlri_bin)
4947            nlri.append(n)
4948
4949        return {
4950            'afi': afi,
4951            'safi': safi,
4952            'withdrawn_routes': nlri,
4953        }
4954
4955    def serialize_value(self):
4956        buf = bytearray()
4957        msg_pack_into(self._VALUE_PACK_STR, buf, 0, self.afi, self.safi)
4958
4959        nlri_bin = bytearray()
4960        for n in self.withdrawn_routes:
4961            nlri_bin += n.serialize()
4962        buf += nlri_bin
4963
4964        return buf
4965
4966    @property
4967    def route_family(self):
4968        return _rf_map[(self.afi, self.safi)]
4969
4970
4971@_PathAttribute.register_type(BGP_ATTR_TYEP_PMSI_TUNNEL_ATTRIBUTE)
4972class BGPPathAttributePmsiTunnel(_PathAttribute):
4973    """
4974    P-Multicast Service Interface Tunnel (PMSI Tunnel) attribute
4975    """
4976
4977    # pmsi_flags, tunnel_type, mpls_label
4978    _VALUE_PACK_STR = '!BB3s'
4979    _PACK_STR_SIZE = struct.calcsize(_VALUE_PACK_STR)
4980    _ATTR_FLAGS = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANSITIVE
4981
4982    # RFC 6514
4983    # +--------------------------------+
4984    # |  Flags (1 octet)               |
4985    # +--------------------------------+
4986    # |  Tunnel Type (1 octets)        |
4987    # +--------------------------------+
4988    # |  MPLS Label (3 octets)         |
4989    # +--------------------------------+
4990    # |  Tunnel Identifier (variable)  |
4991    # +--------------------------------+
4992
4993    # The Flags field has the following format:
4994    #  0 1 2 3 4 5 6 7
4995    # +-+-+-+-+-+-+-+-+
4996    # |  reserved   |L|
4997    # +-+-+-+-+-+-+-+-+
4998    # `L` refers to the Leaf Information Required.
4999
5000    # Current, Tunnel Type supports following.
5001    # + 0 - No tunnel information present
5002    # + 6 - Ingress Replication
5003    TYPE_NO_TUNNEL_INFORMATION_PRESENT = 0
5004    TYPE_INGRESS_REPLICATION = 6
5005
5006    # TODO:
5007    # The following Tunnel Type are not supported.
5008    # Therefore, we will need to support in the future.
5009    # + 1 - RSVP-TE P2MP LSP
5010    # + 2 - mLDP P2MP LSP
5011    # + 3 - PIM-SSM Tree
5012    # + 4 - PIM-SM Tree
5013    # + 5 - BIDIR-PIM Tree
5014    # + 7 - mLDP MP2MP LSP
5015
5016    def __init__(self, pmsi_flags, tunnel_type,
5017                 mpls_label=None, label=None, vni=None, tunnel_id=None,
5018                 flags=0, type_=None, length=None):
5019        super(BGPPathAttributePmsiTunnel, self).__init__(flags=flags,
5020                                                         type_=type_,
5021                                                         length=length)
5022        self.pmsi_flags = pmsi_flags
5023        self.tunnel_type = tunnel_type
5024        self.tunnel_id = tunnel_id
5025
5026        if label:
5027            # If binary type label field value is specified, stores it
5028            # and decodes as MPLS label and VNI.
5029            self._label = label
5030            self._mpls_label, _ = mpls.label_from_bin(label)
5031            self._vni = vxlan.vni_from_bin(label)
5032        else:
5033            # If either MPLS label or VNI is specified, stores it
5034            # and encodes into binary type label field value.
5035            self._label = self._serialize_label(mpls_label, vni)
5036            self._mpls_label = mpls_label
5037            self._vni = vni
5038
5039    @classmethod
5040    def parse_value(cls, buf):
5041        (pmsi_flags,
5042         tunnel_type,
5043         label) = struct.unpack_from(cls._VALUE_PACK_STR, buf)
5044        value = buf[cls._PACK_STR_SIZE:]
5045
5046        return {
5047            'pmsi_flags': pmsi_flags,
5048            'tunnel_type': tunnel_type,
5049            'label': label,
5050            'tunnel_id': _PmsiTunnelId.parse(tunnel_type, value)
5051        }
5052
5053    def serialize_value(self):
5054        buf = bytearray()
5055        msg_pack_into(self._VALUE_PACK_STR, buf, 0,
5056                      self.pmsi_flags, self.tunnel_type, self._label)
5057
5058        if self.tunnel_id is not None:
5059            buf += self.tunnel_id.serialize()
5060
5061        return buf
5062
5063    def _serialize_label(self, mpls_label, vni):
5064        if mpls_label:
5065            return mpls.label_to_bin(mpls_label, is_bos=True)
5066        elif vni:
5067            return vxlan.vni_to_bin(vni)
5068        else:
5069            return b'\x00' * 3
5070
5071    @property
5072    def mpls_label(self):
5073        return self._mpls_label
5074
5075    @mpls_label.setter
5076    def mpls_label(self, mpls_label):
5077        self._label = mpls.label_to_bin(mpls_label, is_bos=True)
5078        self._mpls_label = mpls_label
5079        self._vni = None  # disables VNI
5080
5081    @property
5082    def vni(self):
5083        return self._vni
5084
5085    @vni.setter
5086    def vni(self, vni):
5087        self._label = vxlan.vni_to_bin(vni)
5088        self._mpls_label = None  # disables MPLS label
5089        self._vni = vni
5090
5091    @classmethod
5092    def from_jsondict(cls, dict_, decode_string=base64.b64decode,
5093                      **additional_args):
5094        if isinstance(dict_['tunnel_id'], dict):
5095            tunnel_id = dict_.pop('tunnel_id')
5096            ins = super(BGPPathAttributePmsiTunnel,
5097                        cls).from_jsondict(dict_,
5098                                           decode_string,
5099                                           **additional_args)
5100
5101            mod = import_module(cls.__module__)
5102
5103            for key, value in tunnel_id.items():
5104                tunnel_id_cls = getattr(mod, key)
5105                ins.tunnel_id = tunnel_id_cls.from_jsondict(value,
5106                                                            decode_string,
5107                                                            **additional_args)
5108        else:
5109            ins = super(BGPPathAttributePmsiTunnel,
5110                        cls).from_jsondict(dict_,
5111                                           decode_string,
5112                                           **additional_args)
5113
5114        return ins
5115
5116
5117class _PmsiTunnelId(StringifyMixin, TypeDisp):
5118
5119    @classmethod
5120    def parse(cls, tunnel_type, buf):
5121        subcls = cls._lookup_type(tunnel_type)
5122        return subcls.parser(buf)
5123
5124
5125@_PmsiTunnelId.register_unknown_type()
5126class PmsiTunnelIdUnknown(_PmsiTunnelId):
5127    """
5128    Unknown route type specific _PmsiTunnelId
5129    """
5130
5131    def __init__(self, value):
5132        super(PmsiTunnelIdUnknown, self).__init__()
5133        self.value = value
5134
5135    @classmethod
5136    def parser(cls, buf):
5137        return cls(value=buf)
5138
5139    def serialize(self):
5140        return self.value
5141
5142
5143@_PmsiTunnelId.register_type(
5144    BGPPathAttributePmsiTunnel.TYPE_NO_TUNNEL_INFORMATION_PRESENT)
5145class _PmsiTunnelIdNoInformationPresent(_PmsiTunnelId):
5146
5147    @classmethod
5148    def parser(cls, buf):
5149        return None
5150
5151
5152@_PmsiTunnelId.register_type(
5153    BGPPathAttributePmsiTunnel.TYPE_INGRESS_REPLICATION)
5154class PmsiTunnelIdIngressReplication(_PmsiTunnelId):
5155    # tunnel_endpoint_ip
5156    _VALUE_PACK_STR = '!%ds'
5157    _TYPE = {
5158        'ascii': [
5159            'tunnel_endpoint_ip'
5160        ]
5161    }
5162
5163    def __init__(self, tunnel_endpoint_ip):
5164        super(PmsiTunnelIdIngressReplication, self).__init__()
5165        self.tunnel_endpoint_ip = tunnel_endpoint_ip
5166
5167    @classmethod
5168    def parser(cls, buf):
5169        (tunnel_endpoint_ip, ) = struct.unpack_from(
5170            cls._VALUE_PACK_STR % len(buf),
5171            six.binary_type(buf))
5172        return cls(tunnel_endpoint_ip=ip.bin_to_text(tunnel_endpoint_ip))
5173
5174    def serialize(self):
5175        ip_bin = ip.text_to_bin(self.tunnel_endpoint_ip)
5176        return struct.pack(self._VALUE_PACK_STR % len(ip_bin),
5177                           ip.text_to_bin(self.tunnel_endpoint_ip))
5178
5179
5180class BGPNLRI(IPAddrPrefix):
5181    pass
5182
5183
5184class BGPMessage(packet_base.PacketBase, TypeDisp):
5185    """Base class for BGP-4 messages.
5186
5187    An instance has the following attributes at least.
5188    Most of them are same to the on-wire counterparts but in host byte
5189    order.
5190    __init__ takes the corresponding args in this order.
5191
5192    ========================== ===============================================
5193    Attribute                  Description
5194    ========================== ===============================================
5195    marker                     Marker field.  Ignored when encoding.
5196    len                        Length field.  Ignored when encoding.
5197    type                       Type field.  one of ``BGP_MSG_*`` constants.
5198    ========================== ===============================================
5199    """
5200
5201    _HDR_PACK_STR = '!16sHB'  # marker, len, type
5202    _HDR_LEN = struct.calcsize(_HDR_PACK_STR)
5203    _class_prefixes = ['BGP']
5204
5205    def __init__(self, marker=None, len_=None, type_=None):
5206        super(BGPMessage, self).__init__()
5207        if marker is None:
5208            self._marker = _MARKER
5209        else:
5210            self._marker = marker
5211        self.len = len_
5212        if type_ is None:
5213            type_ = self._rev_lookup_type(self.__class__)
5214        self.type = type_
5215
5216    @classmethod
5217    def parser(cls, buf):
5218        if len(buf) < cls._HDR_LEN:
5219            raise stream_parser.StreamParser.TooSmallException(
5220                '%d < %d' % (len(buf), cls._HDR_LEN))
5221        (marker, len_, type_) = struct.unpack_from(cls._HDR_PACK_STR,
5222                                                   six.binary_type(buf))
5223        msglen = len_
5224        if len(buf) < msglen:
5225            raise stream_parser.StreamParser.TooSmallException(
5226                '%d < %d' % (len(buf), msglen))
5227        binmsg = buf[cls._HDR_LEN:msglen]
5228        rest = buf[msglen:]
5229        subcls = cls._lookup_type(type_)
5230        kwargs = subcls.parser(binmsg)
5231        return subcls(marker=marker, len_=len_, type_=type_,
5232                      **kwargs), cls, rest
5233
5234    def serialize(self, payload=None, prev=None):
5235        # fixup
5236        self._marker = _MARKER
5237        tail = self.serialize_tail()
5238        self.len = self._HDR_LEN + len(tail)
5239
5240        hdr = bytearray(struct.pack(self._HDR_PACK_STR, self._marker,
5241                                    self.len, self.type))
5242        return hdr + tail
5243
5244    def __len__(self):
5245        # XXX destructive
5246        buf = self.serialize()
5247        return len(buf)
5248
5249
5250@BGPMessage.register_type(BGP_MSG_OPEN)
5251class BGPOpen(BGPMessage):
5252    """BGP-4 OPEN Message encoder/decoder class.
5253
5254    An instance has the following attributes at least.
5255    Most of them are same to the on-wire counterparts but in host byte
5256    order.
5257    __init__ takes the corresponding args in this order.
5258
5259    ========================== ===============================================
5260    Attribute                  Description
5261    ========================== ===============================================
5262    marker                     Marker field.  Ignored when encoding.
5263    len                        Length field.  Ignored when encoding.
5264    type                       Type field.
5265    version                    Version field.
5266    my_as                      My Autonomous System field.
5267                               2 octet unsigned integer.
5268    hold_time                  Hold Time field.
5269                               2 octet unsigned integer.
5270    bgp_identifier             BGP Identifier field.
5271                               An IPv4 address.
5272                               For example, '192.0.2.1'
5273    opt_param_len              Optional Parameters Length field.
5274                               Ignored when encoding.
5275    opt_param                  Optional Parameters field.
5276                               A list of BGPOptParam instances.
5277                               The default is [].
5278    ========================== ===============================================
5279    """
5280
5281    _PACK_STR = '!BHH4sB'
5282    _MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR)
5283    _TYPE = {
5284        'ascii': [
5285            'bgp_identifier'
5286        ]
5287    }
5288
5289    def __init__(self, my_as, bgp_identifier, type_=BGP_MSG_OPEN,
5290                 opt_param_len=0, opt_param=None,
5291                 version=_VERSION, hold_time=0, len_=None, marker=None):
5292        opt_param = opt_param if opt_param else []
5293        super(BGPOpen, self).__init__(marker=marker, len_=len_, type_=type_)
5294        self.version = version
5295        self.my_as = my_as
5296        self.bgp_identifier = bgp_identifier
5297        self.hold_time = hold_time
5298        self.opt_param_len = opt_param_len
5299        self.opt_param = opt_param
5300
5301    @property
5302    def opt_param_cap_map(self):
5303        cap_map = {}
5304        for param in self.opt_param:
5305            if param.type == BGP_OPT_CAPABILITY:
5306                cap_map[param.cap_code] = param
5307        return cap_map
5308
5309    def get_opt_param_cap(self, cap_code):
5310        return self.opt_param_cap_map.get(cap_code)
5311
5312    @classmethod
5313    def parser(cls, buf):
5314        (version,
5315         my_as,
5316         hold_time,
5317         bgp_identifier,
5318         opt_param_len) = struct.unpack_from(cls._PACK_STR,
5319                                             six.binary_type(buf))
5320        rest = buf[struct.calcsize(cls._PACK_STR):]
5321        binopts = rest[:opt_param_len]
5322        opt_param = []
5323        while binopts:
5324            opt, binopts = _OptParam.parser(binopts)
5325            opt_param.extend(opt)
5326        return {
5327            "version": version,
5328            "my_as": my_as,
5329            "hold_time": hold_time,
5330            "bgp_identifier": addrconv.ipv4.bin_to_text(bgp_identifier),
5331            "opt_param_len": opt_param_len,
5332            "opt_param": opt_param,
5333        }
5334
5335    def serialize_tail(self):
5336        # fixup
5337        self.version = _VERSION
5338        binopts = bytearray()
5339        for opt in self.opt_param:
5340            binopts += opt.serialize()
5341        self.opt_param_len = len(binopts)
5342
5343        msg = bytearray(struct.pack(self._PACK_STR,
5344                                    self.version,
5345                                    self.my_as,
5346                                    self.hold_time,
5347                                    addrconv.ipv4.text_to_bin(
5348                                        self.bgp_identifier),
5349                                    self.opt_param_len))
5350        msg += binopts
5351        return msg
5352
5353
5354@BGPMessage.register_type(BGP_MSG_UPDATE)
5355class BGPUpdate(BGPMessage):
5356    """BGP-4 UPDATE Message encoder/decoder class.
5357
5358    An instance has the following attributes at least.
5359    Most of them are same to the on-wire counterparts but in host byte
5360    order.
5361    __init__ takes the corresponding args in this order.
5362
5363    .. tabularcolumns:: |l|L|
5364
5365    ========================== ===============================================
5366    Attribute                  Description
5367    ========================== ===============================================
5368    marker                     Marker field.  Ignored when encoding.
5369    len                        Length field.  Ignored when encoding.
5370    type                       Type field.
5371    withdrawn_routes_len       Withdrawn Routes Length field.
5372                               Ignored when encoding.
5373    withdrawn_routes           Withdrawn Routes field.
5374                               A list of BGPWithdrawnRoute instances.
5375                               The default is [].
5376    total_path_attribute_len   Total Path Attribute Length field.
5377                               Ignored when encoding.
5378    path_attributes            Path Attributes field.
5379                               A list of BGPPathAttribute instances.
5380                               The default is [].
5381    nlri                       Network Layer Reachability Information field.
5382                               A list of BGPNLRI instances.
5383                               The default is [].
5384    ========================== ===============================================
5385    """
5386
5387    _MIN_LEN = BGPMessage._HDR_LEN
5388
5389    def __init__(self, type_=BGP_MSG_UPDATE,
5390                 withdrawn_routes_len=None,
5391                 withdrawn_routes=None,
5392                 total_path_attribute_len=None,
5393                 path_attributes=None,
5394                 nlri=None,
5395                 len_=None, marker=None):
5396        withdrawn_routes = withdrawn_routes if withdrawn_routes else []
5397        path_attributes = path_attributes if path_attributes else []
5398        nlri = nlri if nlri else []
5399        super(BGPUpdate, self).__init__(marker=marker, len_=len_, type_=type_)
5400        self.withdrawn_routes_len = withdrawn_routes_len
5401        self.withdrawn_routes = withdrawn_routes
5402        self.total_path_attribute_len = total_path_attribute_len
5403        self.path_attributes = path_attributes
5404        self.nlri = nlri
5405
5406    @property
5407    def pathattr_map(self):
5408        passattr_map = {}
5409        for attr in self.path_attributes:
5410            passattr_map[attr.type] = attr
5411        return passattr_map
5412
5413    def get_path_attr(self, attr_name):
5414        return self.pathattr_map.get(attr_name)
5415
5416    @classmethod
5417    def parser(cls, buf):
5418        offset = 0
5419        buf = six.binary_type(buf)
5420        (withdrawn_routes_len,) = struct.unpack_from('!H', buf, offset)
5421        binroutes = buf[offset + 2:
5422                        offset + 2 + withdrawn_routes_len]
5423        offset += 2 + withdrawn_routes_len
5424        (total_path_attribute_len,) = struct.unpack_from('!H', buf, offset)
5425        binpathattrs = buf[offset + 2:
5426                           offset + 2 + total_path_attribute_len]
5427        binnlri = buf[offset + 2 + total_path_attribute_len:]
5428        withdrawn_routes = []
5429        while binroutes:
5430            r, binroutes = BGPWithdrawnRoute.parser(binroutes)
5431            withdrawn_routes.append(r)
5432        path_attributes = []
5433        while binpathattrs:
5434            pa, binpathattrs = _PathAttribute.parser(binpathattrs)
5435            path_attributes.append(pa)
5436        offset += 2 + total_path_attribute_len
5437        nlri = []
5438        while binnlri:
5439            n, binnlri = BGPNLRI.parser(binnlri)
5440            nlri.append(n)
5441        return {
5442            "withdrawn_routes_len": withdrawn_routes_len,
5443            "withdrawn_routes": withdrawn_routes,
5444            "total_path_attribute_len": total_path_attribute_len,
5445            "path_attributes": path_attributes,
5446            "nlri": nlri,
5447        }
5448
5449    def serialize_tail(self):
5450        # fixup
5451        binroutes = bytearray()
5452        for r in self.withdrawn_routes:
5453            binroutes += r.serialize()
5454        self.withdrawn_routes_len = len(binroutes)
5455        binpathattrs = bytearray()
5456        for pa in self.path_attributes:
5457            binpathattrs += pa.serialize()
5458        self.total_path_attribute_len = len(binpathattrs)
5459        binnlri = bytearray()
5460        for n in self.nlri:
5461            binnlri += n.serialize()
5462
5463        msg = bytearray()
5464        offset = 0
5465        msg_pack_into('!H', msg, offset, self.withdrawn_routes_len)
5466        msg += binroutes
5467        offset += 2 + self.withdrawn_routes_len
5468        msg_pack_into('!H', msg, offset, self.total_path_attribute_len)
5469        msg += binpathattrs
5470        offset += 2 + self.total_path_attribute_len
5471        msg += binnlri
5472        return msg
5473
5474
5475@BGPMessage.register_type(BGP_MSG_KEEPALIVE)
5476class BGPKeepAlive(BGPMessage):
5477    """BGP-4 KEEPALIVE Message encoder/decoder class.
5478
5479    An instance has the following attributes at least.
5480    Most of them are same to the on-wire counterparts but in host byte
5481    order.
5482    __init__ takes the corresponding args in this order.
5483
5484    ========================== ===============================================
5485    Attribute                  Description
5486    ========================== ===============================================
5487    marker                     Marker field.  Ignored when encoding.
5488    len                        Length field.  Ignored when encoding.
5489    type                       Type field.
5490    ========================== ===============================================
5491    """
5492
5493    _MIN_LEN = BGPMessage._HDR_LEN
5494
5495    def __init__(self, type_=BGP_MSG_KEEPALIVE, len_=None, marker=None):
5496        super(BGPKeepAlive, self).__init__(marker=marker, len_=len_,
5497                                           type_=type_)
5498
5499    @classmethod
5500    def parser(cls, buf):
5501        return {}
5502
5503    def serialize_tail(self):
5504        return bytearray()
5505
5506
5507@BGPMessage.register_type(BGP_MSG_NOTIFICATION)
5508class BGPNotification(BGPMessage):
5509    """BGP-4 NOTIFICATION Message encoder/decoder class.
5510
5511    An instance has the following attributes at least.
5512    Most of them are same to the on-wire counterparts but in host byte
5513    order.
5514    __init__ takes the corresponding args in this order.
5515
5516    ========================== ===============================================
5517    Attribute                  Description
5518    ========================== ===============================================
5519    marker                     Marker field.  Ignored when encoding.
5520    len                        Length field.  Ignored when encoding.
5521    type                       Type field.
5522    error_code                 Error code field.
5523    error_subcode              Error subcode field.
5524    data                       Data field.
5525    ========================== ===============================================
5526    """
5527
5528    _PACK_STR = '!BB'
5529    _MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR)
5530
5531    _REASONS = {
5532        (1, 1): 'Message Header Error: not synchronised',
5533        (1, 2): 'Message Header Error: bad message len',
5534        (1, 3): 'Message Header Error: bad message type',
5535        (2, 1): 'Open Message Error: unsupported version',
5536        (2, 2): 'Open Message Error: bad peer AS',
5537        (2, 3): 'Open Message Error: bad BGP identifier',
5538        (2, 4): 'Open Message Error: unsupported optional param',
5539        (2, 5): 'Open Message Error: authentication failure',
5540        (2, 6): 'Open Message Error: unacceptable hold time',
5541        (2, 7): 'Open Message Error: Unsupported Capability',
5542        (2, 8): 'Open Message Error: Unassigned',
5543        (3, 1): 'Update Message Error: malformed attribute list',
5544        (3, 2): 'Update Message Error: unrecognized well-known attr',
5545        (3, 3): 'Update Message Error: missing well-known attr',
5546        (3, 4): 'Update Message Error: attribute flags error',
5547        (3, 5): 'Update Message Error: attribute length error',
5548        (3, 6): 'Update Message Error: invalid origin attr',
5549        (3, 7): 'Update Message Error: as routing loop',
5550        (3, 8): 'Update Message Error: invalid next hop attr',
5551        (3, 9): 'Update Message Error: optional attribute error',
5552        (3, 10): 'Update Message Error: invalid network field',
5553        (3, 11): 'Update Message Error: malformed AS_PATH',
5554        (4, 1): 'Hold Timer Expired',
5555        (5, 1): 'Finite State Machine Error',
5556        (6, 1): 'Cease: Maximum Number of Prefixes Reached',
5557        (6, 2): 'Cease: Administrative Shutdown',
5558        (6, 3): 'Cease: Peer De-configured',
5559        (6, 4): 'Cease: Administrative Reset',
5560        (6, 5): 'Cease: Connection Rejected',
5561        (6, 6): 'Cease: Other Configuration Change',
5562        (6, 7): 'Cease: Connection Collision Resolution',
5563        (6, 8): 'Cease: Out of Resources',
5564    }
5565
5566    def __init__(self,
5567                 error_code,
5568                 error_subcode,
5569                 data=b'',
5570                 type_=BGP_MSG_NOTIFICATION, len_=None, marker=None):
5571        super(BGPNotification, self).__init__(marker=marker, len_=len_,
5572                                              type_=type_)
5573        self.error_code = error_code
5574        self.error_subcode = error_subcode
5575        self.data = data
5576
5577    @classmethod
5578    def parser(cls, buf):
5579        (error_code, error_subcode,) = struct.unpack_from(cls._PACK_STR,
5580                                                          six.binary_type(buf))
5581        data = bytes(buf[2:])
5582        return {
5583            "error_code": error_code,
5584            "error_subcode": error_subcode,
5585            "data": data,
5586        }
5587
5588    def serialize_tail(self):
5589        msg = bytearray(struct.pack(self._PACK_STR, self.error_code,
5590                                    self.error_subcode))
5591        msg += self.data
5592        return msg
5593
5594    @property
5595    def reason(self):
5596        return self._REASONS.get((self.error_code, self.error_subcode))
5597
5598
5599@BGPMessage.register_type(BGP_MSG_ROUTE_REFRESH)
5600class BGPRouteRefresh(BGPMessage):
5601    """BGP-4 ROUTE REFRESH Message (RFC 2918) encoder/decoder class.
5602
5603    An instance has the following attributes at least.
5604    Most of them are same to the on-wire counterparts but in host byte
5605    order.
5606    __init__ takes the corresponding args in this order.
5607
5608    ========================== ===============================================
5609    Attribute                  Description
5610    ========================== ===============================================
5611    marker                     Marker field.  Ignored when encoding.
5612    len                        Length field.  Ignored when encoding.
5613    type                       Type field.
5614    afi                        Address Family Identifier
5615    safi                       Subsequent Address Family Identifier
5616    ========================== ===============================================
5617    """
5618
5619    _PACK_STR = '!HBB'
5620    _MIN_LEN = BGPMessage._HDR_LEN + struct.calcsize(_PACK_STR)
5621
5622    def __init__(self,
5623                 afi, safi, demarcation=0,
5624                 type_=BGP_MSG_ROUTE_REFRESH, len_=None, marker=None):
5625        super(BGPRouteRefresh, self).__init__(marker=marker, len_=len_,
5626                                              type_=type_)
5627        self.afi = afi
5628        self.safi = safi
5629        self.demarcation = demarcation
5630        self.eor_sent = False
5631
5632    @classmethod
5633    def parser(cls, buf):
5634        (afi, demarcation, safi,) = struct.unpack_from(cls._PACK_STR,
5635                                                       six.binary_type(buf))
5636        return {
5637            "afi": afi,
5638            "safi": safi,
5639            "demarcation": demarcation,
5640        }
5641
5642    def serialize_tail(self):
5643        return bytearray(struct.pack(self._PACK_STR, self.afi,
5644                                     self.demarcation, self.safi))
5645
5646
5647class StreamParser(stream_parser.StreamParser):
5648    """Streaming parser for BGP-4 messages.
5649
5650    This is a subclass of ryu.lib.packet.stream_parser.StreamParser.
5651    Its parse method returns a list of BGPMessage subclass instances.
5652    """
5653
5654    def try_parse(self, data):
5655        msg, _, rest = BGPMessage.parser(data)
5656        return msg, rest
5657