~ubuntu-branches/ubuntu/utopic/exabgp/utopic

« back to all changes in this revision

Viewing changes to lib/exabgp/structure/configuration.py

  • Committer: Package Import Robot
  • Author(s): Henry-Nicolas Tourneur
  • Date: 2014-03-08 19:07:00 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20140308190700-xjbibpg1g6001c9x
Tags: 3.3.1-1
* New upstream release
* Bump python minimal required version (2.7)
* Closes: #726066 Debian packaging improvements proposed by Vincent Bernat
* Closes: #703774 not existent rundir (/var/run/exabgp) after reboot

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# encoding: utf-8
2
 
"""
3
 
configuration.py
4
 
 
5
 
Created by Thomas Mangin on 2009-08-25.
6
 
Copyright (c) 2009-2013 Exa Networks. All rights reserved.
7
 
"""
8
 
 
9
 
import os
10
 
import sys
11
 
import stat
12
 
import time
13
 
 
14
 
from pprint import pformat
15
 
from copy import deepcopy
16
 
from struct import pack,unpack
17
 
 
18
 
from exabgp.structure.environment import environment
19
 
 
20
 
from exabgp.protocol.family import AFI,SAFI,known_families
21
 
 
22
 
from exabgp.bgp.neighbor import Neighbor
23
 
 
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
29
 
 
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
33
 
 
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
36
 
 
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
48
 
 
49
 
from exabgp.structure.log import Logger
50
 
 
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
55
 
 
56
 
class Split (int):
57
 
        ID = AttributeID.INTERNAL_SPLIT
58
 
        MULTIPLE = False
59
 
 
60
 
 
61
 
class Watchdog (str):
62
 
        ID = AttributeID.INTERNAL_WATCHDOG
63
 
        MULTIPLE = False
64
 
 
65
 
class Withdrawn (object):
66
 
        ID = AttributeID.INTERNAL_WITHDRAW
67
 
        MULTIPLE = False
68
 
 
69
 
 
70
 
def convert_length (data):
71
 
        number = int(data)
72
 
        if number > 65535:
73
 
                raise ValueError(Configuration._str_bad_length)
74
 
        return number
75
 
 
76
 
def convert_port (data):
77
 
        number = int(data)
78
 
        if number < 0 or number > 65535:
79
 
                raise ValueError(Configuration._str_bad_port)
80
 
        return number
81
 
 
82
 
def convert_dscp (data):
83
 
        number = int(data)
84
 
        if number < 0 or number > 65535:
85
 
                raise ValueError(Configuration._str_bad_dscp)
86
 
        return number
87
 
 
88
 
 
89
 
class Configuration (object):
90
 
        TTL_SECURITY = 255
91
 
 
92
 
#       '  hold-time 180;\n' \
93
 
 
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 .."
97
 
 
98
 
        _str_route_error = \
99
 
        'community, extended-communities and as-path can take a single community as parameter.\n' \
100
 
        'only next-hop is mandatory\n' \
101
 
        '\n' \
102
 
        'syntax:\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' \
109
 
        '  med 100;\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' \
118
 
        '  split /24\n' \
119
 
        '  watchdog watchdog-name\n' \
120
 
        '  withdraw\n' \
121
 
        '}\n' \
122
 
        '\n' \
123
 
        'syntax:\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' \
130
 
        ' med 100' \
131
 
        ' local-preference 100' \
132
 
        ' atomic-aggregate' \
133
 
        ' community 65000' \
134
 
        ' extended-community target:1234:5.6.7.8' \
135
 
        ' originator-id 10.0.0.10' \
136
 
        ' cluster-list 10.10.0.1' \
137
 
        ' label 150' \
138
 
        ' aggregator' \
139
 
        ' split /24' \
140
 
        ' watchdog watchdog-name' \
141
 
        ' withdraw' \
142
 
        ';\n' \
143
 
 
144
 
        _str_flow_error = \
145
 
        'syntax: flow {\n' \
146
 
        '          match {\n' \
147
 
        '             source 10.0.0.0/24;\n' \
148
 
        '             destination 10.0.1.0/24;\n' \
149
 
        '             port 25;\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;'
155
 
        '          }\n' \
156
 
        '          then {\n' \
157
 
        '             discard;\n' \
158
 
        '             rate-limit 9600;\n' \
159
 
        '             redirect 30740:12345;\n' \
160
 
        '             redirect 1.2.3.4:5678;\n' \
161
 
        '          }\n' \
162
 
        '        }\n\n' \
163
 
        'one or more match term, one action\n' \
164
 
        'fragment code is totally untested\n' \
165
 
 
166
 
        _str_process_error = \
167
 
        'syntax: process name-of-process {\n' \
168
 
        '          run /path/to/command with its args;\n' \
169
 
        '        }\n\n' \
170
 
 
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' \
175
 
        '          \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' \
182
 
        '        }\n'
183
 
 
184
 
        _str_capa_error = \
185
 
        'syntax: capability {\n' \
186
 
        '          asn4 enable|disable;\n' \
187
 
        '          add-path disable|send|receive|send/receive;\n' \
188
 
        '        }\n'
189
 
 
190
 
        def __init__ (self,fname,text=False):
191
 
                self.debug = environment.settings().debug.configuration
192
 
                self.api_encoder = environment.settings().api.encoder
193
 
 
194
 
                self.logger = Logger()
195
 
                self._text = text
196
 
                self._fname = fname
197
 
                self._clear()
198
 
 
199
 
        def _clear (self):
200
 
                self.process = {}
201
 
                self.neighbor = {}
202
 
                self.error = ''
203
 
                self._neighbor = {}
204
 
                self._scope = []
205
 
                self._location = ['root']
206
 
                self._line = []
207
 
                self._error = ''
208
 
                self._number = 1
209
 
                self._flow_state = 'out'
210
 
 
211
 
        # Public Interface
212
 
 
213
 
        def reload (self):
214
 
                try:
215
 
                        return self._reload()
216
 
                except KeyboardInterrupt:
217
 
                        self.error = 'configuration reload aborted by ^C or SIGINT'
218
 
                        return False
219
 
 
220
 
        def _reload (self):
221
 
                if self._text:
222
 
                        self._tokens = self._tokenise(self._fname.split('\n'))
223
 
                else:
224
 
                        try:
225
 
                                f = open(self._fname,'r')
226
 
                                self._tokens = self._tokenise(f)
227
 
                                f.close()
228
 
                        except IOError,e:
229
 
                                error = str(e)
230
 
                                if error.count(']'):
231
 
                                        self.error = error.split(']')[1].strip()
232
 
                                else:
233
 
                                        self.error = error
234
 
                                if self.debug: raise
235
 
                                return False
236
 
 
237
 
                self._clear()
238
 
 
239
 
                r = False
240
 
                while not self.finished():
241
 
                        r = self._dispatch(self._scope,'configuration',['group','neighbor'],[])
242
 
                        if r is False: break
243
 
 
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)
246
 
                        return False
247
 
 
248
 
                self.neighbor = self._neighbor
249
 
 
250
 
                if environment.settings().debug.route:
251
 
                        self.decode(environment.settings().debug.route)
252
 
                        sys.exit(0)
253
 
 
254
 
                if environment.settings().debug.selfcheck:
255
 
                        self.selfcheck()
256
 
                        sys.exit(0)
257
 
 
258
 
                return True
259
 
 
260
 
        def parse_api_route (self,command):
261
 
                tokens = self._cleaned(command).split(' ')[1:]
262
 
                if len(tokens) < 4:
263
 
                        return False
264
 
                if tokens[0] != 'route':
265
 
                        return False
266
 
                scope = [{}]
267
 
                if not self._single_static_route(scope,tokens[1:]):
