MPRNLRI.packed_attributes()   F
last analyzed

Complexity

Conditions 14

Size

Total Lines 50
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 30
nop 3
dl 0
loc 50
rs 3.6
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like exabgp.bgp.message.update.attribute.mprnlri.MPRNLRI.packed_attributes() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# encoding: utf-8
2
"""
3
mprnlri.py
4
5
Created by Thomas Mangin on 2009-11-05.
6
Copyright (c) 2009-2017 Exa Networks. All rights reserved.
7
License: 3-clause BSD. (See the COPYRIGHT file)
8
"""
9
10
from struct import unpack
11
12
from exabgp.protocol.ip import NoNextHop
13
from exabgp.protocol.family import AFI
14
from exabgp.protocol.family import SAFI
15
from exabgp.protocol.family import Family
16
17
from exabgp.bgp.message.direction import IN
18
19
# from exabgp.bgp.message.update.attribute.attribute import Attribute
20
from exabgp.bgp.message.update.attribute import Attribute
21
from exabgp.bgp.message.update.attribute import NextHop
22
from exabgp.bgp.message.update.nlri import NLRI
23
24
from exabgp.bgp.message.notification import Notify
25
from exabgp.bgp.message.open.capability import Negotiated
26
27
28
# ==================================================== MP Unreacheable NLRI (15)
29
#
30
31
32
@Attribute.register()
33
class MPRNLRI(Attribute, Family):
34
    FLAG = Attribute.Flag.OPTIONAL
35
    ID = Attribute.CODE.MP_REACH_NLRI
36
37
    def __init__(self, afi, safi, nlris):
38
        Family.__init__(self, afi, safi)
39
        # all the routes must have the same next-hop
40
        self.nlris = nlris
41
42
    def __eq__(self, other):
43
        return self.ID == other.ID and self.FLAG == other.FLAG and self.nlris == other.nlris
44
45
    def __ne__(self, other):
46
        return not self.__eq__(other)
47
48
    def packed_attributes(self, negotiated, maximum=Negotiated.FREE_SIZE):
49
        if not self.nlris:
50
            return
51
52
        # addpath = negotiated.addpath.send(self.afi,self.safi)
53
        # nexthopself = negotiated.nexthopself(self.afi)
54
        mpnlri = {}
55
        for nlri in self.nlris:
56
            if nlri.family() != self.family():  # nlri is not part of specified family
57
                continue
58
            if nlri.nexthop is NoNextHop:
59
                # EOR and Flow may not have any next_hop
60
                nexthop = b''
61
            else:
62
                _, rd_size = Family.size.get(self.family(), (0, 0))
63
                nh_rd = bytes([0]) * rd_size if rd_size else b''
64
                try:
65
                    nexthop = nh_rd + nlri.nexthop.ton(negotiated, nlri.afi)
66
                except TypeError:
67
                    # we could not match "next-hop self" with the BGP AFI of the BGP sesion
68
                    # attempting invalid IPv4 next-hop (0.0.0.0) to try to not kill the session
69
                    # and preserve some form of backward compatibility (for some vendors)
70
                    # the next-hop may have been IPv6 but not valided as the RFC says
71
                    #
72
                    # An UPDATE message that carries no NLRI, other than the one encoded in
73
                    # the MP_REACH_NLRI attribute, SHOULD NOT carry the NEXT_HOP attribute.
74
                    # If such a message contains the NEXT_HOP attribute, the BGP speaker
75
                    # that receives the message SHOULD ignore this attribute.
76
                    #
77
                    # Some vendors may have therefore not valided the next-hop
78
                    # and accepted invalid IPv6 next-hop in the past
79
                    nexthop = bytes([0]) * 4
80
81
            # mpunli[nexthop] = nlri
82
            mpnlri.setdefault(nexthop, []).append(nlri.pack(negotiated))
83
84
        for nexthop, nlris in mpnlri.items():
85
            payload = self.afi.pack() + self.safi.pack() + bytes([len(nexthop)]) + nexthop + bytes([0])
86
            header_length = len(payload)
87
            for nlri in nlris:
88
                if self._len(payload + nlri) > maximum:
89
                    if len(payload) == header_length or len(payload) > maximum:
90
                        raise Notify(6, 0, 'attributes size is so large we can not even pack on MPRNLRI')
91
                    yield self._attribute(payload)
92
                    payload = self.afi.pack() + self.safi.pack() + bytes([len(nexthop)]) + nexthop + bytes([0]) + nlri
93
                    continue
94
                payload = payload + nlri
