exabgp.bgp.message.update.nlri.flow   F
last analyzed

Complexity

Total Complexity 90

Size/Duplication

Total Lines 692
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 452
dl 0
loc 692
rs 2
c 0
b 0
f 0
wmc 90

29 Methods

Rating   Name   Duplication   Size   Complexity  
A IPrefix6.make() 0 5 1
A IOperation.__init__() 0 4 1
A IOperation.encode() 0 2 1
A IPrefix4.pack() 0 4 1
A IPrefix4.__init__() 0 2 1
A IPrefix6.pack() 0 3 1
A BinaryString.__str__() 0 3 1
A IPrefix6.__str__() 0 2 1
A IOperation.pack() 0 4 1
A IPrefix4.__str__() 0 2 1
A IPrefix4.make() 0 4 1
A IPrefix6.__init__() 0 3 1
A Flow.feedback() 0 4 3
B Flow._rules() 0 15 6
A IOperationByte.encode() 0 2 1
A Flow.extensive() 0 4 3
A CommonOperator.eol() 0 3 1
B Flow.json() 0 18 8
A IOperationByteShortLong.encode() 0 6 3
A CommonOperator.operator() 0 3 1
A CommonOperator.length() 0 3 1
B Flow.pack_nlri() 0 26 6
A Flow.__len__() 0 2 1
C Flow.unpack_nlri() 0 58 11
A Flow.__init__() 0 5 1
A Flow.__str__() 0 2 1
A NumericString.__str__() 0 5 2
B Flow.add() 0 18 6
A IOperationByteShort.encode() 0 4 2

10 Functions

Rating   Name   Duplication   Size   Complexity  
A ClassValue() 0 6 3
A converter() 0 10 3
A decoder() 0 5 1
A PortValue() 0 7 2
A _number() 0 5 2
A PacketLength() 0 6 2
A DSCPValue() 0 6 3
A _len_to_bit() 0 2 1
A _bit_to_len() 0 2 1
A LabelValue() 0 6 3

How to fix   Complexity   

Complexity

Complex classes like exabgp.bgp.message.update.nlri.flow 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
flow.py
4
5
Created by Thomas Mangin on 2010-01-14.
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 pack
11
12
from exabgp.protocol.ip import NoNextHop
13
from exabgp.protocol.ip.port import Port
14
from exabgp.protocol.family import AFI
15
from exabgp.protocol.family import SAFI
16
from exabgp.bgp.message.direction import OUT
17
from exabgp.bgp.message.notification import Notify
18
from exabgp.bgp.message.update.nlri.cidr import CIDR
19
20
from exabgp.protocol import Protocol
21
from exabgp.protocol.ip.icmp import ICMPType
22
from exabgp.protocol.ip.icmp import ICMPCode
23
from exabgp.protocol.ip.fragment import Fragment
24
from exabgp.protocol.ip.tcp.flag import TCPFlag
25
26
from exabgp.bgp.message.update.nlri.nlri import NLRI
27
from exabgp.bgp.message.update.nlri.qualifier import RouteDistinguisher
28
29
30
# =================================================================== Flow Components
31
32
33
class IComponent(object):
34
    # all have ID
35
    # should have an interface for serialisation and put it here
36
    FLAG = False
37
38
39
class CommonOperator(object):
40
    # power (2,x) is the same as 1 << x which is what the RFC say the len is
41
    power = {
42
        0: 1,
43
        1: 2,
44
        2: 4,
45
        3: 8,
46
    }
47
    rewop = {
48
        1: 0,
49
        2: 1,
50
        4: 2,
51
        8: 3,
52
    }
53
    len_position = 0x30
54
55
    EOL = 0x80  # 0b10000000
56
    AND = 0x40  # 0b01000000
57
    LEN = 0x30  # 0b00110000
58
    NOP = 0x00
59
60
    OPERATOR = 0xFF ^ (EOL | LEN)
61
62
    @staticmethod
63
    def eol(data):
64
        return data & CommonOperator.EOL
65
66
    @staticmethod
67
    def operator(data):
68
        return data & CommonOperator.OPERATOR
69
70
    @staticmethod
71
    def length(data):
72
        return 1 << ((data & CommonOperator.LEN) >> 4)
73
74
75
class NumericOperator(CommonOperator):
76
    # reserved= 0x08  # 0b00001000
77
    LT = 0x04  # 0b00000100
78
    GT = 0x02  # 0b00000010
79
    EQ = 0x01  # 0b00000001