268
 
                        return None
269
 
                return scope[0]['routes']
270
 
 
271
 
        def parse_api_flow (self,command):
272
 
                self._tokens = self._tokenise(' '.join(self._cleaned(command).split(' ')[2:]).split('\\n'))
273
 
                scope = [{}]
274
 
                if not self._dispatch(scope,'flow',['route',],[]):
275
 
                        return None
276
 
                if not self._check_flow_route(scope):
277
 
                        return None
278
 
                return scope[0]['routes']
279
 
 
280
 
        def add_route_all_peers (self,route):
281
 
                for neighbor in self.neighbor:
282
 
                        self.neighbor[neighbor].add_route(route)
283
 
 
284
 
        def remove_route_all_peers (self,route):
285
 
                result = False
286
 
                for neighbor in self.neighbor:
287
 
                        if self.neighbor[neighbor].remove_route(route):
288
 
                                result = True
289
 
                return result
290
 
 
291
 
        # Tokenisation
292
 
 
293
 
        def _cleaned (self,line):
294
 
                return line.strip().replace('\t',' ').replace(']',' ]').replace('[','[ ').replace(')',' )').replace('(','( ').lower()
295
 
 
296
 
        def _tokenise (self,text):
297
 
                r = []
298
 
                config = ''
299
 
                for line in text:
300
 
                        self.logger.configuration('loading | %s' % line.rstrip())
301
 
                        replaced = self._cleaned(line)
302
 
                        config += line
303
 
                        if not replaced:
304
 
                                continue
305
 
                        if replaced.startswith('#'):
306
 
                                continue
307
 
                        if replaced[:3] == 'md5':
308
 
                                password = line.strip()[3:].strip()
309
 
                                if password[-1] == ';':
310
 
                                        password = password[:-1]
311
 
                                r.append(['md5',password,';'])
312
 
                        else:
313
 
                                r.append([t for t in replaced[:-1].split(' ') if t] + [replaced[-1]])
314
 
                self.logger.config(config)
315
 
                return r
316
 
 
317
 
        def tokens (self):
318
 
                self._number += 1
319
 
                self._line = self._tokens.pop(0)
320
 
                return self._line
321
 
 
322
 
        def number (self):
323
 
                return self._number
324
 
 
325
 
        def line (self):
326
 
                return ' '.join(self._line)
327
 
 
328
 
        def finished (self):
329
 
                return len(self._tokens) == 0
330
 
 
331
 
        # Flow control ......................
332
 
 
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):
335
 
                try:
336
 
                        tokens = self.tokens()
337
 
                except IndexError:
338
 
                        self._error = 'configuration file incomplete (most likely missing })'
339
 
                        if self.debug: raise
340
 
                        return False
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))
344
 
                end = tokens[-1]
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)
350
 
                if end == '}':
351
 
                        if len(self._location) == 1:
352
 
                                self._error = 'closing too many parenthesis'
353
 
                                if self.debug: raise
354
 
                                return False
355
 
                        self._location.pop(-1)
356
 
                        return None
357
 
                return False
358
 
 
359
 
        def _multi_line (self,scope,name,tokens,valid):
360
 
                command = tokens[0]
361
 
 
362
 
                if valid and command not in valid:
363
 
                        self._error = 'option %s in not valid here' % command
364
 
                        if self.debug: raise
365
 
                        return False
366
 
 
367
 
                if name == 'configuration':
368
 
                        if command == 'neighbor':
369
 
                                if self._multi_neighbor(scope,tokens[1:]):
370
 
                                        return self._make_neighbor(scope)
371
 
                                return False
372
 
                        if command == 'group':
373
 
                                if len(tokens) != 2:
374
 
                                        self._error = 'syntax: group <name> { <options> }'
375
 
                                        if self.debug: raise
376
 
                                        return False
377
 
                                return self._multi_group(scope,tokens[1])
378
 
 
379
 
                if name == 'group':
380
 
                        if command == 'neighbor':
381
 
                                if self._multi_neighbor(scope,tokens[1:]):
382
 
                                        return self._make_neighbor(scope)
383
 
                                return False
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:])
389
 
 
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:])
396
 
 
397
 
                if name == 'static':
398
 
                        if command == 'route':
399
 
                                if self._multi_static_route(scope,tokens[1:]):
400
 
                                        return self._check_static_route(scope)
401
 
                                return False
402
 
 
403
 
                if name == 'flow':
404
 
                        if command == 'route':
405
 
                                if self._multi_flow_route(scope,tokens[1:]):
406
 
                                        return self._check_flow_route(scope)
407
 
                                return False
408
 
 
409
 
                if name == 'flow-route':
410
 
                        if command == 'match':
411
 
                                if self._multi_match(scope,tokens[1:]):
412
 
                                        return True
413
 
                                return False
414
 
                        if command == 'then':
415
 
                                if self._multi_then(scope,tokens[1:]):
416
 
                                        return True
417
 
                                return False
418
 
                return False
419
 
 
420
 
        def _single_line (self,scope,name,tokens,valid):
421
 
                command = tokens[0]
422
 
                if valid and command not in valid:
423
 
                        self._error = 'invalid keyword "%s"' % command
424
 
                        if self.debug: raise
425
 
                        return False
426
 
 
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:])
446
 
 
447
 
                        if command == 'community': return self._route_community(scope,tokens[1:])
448
 
                        if command == 'extended-community': return self._route_extended_community(scope,tokens[1:])
449
 
 
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:])
463
 
 
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:])
468
 
 
469
 
                        if command == 'community': return self._route_community(scope,tokens[1:])
470
 
                        if command == 'extended-community': return self._route_extended_community(scope,tokens[1:])
471
 
 
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:])
482
 
                        # deprecated
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:])
487
 
 
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:])
494
 
 
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:])
501
 
 
502
 
                elif name == 'process':
503
 
                        if command == 'run': return self._set_process_run(scope,'process-run',tokens[1:])
504
 
                        # legacy ...
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:])
508
 
                                return True
509
 
                        # legacy ...
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:])
513
 
                                return True
514
 
                        # new interface
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:])
520
 
 
521
 
                elif name == 'static':
522
 
                        if command == 'route': return self._single_static_route(scope,tokens[1:])
523
 
 
524
 
                return False
525
 
 
526
 
        # Programs used to control exabgp
527
 
 
528
 
        def _multi_process (self,scope,tokens):
529
 
                while True:
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
532
 
                        if r is None: break
533
 
 
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 '*'
536
 
 
537
 
                run = scope[-1].pop('process-run','')
538
 
                if run:
539
 
                        if len(tokens) != 1:
540
 
                                self._error = self._str_process_error
541
 
                                if self.debug: raise
542
 
                                return False
543
 
                        self.process[name]['encoder'] = scope[-1].get('encoder','') or self.api_encoder
544
 
                        self.process[name]['run'] = run
545
 
                        return True
546
 
                elif len(tokens):
547
 
                        self._error = self._str_process_error
548
 
                        if self.debug: raise
549
 
                        return False
550
 
 
551
 
        def _set_process_command (self,scope,command,value):
552
 
                scope[-1][command] = True
553
 
                return True
554
 
 
555
 
        def _set_process_encoder (self,scope,command,value):
556
 
                if value and value[0] in ('text','json'):
557
 
                        scope[-1][command] = value[0]
558
 
                        return True
559
 
 
560
 
                self._error = self._str_process_error
561
 
                if self.debug: raise
562
 
                return False
563
 
 
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 ['"',"'"]:
567
 
                        line = line[1:-1]
568
 
                if ' ' in line:
569
 
                        prg,args = line.split(' ',1)
570
 
                else:
571
 
                        prg = line
572
 
                        args = ''
573
 
 
574
 
                if not prg:
