9
from struct import unpack
11
def bytestream (value):
12
return ''.join(['%02X' % ord(_) for _ in value])
22
return ''.join(spaced(value))
25
class BGPHandler(asyncore.dispatcher_with_send):
27
keepalive = chr(0xFF)*16 + chr(0x0) + chr(0x13) + chr(0x4)
32
chr(3) : 'NOTIFICATION',
36
def kind (self,header):
39
def isupdate (self,header):
40
return header[18] == chr(2)
42
def isnotification (self,header):
43
return header[18] == chr(4)
45
def name (self,header):
46
return self._name.get(header[18],'SOME WEIRD RFC PACKET')
48
def routes (self,body):
49
len_w = unpack('!H',body[0:2])[0]
50
withdrawn = [ord(_) for _ in body[2:2+len_w]]
51
len_a = unpack('!H',body[2+len_w:2+len_w+2])[0]
52
announced = [ord(_) for _ in body[2+len_w + 2+len_a:]]
54
if not withdrawn and not announced:
56
yield 'eor:%d:%d' % (ord(body[-2]),ord(body[-1])),'raw:%s' % bytestream(body)
57
else: # undecoded MP route
58
yield 'mp:','raw:%s' % bytestream(body)
61
if len(withdrawn) > 5:
62
yield '', 'raw:%s' % bytestream(body)
66
for index in range(4):
67
if index*8 >= m: break
68
r[index] = withdrawn.pop(0)
69
yield 'withdraw:%s' % '.'.join(str(_) for _ in r) + '/' + str(m), 'raw:%s' % bytestream(body)
72
if len(announced) > 5:
73
yield '', 'raw:%s' % bytestream(body)
77
for index in range(4):
78
if index*8 >= m: break
79
r[index] = announced.pop(0)
80
yield 'announce:%s' % '.'.join(str(_) for _ in r) + '/' + str(m), 'raw:%s' % bytestream(body)
82
def notification (self,body):
83
yield 'notification:%d,%d' % (ord(body[0]),ord(body[1])), 'raw:%s' % bytestream(body)
85
def announce (self,*args):
86
print self.ip,self.port,' '.join(str(_) for _ in args) if len(args) > 1 else args[0]
88
def setup (self,ip,port,letter,messages,exit):
92
self.handle_read = self.handle_open
97
sequence,announcement = rule.split(':',1)
98
if announcement.startswith('raw:'):
99
raw = ''.join(announcement[4:].replace(':',''))
100
stream = raw[38:] if raw.startswith('F'*16) else raw
101
announcement = 'raw:' + stream
103
self.sequence.setdefault(sequence,[]).append(announcement)
104
self.update_sequence()
107
def update_sequence (self):
108
keys = sorted(list(self.sequence))
111
self.messages = self.sequence[key]
113
del self.sequence[key]
117
def read_message (self):
119
while len(header) != 19:
121
left = 19-len(header)
122
header += self.recv(left)
123
if left == 19-len(header): # ugly
124
# the TCP session is gone.
126
except socket.error,e:
127
if e.args[0] in (errno.EWOULDBLOCK,errno.EAGAIN):
131
length = unpack('!H',header[16:18])[0] - 19
134
while len(body) != length:
136
left = length-len(body)
137
body += self.recv(left)
138
except socket.error,e:
139
if e.args[0] in (errno.EWOULDBLOCK,errno.EAGAIN):
145
def handle_open (self):
146
# reply with a IBGP response with the same capability (just changing routerID)
147
header,body = self.read_message()
148
routerid = chr((ord(body[8])+1) & 0xFF)
149
o = header+body[:8]+routerid+body[9:]
151
self.send(self.keepalive)
152
self.handle_read = self.handle_keepalive
154
def handle_keepalive (self):
155
header,body = self.read_message()
158
self.announce('connection closed')
162
parser = self._decoder.get(self.kind(header),None)
165
for parsed,raw in parser(self,body):
166
announcement = raw if self.raw else parsed
168
if announcement.startswith('eor:'): # skip EOR
169
self.announce('skip eor',announcement)
172
if announcement.startswith('mp:'): # skip unparsed MP
173
self.announce('skip announcement for body :',dump(body))
176
if announcement in self.messages:
177
self.messages.remove(announcement)
178
self.announce('expected announcement received (%s%s):' % (self.letter,self.step),announcement)
181
self.announce('\n','unexpected announcement (%s%s):' % (self.letter,self.step),'%s %s' % (bytestream(header),bytestream(body)))
183
self.announce('\n','unexpected announcement:',announcement)
185
self.announce('valid options were:',', '.join(self.messages),'\n')
188
if not self.messages:
189
if not self.update_sequence():
191
self.announce('successful')
194
self.send(self.keepalive)
198
chr(3) : notification,
201
class BGPServer (asyncore.dispatcher):
202
def __init__ (self,host,port,messages):
203
asyncore.dispatcher.__init__(self)
204
self.create_socket(socket.AF_INET,socket.SOCK_STREAM)
205
self.set_reuse_addr()
206
self.bind((host,port))
211
for message in messages:
212
if message[0].isalpha():
213
index,content = message[:1].upper(), message[1:]
215
index,content = 'A',message
216
self.messages.setdefault(index,[]).append(content)
218
def handle_accept (self):
220
for number in range(ord('A'),ord('Z')+1):
222
if letter in self.messages:
223
messages = self.messages[letter]
224
del self.messages[letter]
228
print 'we used all the test data available, can not handle this new connection'
231
print 'using :\n', '\n'.join(messages), '\n'
233
exit = not len(self.messages.keys())
238
handler = BGPHandler(sock).setup(*addr,letter=letter,messages=messages,exit=exit)
247
for name in ['nobody',]:
249
user = pwd.getpwnam(name)
250
nuid = int(user.pw_uid)
251
ngid = int(user.pw_uid)
261
if len(sys.argv) <= 1:
262
print 'a list of expected route announcement/withdrawl in the format <number>:announce:<ipv4-route> <number>:withdraw:<ipv4-route> <number>:raw:<exabgp hex dump : separated>'
263
print 'for example:',sys.argv[0],'1:announce:10.0.0.0/8 1:announce:192.0.2.0/24 2:withdraw:10.0.0.0/8 '
264
print 'routes with the same <number> can arrive in any order'
268
with open(sys.argv[1]) as content:
269
messages = [_.strip() for _ in content.readlines() if _.strip() and '#' not in _]
271
print 'coud not open file', sys.argv[1]
274
BGPServer('localhost',int(os.environ.get('exabgp.tcp.port','179')),messages)
277
except socket.error,e:
278
if e.errno == errno.EACCES:
279
print "failure: could not bind to port %s - most likely not run as root" % os.environ.get('exabgp.tcp.port','179')
280
elif e.errno == errno.EADDRINUSE:
281
print "failure: could not bind to port %s - port already in use" % os.environ.get('exabgp.tcp.port','179')
283
print "failure", str(e)
285
if __name__ == '__main__':