Completed
Push — master ( 67ca5c...025df3 )
by Thomas
11:14
created

MPRNLRI.packed_attributes()   F

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