575
 
                        self._error = 'prg requires the program to prg as an argument (quoted or unquoted)'
576
 
                        if self.debug: raise
577
 
                        return False
578
 
                if prg[0] != '/':
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)
583
 
                        else:
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
587
 
                        if self.debug: raise
588
 
                        return False
589
 
 
590
 
                # XXX: Yep, race conditions are possible, those are sanity checks not security ones ...
591
 
                s = os.stat(prg)
592
 
 
593
 
                if stat.S_ISDIR(s.st_mode):
594
 
                        self._error = 'can not execute directories "%s"' % prg
595
 
                        if self.debug: raise
596
 
                        return False
597
 
 
598
 
                if s.st_mode & stat.S_ISUID:
599
 
                        self._error = 'refusing to run setuid programs "%s"' % prg
600
 
                        if self.debug: raise
601
 
                        return False
602
 
 
603
 
                check = stat.S_IXOTH
604
 
                if s.st_uid == os.getuid():
605
 
                        check |= stat.S_IXUSR
606
 
                if s.st_gid == os.getgid():
607
 
                        check |= stat.S_IXGRP
608
 
 
609
 
                if not check & s.st_mode:
610
 
                        self._error = 'exabgp will not be able to run this program "%s"' % prg
611
 
                        if self.debug: raise
612
 
                        return False
613
 
 
614
 
                if args:
615
 
                        scope[-1][command] = [prg] + args.split(' ')
616
 
                else:
617
 
                        scope[-1][command] = [prg,]
618
 
                return True
619
 
 
620
 
        # Limit the AFI/SAFI pair announced to peers
621
 
 
622
 
        def _multi_family (self,scope,tokens):
623
 
                # we know all the families we should use
624
 
                self._family = False
625
 
                scope[-1]['families'] = []
626
 
                while True:
627
 
                        r = self._dispatch(scope,'family',[],['inet','inet4','inet6','minimal','all'])
628
 
                        if r is False: return False
629
 
                        if r is None: break
630
 
                self._family = False
631
 
                return True
632
 
 
633
 
        def _set_family_inet4 (self,scope,tokens):
634
 
                if self._family:
635
 
                        self._error = 'inet/inet4 can not be used with all or minimal'
636
 
                        if self.debug: raise
637
 
                        return False
638
 
 
639
 
                safi = tokens.pop(0)
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)))
650
 
                else:
651
 
                        return False
652
 
                return True
653
 
 
654
 
        def _set_family_inet6 (self,scope,tokens):
655
 
                if self._family:
656
 
                        self._error = 'inet6 can not be used with all or minimal'
657
 
                        if self.debug: raise
658
 
                        return False
659
 
 
660
 
                safi = tokens.pop(0)
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)))
665
 
                else:
666
 
                        return False
667
 
                return True
668
 
 
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'
672
 
                        if self.debug: raise
673
 
                        return False
674
 
                scope[-1]['families'] = 'minimal'
675
 
                self._family = True
676
 
                return True
677
 
 
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'
681
 
                        if self.debug: raise
682
 
                        return False
683
 
                scope[-1]['families'] = 'all'
684
 
                self._family = True
685
 
                return True
686
 
 
687
 
        # capacity
688
 
 
689
 
        def _multi_capability (self,scope,tokens):
690
 
                # we know all the families we should use
691
 
                self._capability = False
692
 
                while True:
693
 
                        r = self._dispatch(scope,'capability',[],['route-refresh','graceful-restart','multi-session','add-path','asn4'])
694
 
                        if r is False: return False
695
 
                        if r is None: break
696
 
                return True
697
 
 
698
 
        def _set_routerefresh (self,scope,command,value):
699
 
                scope[-1][command] = True
700
 
                return True
701
 
 
702
 
        def _set_gracefulrestart (self,scope,command,value):
703
 
                if not len(value):
704
 
                        scope[-1][command] = None
705
 
                        return True
706
 
                try:
707
 
                        # README: Should it be a subclass of int ?
708
 
                        grace = int(value[0])
709
 
                        if grace < 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
714
 
                        return True
715
 
                except ValueError:
716
 
                        self._error = '"%s" is an invalid graceful-restart time' % ' '.join(value)
717
 
                        if self.debug: raise
718
 
                        return False
719
 
                return True
720
 
 
721
 
        def _set_multisession (self,scope,command,value):
722
 
                scope[-1][command] = True
723
 
                return True
724
 
 
725
 
        def _set_addpath (self,scope,command,value):
726
 
                try:
727
 
                        ap = value[0].lower()
728
 
                        apv = 0
729
 
                        if ap.endswith('receive'):
730
 
                                apv += 1
731
 
                        if ap.startswith('send'):
732
 
                                apv += 2
733
 
                        if not apv and ap not in ('disable','disabled'):
734
 
                                raise ValueError('invalid add-path')
735
 
                        scope[-1][command] = apv
736
 
                        return True
737
 
                except ValueError:
738
 
                        self._error = '"%s" is an invalid add-path' % ' '.join(value)
739
 
                        if self.debug: raise
740
 
                        return False
741
 
 
742
 
        def _set_asn4 (self,scope,command,value):
743
 
                try:
744
 
                        if not value:
745
 
                                scope[-1][command] = True
746
 
                                return True
747
 
                        asn4 = value[0].lower()
748
 
                        if asn4 in ('disable','disabled'):
749
 
                                scope[-1][command] = False
750
 
                                return True
751
 
                        if asn4 in ('enable','enabled'):
752
 
                                scope[-1][command] = True
753
 
                                return True
754
 
                        self._error = '"%s" is an invalid asn4 parameter options are enable (default) and disable)' % ' '.join(value)
755
 
                        return False
756
 
                except ValueError:
757
 
                        self._error = '"%s" is an invalid asn4 parameter options are enable (default) and disable)' % ' '.join(value)
758
 
                        if self.debug: raise
759
 
                        return False
760
 
 
761
 
 
762
 
        # route grouping with watchdog
763
 
 
764
 
        def _route_watchdog (self,scope,tokens):
765
 
                w = tokens.pop(0)
766
 
                if w.lower() in ['announce','withdraw']:
767
 
                        raise ValueError('invalid watchdog name %s' % w)
768
 
                try:
769
 
                        scope[-1]['routes'][-1].attributes.add(Watchdog(w))
770
 
                        return True
771
 
                except ValueError:
772
 
                        self._error = self._str_route_error
773
 
                        if self.debug: raise
774
 
                        return False
775
 
 
776
 
        def _route_withdraw (self,scope,tokens):
777
 
                try:
778
 
                        scope[-1]['routes'][-1].attributes.add(Withdrawn())
779
 
                        return True
780
 
                except ValueError:
781
 
                        self._error = self._str_route_error
782
 
                        if self.debug: raise
783
 
                        return False
784
 
 
785
 
        # Group Neighbor
786
 
 
787
 
        def _multi_group (self,scope,address):
788
 
                scope.append({})
789
 
                while True:
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'])
791
 
                        if r is False:
792
 
                                return False
793
 
                        if r is None:
794
 
                                scope.pop(-1)
795
 
                                return True
796
 
 
797
 
        def _make_neighbor (self,scope):
798
 
                # we have local_scope[-2] as the group template and local_scope[-1] as the peer specific
799
 
                if len(scope) > 1:
800
 
                        for key,content in scope[-2].iteritems():
801
 
                                if key not in scope[-1]:
802
 
                                        scope[-1][key] = deepcopy(content)
803
 
 
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))
811
 
                        else:
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")
815
 
 
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
830
 
 
831
 
                        v = local_scope.get('routes',[])
832
 
                        for route in v:
833
 
                                # This add the family to neighbor.families()