80
    NEQ = LT | GT
81
    TRUE = LT | GT | EQ
82
    FALSE = 0x00
83
84
85
class BinaryOperator(CommonOperator):
86
    # reserved= 0x0C  # 0b00001100
87
    INCLUDE = 0x00  # 0b00000000
88
    NOT = 0x02  # 0b00000010
89
    MATCH = 0x01  # 0b00000001
90
91
92
def _len_to_bit(value):
93
    return NumericOperator.rewop[value] << 4
94
95
96
def _bit_to_len(value):
97
    return NumericOperator.power[(value & CommonOperator.len_position) >> 4]
98
99
100
def _number(string):
101
    value = 0
102
    for c in string:
103
        value = (value << 8) + c
104
    return value
105
106
107
# Interface ..................
108
109
110
class IPv4(object):
111
    afi = AFI.ipv4
112
113
114
class IPv6(object):
115
    afi = AFI.ipv6
116
117
118
class IPrefix(object):
119
    pass
120
121
122
# Prococol
123
124
125
class IPrefix4(IPrefix, IComponent, IPv4):
126
    # Must be defined in subclasses
127
    CODE = -1
128
    NAME = ''
129
130
    # not used, just present for simplying the nlri generation
131
    operations = 0x0
132
133
    def __init__(self, raw, netmask):
134
        self.cidr = CIDR(raw, netmask)
135
136
    def pack(self):
137
        raw = self.cidr.pack_nlri()
138
        # ID is defined in subclasses
139
        return bytes([self.ID]) + raw  # pylint: disable=E1101
140
141
    def __str__(self):
142
        return str(self.cidr)
143
144
    @classmethod
145
    def make(cls, bgp):
146
        prefix, mask = CIDR.decode(AFI.ipv4, bgp)
147
        return cls(prefix, mask), bgp[CIDR.size(mask) + 1 :]
148
149
150
class IPrefix6(IPrefix, IComponent, IPv6):
151
    # Must be defined in subclasses
152
    CODE = -1
153
    NAME = ''
154
155
    # not used, just present for simplying the nlri generation
156
    operations = 0x0
157
158
    def __init__(self, raw, netmask, offset):
159
        self.cidr = CIDR(raw, netmask)
160
        self.offset = offset
161
162
    def pack(self):
163
        # ID is defined in subclasses
164
        return bytes([self.ID, self.cidr.mask, self.offset]) + self.cidr.pack_ip()  # pylint: disable=E1101
165
166
    def __str__(self):
167
        return "%s/%s" % (self.cidr, self.offset)
168
169
    @classmethod
170
    def make(cls, bgp):
171
        offset = bgp[1]
172
        prefix, mask = CIDR.decode(AFI.ipv6, bgp[0:1] + bgp[2:])
173
        return cls(prefix, mask, offset), bgp[CIDR.size(mask) + 2 :]
174
175
176
class IOperation(IComponent):
177
    # need to implement encode which encode the value of the operator
178
179
    def __init__(self, operations, value):
180
        self.operations = operations
181
        self.value = value
182
        self.first = None  # handled by pack/str
183
184
    def pack(self):
185
        l, v = self.encode(self.value)
186
        op = self.operations | _len_to_bit(l)
187
        return bytes([op]) + v
188
189
    def encode(self, value):
190
        raise NotImplementedError('this method must be implemented by subclasses')
191
192
    # def decode (self, value):
193
    # 	raise NotImplementedError('this method must be implemented by subclasses')
194
195
196
# class IOperationIPv4 (IOperation):
197
# 	def encode (self, value):
198
# 		return 4, socket.pton(socket.AF_INET,value)
199
200
201
class IOperationByte(IOperation):
202
    def encode(self, value):
203
        return 1, bytes([value])
204
205
    # def decode (self, bgp):
206
    # 	return bgp[0],bgp[1:]
207
208
209
class IOperationByteShort(IOperation):
210
    def encode(self, value):
211
        if value < (1 << 8):
212
            return 1, bytes([value])
213
        return 2, pack('!H', value)
214
215
    # XXX: buggy as it assumes 2 bytes but may be less
216
    # def decode (self, bgp):
217
    # 	import pdb; pdb.set_trace()
218
    # 	return unpack('!H',bgp[:2])[0],bgp[2:]
219
220
221
class IOperationByteShortLong(IOperation):
222
    def encode(self, value):
223
        if value < (1 << 8):
