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)) |
|
|
|
|
389
|
|
|
decoder = staticmethod(decoder(ord, Protocol)) |
|
|
|
|
390
|
|
|
|
391
|
|
|
|
392
|
|
|
class FlowNextHeader(IOperationByte, NumericString, IPv6): |
393
|
|
|
ID = 0x03 |
394
|
|
|
NAME = 'next-header' |
395
|
|
|
converter = staticmethod(converter(Protocol.named, Protocol)) |
|
|
|
|
396
|
|
|
decoder = staticmethod(decoder(ord, Protocol)) |
|
|
|
|
397
|
|
|
|
398
|
|
|
|
399
|
|
|
class FlowAnyPort(IOperationByteShort, NumericString, IPv4, IPv6): |
400
|
|
|
ID = 0x04 |
401
|
|
|
NAME = 'port' |
402
|
|
|
converter = staticmethod(converter(PortValue)) |
|
|
|
|
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)) |
|
|
|
|
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)) |
|
|
|
|
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)) |
|
|
|
|
424
|
|
|
decoder = staticmethod(decoder(_number, ICMPType)) |
|
|
|
|
425
|
|
|
|
426
|
|
|
|
427
|
|
|
class FlowICMPCode(IOperationByte, NumericString, IPv4, IPv6): |
428
|
|
|
ID = 0x08 |
429
|
|
|
NAME = 'icmp-code' |
430
|
|
|
converter = staticmethod(converter(ICMPCode.named, ICMPCode)) |
|
|
|
|
431
|
|
|
decoder = staticmethod(decoder(_number, ICMPCode)) |
|
|
|
|
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)) |
|
|
|
|
439
|
|
|
decoder = staticmethod(decoder(ord, TCPFlag)) |
|
|
|
|
440
|
|
|
|
441
|
|
|
|
442
|
|
|
class FlowPacketLength(IOperationByteShort, NumericString, IPv4, IPv6): |
443
|
|
|
ID = 0x0A |
444
|
|
|
NAME = 'packet-length' |
445
|
|
|
converter = staticmethod(converter(PacketLength)) |
|
|
|
|
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)) |
|
|
|
|
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)) |
|
|
|
|
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)) |
|
|
|
|
471
|
|
|
decoder = staticmethod(decoder(ord, Fragment)) |
|
|
|
|
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)) |
|
|
|
|
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
|
|
|
|