834
 
                                neighbor.add_route(route)
835
 
 
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)
841
 
 
842
 
                local_scope = scope[-1]
843
 
                neighbor.description = local_scope.get('description','')
844
 
 
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)
848
 
 
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)
857
 
 
858
 
                missing = neighbor.missing()
859
 
                if missing:
860
 
                        self._error = 'incomplete neighbor, missing %s' % missing
861
 
                        if self.debug: raise
862
 
                        return False
863
 
                if neighbor.local_address.afi != neighbor.peer_address.afi:
864
 
                        self._error = 'local-address and peer-address must be of the same family'
865
 
                        if self.debug: raise
866
 
                        return False
867
 
                if neighbor.peer_address.ip in self._neighbor:
868
 
                        self._error = 'duplicate peer definition %s' % neighbor.peer_address.ip
869
 
                        if self.debug: raise
870
 
                        return False
871
 
 
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()
882
 
                else:
883
 
                        families = openfamilies
884
 
 
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:
888
 
                                afi,safi = family
889
 
                                self._error = 'Trying to announce a route of type %s,%s when we are not announcing the family to our peer' % (afi,safi)
890
 
                                if self.debug: raise
891
 
                                return False
892
 
 
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)
899
 
 
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():
905
 
                                        if f == family:
906
 
                                                continue
907
 
                                        m_neighbor.remove_family_and_routes(f)
908
 
                                self._neighbor[m_neighbor.name()] = m_neighbor
909
 
                else:
910
 
                        self._neighbor[neighbor.name()] = neighbor
911
 
 
912
 
                for line in str(neighbor).split('\n'):
913
 
                        self.logger.configuration(line)
914
 
                self.logger.configuration("\n")
915
 
 
916
 
                return True
917
 
 
918
 
 
919
 
        def _multi_neighbor (self,scope,tokens):
920
 
                if len(tokens) != 1:
921
 
                        self._error = 'syntax: neighbor <ip> { <options> }'
922
 
                        if self.debug: raise
923
 
                        return False
924
 
 
925
 
                address = tokens[0]
926
 
                scope.append({})
927
 
                try:
928
 
                        scope[-1]['peer-address'] = Inet(*inet(address))
929
 
                except:
930
 
                        self._error = '"%s" is not a valid IP address' % address
931
 
                        if self.debug: raise
932
 
                        return False
933
 
                while True:
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
937
 
 
938
 
        # Command Neighbor
939
 
 
940
 
        def _set_router_id (self,scope,command,value):
941
 
                try:
942
 
                        ip = RouterID(value[0])
943
 
                except (IndexError,ValueError):
944
 
                        self._error = '"%s" is an invalid IP address' % ' '.join(value)
945
 
                        if self.debug: raise
946
 
                        return False
947
 
                scope[-1][command] = ip
948
 
                return True
949
 
 
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>"'
954
 
                        if self.debug: raise
955
 
                        return False
956
 
                scope[-1]['description'] = text[1:-1]
957
 
                return True
958
 
 
959
 
        # will raise ValueError if the ASN is not correct
960
 
        def _newASN (self,value):
961
 
                if value.count('.'):
962
 
                        high,low = value.split('.',1)
963
 
                        asn = (int(high) << 16) + int(low)
964
 
                else:
965
 
                        asn = int(value)
966
 
                return ASN(asn)
967
 
 
968
 
        def _set_asn (self,scope,command,value):
969
 
                try:
970
 
                        scope[-1][command] = self._newASN(value[0])
971
 
                        return True
972
 
                except ValueError:
973
 
                        self._error = '"%s" is an invalid ASN' % ' '.join(value)
974
 
                        if self.debug: raise
975
 
                        return False
976
 
 
977
 
        def _set_ip (self,scope,command,value):
978
 
                try:
979
 
                        ip = Inet(*inet(value[0]))
980
 
                except (IndexError,ValueError):
981
 
                        self._error = '"%s" is an invalid IP address' % ' '.join(value)
982
 
                        if self.debug: raise
983
 
                        return False
984
 
                scope[-1][command] = ip
985
 
                return True
986
 
 
987
 
        def _set_holdtime (self,scope,command,value):
988
 
                try:
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
995
 
                        return True
996
 
                except ValueError:
997
 
                        self._error = '"%s" is an invalid hold-time' % ' '.join(value)
998
 
                        if self.debug: raise
999
 
                        return False
1000
 
 
1001
 
        def _set_md5 (self,scope,command,value):
1002
 
                md5 = value[0]
1003
 
                if len(md5) > 2 and md5[0] == md5[-1] and md5[0] in ['"',"'"]:
1004
 
                        md5 = md5[1:-1]
1005
 
                if len(md5) > 80:
1006
 
                        self._error = 'md5 password must be no larger than 80 characters'
1007
 
                        if self.debug: raise
1008
 
                        return False
1009
 
                if not md5:
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
1012
 
                        return False
1013
 
                scope[-1][command] = md5
1014
 
                return True
1015
 
 
1016
 
        def _set_ttl (self,scope,command,value):
1017
 
                if not len(value):
1018
 
                        scope[-1][command] = self.TTL_SECURITY
1019
 
                        return True
1020
 
                try:
1021
 
                        # README: Should it be a subclass of int ?
1022
 
                        ttl = int(value[0])
1023
 
                        if ttl < 0:
1024
 
                                raise ValueError('ttl-security can not be negative')
1025
 
                        if ttl >= 255:
1026
 
                                raise ValueError('ttl must be smaller than 256')
1027
 
                        scope[-1][command] = ttl
1028
 
                        return True
1029
 
                except ValueError:
1030
 
                        self._error = '"%s" is an invalid ttl-security' % ' '.join(value)
1031
 
                        if self.debug: raise
1032
 
                        return False
1033
 
                return True
1034
 
 
1035
 
        def _set_group_updates (self,scope,command,value):
1036
 
                scope[-1][command] = True
1037
 
                return True
1038
 
 
1039
 
        #  Group Static ................
1040
 
 
1041
 
        def _multi_static (self,scope,tokens):
1042
 
                if len(tokens) != 0:
1043
 
                        self._error = 'syntax: static { route; route; ... }'
1044
 
                        if self.debug: raise
1045
 
                        return False
1046
 
                while True:
1047
 
                        r = self._dispatch(scope,'static',['route',],['route',])
1048
 
                        if r is False: return False
1049
 
                        if r is None: return True
1050
 
 
1051
 
        # Group Route  ........
1052
 
 
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:
1057
 
                        return True
1058
 
 
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]
1062
 
                if mask >= split:
1063
 
                        return True
1064
 
 
1065
 
                # get a local copy of the route
1066
 
                route = scope[-1]['routes'].pop(-1)
1067
 
 
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)
1072
 
 
1073
 
                # convert the IP into a integer/long
1074
 
                ip = 0
1075
 
                for c in route.nlri.packed:
1076
 
                        ip = ip << 8
1077
 
                        ip += ord(c)
1078
 
 
1079
 
                afi = route.nlri.afi
1080
 
                safi = route.nlri.safi
1081
 
                # Really ugly
1082
 
                labels = route.nlri.labels
1083
 
                rd = route.nlri.rd
1084
 
                path_info = route.nlri.path_info
1085
 
 
1086
 
                route.mask = split
1087
 
                route.nlri = None
1088
 
                # generate the new routes
1089
 
                for _ in range(number):
1090
 
                        r = deepcopy(route)
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
1094
 
                        r.nlri.rd = rd
1095
 
                        r.nlri.path_info = path_info
1096
 
                        # next ip
1097
 
                        ip += increment
1098
 
                        # save route
1099
 
                        scope[-1]['routes'].append(r)
1100
 
 
1101
 
                return True