224
            return 1, bytes([value])
225
        if value < (1 << 16):
226
            return 2, pack('!H', value)
227
        return 4, pack('!L', value)
228
229
    # XXX: buggy as it assumes 4 bytes but may be less
230
    # def decode (self, bgp):
231
    # 	return unpack('!L',bgp[:4])[0],bgp[4:]
232
233
234
# String representation for Numeric and Binary Tests
235
236
237
class NumericString(object):
238
    OPERATION = 'numeric'
239
    operations = None
240
    value = None
241
242
    _string = {
243
        NumericOperator.TRUE: 'true',
244
        NumericOperator.LT: '<',
245
        NumericOperator.GT: '>',
246
        NumericOperator.EQ: '=',
247
        NumericOperator.LT | NumericOperator.EQ: '<=',
248
        NumericOperator.GT | NumericOperator.EQ: '>=',
249
        NumericOperator.NEQ: '!=',
250
        NumericOperator.FALSE: 'false',
251
        NumericOperator.AND | NumericOperator.TRUE: '&true',
252
        NumericOperator.AND | NumericOperator.LT: '&<',
253
        NumericOperator.AND | NumericOperator.GT: '&>',
254
        NumericOperator.AND | NumericOperator.EQ: '&=',
255
        NumericOperator.AND | NumericOperator.LT | NumericOperator.EQ: '&<=',
256
        NumericOperator.AND | NumericOperator.GT | NumericOperator.EQ: '&>=',
257
        NumericOperator.AND | NumericOperator.NEQ: '&!=',
258
        NumericOperator.AND | NumericOperator.FALSE: '&false',
259
    }
260
261
    def __str__(self):
262
        op = self.operations & (CommonOperator.EOL ^ 0xFF)
263
        if op in [NumericOperator.TRUE, NumericOperator.FALSE]:
264
            return self._string[op]
265
        return "%s%s" % (self._string.get(op, "%02X" % op), self.value)
266
267
268
class BinaryString(object):
269
    OPERATION = 'binary'
270
    operations = None
271
    value = None
272
273
    _string = {
274
        BinaryOperator.INCLUDE: '',
275
        BinaryOperator.NOT: '!',
276
        BinaryOperator.MATCH: '=',
277
        BinaryOperator.NOT | BinaryOperator.MATCH: '!=',
278
        BinaryOperator.AND | BinaryOperator.INCLUDE: '&',
279
        BinaryOperator.AND | BinaryOperator.NOT: '&!',
280
        BinaryOperator.AND | BinaryOperator.MATCH: '&=',
281
        BinaryOperator.AND | BinaryOperator.NOT | BinaryOperator.MATCH: '&!=',
282
    }
283
284
    def __str__(self):
285
        op = self.operations & (CommonOperator.EOL ^ 0xFF)
286
        return "%s%s" % (self._string.get(op, "%02X" % op), self.value)
287
288
289
# Components ..............................
290
291
292
def converter(function, klass=None):
293
    def _integer(value):
294
        if klass is None:
295
            return function(value)
296
        try:
297
            return klass(value)
298
        except ValueError:
299
            return function(value)
300
301
    return _integer
302
303
304
def decoder(function, klass=int):
305
    def _inner(value):
306
        return klass(function(value))
307
308
    return _inner
309
310
311
def PacketLength(data):
312
    _str_bad_length = "cloudflare already found that invalid max-packet length for for you .."
313
    number = int(data)
314
    if number > 0xFFFF:
315
        raise ValueError(_str_bad_length)
316
    return number
317
318
319
def PortValue(data):
320
    _str_bad_port = "you tried to set an invalid port number .."
321
    try:
322
        number = Port.named(data)
323
    except ValueError:
324
        raise ValueError(_str_bad_port)
325
    return number
326
327
328
def DSCPValue(data):
329
    _str_bad_dscp = "you tried to filter a flow using an invalid dscp for a component .."
330
    number = int(data)
331
    if number < 0 or number > 0x3F:  # 0b00111111
332
        raise ValueError(_str_bad_dscp)
333
    return number
334
335
336
def ClassValue(data):
337
    _str_bad_class = "you tried to filter a flow using an invalid traffic class for a component .."
338
    number = int(data)
339
    if number < 0 or number > 0xFFFF:
340
        raise ValueError(_str_bad_class)
341
    return number
342
343
344
def LabelValue(data):
345
    _str_bad_label = "you tried to filter a flow using an invalid traffic label for a component .."
