5
Created by Thomas Mangin on 2009-08-25.
6
Copyright (c) 2009-2013 Exa Networks. All rights reserved.
14
from pprint import pformat
15
from copy import deepcopy
16
from struct import pack,unpack
18
from exabgp.structure.environment import environment
20
from exabgp.protocol.family import AFI,SAFI,known_families
22
from exabgp.bgp.neighbor import Neighbor
24
from exabgp.protocol import NamedProtocol
25
from exabgp.protocol.ip.inet import Inet,inet
26
from exabgp.protocol.ip.icmp import NamedICMPType,NamedICMPCode
27
from exabgp.protocol.ip.fragment import NamedFragment
28
from exabgp.protocol.ip.tcp.flags import NamedTCPFlags
30
from exabgp.bgp.message.open.asn import ASN
31
from exabgp.bgp.message.open.holdtime import HoldTime
32
from exabgp.bgp.message.open.routerid import RouterID
34
from exabgp.bgp.message.update.nlri.route import Route,NLRI,PathInfo,Labels,RouteDistinguisher,pack_int
35
from exabgp.bgp.message.update.nlri.flow import BinaryOperator,NumericOperator,Flow,Source,Destination,SourcePort,DestinationPort,AnyPort,IPProtocol,TCPFlag,Fragment,PacketLength,ICMPType,ICMPCode,DSCP
37
from exabgp.bgp.message.update.attribute.id import AttributeID
38
from exabgp.bgp.message.update.attribute.origin import Origin
39
from exabgp.bgp.message.update.attribute.nexthop import cachedNextHop
40
from exabgp.bgp.message.update.attribute.aspath import ASPath
41
from exabgp.bgp.message.update.attribute.med import MED
42
from exabgp.bgp.message.update.attribute.localpref import LocalPreference
43
from exabgp.bgp.message.update.attribute.atomicaggregate import AtomicAggregate
44
from exabgp.bgp.message.update.attribute.aggregator import Aggregator
45
from exabgp.bgp.message.update.attribute.communities import Community,cachedCommunity,Communities,ECommunity,ECommunities,to_ExtendedCommunity,to_FlowTrafficRate,to_FlowRedirectASN,to_FlowRedirectIP
46
from exabgp.bgp.message.update.attribute.originatorid import OriginatorID
47
from exabgp.bgp.message.update.attribute.clusterlist import ClusterList
49
from exabgp.structure.log import Logger
51
# Duck class, faking part of the Attribute interface
52
# We add this to routes when when need o split a route in smaller route
53
# The value stored is the longer netmask we want to use
54
# As this is not a real BGP attribute this stays in the configuration file
57
ID = AttributeID.INTERNAL_SPLIT
62
ID = AttributeID.INTERNAL_WATCHDOG
65
class Withdrawn (object):
66
ID = AttributeID.INTERNAL_WITHDRAW
70
def convert_length (data):
73
raise ValueError(Configuration._str_bad_length)
76
def convert_port (data):
78
if number < 0 or number > 65535:
79
raise ValueError(Configuration._str_bad_port)
82
def convert_dscp (data):
84
if number < 0 or number > 65535:
85
raise ValueError(Configuration._str_bad_dscp)
89
class Configuration (object):
92
# ' hold-time 180;\n' \
94
_str_bad_length = "cloudflare already found that invalid max-packet length for for you .."
95
_str_bad_flow = "you tried to filter a flow using an invalid port for a component .."
96
_str_bad_dscp = "you tried to filter a flow using an invalid dscp for a component .."
99
'community, extended-communities and as-path can take a single community as parameter.\n' \
100
'only next-hop is mandatory\n' \
103
'route 10.0.0.1/22 {\n' \
104
' path-information 0.0.0.1;\n' \
105
' route-distinguisher|rd 255.255.255.255:65535|65535:65536|65536:65535' \
106
' next-hop 192.0.1.254;\n' \
107
' origin IGP|EGP|INCOMPLETE;\n' \
108
' as-path [ AS-SEQUENCE-ASN1 AS-SEQUENCE-ASN2 ( AS-SET-ASN3 )] ;\n' \
110
' local-preference 100;\n' \
111
' atomic-aggregate;\n' \
112
' community [ 65000 65001 65002 ];\n' \
113
' extended-community [ target:1234:5.6.7.8 target:1.2.3.4:5678 origin:1234:5.6.7.8 origin:1.2.3.4:5678 0x0002FDE800000001 ]\n' \
114
' originator-id 10.0.0.10;\n' \
115
' cluster-list [ 10.10.0.1 10.10.0.2 ];\n' \
116
' label [ 100 200 ];\n' \
117
' aggregator ( 65000:10.0.0.10 )\n' \
119
' watchdog watchdog-name\n' \
124
'route 10.0.0.1/22' \
125
' path-information 0.0.0.1' \
126
' route-distinguisher|rd 255.255.255.255:65535|65535:65536|65536:65535' \
127
' next-hop 192.0.2.1' \
128
' origin IGP|EGP|INCOMPLETE' \
129
' as-path AS-SEQUENCE-ASN' \
131
' local-preference 100' \
132
' atomic-aggregate' \
134
' extended-community target:1234:5.6.7.8' \
135
' originator-id 10.0.0.10' \
136
' cluster-list 10.10.0.1' \
140
' watchdog watchdog-name' \
147
' source 10.0.0.0/24;\n' \
148
' destination 10.0.1.0/24;\n' \
150
' source-port >1024\n' \
151
' destination-port =80 =3128 >8080&<8088;\n' \
152
' protocol [ udp tcp ];\n' \
153
' fragment [ not-a-fragment dont-fragment is-fragment first-fragment last-fragment ];\n' \
154
' packet-length >200&<300 >400&<500;'
158
' rate-limit 9600;\n' \
159
' redirect 30740:12345;\n' \
160
' redirect 1.2.3.4:5678;\n' \
163
'one or more match term, one action\n' \
164
'fragment code is totally untested\n' \
166
_str_process_error = \
167
'syntax: process name-of-process {\n' \
168
' run /path/to/command with its args;\n' \
171
_str_family_error = \
172
'syntax: family {\n' \
173
' all; # default if not family block is present, announce all we know\n' \
174
' minimal # use the AFI/SAFI required to announce the routes in the configuration\n' \
176
' [inet|inet4] unicast;\n' \
177
' [inet|inet4] multicast;\n' \
178
' [inet|inet4] nlri-mpls;\n' \
179
' [inet|inet4] mpls-vpn;\n' \
180
' [inet|inet4] flow-vpnv4;\n' \
181
' inet6 unicast;\n' \
185
'syntax: capability {\n' \
186
' asn4 enable|disable;\n' \
187
' add-path disable|send|receive|send/receive;\n' \
190
def __init__ (self,fname,text=False):
191
self.debug = environment.settings().debug.configuration
192
self.api_encoder = environment.settings().api.encoder
194
self.logger = Logger()
205
self._location = ['root']
209
self._flow_state = 'out'
215
return self._reload()
216
except KeyboardInterrupt:
217
self.error = 'configuration reload aborted by ^C or SIGINT'
222
self._tokens = self._tokenise(self._fname.split('\n'))
225
f = open(self._fname,'r')
226
self._tokens = self._tokenise(f)
231
self.error = error.split(']')[1].strip()
240
while not self.finished():
241
r = self._dispatch(self._scope,'configuration',['group','neighbor'],[])
244
if r not in [True,None]:
245
self.error = "\nsyntax error in section %s\nline %d : %s\n\n%s" % (self._location[-1],self.number(),self.line(),self._error)
248
self.neighbor = self._neighbor
250
if environment.settings().debug.route:
251
self.decode(environment.settings().debug.route)
254
if environment.settings().debug.selfcheck:
260
def parse_api_route (self,command):
261
tokens = self._cleaned(command).split(' ')[1:]
264
if tokens[0] != 'route':
267
if not self._single_static_route(scope,tokens[1:]):
269
return scope[0]['routes']
271
def parse_api_flow (self,command):
272
self._tokens = self._tokenise(' '.join(self._cleaned(command).split(' ')[2:]).split('\\n'))
274
if not self._dispatch(scope,'flow',['route',],[]):
276
if not self._check_flow_route(scope):
278
return scope[0]['routes']
280
def add_route_all_peers (self,route):
281
for neighbor in self.neighbor:
282
self.neighbor[neighbor].add_route(route)
284
def remove_route_all_peers (self,route):
286
for neighbor in self.neighbor:
287
if self.neighbor[neighbor].remove_route(route):
293
def _cleaned (self,line):
294
return line.strip().replace('\t',' ').replace(']',' ]').replace('[','[ ').replace(')',' )').replace('(','( ').lower()
296
def _tokenise (self,text):
300
self.logger.configuration('loading | %s' % line.rstrip())
301
replaced = self._cleaned(line)
305
if replaced.startswith('#'):
307
if replaced[:3] == 'md5':
308
password = line.strip()[3:].strip()
309
if password[-1] == ';':
310
password = password[:-1]
311
r.append(['md5',password,';'])
313
r.append([t for t in replaced[:-1].split(' ') if t] + [replaced[-1]])
314
self.logger.config(config)
319
self._line = self._tokens.pop(0)
326
return ' '.join(self._line)
329
return len(self._tokens) == 0
331
# Flow control ......................
333
# name is not used yet but will come really handy if we have name collision :D
334
def _dispatch (self,scope,name,multi,single):
336
tokens = self.tokens()
338
self._error = 'configuration file incomplete (most likely missing })'
341
self.logger.configuration('analysing tokens %s ' % str(tokens))
342
self.logger.configuration(' valid block options %s' % str(multi))
343
self.logger.configuration(' valid parameters %s' % str(single))
345
if multi and end == '{':
346
self._location.append(tokens[0])
347
return self._multi_line(scope,name,tokens[:-1],multi)
348
if single and end == ';':
349
return self._single_line(scope,name,tokens[:-1],single)
351
if len(self._location) == 1:
352
self._error = 'closing too many parenthesis'
355
self._location.pop(-1)
359
def _multi_line (self,scope,name,tokens,valid):
362
if valid and command not in valid:
363
self._error = 'option %s in not valid here' % command
367
if name == 'configuration':
368
if command == 'neighbor':
369
if self._multi_neighbor(scope,tokens[1:]):
370
return self._make_neighbor(scope)
372
if command == 'group':
374
self._error = 'syntax: group <name> { <options> }'
377
return self._multi_group(scope,tokens[1])
380
if command == 'neighbor':
381
if self._multi_neighbor(scope,tokens[1:]):
382
return self._make_neighbor(scope)
384
if command == 'static': return self._multi_static(scope,tokens[1:])
385
if command == 'flow': return self._multi_flow(scope,tokens[1:])
386
if command == 'process': return self._multi_process(scope,tokens[1:])
387
if command == 'family': return self._multi_family(scope,tokens[1:])
388
if command == 'capability': return self._multi_capability(scope,tokens[1:])
390
if name == 'neighbor':
391
if command == 'static': return self._multi_static(scope,tokens[1:])
392
if command == 'flow': return self._multi_flow(scope,tokens[1:])
393
if command == 'process': return self._multi_process(scope,tokens[1:])
394
if command == 'family': return self._multi_family(scope,tokens[1:])
395
if command == 'capability': return self._multi_capability(scope,tokens[1:])
398
if command == 'route':
399
if self._multi_static_route(scope,tokens[1:]):
400
return self._check_static_route(scope)
404
if command == 'route':
405
if self._multi_flow_route(scope,tokens[1:]):
406
return self._check_flow_route(scope)
409
if name == 'flow-route':
410
if command == 'match':
411
if self._multi_match(scope,tokens[1:]):
414
if command == 'then':
415
if self._multi_then(scope,tokens[1:]):
420
def _single_line (self,scope,name,tokens,valid):
422
if valid and command not in valid:
423
self._error = 'invalid keyword "%s"' % command
427
elif name == 'route':
428
if command == 'origin': return self._route_origin(scope,tokens[1:])
429
if command == 'as-path': return self._route_aspath(scope,tokens[1:])
430
# For legacy with version 2.0.x
431
if command == 'as-sequence': return self._route_aspath(scope,tokens[1:])
432
if command == 'med': return self._route_med(scope,tokens[1:])
433
if command == 'next-hop': return self._route_next_hop(scope,tokens[1:])
434
if command == 'local-preference': return self._route_local_preference(scope,tokens[1:])
435
if command == 'atomic-aggregate': return self._route_atomic_aggregate(scope,tokens[1:])
436
if command == 'aggregator': return self._route_aggregator(scope,tokens[1:])
437
if command == 'path-information': return self._route_path_information(scope,tokens[1:])
438
if command == 'originator-id': return self._route_originator_id(scope,tokens[1:])
439
if command == 'cluster-list': return self._route_cluster_list(scope,tokens[1:])
440
if command == 'split': return self._route_split(scope,tokens[1:])
441
if command == 'label': return self._route_label(scope,tokens[1:])
442
if command in ('rd','route-distinguisher'): return self._route_rd(scope,tokens[1:])
443
if command == 'watchdog': return self._route_watchdog(scope,tokens[1:])
444
# withdrawn is here to not break legacy code
445
if command in ('withdraw','withdrawn'): return self._route_withdraw(scope,tokens[1:])
447
if command == 'community': return self._route_community(scope,tokens[1:])
448
if command == 'extended-community': return self._route_extended_community(scope,tokens[1:])
450
elif name == 'flow-match':
451
if command == 'source': return self._flow_source(scope,tokens[1:])
452
if command == 'destination': return self._flow_destination(scope,tokens[1:])
453
if command == 'port': return self._flow_route_anyport(scope,tokens[1:])
454
if command == 'source-port': return self._flow_route_source_port(scope,tokens[1:])
455
if command == 'destination-port': return self._flow_route_destination_port(scope,tokens[1:])
456
if command == 'protocol': return self._flow_route_protocol(scope,tokens[1:])
457
if command == 'tcp-flags': return self._flow_route_tcp_flags(scope,tokens[1:])
458
if command == 'icmp-type': return self._flow_route_icmp_type(scope,tokens[1:])
459
if command == 'icmp-code': return self._flow_route_icmp_code(scope,tokens[1:])
460
if command == 'fragment': return self._flow_route_fragment(scope,tokens[1:])
461
if command == 'dscp': return self._flow_route_dscp(scope,tokens[1:])
462
if command == 'packet-length': return self._flow_route_packet_length(scope,tokens[1:])
464
elif name == 'flow-then':
465
if command == 'discard': return self._flow_route_discard(scope,tokens[1:])
466
if command == 'rate-limit': return self._flow_route_rate_limit(scope,tokens[1:])
467
if command == 'redirect': return self._flow_route_redirect(scope,tokens[1:])
469
if command == 'community': return self._route_community(scope,tokens[1:])
470
if command == 'extended-community': return self._route_extended_community(scope,tokens[1:])
472
if name in ('neighbor','group'):
473
if command == 'description': return self._set_description(scope,tokens[1:])
474
if command == 'router-id': return self._set_router_id(scope,'router-id',tokens[1:])
475
if command == 'local-address': return self._set_ip(scope,'local-address',tokens[1:])
476
if command == 'local-as': return self._set_asn(scope,'local-as',tokens[1:])
477
if command == 'peer-as': return self._set_asn(scope,'peer-as',tokens[1:])
478
if command == 'hold-time': return self._set_holdtime(scope,'hold-time',tokens[1:])
479
if command == 'md5': return self._set_md5(scope,'md5',tokens[1:])
480
if command == 'ttl-security': return self._set_ttl(scope,'ttl-security',tokens[1:])
481
if command == 'group-updates': return self._set_group_updates(scope,'group-updates',tokens[1:])
483
if command == 'route-refresh': return self._set_routerefresh(scope,'route-refresh',tokens[1:])
484
if command == 'graceful-restart': return self._set_gracefulrestart(scope,'graceful-restart',tokens[1:])
485
if command == 'multi-session': return self._set_multisession(scope,'multi-session',tokens[1:])
486
if command == 'add-path': return self._set_addpath(scope,'add-path',tokens[1:])
488
elif name == 'family':
489
if command == 'inet': return self._set_family_inet4(scope,tokens[1:])
490
if command == 'inet4': return self._set_family_inet4(scope,tokens[1:])
491
if command == 'inet6': return self._set_family_inet6(scope,tokens[1:])
492
if command == 'minimal': return self._set_family_minimal(scope,tokens[1:])
493
if command == 'all': return self._set_family_all(scope,tokens[1:])
495
elif name == 'capability':
496
if command == 'route-refresh': return self._set_routerefresh(scope,'route-refresh',tokens[1:])
497
if command == 'graceful-restart': return self._set_gracefulrestart(scope,'graceful-restart',tokens[1:])
498
if command == 'multi-session': return self._set_multisession(scope,'multi-session',tokens[1:])
499
if command == 'add-path': return self._set_addpath(scope,'add-path',tokens[1:])
500
if command == 'asn4': return self._set_asn4(scope,'asn4',tokens[1:])
502
elif name == 'process':
503
if command == 'run': return self._set_process_run(scope,'process-run',tokens[1:])
505
if command == 'parse-routes':
506
self._set_process_command(scope,'neighbor-changes',tokens[1:])
507
self._set_process_command(scope,'receive-routes',tokens[1:])
510
if command == 'peer-updates':
511
self._set_process_command(scope,'neighbor-changes',tokens[1:])
512
self._set_process_command(scope,'receive-routes',tokens[1:])
515
if command == 'encoder': return self._set_process_encoder(scope,'encoder',tokens[1:])
516
if command == 'receive-packets': return self._set_process_command(scope,'receive-packets',tokens[1:])
517
if command == 'send-packets': return self._set_process_command(scope,'send-packets',tokens[1:])
518
if command == 'receive-routes': return self._set_process_command(scope,'receive-routes',tokens[1:])
519
if command == 'neighbor-changes': return self._set_process_command(scope,'neighbor-changes',tokens[1:])
521
elif name == 'static':
522
if command == 'route': return self._single_static_route(scope,tokens[1:])
526
# Programs used to control exabgp
528
def _multi_process (self,scope,tokens):
530
r = self._dispatch(scope,'process',[],['run','encoder','receive-packets','send-packets','receive-routes','neighbor-changes', 'peer-updates','parse-routes'])
531
if r is False: return False
534
name = tokens[0] if len(tokens) >= 1 else 'conf-only-%s' % str(time.time())[-6:]
535
self.process.setdefault(name,{})['neighbor'] = scope[-1]['peer-address'] if 'peer-address' in scope[-1] else '*'
537
run = scope[-1].pop('process-run','')
540
self._error = self._str_process_error
543
self.process[name]['encoder'] = scope[-1].get('encoder','') or self.api_encoder
544
self.process[name]['run'] = run
547
self._error = self._str_process_error
551
def _set_process_command (self,scope,command,value):
552
scope[-1][command] = True
555
def _set_process_encoder (self,scope,command,value):
556
if value and value[0] in ('text','json'):
557
scope[-1][command] = value[0]
560
self._error = self._str_process_error
564
def _set_process_run (self,scope,command,value):
565
line = ' '.join(value).strip()
566
if len(line) > 2 and line[0] == line[-1] and line[0] in ['"',"'"]:
569
prg,args = line.split(' ',1)
575
self._error = 'prg requires the program to prg as an argument (quoted or unquoted)'
579
if prg.startswith('etc/exabgp'):
580
parts = prg.split('/')
581
path = [os.environ.get('ETC','etc'),] + parts[2:]
582
prg = os.path.join(*path)
584
prg = os.path.abspath(os.path.join(os.path.dirname(self._fname),prg))
585
if not os.path.exists(prg):
586
self._error = 'can not locate the the program "%s"' % prg
590
# XXX: Yep, race conditions are possible, those are sanity checks not security ones ...
593
if stat.S_ISDIR(s.st_mode):
594
self._error = 'can not execute directories "%s"' % prg
598
if s.st_mode & stat.S_ISUID:
599
self._error = 'refusing to run setuid programs "%s"' % prg
604
if s.st_uid == os.getuid():
605
check |= stat.S_IXUSR
606
if s.st_gid == os.getgid():
607
check |= stat.S_IXGRP
609
if not check & s.st_mode:
610
self._error = 'exabgp will not be able to run this program "%s"' % prg
615
scope[-1][command] = [prg] + args.split(' ')
617
scope[-1][command] = [prg,]
620
# Limit the AFI/SAFI pair announced to peers
622
def _multi_family (self,scope,tokens):
623
# we know all the families we should use
625
scope[-1]['families'] = []
627
r = self._dispatch(scope,'family',[],['inet','inet4','inet6','minimal','all'])
628
if r is False: return False
633
def _set_family_inet4 (self,scope,tokens):
635
self._error = 'inet/inet4 can not be used with all or minimal'
640
if safi == 'unicast':
641
scope[-1]['families'].append((AFI(AFI.ipv4),SAFI(SAFI.unicast)))
642
elif safi == 'multicast':
643
scope[-1]['families'].append((AFI(AFI.ipv4),SAFI(SAFI.multicast)))
644
elif safi == 'nlri-mpls':
645
scope[-1]['families'].append((AFI(AFI.ipv4),SAFI(SAFI.nlri_mpls)))
646
elif safi == 'mpls-vpn':
647
scope[-1]['families'].append((AFI(AFI.ipv4),SAFI(SAFI.mpls_vpn)))
648
elif safi in ('flow-vpnv4','flow'):
649
scope[-1]['families'].append((AFI(AFI.ipv4),SAFI(SAFI.flow_ipv4)))
654
def _set_family_inet6 (self,scope,tokens):
656
self._error = 'inet6 can not be used with all or minimal'
661
if safi == 'unicast':
662
scope[-1]['families'].append((AFI(AFI.ipv6),SAFI(SAFI.unicast)))
663
elif safi == 'mpls-vpn':
664
scope[-1]['families'].append((AFI(AFI.ipv6),SAFI(SAFI.mpls_vpn)))
669
def _set_family_minimal (self,scope,tokens):
670
if scope[-1]['families']:
671
self._error = 'minimal can not be used with any other options'
674
scope[-1]['families'] = 'minimal'
678
def _set_family_all (self,scope,tokens):
679
if scope[-1]['families']:
680
self._error = 'all can not be used with any other options'
683
scope[-1]['families'] = 'all'
689
def _multi_capability (self,scope,tokens):
690
# we know all the families we should use
691
self._capability = False
693
r = self._dispatch(scope,'capability',[],['route-refresh','graceful-restart','multi-session','add-path','asn4'])
694
if r is False: return False
698
def _set_routerefresh (self,scope,command,value):
699
scope[-1][command] = True
702
def _set_gracefulrestart (self,scope,command,value):
704
scope[-1][command] = None
707
# README: Should it be a subclass of int ?
708
grace = int(value[0])
710
raise ValueError('graceful-restart can not be negative')
711
if grace >= pow(2,16):
712
raise ValueError('graceful-restart must be smaller than %d' % pow(2,16))
713
scope[-1][command] = grace
716
self._error = '"%s" is an invalid graceful-restart time' % ' '.join(value)
721
def _set_multisession (self,scope,command,value):
722
scope[-1][command] = True
725
def _set_addpath (self,scope,command,value):
727
ap = value[0].lower()
729
if ap.endswith('receive'):
731
if ap.startswith('send'):
733
if not apv and ap not in ('disable','disabled'):
734
raise ValueError('invalid add-path')
735
scope[-1][command] = apv
738
self._error = '"%s" is an invalid add-path' % ' '.join(value)
742
def _set_asn4 (self,scope,command,value):
745
scope[-1][command] = True
747
asn4 = value[0].lower()
748
if asn4 in ('disable','disabled'):
749
scope[-1][command] = False
751
if asn4 in ('enable','enabled'):
752
scope[-1][command] = True
754
self._error = '"%s" is an invalid asn4 parameter options are enable (default) and disable)' % ' '.join(value)
757
self._error = '"%s" is an invalid asn4 parameter options are enable (default) and disable)' % ' '.join(value)
762
# route grouping with watchdog
764
def _route_watchdog (self,scope,tokens):
766
if w.lower() in ['announce','withdraw']:
767
raise ValueError('invalid watchdog name %s' % w)
769
scope[-1]['routes'][-1].attributes.add(Watchdog(w))
772
self._error = self._str_route_error
776
def _route_withdraw (self,scope,tokens):
778
scope[-1]['routes'][-1].attributes.add(Withdrawn())
781
self._error = self._str_route_error
787
def _multi_group (self,scope,address):
790
r = self._dispatch(scope,'group',['static','flow','neighbor','process','family','capability'],['description','router-id','local-address','local-as','peer-as','hold-time','add-path','graceful-restart','md5','ttl-security','multi-session','group-updates','route-refresh','asn4'])
797
def _make_neighbor (self,scope):
798
# we have local_scope[-2] as the group template and local_scope[-1] as the peer specific
800
for key,content in scope[-2].iteritems():
801
if key not in scope[-1]:
802
scope[-1][key] = deepcopy(content)
804
self.logger.configuration("\nPeer configuration complete :")
805
for _key in scope[-1].keys():
806
stored = scope[-1][_key]
807
if hasattr(stored,'__iter__'):
808
for category in scope[-1][_key]:
809
for _line in pformat(str(category),3,3,3).split('\n'):
810
self.logger.configuration(" %s: %s" %(_key,_line))
812
for _line in pformat(str(stored),3,3,3).split('\n'):
813
self.logger.configuration(" %s: %s" %(_key,_line))
814
self.logger.configuration("\n")
816
neighbor = Neighbor()
817
for local_scope in scope:
818
v = local_scope.get('router-id','')
819
if v: neighbor.router_id = v
820
v = local_scope.get('peer-address','')
821
if v: neighbor.peer_address = v
822
v = local_scope.get('local-address','')
823
if v: neighbor.local_address = v
824
v = local_scope.get('local-as','')
825
if v: neighbor.local_as = v
826
v = local_scope.get('peer-as','')
827
if v: neighbor.peer_as = v
828
v = local_scope.get('hold-time','')
829
if v: neighbor.hold_time = v
831
v = local_scope.get('routes',[])
833
# This add the family to neighbor.families()
834
neighbor.add_route(route)
836
for local_scope in (scope[0],scope[-1]):
837
neighbor.api.receive_packets |= local_scope.get('receive-packets',False)
838
neighbor.api.send_packets |= local_scope.get('send-packets',False)
839
neighbor.api.receive_routes |= local_scope.get('receive-routes',False)
840
neighbor.api.neighbor_changes |= local_scope.get('neighbor-changes',False)
842
local_scope = scope[-1]
843
neighbor.description = local_scope.get('description','')
845
neighbor.md5 = local_scope.get('md5',None)
846
neighbor.ttl = local_scope.get('ttl-security',None)
847
neighbor.group_updates = local_scope.get('group-updates',False)
849
neighbor.route_refresh = local_scope.get('route-refresh',0)
850
neighbor.graceful_restart = local_scope.get('graceful-restart',0)
851
if neighbor.graceful_restart is None:
852
# README: Should it be a subclass of int ?
853
neighbor.graceful_restart = int(neighbor.hold_time)
854
neighbor.multisession = local_scope.get('multi-session',False)
855
neighbor.add_path = local_scope.get('add-path','')
856
neighbor.asn4 = local_scope.get('asn4',True)
858
missing = neighbor.missing()
860
self._error = 'incomplete neighbor, missing %s' % missing
863
if neighbor.local_address.afi != neighbor.peer_address.afi:
864
self._error = 'local-address and peer-address must be of the same family'
867
if neighbor.peer_address.ip in self._neighbor:
868
self._error = 'duplicate peer definition %s' % neighbor.peer_address.ip
872
openfamilies = local_scope.get('families','everything')
873
# announce every family we known
874
if neighbor.multisession and openfamilies == 'everything':
875
# announce what is needed, and no more, no need to have lots of TCP session doing nothing
876
families = neighbor.families()
877
elif openfamilies in ('all','everything'):
878
families = known_families()
879
# only announce what you have as routes
880
elif openfamilies == 'minimal':
881
families = neighbor.families()
883
families = openfamilies
885
# check we are not trying to announce routes without the right MP announcement
886
for family in neighbor.families():
887
if family not in families:
889
self._error = 'Trying to announce a route of type %s,%s when we are not announcing the family to our peer' % (afi,safi)
893
# add the families to the list of families known
894
initial_families = list(neighbor.families())
895
for family in families:
896
if family not in initial_families :
897
# we are modifying the data used by .families() here
898
neighbor.add_family(family)
900
# create one neighbor object per family for multisession
901
if neighbor.multisession:
902
for family in neighbor.families():
903
m_neighbor = deepcopy(neighbor)
904
for f in neighbor.families():
907
m_neighbor.remove_family_and_routes(f)
908
self._neighbor[m_neighbor.name()] = m_neighbor
910
self._neighbor[neighbor.name()] = neighbor
912
for line in str(neighbor).split('\n'):
913
self.logger.configuration(line)
914
self.logger.configuration("\n")
919
def _multi_neighbor (self,scope,tokens):
921
self._error = 'syntax: neighbor <ip> { <options> }'
928
scope[-1]['peer-address'] = Inet(*inet(address))
930
self._error = '"%s" is not a valid IP address' % address
934
r = self._dispatch(scope,'neighbor',['static','flow','process','family','capability'],['description','router-id','local-address','local-as','peer-as','hold-time','add-path','graceful-restart','md5','ttl-security','multi-session','group-updates','asn4'])
935
if r is False: return False
936
if r is None: return True
940
def _set_router_id (self,scope,command,value):
942
ip = RouterID(value[0])
943
except (IndexError,ValueError):
944
self._error = '"%s" is an invalid IP address' % ' '.join(value)
947
scope[-1][command] = ip
950
def _set_description (self,scope,tokens):
951
text = ' '.join(tokens)
952
if len(text) < 2 or text[0] != '"' or text[-1] != '"' or text[1:-1].count('"'):
953
self._error = 'syntax: description "<description>"'
956
scope[-1]['description'] = text[1:-1]
959
# will raise ValueError if the ASN is not correct
960
def _newASN (self,value):
962
high,low = value.split('.',1)
963
asn = (int(high) << 16) + int(low)
968
def _set_asn (self,scope,command,value):
970
scope[-1][command] = self._newASN(value[0])
973
self._error = '"%s" is an invalid ASN' % ' '.join(value)
977
def _set_ip (self,scope,command,value):
979
ip = Inet(*inet(value[0]))
980
except (IndexError,ValueError):
981
self._error = '"%s" is an invalid IP address' % ' '.join(value)
984
scope[-1][command] = ip
987
def _set_holdtime (self,scope,command,value):
989
holdtime = HoldTime(value[0])
990
if holdtime < 3 and holdtime != 0:
991
raise ValueError('holdtime must be zero or at least three seconds')
992
if holdtime >= pow(2,16):
993
raise ValueError('holdtime must be smaller than %d' % pow(2,16))
994
scope[-1][command] = holdtime
997
self._error = '"%s" is an invalid hold-time' % ' '.join(value)
1001
def _set_md5 (self,scope,command,value):
1003
if len(md5) > 2 and md5[0] == md5[-1] and md5[0] in ['"',"'"]:
1006
self._error = 'md5 password must be no larger than 80 characters'
1007
if self.debug: raise
1010
self._error = 'md5 requires the md5 password as an argument (quoted or unquoted). FreeBSD users should use "kernel" as the argument.'
1011
if self.debug: raise
1013
scope[-1][command] = md5
1016
def _set_ttl (self,scope,command,value):
1018
scope[-1][command] = self.TTL_SECURITY
1021
# README: Should it be a subclass of int ?
1024
raise ValueError('ttl-security can not be negative')
1026
raise ValueError('ttl must be smaller than 256')
1027
scope[-1][command] = ttl
1030
self._error = '"%s" is an invalid ttl-security' % ' '.join(value)
1031
if self.debug: raise
1035
def _set_group_updates (self,scope,command,value):
1036
scope[-1][command] = True
1039
# Group Static ................
1041
def _multi_static (self,scope,tokens):
1042
if len(tokens) != 0:
1043
self._error = 'syntax: static { route; route; ... }'
1044
if self.debug: raise
1047
r = self._dispatch(scope,'static',['route',],['route',])
1048
if r is False: return False
1049
if r is None: return True
1051
# Group Route ........
1053
def _split_last_route (self,scope):
1054
# if the route does not need to be broken in smaller routes, return
1055
route = scope[-1]['routes'][-1]
1056
if not AttributeID.INTERNAL_SPLIT in route.attributes:
1059
# ignore if the request is for an aggregate, or the same size
1060
mask = route.nlri.mask
1061
split = route.attributes[AttributeID.INTERNAL_SPLIT]
1065
# get a local copy of the route
1066
route = scope[-1]['routes'].pop(-1)
1068
# calculate the number of IP in the /<size> of the new route
1069
increment = pow(2,(len(route.nlri.packed)*8) - split)
1070
# how many new routes are we going to create from the initial one
1071
number = pow(2,split - route.nlri.mask)
1073
# convert the IP into a integer/long
1075
for c in route.nlri.packed:
1079
afi = route.nlri.afi
1080
safi = route.nlri.safi
1082
labels = route.nlri.labels
1084
path_info = route.nlri.path_info
1088
# generate the new routes
1089
for _ in range(number):
1091
# update ip to the next route, this recalculate the "ip" field of the Inet class
1092
r.nlri = NLRI(afi,safi,pack_int(afi,ip,split),split)
1093
r.nlri.labels = labels
1095
r.nlri.path_info = path_info
1099
scope[-1]['routes'].append(r)
1103
def _insert_static_route (self,scope,tokens):
1107
self._error = self._str_route_error
1108
if self.debug: raise
1111
ip,mask = ip.split('/')
1115
route = Route(NLRI(*inet(ip),mask=mask))
1117
self._error = self._str_route_error
1118
if self.debug: raise
1121
if 'routes' not in scope[-1]:
1122
scope[-1]['routes'] = []
1124
scope[-1]['routes'].append(route)
1127
def pop_last_static_route (self,scope):
1128
route = scope[-1]['routes'][-1]
1129
scope[-1]['routes'] = scope[-1]['routes'][:-1]
1132
def remove_route (self,route,scope):
1133
for r in scope[-1]['routes']:
1135
scope[-1]['routes'].remove(r)
1139
def _check_static_route (self,scope):
1140
route = scope[-1]['routes'][-1]
1141
if not route.attributes.has(AttributeID.NEXT_HOP):
1142
self._error = 'syntax: route IP/MASK { next-hop IP; }'
1143
if self.debug: raise
1147
def _multi_static_route (self,scope,tokens):
1148
if len(tokens) != 1:
1149
self._error = self._str_route_error
1150
if self.debug: raise
1153
if not self._insert_static_route(scope,tokens):
1157
r = self._dispatch(scope,'route',[],['next-hop','origin','as-path','as-sequence','med','local-preference','atomic-aggregate','aggregator','path-information','community','originator-id','cluster-list','extended-community','split','label','rd','route-distinguisher','watchdog','withdraw'])
1158
if r is False: return False
1159
if r is None: return self._split_last_route(scope)
1161
def _single_static_route (self,scope,tokens):
1165
have_next_hop = False
1167
if not self._insert_static_route(scope,tokens):
1171
command = tokens.pop(0)
1172
if command == 'withdraw':
1173
if self._route_withdraw(scope,tokens):
1180
if command == 'next-hop':
1181
if self._route_next_hop(scope,tokens):
1182
have_next_hop = True
1185
if command == 'origin':
1186
if self._route_origin(scope,tokens):
1189
if command == 'as-path':
1190
if self._route_aspath(scope,tokens):
1193
if command == 'as-sequence':
1194
if self._route_aspath(scope,tokens):
1197
if command == 'med':
1198
if self._route_med(scope,tokens):
1201
if command == 'local-preference':
1202
if self._route_local_preference(scope,tokens):
1205
if command == 'atomic-aggregate':
1206
if self._route_atomic_aggregate(scope,tokens):
1209
if command == 'aggregator':
1210
if self._route_aggregator(scope,tokens):
1213
if command == 'path-information':
1214
if self._route_path_information(scope,tokens):
1217
if command == 'community':
1218
if self._route_community(scope,tokens):
1221
if command == 'originator-id':
1222
if self._route_originator_id(scope,tokens):
1225
if command == 'cluster-list':
1226
if self._route_cluster_list(scope,tokens):
1229
if command == 'extended-community':
1230
if self._route_extended_community(scope,tokens):
1233
if command == 'split':
1234
if self._route_split(scope,tokens):
1237
if command == 'label':
1238
if self._route_label(scope,tokens):
1241
if command in ('rd','route-distinguisher'):
1242
if self._route_rd(scope,tokens):
1245
if command == 'watchdog':
1246
if self._route_watchdog(scope,tokens):
1251
if not have_next_hop:
1252
self._error = 'every route requires a next-hop'
1253
if self.debug: raise
1256
return self._split_last_route(scope)
1260
def _route_next_hop (self,scope,tokens):
1262
# next-hop self is unsupported
1264
if ip.lower() == 'self':
1265
la = scope[-1]['local-address']
1266
nh = la.afi,la.safi,la.pack()
1269
scope[-1]['routes'][-1].attributes.add(cachedNextHop(*nh))
1272
self._error = self._str_route_error
1273
if self.debug: raise
1276
def _route_origin (self,scope,tokens):
1277
data = tokens.pop(0).lower()
1279
scope[-1]['routes'][-1].attributes.add(Origin(Origin.IGP))
1282
scope[-1]['routes'][-1].attributes.add(Origin(Origin.EGP))
1284
if data == 'incomplete':
1285
scope[-1]['routes'][-1].attributes.add(Origin(Origin.INCOMPLETE))
1287
self._error = self._str_route_error
1288
if self.debug: raise
1291
def _route_aspath (self,scope,tokens):
1301
self._error = self._str_route_error
1302
if self.debug: raise
1309
self._error = self._str_route_error
1310
if self.debug: raise
1314
as_set.append(self._newASN(asn))
1319
as_seq.append(self._newASN(asn))
1321
as_seq.append(self._newASN(asn))
1323
self._error = self._str_route_error
1324
if self.debug: raise
1326
scope[-1]['routes'][-1].attributes.add(ASPath(as_seq,as_set))
1329
def _route_med (self,scope,tokens):
1331
scope[-1]['routes'][-1].attributes.add(MED(pack('!L',int(tokens.pop(0)))))
1334
self._error = self._str_route_error
1335
if self.debug: raise
1338
def _route_local_preference (self,scope,tokens):
1340
scope[-1]['routes'][-1].attributes.add(LocalPreference(pack('!L',int(tokens.pop(0)))))
1343
self._error = self._str_route_error
1344
if self.debug: raise
1347
def _route_atomic_aggregate (self,scope,tokens):
1349
scope[-1]['routes'][-1].attributes.add(AtomicAggregate())
1352
self._error = self._str_route_error
1353
if self.debug: raise
1356
def _route_aggregator (self,scope,tokens):
1359
if tokens.pop(0) != '(':
1360
raise ValueError('invalid aggregator syntax')
1361
asn,address = tokens.pop(0).split(':')
1362
if tokens.pop(0) != ')':
1363
raise ValueError('invalid aggregator syntax')
1365
local_address = RouterID(address)
1367
local_as = scope[-1]['local-as']
1368
local_address = scope[-1]['local-address']
1369
except (ValueError,IndexError):
1370
self._error = self._str_route_error
1371
if self.debug: raise
1374
self._error = 'local-as and/or local-address missing from neighbor/group to make aggregator'
1375
if self.debug: raise
1378
self._error = self._str_route_error
1379
if self.debug: raise
1382
scope[-1]['routes'][-1].attributes.add(Aggregator(local_as.pack(True)+local_address.pack()))
1385
def _route_path_information (self,scope,tokens):
1389
scope[-1]['routes'][-1].nlri.path_info = PathInfo(integer=int(pi))
1391
scope[-1]['routes'][-1].nlri.path_info = PathInfo(ip=pi)
1394
self._error = self._str_route_error
1395
if self.debug: raise
1398
def _parse_community (self,scope,data):
1399
separator = data.find(':')
1401
prefix = int(data[:separator])
1402
suffix = int(data[separator+1:])
1403
if prefix >= pow(2,16):
1404
raise ValueError('invalid community %s (prefix too large)' % data)
1405
if suffix >= pow(2,16):
1406
raise ValueError('invalid community %s (suffix too large)' % data)
1407
return cachedCommunity(pack('!L',(prefix<<16) + suffix))
1408
elif len(data) >=2 and data[1] in 'xX':
1409
value = long(data,16)
1410
if value >= pow(2,32):
1411
raise ValueError('invalid community %s (too large)' % data)
1412
return cachedCommunity(pack('!L',value))
1415
if low == 'no-export':
1416
return cachedCommunity(Community.NO_EXPORT)
1417
elif low == 'no-advertise':
1418
return cachedCommunity(Community.NO_ADVERTISE)
1419
elif low == 'no-export-subconfed':
1420
return cachedCommunity(Community.NO_EXPORT_SUBCONFED)
1421
# no-peer is not a correct syntax but I am sure someone will make the mistake :)
1422
elif low == 'nopeer' or low == 'no-peer':
1423
return cachedCommunity(Community.NO_PEER)
1424
elif data.isdigit():
1425
value = unpack('!L',data)[0]
1426
if value >= pow(2,32):
1427
raise ValueError('invalid community %s (too large)' % data)
1428
return cachedCommunity(pack('!L',value))
1430
raise ValueError('invalid community name %s' % data)
1432
def _route_originator_id (self,scope,tokens):
1434
scope[-1]['routes'][-1].attributes.add(OriginatorID(*inet(tokens.pop(0))))
1437
self._error = self._str_route_error
1438
if self.debug: raise
1441
def _route_cluster_list (self,scope,tokens):
1443
clusterid = tokens.pop(0)
1445
if clusterid == '[':
1448
clusterid = tokens.pop(0)
1450
self._error = self._str_route_error
1451
if self.debug: raise
1453
if clusterid == ']':
1455
_list += ''.join([chr(int(_)) for _ in clusterid.split('.')])
1457
_list = ''.join([chr(int(_)) for _ in clusterid.split('.')])
1459
raise ValueError('no cluster-id in the cluster-list')
1460
clusterlist = ClusterList(_list)
1462
self._error = self._str_route_error
1463
if self.debug: raise
1465
scope[-1]['routes'][-1].attributes.add(clusterlist)
1468
def _route_community (self,scope,tokens):
1469
communities = Communities()
1470
community = tokens.pop(0)
1472
if community == '[':
1475
community = tokens.pop(0)
1477
self._error = self._str_route_error
1478
if self.debug: raise
1480
if community == ']':
1482
communities.add(self._parse_community(scope,community))
1484
communities.add(self._parse_community(scope,community))
1486
self._error = self._str_route_error
1487
if self.debug: raise
1489
scope[-1]['routes'][-1].attributes.add(communities)
1492
def _parse_extended_community (self,scope,data):
1493
if data[:2].lower() == '0x':
1496
for i in range(2,len(data),2):
1497
raw += chr(int(data[i:i+2],16))
1499
raise ValueError('invalid extended community %s' % data)
1501
raise ValueError('invalid extended community %s' % data)
1502
return ECommunity(raw)
1503
elif data.count(':'):
1504
return to_ExtendedCommunity(data)
1506
raise ValueError('invalid extended community %s - lc+gc' % data)
1508
def _route_extended_community (self,scope,tokens):
1509
extended_communities = ECommunities()
1510
extended_community = tokens.pop(0)
1512
if extended_community == '[':
1515
extended_community = tokens.pop(0)
1517
self._error = self._str_route_error
1518
if self.debug: raise
1520
if extended_community == ']':
1522
extended_communities.add(self._parse_extended_community(scope,extended_community))
1524
extended_communities.add(self._parse_extended_community(scope,extended_community))
1526
self._error = self._str_route_error
1527
if self.debug: raise
1529
scope[-1]['routes'][-1].attributes.add(extended_communities)
1533
def _route_split (self,scope,tokens):
1535
size = tokens.pop(0)
1536
if not size or size[0] != '/':
1537
raise ValueError('route "as" require a CIDR')
1538
scope[-1]['routes'][-1].attributes.add(Split(int(size[1:])))
1541
self._error = self._str_route_error
1542
if self.debug: raise
1545
def _route_label (self,scope,tokens):
1547
label = tokens.pop(0)
1552
label = tokens.pop(0)
1554
self._error = self._str_route_error
1555
if self.debug: raise
1559
labels.append(int(label))
1561
labels.append(int(label))
1563
self._error = self._str_route_error
1564
if self.debug: raise
1567
nlri = scope[-1]['routes'][-1].nlri
1568
if not nlri.safi.has_label():
1569
nlri.safi = SAFI(SAFI.nlri_mpls)
1570
nlri.labels = Labels(labels)
1573
def _route_rd (self,scope,tokens):
1576
data = tokens.pop(0)
1578
self._error = self._str_route_error
1579
if self.debug: raise
1582
separator = data.find(':')
1584
prefix = data[:separator]
1585
suffix = int(data[separator+1:])
1588
bytes = [chr(0),chr(1)]
1589
bytes.extend([chr(int(_)) for _ in prefix.split('.')])
1590
bytes.extend([suffix>>8,suffix&0xFF])
1593
number = int(prefix)
1594
if number < pow(2,16) and suffix < pow(2,32):
1595
rd = chr(0) + chr(0) + pack('!H',number) + pack('!L',suffix)
1596
elif number < pow(2,32) and suffix < pow(2,16):
1597
rd = chr(0) + chr(2) + pack('!L',number) + pack('!H',suffix)
1599
raise ValueError('invalid route-distinguisher %s' % data)
1601
nlri = scope[-1]['routes'][-1].nlri
1602
# overwrite nlri-mpls
1603
nlri.safi = SAFI(SAFI.mpls_vpn)
1604
nlri.rd = RouteDistinguisher(rd)
1607
self._error = self._str_route_error
1608
if self.debug: raise
1612
# Group Flow ........
1614
def _multi_flow (self,scope,tokens):
1615
if len(tokens) != 0:
1616
self._error = self._str_flow_error
1617
if self.debug: raise
1621
r = self._dispatch(scope,'flow',['route',],[])
1622
if r is False: return False
1626
def _insert_flow_route (self,scope,tokens=None):
1627
if self._flow_state != 'out':
1628
self._error = self._str_flow_error
1629
if self.debug: raise
1632
self._flow_state = 'match'
1637
self._error = self._str_flow_error
1638
if self.debug: raise
1641
if 'routes' not in scope[-1]:
1642
scope[-1]['routes'] = []
1644
scope[-1]['routes'].append(flow)
1647
def _check_flow_route (self,scope):
1648
self.logger.configuration('warning: no check on flows are implemented')
1651
def _multi_flow_route (self,scope,tokens):
1653
self._error = self._str_flow_error
1654
if self.debug: raise
1657
if not self._insert_flow_route(scope):
1661
r = self._dispatch(scope,'flow-route',['match','then'],[])
1662
if r is False: return False
1665
if self._flow_state != 'out':
1666
self._error = self._str_flow_error
1667
if self.debug: raise
1672
# ..........................................
1674
def _multi_match (self,scope,tokens):
1675
if len(tokens) != 0:
1676
self._error = self._str_flow_error
1677
if self.debug: raise
1680
if self._flow_state != 'match':
1681
self._error = self._str_flow_error
1682
if self.debug: raise
1685
self._flow_state = 'then'
1688
r = self._dispatch(scope,'flow-match',[],['source','destination','port','source-port','destination-port','protocol','tcp-flags','icmp-type','icmp-code','fragment','dscp','packet-length'])
1689
if r is False: return False
1693
def _multi_then (self,scope,tokens):
1694
if len(tokens) != 0:
1695
self._error = self._str_flow_error
1696
if self.debug: raise
1699
if self._flow_state != 'then':
1700
self._error = self._str_flow_error
1701
if self.debug: raise
1704
self._flow_state = 'out'
1707
r = self._dispatch(scope,'flow-then',[],['discard','rate-limit','redirect','community'])
1708
if r is False: return False
1714
def _flow_source (self,scope,tokens):
1716
ip,nm = tokens.pop(0).split('/')
1717
scope[-1]['routes'][-1].add_and(Source(ip,nm))
1720
self._error = self._str_route_error
1721
if self.debug: raise
1724
def _flow_destination (self,scope,tokens):
1726
ip,nm = tokens.pop(0).split('/')
1727
scope[-1]['routes'][-1].add_and(Destination(ip,nm))
1730
self._error = self._str_route_error
1731
if self.debug: raise
1734
# to parse the port configuration of flow
1736
def _operator (self,string):
1738
if string[0] == '=':
1739
return NumericOperator.EQ,string[1:]
1740
elif string[0] == '>':
1741
operator = NumericOperator.GT
1742
elif string[0] == '<':
1743
operator = NumericOperator.LT
1745
raise ValueError('Invalid operator in test %s' % string)
1746
if string[1] == '=':
1747
operator += NumericOperator.EQ
1748
return operator,string[2:]
1750
return operator,string[1:]
1752
raise('Invalid expression (too short) %s' % string)
1754
def _value (self,string):
1761
return string[:l],string[l:]
1763
# parse =80 or >80 or <25 or &>10<20
1764
def _flow_generic_expression (self,scope,tokens,converter,klass):
1767
AND = BinaryOperator.NOP
1769
operator,_ = self._operator(test)
1770
value,test = self._value(_)
1771
number = converter(value)
1772
scope[-1]['routes'][-1].add_or(klass(AND|operator,number))
1775
AND = BinaryOperator.AND
1778
raise ValueError("Can not finish an expresion on an &")
1780
raise ValueError("Unknown binary operator %s" % test[0])
1782
except ValueError,e:
1783
self._error = self._str_route_error + str(e)
1784
if self.debug: raise
1787
# parse [ content1 content2 content3 ]
1788
def _flow_generic_list (self,scope,tokens,converter,klass):
1789
name = tokens.pop(0)
1790
AND = BinaryOperator.NOP
1794
name = tokens.pop(0)
1801
number = converter(name)
1802
scope[-1]['routes'][-1].add_or(klass(NumericOperator.EQ|AND,number))
1804
self._error = self._str_flow_error
1805
if self.debug: raise
1811
number = converter(name)
1812
scope[-1]['routes'][-1].add_or(klass(NumericOperator.EQ|AND,number))
1814
self._error = self._str_flow_error
1815
if self.debug: raise
1819
def _flow_generic_condition (self,scope,tokens,converter,klass):
1820
if tokens[0][0] in ['=','>','<']:
1821
return self._flow_generic_expression(scope,tokens,converter,klass)
1822
return self._flow_generic_list(scope,tokens,converter,klass)
1824
def _flow_route_anyport (self,scope,tokens):
1825
return self._flow_generic_condition(scope,tokens,convert_port,AnyPort)
1827
def _flow_route_source_port (self,scope,tokens):
1828
return self._flow_generic_condition(scope,tokens,convert_port,SourcePort)
1830
def _flow_route_destination_port (self,scope,tokens):
1831
return self._flow_generic_condition(scope,tokens,convert_port,DestinationPort)
1833
def _flow_route_packet_length (self,scope,tokens):
1834
return self._flow_generic_condition(scope,tokens,convert_length,PacketLength)
1836
def _flow_route_tcp_flags (self,scope,tokens):
1837
return self._flow_generic_list(scope,tokens,NamedTCPFlags,TCPFlag)
1839
def _flow_route_protocol (self,scope,tokens):
1840
return self._flow_generic_list(scope,tokens,NamedProtocol,IPProtocol)
1842
def _flow_route_icmp_type (self,scope,tokens):
1843
return self._flow_generic_list(scope,tokens,NamedICMPType,ICMPType)
1845
def _flow_route_icmp_code (self,scope,tokens):
1846
return self._flow_generic_list(scope,tokens,NamedICMPCode,ICMPCode)
1848
def _flow_route_fragment (self,scope,tokens):
1849
return self._flow_generic_list(scope,tokens,NamedFragment,Fragment)
1851
def _flow_route_dscp (self,scope,tokens):
1852
return self._flow_generic_condition(scope,tokens,convert_dscp,DSCP)
1854
def _flow_route_discard (self,scope,tokens):
1855
# README: We are setting the ASN as zero as that what Juniper (and Arbor) did when we created a local flow route
1857
scope[-1]['routes'][-1].add_action(to_FlowTrafficRate(ASN(0),0))
1860
self._error = self._str_route_error
1861
if self.debug: raise
1864
def _flow_route_rate_limit (self,scope,tokens):
1865
# README: We are setting the ASN as zero as that what Juniper (and Arbor) did when we created a local flow route
1867
speed = int(tokens[0])
1868
if speed < 9600 and speed != 0:
1869
self.logger.warning("rate-limiting flow under 9600 bytes per seconds may not work",'configuration')
1870
if speed > 1000000000000:
1871
speed = 1000000000000
1872
self.logger.warning("rate-limiting changed for 1 000 000 000 000 bytes from %s" % tokens[0],'configuration')
1873
scope[-1]['routes'][-1].add_action(to_FlowTrafficRate(ASN(0),speed))
1876
self._error = self._str_route_error
1877
if self.debug: raise
1880
def _flow_route_redirect (self,scope,tokens):
1881
# README: We are setting the ASN as zero as that what Juniper (and Arbor) did when we created a local flow route
1883
prefix,suffix=tokens[0].split(':',1)
1884
if prefix.count('.'):
1885
ip = prefix.split('.')
1887
raise ValueError('invalid IP %s' % prefix)
1891
ipn += int(ip.pop(0))
1892
number = int(suffix)
1893
if number >= pow(2,16):
1894
raise ValueError('number is too large, max 16 bits %s' % number)
1895
scope[-1]['routes'][-1].add_action(to_FlowRedirectIP(ipn,number))
1899
route_target = int(suffix)
1900
if asn >= pow(2,16):
1901
raise ValueError('asn is a 32 bits number, it can only be 16 bit %s' % route_target)
1902
if route_target >= pow(2,32):
1903
raise ValueError('route target is a 32 bits number, value too large %s' % route_target)
1904
scope[-1]['routes'][-1].add_action(to_FlowRedirectASN(asn,route_target))
1907
self._error = self._str_route_error
1908
if self.debug: raise
1912
def decode (self,route):
1913
# self check to see if we can decode what we encode
1914
from exabgp.bgp.message.update import Update
1915
from exabgp.bgp.message.open import Open
1916
from exabgp.bgp.message.open.capability import Capabilities
1917
from exabgp.bgp.message.open.capability.negotiated import Negotiated
1918
from exabgp.bgp.message.open.capability.id import CapabilityID
1920
self.logger.info('\ndecoding routes in configuration','parser')
1922
if route.startswith('F'*32):
1923
route = route[19*2:]
1924
# prepend = route[:19*2]
1928
n = self.neighbor[self.neighbor.keys()[0]]
1931
for f in known_families():
1933
path[f] = n.add_path
1935
capa = Capabilities().new(n,False)
1936
capa[CapabilityID.ADD_PATH] = path
1937
capa[CapabilityID.MULTIPROTOCOL_EXTENSIONS] = n.families()
1939
o1 = Open().new(4,n.local_as,str(n.local_address),capa,180)
1940
o2 = Open().new(4,n.peer_as,str(n.peer_address),capa,180)
1941
negotiated = Negotiated()
1943
negotiated.received(o2)
1946
injected = ''.join(chr(int(_,16)) for _ in (route[i*2:(i*2)+2] for i in range(len(route)/2)))
1947
# This does not take the BGP header - let's assume we will not break that :)
1948
update = Update().factory(negotiated,injected)
1949
self.logger.info('','parser')
1950
for route in update.routes:
1951
self.logger.info('decoded route %s' % route.extensive(),'parser')
1957
# injected = ['0x0', '0x0', '0x0', '0x2e', '0x40', '0x1', '0x1', '0x0', '0x40', '0x2', '0x8', '0x2', '0x3', '0x78', '0x14', '0xab', '0xe9', '0x5b', '0xa0', '0x40', '0x3', '0x4', '0x52', '0xdb', '0x0', '0x4f', '0xc0', '0x8', '0x8', '0x78', '0x14', '0xc9', '0x46', '0x78', '0x14', '0xfd', '0xea', '0xe0', '0x11', '0xa', '0x2', '0x2', '0x0', '0x0', '0xab', '0xe9', '0x0', '0x3', '0x5', '0x54', '0x17', '0x9f', '0x65', '0x9e', '0x15', '0x9f', '0x65', '0x80', '0x18', '0x9f', '0x65', '0x9f']
1959
# injected = '\x00\x00\x00\x07\x90\x0f\x00\x03\x00\x02\x01'
1961
def selfcheck (self):
1962
# self check to see if we can decode what we encode
1963
from exabgp.structure.utils import dump
1964
from exabgp.bgp.message.update import Update
1965
from exabgp.bgp.message.open import Open
1966
from exabgp.bgp.message.open.capability import Capabilities
1967
from exabgp.bgp.message.open.capability.negotiated import Negotiated
1968
from exabgp.bgp.message.open.capability.id import CapabilityID
1970
self.logger.info('\ndecoding routes in configuration','parser')
1972
n = self.neighbor[self.neighbor.keys()[0]]
1975
for f in known_families():
1977
path[f] = n.add_path
1979
capa = Capabilities().new(n,False)
1980
capa[CapabilityID.ADD_PATH] = path
1981
capa[CapabilityID.MULTIPROTOCOL_EXTENSIONS] = n.families()
1983
o1 = Open().new(4,n.local_as,str(n.local_address),capa,180)
1984
o2 = Open().new(4,n.peer_as,str(n.peer_address),capa,180)
1985
negotiated = Negotiated()
1987
negotiated.received(o2)
1990
for nei in self.neighbor.keys():
1991
for family in self.neighbor[nei].families():
1992
if not family in self.neighbor[nei]._routes:
1994
for route in self.neighbor[nei]._routes[family]:
1995
str1 = route.extensive()
1996
update = Update().new([route])
1997
packed = update.announce(negotiated)
1998
self.logger.info('parsed route requires %d updates' % len(packed),'parser')
2000
self.logger.info('update size is %d' % len(pack),'parser')
2001
# This does not take the BGP header - let's assume we will not break that :)
2002
update = Update().factory(negotiated,pack[19:])
2003
self.logger.info('','parser')
2004
for route in update.routes:
2005
str2 = route.extensive()
2006
self.logger.info('parsed route %s' % str1,'parser')
2007
self.logger.info('recoded route %s' % str2,'parser')
2008
self.logger.info('recoded hex %s\n' % dump(pack),'parser')