1102
 
 
1103
 
        def _insert_static_route (self,scope,tokens):
1104
 
                try:
1105
 
                        ip = tokens.pop(0)
1106
 
                except IndexError:
1107
 
                        self._error = self._str_route_error
1108
 
                        if self.debug: raise
1109
 
                        return False
1110
 
                try:
1111
 
                        ip,mask = ip.split('/')
1112
 
                except ValueError:
1113
 
                        mask = '32'
1114
 
                try:
1115
 
                        route = Route(NLRI(*inet(ip),mask=mask))
1116
 
                except ValueError:
1117
 
                        self._error = self._str_route_error
1118
 
                        if self.debug: raise
1119
 
                        return False
1120
 
 
1121
 
                if 'routes' not in scope[-1]:
1122
 
                        scope[-1]['routes'] = []
1123
 
 
1124
 
                scope[-1]['routes'].append(route)
1125
 
                return True
1126
 
 
1127
 
        def pop_last_static_route (self,scope):
1128
 
                route = scope[-1]['routes'][-1]
1129
 
                scope[-1]['routes'] = scope[-1]['routes'][:-1]
1130
 
                return route
1131
 
 
1132
 
        def remove_route (self,route,scope):
1133
 
                for r in scope[-1]['routes']:
1134
 
                        if r == route:
1135
 
                                scope[-1]['routes'].remove(r)
1136
 
                                return True
1137
 
                return False
1138
 
 
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
1144
 
                        return False
1145
 
                return True
1146
 
 
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
1151
 
                        return False
1152
 
 
1153
 
                if not self._insert_static_route(scope,tokens):
1154
 
                        return False
1155
 
 
1156
 
                while True:
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)
1160
 
 
1161
 
        def _single_static_route (self,scope,tokens):
1162
 
                if len(tokens) <3:
1163
 
                        return False
1164
 
 
1165
 
                have_next_hop = False
1166
 
 
1167
 
                if not self._insert_static_route(scope,tokens):
1168
 
                        return False
1169
 
 
1170
 
                while len(tokens):
1171
 
                        command = tokens.pop(0)
1172
 
                        if command == 'withdraw':
1173
 
                                if self._route_withdraw(scope,tokens):
1174
 
                                        continue
1175
 
                                return False
1176
 
 
1177
 
                        if len(tokens) < 1:
1178
 
                                return False
1179
 
 
1180
 
                        if command == 'next-hop':
1181
 
                                if self._route_next_hop(scope,tokens):
1182
 
                                        have_next_hop = True
1183
 
                                        continue
1184
 
                                return False
1185
 
                        if command == 'origin':
1186
 
                                if self._route_origin(scope,tokens):
1187
 
                                        continue
1188
 
                                return False
1189
 
                        if command == 'as-path':
1190
 
                                if self._route_aspath(scope,tokens):
1191
 
                                        continue
1192
 
                                return False
1193
 
                        if command == 'as-sequence':
1194
 
                                if self._route_aspath(scope,tokens):
1195
 
                                        continue
1196
 
                                return False
1197
 
                        if command == 'med':
1198
 
                                if self._route_med(scope,tokens):
1199
 
                                        continue
1200
 
                                return False
1201
 
                        if command == 'local-preference':
1202
 
                                if self._route_local_preference(scope,tokens):
1203
 
                                        continue
1204
 
                                return False
1205
 
                        if command == 'atomic-aggregate':
1206
 
                                if self._route_atomic_aggregate(scope,tokens):
1207
 
                                        continue
1208
 
                                return False
1209
 
                        if command == 'aggregator':
1210
 
                                if self._route_aggregator(scope,tokens):
1211
 
                                        continue
1212
 
                                return False
1213
 
                        if command == 'path-information':
1214
 
                                if self._route_path_information(scope,tokens):
1215
 
                                        continue
1216
 
                                return False
1217
 
                        if command == 'community':
1218
 
                                if self._route_community(scope,tokens):
1219
 
                                        continue
1220
 
                                return False
1221
 
                        if command == 'originator-id':
1222
 
                                if self._route_originator_id(scope,tokens):
1223
 
                                        continue
1224
 
                                return False
1225
 
                        if command == 'cluster-list':
1226
 
                                if self._route_cluster_list(scope,tokens):
1227
 
                                        continue
1228
 
                                return False
1229
 
                        if command == 'extended-community':
1230
 
                                if self._route_extended_community(scope,tokens):
1231
 
                                        continue
1232
 
                                return False
1233
 
                        if command == 'split':
1234
 
                                if self._route_split(scope,tokens):
1235
 
                                        continue
1236
 
                                return False
1237
 
                        if command == 'label':
1238
 
                                if self._route_label(scope,tokens):
1239
 
                                        continue
1240
 
                                return False
1241
 
                        if command in ('rd','route-distinguisher'):
1242
 
                                if self._route_rd(scope,tokens):
1243
 
                                        continue
1244
 
                                return False
1245
 
                        if command == 'watchdog':
1246
 
                                if self._route_watchdog(scope,tokens):
1247
 
                                        continue
1248
 
                                return False
1249
 
                        return False
1250
 
 
1251
 
                if not have_next_hop:
1252
 
                        self._error = 'every route requires a next-hop'
1253
 
                        if self.debug: raise
1254
 
                        return False
1255
 
 
1256
 
                return self._split_last_route(scope)
1257
 
 
1258
 
        # Command Route
1259
 
 
1260
 
        def _route_next_hop (self,scope,tokens):
1261
 
                try:
1262
 
                        # next-hop self is unsupported
1263
 
                        ip = tokens.pop(0)
1264
 
                        if ip.lower() == 'self':
1265
 
                                la = scope[-1]['local-address']
1266
 
                                nh = la.afi,la.safi,la.pack()
1267
 
                        else:
1268
 
                                nh = inet(ip)
1269
 
                        scope[-1]['routes'][-1].attributes.add(cachedNextHop(*nh))
1270
 
                        return True
1271
 
                except:
1272
 
                        self._error = self._str_route_error
1273
 
                        if self.debug: raise
1274
 
                        return False
1275
 
 
1276
 
        def _route_origin (self,scope,tokens):
1277
 
                data = tokens.pop(0).lower()
1278
 
                if data == 'igp':
1279
 
                        scope[-1]['routes'][-1].attributes.add(Origin(Origin.IGP))
1280
 
                        return True
1281
 
                if data == 'egp':
1282
 
                        scope[-1]['routes'][-1].attributes.add(Origin(Origin.EGP))
1283
 
                        return True
1284
 
                if data == 'incomplete':
1285
 
                        scope[-1]['routes'][-1].attributes.add(Origin(Origin.INCOMPLETE))
1286
 
                        return True
1287
 
                self._error = self._str_route_error
1288
 
                if self.debug: raise
1289
 
                return False
1290
 
 
1291
 
        def _route_aspath (self,scope,tokens):
1292
 
                as_seq = []
1293
 
                as_set = []
1294
 
                asn = tokens.pop(0)
1295
 
                try:
1296
 
                        if asn == '[':
1297
 
                                while True:
1298
 
                                        try:
1299
 
                                                asn = tokens.pop(0)
1300
 
                                        except IndexError:
1301
 
                                                self._error = self._str_route_error
1302
 
                                                if self.debug: raise
1303
 
                                                return False
1304
 
                                        if asn == '(':
1305
 
                                                while True:
1306
 
                                                        try:
1307
 
                                                                asn = tokens.pop(0)
1308
 
                                                        except IndexError:
1309
 
                                                                self._error = self._str_route_error
1310
 
                                                                if self.debug: raise
1311
 
                                                                return False
1312
 
                                                        if asn == ')':
1313
 
                                                                break