346
    number = int(data)
347
    if number < 0 or number > 0xFFFFF:  # 20 bits 5 bytes
348
        raise ValueError(_str_bad_label)
349
    return number
350
351
352
# Protocol Shared
353
354
355
class FlowDestination(object):
356
    ID = 0x01
357
    NAME = 'destination'
358
359
360
class FlowSource(object):
361
    ID = 0x02
362
    NAME = 'source'
363
364
365
# Prefix
366
class Flow4Destination(IPrefix4, FlowDestination):
367
    NAME = 'destination-ipv4'
368
369
370
# Prefix
371
class Flow4Source(IPrefix4, FlowSource):
372
    NAME = 'source-ipv4'
373
374
375
# Prefix
376
class Flow6Destination(IPrefix6, FlowDestination):
377
    NAME = 'destination-ipv6'
378
379
380
# Prefix
381
class Flow6Source(IPrefix6, FlowSource):
382
    NAME = 'source-ipv6'
383
384
385
class FlowIPProtocol(IOperationByte, NumericString, IPv4):
386
    ID = 0x03
387
    NAME = 'protocol'
388
    converter = staticmethod(converter(Protocol.named, Protocol))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
389
    decoder = staticmethod(decoder(ord, Protocol))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
390
391
392
class FlowNextHeader(IOperationByte, NumericString, IPv6):
393
    ID = 0x03
394
    NAME = 'next-header'
395
    converter = staticmethod(converter(Protocol.named, Protocol))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
396
    decoder = staticmethod(decoder(ord, Protocol))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
397
398
399
class FlowAnyPort(IOperationByteShort, NumericString, IPv4, IPv6):
400
    ID = 0x04
401
    NAME = 'port'