95
            if len(payload) == header_length or len(payload) > maximum:
96
                raise Notify(6, 0, 'attributes size is so large we can not even pack on MPRNLRI')
97
            yield self._attribute(payload)
98
99
    def pack(self, negotiated):
100
        return b''.join(self.packed_attributes(negotiated))
101
102
    def __len__(self):
103
        raise RuntimeError('we can not give you the size of an MPRNLRI - was it with our witout addpath ?')
104
        # return len(self.pack(False))
105
106
    def __repr__(self):
107
        return "MP_REACH_NLRI for %s %s with %d NLRI(s)" % (self.afi, self.safi, len(self.nlris))
108
109
    @classmethod
110
    def unpack(cls, data, negotiated):
111
        nlris = []
112
113
        # -- Reading AFI/SAFI
114
        _afi, _safi = unpack('!HB', data[:3])
115
        afi, safi = AFI.create(_afi), SAFI.create(_safi)
116
        offset = 3
117
        nh_afi = afi
118
119
        # we do not want to accept unknown families
120
        if negotiated and (afi, safi) not in negotiated.families:
121
            raise Notify(3, 0, 'presented a non-negotiated family %s/%s' % (afi, safi))
122
123
        # -- Reading length of next-hop
124
        len_nh = data[offset]
125
        offset += 1
126
127
        if (afi, safi) not in Family.size:
128
            raise Notify(3, 0, 'unsupported %s %s' % (afi, safi))
129
130
        length, rd = Family.size[(afi, safi)]
131
132
        # Is the peer going to send us some Path Information with the route (AddPath)
133
        # It need to be done before adapting the family for another possible next-hop
134
        addpath = negotiated.addpath.receive(afi, safi)
135
136
        if negotiated.nexthop:
137
            if len_nh in (16, 32, 24):
138
                nh_afi = AFI.ipv6
139
            elif len_nh in (4, 12):
140
                nh_afi = AFI.ipv4
141
            else:
142
                raise Notify(3, 0, 'unsupported family %s %s with extended next-hop capability enabled' % (afi, safi))
143
            length, _ = Family.size[(nh_afi, safi)]
144
145
        if len_nh not in length:
146
            raise Notify(
147
                3,
148
                0,
149
                'invalid %s %s next-hop length %d expected %s'
150
                % (afi, safi, len_nh, ' or '.join(str(_) for _ in length)),
151
            )
152
153
        size = len_nh - rd
154
155
        # XXX: FIXME: GET IT FROM CACHE HERE ?
156
        nhs = data[offset + rd : offset + rd + size]
157
        nexthops = [nhs[pos : pos + 16] for pos in range(0, len(nhs), 16)]
158
159
        # chech the RD is well zero
160
        if rd and sum([int(_) for _ in data[offset:8]]) != 0:
161
            raise Notify(3, 0, "MP_REACH_NLRI next-hop's route-distinguisher must be zero")
162
163
        offset += len_nh
164
165
        # Skip a reserved bit as somone had to bug us !
166
        reserved = data[offset]
167
        offset += 1
168
169
        if reserved != 0:
170
            raise Notify(3, 0, 'the reserved bit of MP_REACH_NLRI is not zero')
171
172
        # Reading the NLRIs
173
        data = data[offset:]
174
175
        if not data:
176
            raise Notify(3, 0, 'No data to decode in an MPREACHNLRI but it is not an EOR %d/%d' % (afi, safi))
177
178
        while data:
179
            if nexthops:
180
                for nexthop in nexthops:
181
                    nlri, left = NLRI.unpack_nlri(afi, safi, data, IN.ANNOUNCED, addpath)
182
                    # allow unpack_nlri to return none for "treat as withdraw" controlled by NLRI.unpack_nlri
183
                    if nlri:
184
                        nlri.nexthop = NextHop.unpack(nexthop)
185
                        nlris.append(nlri)
186
            else:
187
                nlri, left = NLRI.unpack_nlri(afi, safi, data, IN.ANNOUNCED, addpath)
188
                # allow unpack_nlri to return none for "treat as withdraw" controlled by NLRI.unpack_nlri
189
                if nlri:
190
                    nlris.append(nlri)
191
192
            if left == data:
0 ignored issues
show
introduced by
The variable left does not seem to be defined for all execution paths.
Loading history...
193
                raise RuntimeError("sub-calls should consume data")
194
195
            data = left
196
        return cls(afi, safi, nlris)
197
198
199
EMPTY_MPRNLRI = MPRNLRI(AFI.undefined, SAFI.undefined, [])
200