1314
 
                                                        as_set.append(self._newASN(asn))
1315
 
                                        if asn == ')':
1316
 
                                                continue
1317
 
                                        if asn == ']':
1318
 
                                                break
1319
 
                                        as_seq.append(self._newASN(asn))
1320
 
                        else:
1321
 
                                as_seq.append(self._newASN(asn))
1322
 
                except ValueError:
1323
 
                        self._error = self._str_route_error
1324
 
                        if self.debug: raise
1325
 
                        return False
1326
 
                scope[-1]['routes'][-1].attributes.add(ASPath(as_seq,as_set))
1327
 
                return True
1328
 
 
1329
 
        def _route_med (self,scope,tokens):
1330
 
                try:
1331
 
                        scope[-1]['routes'][-1].attributes.add(MED(pack('!L',int(tokens.pop(0)))))
1332
 
                        return True
1333
 
                except ValueError:
1334
 
                        self._error = self._str_route_error
1335
 
                        if self.debug: raise
1336
 
                        return False
1337
 
 
1338
 
        def _route_local_preference (self,scope,tokens):
1339
 
                try:
1340
 
                        scope[-1]['routes'][-1].attributes.add(LocalPreference(pack('!L',int(tokens.pop(0)))))
1341
 
                        return True
1342
 
                except ValueError:
1343
 
                        self._error = self._str_route_error
1344
 
                        if self.debug: raise
1345
 
                        return False
1346
 
 
1347
 
        def _route_atomic_aggregate (self,scope,tokens):
1348
 
                try:
1349
 
                        scope[-1]['routes'][-1].attributes.add(AtomicAggregate())
1350
 
                        return True
1351
 
                except ValueError:
1352
 
                        self._error = self._str_route_error
1353
 
                        if self.debug: raise
1354
 
                        return False
1355
 
 
1356
 
        def _route_aggregator (self,scope,tokens):
1357
 
                try:
1358
 
                        if 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')
1364
 
                                local_as = ASN(asn)
1365
 
                                local_address = RouterID(address)
1366
 
                        else:
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
1372
 
                        return False
1373
 
                except KeyError:
1374
 
                        self._error = 'local-as and/or local-address missing from neighbor/group to make aggregator'
1375
 
                        if self.debug: raise
1376
 
                        return False
1377
 
                except ValueError:
1378
 
                        self._error = self._str_route_error
1379
 
                        if self.debug: raise
1380
 
                        return False
1381
 
 
1382
 
                scope[-1]['routes'][-1].attributes.add(Aggregator(local_as.pack(True)+local_address.pack()))
1383
 
                return True
1384
 
 
1385
 
        def _route_path_information (self,scope,tokens):
1386
 
                try:
1387
 
                        pi = tokens.pop(0)
1388
 
                        if pi.isdigit():
1389
 
                                scope[-1]['routes'][-1].nlri.path_info = PathInfo(integer=int(pi))
1390
 
                        else:
1391
 
                                scope[-1]['routes'][-1].nlri.path_info = PathInfo(ip=pi)
1392
 
                        return True
1393
 
                except ValueError:
1394
 
                        self._error = self._str_route_error
1395
 
                        if self.debug: raise
1396
 
                        return False
1397
 
 
1398
 
        def _parse_community (self,scope,data):
1399
 
                separator = data.find(':')
1400
 
                if separator > 0:
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))
1413
 
                else:
1414
 
                        low = data.lower()
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))
1429
 
                        else:
1430
 
                                raise ValueError('invalid community name %s' % data)
1431
 
 
1432
 
        def _route_originator_id (self,scope,tokens):
1433
 
                try:
1434
 
                        scope[-1]['routes'][-1].attributes.add(OriginatorID(*inet(tokens.pop(0))))
1435
 
                        return True
1436
 
                except:
1437
 
                        self._error = self._str_route_error
1438
 
                        if self.debug: raise
1439
 
                        return False
1440
 
 
1441
 
        def _route_cluster_list (self,scope,tokens):
1442
 
                _list = ''
1443
 
                clusterid = tokens.pop(0)
1444
 
                try:
1445
 
                        if clusterid == '[':
1446
 
                                while True:
1447
 
                                        try:
1448
 
                                                clusterid = tokens.pop(0)
1449
 
                                        except IndexError:
1450
 
                                                self._error = self._str_route_error
1451
 
                                                if self.debug: raise
1452
 
                                                return False
1453
 
                                        if clusterid == ']':
1454
 
                                                break
1455
 
                                        _list += ''.join([chr(int(_)) for _ in clusterid.split('.')])
1456
 
                        else:
1457
 
                                _list = ''.join([chr(int(_)) for _ in clusterid.split('.')])
1458
 
                        if not _list:
1459
 
                                raise ValueError('no cluster-id in the cluster-list')
1460
 
                        clusterlist = ClusterList(_list)
1461
 
                except ValueError:
1462
 
                        self._error = self._str_route_error
1463
 
                        if self.debug: raise
1464
 
                        return False
1465
 
                scope[-1]['routes'][-1].attributes.add(clusterlist)
1466
 
                return True
1467
 
 
1468
 
        def _route_community (self,scope,tokens):
1469
 
                communities = Communities()
1470
 
                community = tokens.pop(0)
1471
 
                try:
1472
 
                        if community == '[':
1473
 
                                while True:
1474
 
                                        try:
1475
 
                                                community = tokens.pop(0)
1476
 
                                        except IndexError:
1477
 
                                                self._error = self._str_route_error
1478
 
                                                if self.debug: raise
1479
 
                                                return False
1480
 
                                        if community == ']':
1481
 
                                                break
1482
 
                                        communities.add(self._parse_community(scope,community))
1483
 
                        else:
1484
 
                                communities.add(self._parse_community(scope,community))
1485
 
                except ValueError:
1486
 
                        self._error = self._str_route_error
1487
 
                        if self.debug: raise
1488
 
                        return False
1489
 
                scope[-1]['routes'][-1].attributes.add(communities)
1490
 
                return True
1491
 
 
1492
 
        def _parse_extended_community (self,scope,data):
1493
 
                if data[:2].lower() == '0x':
1494
 
                        try:
1495
 
                                raw = ''
1496
 
                                for i in range(2,len(data),2):
1497
 
                                        raw += chr(int(data[i:i+2],16))
1498
 
                        except ValueError:
1499
 
                                raise ValueError('invalid extended community %s' % data)
1500
 
                        if len(raw) != 8:
1501
 
                                raise ValueError('invalid extended community %s' % data)
1502
 
                        return ECommunity(raw)
1503
 
                elif data.count(':'):
1504
 
                        return to_ExtendedCommunity(data)
1505
 
                else:
1506
 
                        raise ValueError('invalid extended community %s - lc+gc' % data)
1507
 
 
1508
 
        def _route_extended_community (self,scope,tokens):
1509
 
                extended_communities = ECommunities()
1510
 
                extended_community = tokens.pop(0)
1511
 
                try:
1512
 
                        if extended_community == '[':
1513
 
                                while True:
1514
 
                                        try:
1515
 
                                                extended_community = tokens.pop(0)
1516
 
                                        except IndexError:
1517
 
                                                self._error = self._str_route_error
1518
 
                                                if self.debug: raise
1519
 
                                                return False
1520
 
                                        if extended_community == ']':
1521
 
                                                break
1522
 
                                        extended_communities.add(self._parse_extended_community(scope,extended_community))
1523
 
                        else:
1524
 
                                extended_communities.add(self._parse_extended_community(scope,extended_community))
1525
 
                except ValueError:
1526
 
                        self._error = self._str_route_error
1527
 
                        if self.debug: raise
1528
 
                        return False
1529
 
                scope[-1]['routes'][-1].attributes.add(extended_communities)