402
    converter = staticmethod(converter(PortValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
403
    decoder = staticmethod(_number)
404
405
406
class FlowDestinationPort(IOperationByteShort, NumericString, IPv4, IPv6):
407
    ID = 0x05
408
    NAME = 'destination-port'
409
    converter = staticmethod(converter(PortValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
410
    decoder = staticmethod(_number)
411
412
413
class FlowSourcePort(IOperationByteShort, NumericString, IPv4, IPv6):
414
    ID = 0x06
415
    NAME = 'source-port'
416
    converter = staticmethod(converter(PortValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
417
    decoder = staticmethod(_number)
418
419
420
class FlowICMPType(IOperationByte, NumericString, IPv4, IPv6):
421
    ID = 0x07
422
    NAME = 'icmp-type'
423
    converter = staticmethod(converter(ICMPType.named, ICMPType))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
424
    decoder = staticmethod(decoder(_number, ICMPType))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
425
426
427
class FlowICMPCode(IOperationByte, NumericString, IPv4, IPv6):
428
    ID = 0x08
429
    NAME = 'icmp-code'
430
    converter = staticmethod(converter(ICMPCode.named, ICMPCode))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
431
    decoder = staticmethod(decoder(_number, ICMPCode))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
432
433
434
class FlowTCPFlag(IOperationByte, BinaryString, IPv4, IPv6):
435
    ID = 0x09
436
    NAME = 'tcp-flags'
437
    FLAG = True
438
    converter = staticmethod(converter(TCPFlag.named))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
439
    decoder = staticmethod(decoder(ord, TCPFlag))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
440
441
442
class FlowPacketLength(IOperationByteShort, NumericString, IPv4, IPv6):
443
    ID = 0x0A
444
    NAME = 'packet-length'
445
    converter = staticmethod(converter(PacketLength))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
446
    decoder = staticmethod(_number)
447
448
449
# RFC2474
450
class FlowDSCP(IOperationByte, NumericString, IPv4):
451
    ID = 0x0B
452
    NAME = 'dscp'
453
    converter = staticmethod(converter(DSCPValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
454
    decoder = staticmethod(_number)
455
456
457
# RFC2460
458
class FlowTrafficClass(IOperationByte, NumericString, IPv6):
459
    ID = 0x0B
460
    NAME = 'traffic-class'
461
    converter = staticmethod(converter(ClassValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
462
    decoder = staticmethod(_number)
463
464
465
# BinaryOperator
466
class FlowFragment(IOperationByteShort, BinaryString, IPv4):
467
    ID = 0x0C
468
    NAME = 'fragment'
469
    FLAG = True
470
    converter = staticmethod(converter(Fragment.named))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
471
    decoder = staticmethod(decoder(ord, Fragment))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable decoder does not seem to be defined.
Loading history...
472
473
474
# draft-raszuk-idr-flow-spec-v6-01
475
class FlowFlowLabel(IOperationByteShortLong, NumericString, IPv6):
476
    ID = 0x0D
477
    NAME = 'flow-label'
478
    converter = staticmethod(converter(LabelValue))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable converter does not seem to be defined.
Loading history...
479
    decoder = staticmethod(_number)
480
481
482
# ..........................................................
483
484
decode = {AFI.ipv4: {}, AFI.ipv6: {}}
485
factory = {AFI.ipv4: {}, AFI.ipv6: {}}
486
487
for content in dir():
488
    kls = globals().get(content, None)
489
    if not isinstance(kls, type(IComponent)):
490
        continue
491
    if not issubclass(kls, IComponent):
492
        continue
493
494
    _ID = getattr(kls, 'ID', None)
495
    if not _ID:
496
        continue
497
498
    _afis = []
499
    if issubclass(kls, IPv4):
500
        _afis.append(AFI.ipv4)
501
    if issubclass(kls, IPv6):
502
        _afis.append(AFI.ipv6)
503
504
    for _afi in _afis:
505
        factory[_afi][_ID] = kls
506
        name = getattr(kls, 'NAME')
507
508
        if issubclass(kls, IOperation):
509
            if issubclass(kls, BinaryString):
510
                decode[_afi][_ID] = 'binary'
511
            elif issubclass(kls, NumericString):
512
                decode[_afi][_ID] = 'numeric'
513
            else:
514
                raise RuntimeError('invalid class defined (string)')
515
        elif issubclass(kls, IPrefix):
516
            decode[_afi][_ID] = 'prefix'
517
        else:
518
            raise RuntimeError('unvalid class defined (type)')
519
520
521
# ..........................................................
522
523
524
@NLRI.register(AFI.ipv4, SAFI.flow_ip)
525
@NLRI.register(AFI.ipv6, SAFI.flow_ip)
526
@NLRI.register(AFI.ipv4, SAFI.flow_vpn)
527
@NLRI.register(AFI.ipv6, SAFI.flow_vpn)
528
class Flow(NLRI):
529
    def __init__(self, afi=AFI.ipv4, safi=SAFI.flow_ip, action=OUT.UNSET):
530
        NLRI.__init__(self, afi, safi, action)
531
        self.rules = {}
532
        self.nexthop = NoNextHop
533
        self.rd = RouteDistinguisher.NORD
534
535
    def feedback(self, action):
536
        if self.nexthop is None and action == OUT.ANNOUNCE:
537
            return 'flow nlri next-hop missing'
538
        return ''
539
540
    def __len__(self):
541
        return len(self.pack())
542
543
    def add(self, rule):
544
        ID = rule.ID
545
        if ID in (FlowDestination.ID, FlowSource.ID):
546
            # re-enabled multiple source/destination as it is allowed by some vendor
547
            # if ID in self.rules:
548
            # 	return False
549
            if ID == FlowDestination.ID:
550
                pair = self.rules.get(FlowSource.ID, [])
551
            else:
552
                pair = self.rules.get(FlowDestination.ID, [])
553
            if pair:
554
                if rule.afi != pair[0].afi:
555
                    return False
556
            # TODO: verify if this is correct - why reset the afi of the NLRI object after initialisation?
557
            if rule.NAME.endswith('ipv6'):  # better way to check this ?
558
                self.afi = AFI.ipv6
559
        self.rules.setdefault(ID, []).append(rule)
560
        return True
561
562
    # The API requires addpath, but it is irrelevant here.
563
    def pack_nlri(self, negotiated=None):
564
        ordered_rules = []
565
        # the order is a RFC requirement
566
        for ID in sorted(self.rules.keys()):
567
            rules = self.rules[ID]
568
            # for each component get all the operation to do
569
            # the format use does not prevent two opposing rules meaning that no packet can ever match
570
            for rule in rules:
571
                rule.operations &= CommonOperator.EOL ^ 0xFF
572
            rules[-1].operations |= CommonOperator.EOL
573
            # and add it to the last rule
574
            if ID not in (FlowDestination.ID, FlowSource.ID):
575
                ordered_rules.append(bytes([ID]))
576
            ordered_rules.append(b''.join(rule.pack() for rule in rules))
577
578
        components = self.rd.pack() + b''.join(ordered_rules)
579
580
        lc = len(components)
581
        if lc < 0xF0:
582
            return bytes([lc]) + components
583
        if lc < 0x0FFF:
584
            return pack('!H', lc | 0xF000) + components
585
        raise Notify(
586
            3,
587
            0,
588
            "my administrator attempted to announce a Flow Spec rule larger than encoding allows, protecting the innocent the only way I can",
589
        )
590
591
    def _rules(self):
592
        string = []
593
        for index in sorted(self.rules):
594
            rules = self.rules[index]
595
            s = []
596
            for idx, rule in enumerate(rules):
597
                # only add ' ' after the first element
598
                if idx and not rule.operations & NumericOperator.AND:
599
                    s.append(' ')
600
                s.append(rule)
601
            line = ''.join(str(_) for _ in s)
602
            if len(s) > 1:
603
                line = '[ %s ]' % line
604
            string.append(' %s %s' % (rules[0].NAME, line))
605
        return ''.join(string)
606
607
    def extensive(self):
608
        nexthop = ' next-hop %s' % self.nexthop if self.nexthop is not NoNextHop else ''
609
        rd = '' if self.rd is RouteDistinguisher.NORD else str(self.rd)
610
        return 'flow' + self._rules() + rd + nexthop
611
612
    def __str__(self):
613
        return self.extensive()
614
615
    def json(self, compact=None):
616
        string = []
617
        for index in sorted(self.rules):
618
            rules = self.rules[index]
619
            s = []
620
            for idx, rule in enumerate(rules):
621
                # only add ' ' after the first element
622
                if idx and not rule.operations & NumericOperator.AND:
623
                    s.append(', ')
624
                if rule.FLAG:
625
                    s.append(', '.join('"%s"' % flag for flag in rule.value.named_bits()))
626
                else:
627
                    s.append('"%s"' % rule)
628
            string.append(' "%s": [ %s ]' % (rules[0].NAME, ''.join(str(_) for _ in s).replace('""', '')))
629
        nexthop = ', "next-hop": "%s"' % self.nexthop if self.nexthop is not NoNextHop else ''
630
        rd = '' if self.rd is RouteDistinguisher.NORD else ', %s' % self.rd.json()
631
        compatibility = ', "string": "%s"' % self.extensive()
632
        return '{' + ','.join(string) + rd + nexthop + compatibility + ' }'
633
634
    @classmethod
635
    def unpack_nlri(cls, afi, safi, bgp, action, addpath):
636
        length, bgp = bgp[0], bgp[1:]
637
638
        if length & 0xF0 == 0xF0:  # bigger than 240
639
            extra, bgp = bgp[0], bgp[1:]
640
            length = ((length & 0x0F) << 16) + extra
641
642
        if length > len(bgp):
643
            raise Notify(3, 10, 'invalid length at the start of the the flow')
644
645
        over = bgp[length:]
646
647
        bgp = bgp[:length]
648
        nlri = cls(afi, safi, action)
649
650
        try:
651
            if safi == SAFI.flow_vpn:
652
                nlri.rd = RouteDistinguisher(bgp[:8])
653
                bgp = bgp[8:]
654
655
            seen = []
656
657
            while bgp:
658
                what, bgp = bgp[0], bgp[1:]
659
660
                if what not in decode.get(afi, {}):
661
                    raise Notify(3, 10, 'unknown flowspec component received for address family %d' % what)
662
663
                seen.append(what)
664
                if sorted(seen) != seen:
665
                    raise Notify(3, 10, 'components are not sent in the right order %s' % seen)
666
667
                decoded = decode[afi][what]
668
                klass = factory[afi][what]
669
670
                if decoded == 'prefix':
671
                    adding, bgp = klass.make(bgp)
672
                    if not nlri.add(adding):
673
                        raise Notify(
674
                            3,
675
                            10,
676
                            'components are incompatible (two sources, two destinations, mix ipv4/ipv6) %s' % seen,
677
                        )
678
                else:
679
                    end = False
680
                    while not end:
681
                        byte, bgp = bgp[0], bgp[1:]
682
                        end = CommonOperator.eol(byte)
683
                        operator = CommonOperator.operator(byte)
684
                        length = CommonOperator.length(byte)
685
                        value, bgp = bgp[:length], bgp[length:]
686
                        adding = klass.decoder(value)
687
                        nlri.add(klass(operator, adding))
688
689
            return nlri, bgp + over
690
        except (Notify, ValueError, IndexError) as exc:
691
            return None, over
692