1530
 
                return True
1531
 
 
1532
 
 
1533
 
        def _route_split (self,scope,tokens):
1534
 
                try:
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:])))
1539
 
                        return True
1540
 
                except ValueError:
1541
 
                        self._error = self._str_route_error
1542
 
                        if self.debug: raise
1543
 
                        return False
1544
 
 
1545
 
        def _route_label (self,scope,tokens):
1546
 
                labels = []
1547
 
                label = tokens.pop(0)
1548
 
                try:
1549
 
                        if label == '[':
1550
 
                                while True:
1551
 
                                        try:
1552
 
                                                label = tokens.pop(0)
1553
 
                                        except IndexError:
1554
 
                                                self._error = self._str_route_error
1555
 
                                                if self.debug: raise
1556
 
                                                return False
1557
 
                                        if label == ']':
1558
 
                                                break
1559
 
                                        labels.append(int(label))
1560
 
                        else:
1561
 
                                labels.append(int(label))
1562
 
                except ValueError:
1563
 
                        self._error = self._str_route_error
1564
 
                        if self.debug: raise
1565
 
                        return False
1566
 
 
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)
1571
 
                return True
1572
 
 
1573
 
        def _route_rd (self,scope,tokens):
1574
 
                try:
1575
 
                        try:
1576
 
                                data = tokens.pop(0)
1577
 
                        except IndexError:
1578
 
                                self._error = self._str_route_error
1579
 
                                if self.debug: raise
1580
 
                                return False
1581
 
 
1582
 
                        separator = data.find(':')
1583
 
                        if separator > 0:
1584
 
                                prefix = data[:separator]
1585
 
                                suffix = int(data[separator+1:])
1586
 
 
1587
 
                        if '.' in prefix:
1588
 
                                bytes = [chr(0),chr(1)]
1589
 
                                bytes.extend([chr(int(_)) for _ in prefix.split('.')])
1590
 
                                bytes.extend([suffix>>8,suffix&0xFF])
1591
 
                                rd = ''.join(bytes)
1592
 
                        else:
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)
1598
 
                                else:
1599
 
                                        raise ValueError('invalid route-distinguisher %s' % data)
1600
 
 
1601
 
                        nlri = scope[-1]['routes'][-1].nlri
1602
 
                        # overwrite nlri-mpls
1603
 
                        nlri.safi = SAFI(SAFI.mpls_vpn)
1604
 
                        nlri.rd = RouteDistinguisher(rd)
1605
 
                        return True
1606
 
                except ValueError:
1607
 
                        self._error = self._str_route_error
1608
 
                        if self.debug: raise
1609
 
                        return False
1610
 
 
1611
 
 
1612
 
        # Group Flow  ........
1613
 
 
1614
 
        def _multi_flow (self,scope,tokens):
1615
 
                if len(tokens) != 0:
1616
 
                        self._error = self._str_flow_error
1617
 
                        if self.debug: raise
1618
 
                        return False
1619
 
 
1620
 
                while True:
1621
 
                        r = self._dispatch(scope,'flow',['route',],[])
1622
 
                        if r is False: return False
1623
 
                        if r is None: break
1624
 
                return True
1625
 
 
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
1630
 
                        return False
1631
 
 
1632
 
                self._flow_state = 'match'
1633
 
 
1634
 
                try:
1635
 
                        flow = Flow()
1636
 
                except ValueError:
1637
 
                        self._error = self._str_flow_error
1638
 
                        if self.debug: raise
1639
 
                        return False
1640
 
 
1641
 
                if 'routes' not in scope[-1]:
1642
 
                        scope[-1]['routes'] = []
1643
 
 
1644
 
                scope[-1]['routes'].append(flow)
1645
 
                return True
1646
 
 
1647
 
        def _check_flow_route (self,scope):
1648
 
                self.logger.configuration('warning: no check on flows are implemented')
1649
 
                return True
1650
 
 
1651
 
        def _multi_flow_route (self,scope,tokens):
1652
 
                if len(tokens) > 1:
1653
 
                        self._error = self._str_flow_error
1654
 
                        if self.debug: raise
1655
 
                        return False
1656
 
 
1657
 
                if not self._insert_flow_route(scope):
1658
 
                        return False
1659
 
 
1660
 
                while True:
1661
 
                        r = self._dispatch(scope,'flow-route',['match','then'],[])
1662
 
                        if r is False: return False
1663
 
                        if r is None: break
1664
 
 
1665
 
                if self._flow_state != 'out':
1666
 
                        self._error = self._str_flow_error
1667
 
                        if self.debug: raise
1668
 
                        return False
1669
 
 
1670
 
                return True
1671
 
 
1672
 
        # ..........................................
1673
 
 
1674
 
        def _multi_match (self,scope,tokens):
1675
 
                if len(tokens) != 0:
1676
 
                        self._error = self._str_flow_error
1677
 
                        if self.debug: raise
1678
 
                        return False
1679
 
 
1680
 
                if self._flow_state != 'match':
1681
 
                        self._error = self._str_flow_error
1682
 
                        if self.debug: raise
1683
 
                        return False
1684
 
 
1685
 
                self._flow_state = 'then'
1686
 
 
1687
 
                while True:
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
1690
 
                        if r is None: break
1691
 
                return True
1692
 
 
1693
 
        def _multi_then (self,scope,tokens):
1694
 
                if len(tokens) != 0:
1695
 
                        self._error = self._str_flow_error
1696
 
                        if self.debug: raise
1697
 
                        return False
1698
 
 
1699
 
                if self._flow_state != 'then':
1700
 
                        self._error = self._str_flow_error
1701
 
                        if self.debug: raise
1702
 
                        return False
1703
 
 
1704
 
                self._flow_state = 'out'
1705
 
 
1706
 
                while True:
1707
 
                        r = self._dispatch(scope,'flow-then',[],['discard','rate-limit','redirect','community'])
1708
 
                        if r is False: return False
1709
 
                        if r is None: break
1710
 
                return True
1711
 
 
1712
 
        # Command Flow
1713
 
 
1714
 
        def _flow_source (self,scope,tokens):
1715
 
                try:
1716
 
                        ip,nm = tokens.pop(0).split('/')
1717
 
                        scope[-1]['routes'][-1].add_and(Source(ip,nm))
1718
 
                        return True
1719
 
                except ValueError:
1720
 
                        self._error = self._str_route_error
1721
 
                        if self.debug: raise
1722
 
                        return False
1723
 
 
1724
 
        def _flow_destination (self,scope,tokens):
1725
 
                try:
1726
 
                        ip,nm = tokens.pop(0).split('/')
1727
 
                        scope[-1]['routes'][-1].add_and(Destination(ip,nm))
1728
 
                        return True
1729
 
                except ValueError:
1730
 
                        self._error = self._str_route_error
1731
 
                        if self.debug: raise
1732
 
                        return False
1733
 
 
1734
 
        # to parse the port configuration of flow
1735
 
 
1736
 
        def _operator (self,string):
1737
 
                try:
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
1744
 
                        else:
1745
 
                                raise ValueError('Invalid operator in test %s' % string)
1746
 
                        if string[1] == '=':
1747
 
                                operator += NumericOperator.EQ
1748
 
                                return operator,string[2:]
1749
 
                        else:
1750
 
                                return operator,string[1:]
1751
 
                except IndexError:
1752
 
                        raise('Invalid expression (too short) %s' % string)
1753
 
 
1754
 
        def _value (self,string):
1755
 
                l = 0
1756
 
                for c in string:
1757
 
                        if c not in ['&',]:
1758
 
                                l += 1
1759
 
                                continue
1760
 
                        break
1761
 
                return string[:l],string[l:]
1762
 
 
1763
 
        # parse =80 or >80 or <25 or &>10<20
1764
 
        def _flow_generic_expression (self,scope,tokens,converter,klass):
1765
 
                try:
1766
 
                        for test in tokens:
1767
 
                                AND = BinaryOperator.NOP
1768
 
                                while test:
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))
1773
 
                                        if test:
1774
 
                                                if test[0] == '&':
1775
 
                                                        AND = BinaryOperator.AND
1776
 
                                                        test = test[1:]
1777
 
                                                        if not test:
1778
 
                                                                raise ValueError("Can not finish an expresion on an &")
1779
 
                                                else:
1780
 
                                                        raise ValueError("Unknown binary operator %s" % test[0])
1781
 
                        return True
1782
 
                except ValueError,e:
1783
 
                        self._error = self._str_route_error + str(e)
1784
 
                        if self.debug: raise
1785
 
                        return False
1786
 
 
1787
 
        # parse [ content1 content2 content3 ]
1788
 
        def _flow_generic_list (self,scope,tokens,converter,klass):
1789
 
                name = tokens.pop(0)
1790
 
                AND = BinaryOperator.NOP
1791
 
                try:
1792
 
                        if name == '[':
1793
 
                                while True:
1794
 
                                        name = tokens.pop(0)
1795
 
                                        if name == ']':
1796
 
                                                break
1797
 
                                        try:
1798
 
                                                try:
1799
 
                                                        number = int(name)
1800
 
                                                except ValueError:
1801
 
                                                        number = converter(name)
1802
 
                                                scope[-1]['routes'][-1].add_or(klass(NumericOperator.EQ|AND,number))
1803
 
                                        except IndexError:
1804
 
                                                self._error = self._str_flow_error
1805
 
                                                if self.debug: raise
1806
 
                                                return False
1807
 
                        else:
1808
 
                                try:
1809
 
                                        number = int(name)
1810
 
                                except ValueError:
1811
 
                                        number = converter(name)
1812
 
                                scope[-1]['routes'][-1].add_or(klass(NumericOperator.EQ|AND,number))
1813
 
                except ValueError:
1814
 
                        self._error = self._str_flow_error
1815
 
                        if self.debug: raise
1816
 
                        return False
1817
 
                return True
1818
 
 
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)
1823
 
 
1824
 
        def _flow_route_anyport (self,scope,tokens):
1825
 
                return self._flow_generic_condition(scope,tokens,convert_port,AnyPort)
1826
 
 
1827
 
        def _flow_route_source_port (self,scope,tokens):
1828
 
                return self._flow_generic_condition(scope,tokens,convert_port,SourcePort)
1829
 
 
1830
 
        def _flow_route_destination_port (self,scope,tokens):
1831
 
                return self._flow_generic_condition(scope,tokens,convert_port,DestinationPort)
1832
 
 
1833
 
        def _flow_route_packet_length (self,scope,tokens):
1834
 
                return self._flow_generic_condition(scope,tokens,convert_length,PacketLength)
1835
 
 
1836
 
        def _flow_route_tcp_flags (self,scope,tokens):
1837
 
                return self._flow_generic_list(scope,tokens,NamedTCPFlags,TCPFlag)
1838
 
 
1839
 
        def _flow_route_protocol (self,scope,tokens):
1840
 
                return self._flow_generic_list(scope,tokens,NamedProtocol,IPProtocol)
1841
 
 
1842
 
        def _flow_route_icmp_type (self,scope,tokens):
1843
 
                return self._flow_generic_list(scope,tokens,NamedICMPType,ICMPType)
1844
 
 
1845
 
        def _flow_route_icmp_code (self,scope,tokens):
1846
 
                return self._flow_generic_list(scope,tokens,NamedICMPCode,ICMPCode)
1847
 
 
1848
 
        def _flow_route_fragment (self,scope,tokens):
1849
 
                return self._flow_generic_list(scope,tokens,NamedFragment,Fragment)
1850
 
 
1851
 
        def _flow_route_dscp (self,scope,tokens):
1852
 
                return self._flow_generic_condition(scope,tokens,convert_dscp,DSCP)
1853
 
 
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
1856
 
                try:
1857
 
                        scope[-1]['routes'][-1].add_action(to_FlowTrafficRate(ASN(0),0))
1858
 
                        return True
1859
 
                except ValueError:
1860
 
                        self._error = self._str_route_error
1861
 
                        if self.debug: raise
1862
 
                        return False
1863
 
 
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
1866
 
                try:
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))
1874
 
                        return True
1875
 
                except ValueError:
1876
 
                        self._error = self._str_route_error
1877
 
                        if self.debug: raise
1878
 
                        return False
1879
 
 
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
1882
 
                try:
1883
 
                        prefix,suffix=tokens[0].split(':',1)
1884
 
                        if prefix.count('.'):
1885
 
                                ip = prefix.split('.')
1886
 
                                if len(ip) != 4:
1887
 
                                        raise ValueError('invalid IP %s' % prefix)
1888
 
                                ipn = 0
1889
 
                                while ip:
1890
 
                                        ipn <<= 8
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))
1896
 
                                return True
1897
 
                        else:
1898
 
                                asn = int(prefix)
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))
1905
 
                                return True
1906
 
                except ValueError:
1907
 
                        self._error = self._str_route_error
1908
 
                        if self.debug: raise
1909
 
                        return False
1910
 
 
1911
 
 
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
1919
 
 
1920
 
                self.logger.info('\ndecoding routes in configuration','parser')
1921
 
 
1922
 
                if route.startswith('F'*32):
1923
 
                        route = route[19*2:]
1924
 
                #       prepend = route[:19*2]
1925
 
                #else:
1926
 
                #       prepend = ''
1927
 
 
1928
 
                n = self.neighbor[self.neighbor.keys()[0]]
1929
 
 
1930
 
                path = {}
1931
 
                for f in known_families():
1932
 
                        if n.add_path:
1933
 
                                path[f] = n.add_path
1934
 
 
1935
 
                capa = Capabilities().new(n,False)
1936
 
                capa[CapabilityID.ADD_PATH] = path
1937
 
                capa[CapabilityID.MULTIPROTOCOL_EXTENSIONS] = n.families()
1938
 
 
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()
1942
 
                negotiated.sent(o1)
1943
 
                negotiated.received(o2)
1944
 
                #grouped = False
1945
 
 
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')
1952
 
                import sys
1953
 
                sys.exit(0)
1954
 
 
1955
 
 
1956
 
# ASN4 merge test
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']
1958
 
# EOR
1959
 
#               injected = '\x00\x00\x00\x07\x90\x0f\x00\x03\x00\x02\x01'
1960
 
 
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
1969
 
 
1970
 
                self.logger.info('\ndecoding routes in configuration','parser')
1971
 
 
1972
 
                n = self.neighbor[self.neighbor.keys()[0]]
1973
 
 
1974
 
                path = {}
1975
 
                for f in known_families():
1976
 
                        if n.add_path:
1977
 
                                path[f] = n.add_path
1978
 
 
1979
 
                capa = Capabilities().new(n,False)
1980
 
                capa[CapabilityID.ADD_PATH] = path
1981
 
                capa[CapabilityID.MULTIPROTOCOL_EXTENSIONS] = n.families()
1982
 
 
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()
1986
 
                negotiated.sent(o1)
1987
 
                negotiated.received(o2)
1988
 
                #grouped = False
1989
 
 
1990
 
                for nei in self.neighbor.keys():
1991
 
                        for family in self.neighbor[nei].families():
1992
 
                                if not family in self.neighbor[nei]._routes:
1993
 
                                        continue
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')
1999
 
                                        for pack in packed:
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')
2009
 
                import sys
2010
 
                sys.exit(0)