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

« back to all changes in this revision

Viewing changes to dev/sbin/ibgp-check

  • 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
#!/usr/bin/env python
 
2
 
 
3
import os
 
4
import pwd
 
5
import sys
 
6
import asyncore
 
7
import socket
 
8
import errno
 
9
from struct import unpack
 
10
 
 
11
def bytestream (value):
 
12
        return ''.join(['%02X' % ord(_) for _ in value])
 
13
 
 
14
def dump (value):
 
15
        def spaced (value):
 
16
                even = None
 
17
                for v in value:
 
18
                        if even is False:
 
19
                                yield ' '
 
20
                        yield '%02X' % ord(v)
 
21
                        even = not even
 
22
        return ''.join(spaced(value))
 
23
 
 
24
 
 
25
class BGPHandler(asyncore.dispatcher_with_send):
 
26
 
 
27
        keepalive = chr(0xFF)*16 + chr(0x0) + chr(0x13) + chr(0x4)
 
28
 
 
29
        _name = {
 
30
                chr(1) : 'OPEN',
 
31
                chr(2) : 'UPDATE',
 
32
                chr(3) : 'NOTIFICATION',
 
33
                chr(4) : 'KEEPALIVE',
 
34
        }
 
35
 
 
36
        def kind (self,header):
 
37
                return header[18]
 
38
 
 
39
        def isupdate (self,header):
 
40
                return header[18] == chr(2)
 
41
 
 
42
        def isnotification (self,header):
 
43
                return header[18] == chr(4)
 
44
 
 
45
        def name (self,header):
 
46
                return self._name.get(header[18],'SOME WEIRD RFC PACKET')
 
47
 
 
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:]]
 
53
 
 
54
                if not withdrawn and not announced:
 
55
                        if len(body) == 11:
 
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)
 
59
 
 
60
                while withdrawn:
 
61
                        if len(withdrawn) > 5:
 
62
                                yield '', 'raw:%s' % bytestream(body)
 
63
                                break
 
64
                        m = withdrawn.pop(0)
 
65
                        r = [0,0,0,0]
 
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)
 
70
 
 
71
                while announced:
 
72
                        if len(announced) > 5:
 
73
                                yield '', 'raw:%s' % bytestream(body)
 
74
                                break
 
75
                        m = announced.pop(0)
 
76
                        r = [0,0,0,0]
 
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)
 
81
 
 
82
        def notification (self,body):
 
83
                yield 'notification:%d,%d' % (ord(body[0]),ord(body[1])), 'raw:%s' % bytestream(body)
 
84
 
 
85
        def announce (self,*args):
 
86
                print self.ip,self.port,' '.join(str(_) for _ in args) if len(args) > 1 else args[0]
 
87
 
 
88
        def setup (self,ip,port,letter,messages,exit):
 
89
                self.ip = ip
 
90
                self.port = port
 
91
                self.exit = exit
 
92
                self.handle_read = self.handle_open
 
93
                self.sequence = {}
 
94
                self.raw = False
 
95
                self.letter = letter
 
96
                for rule in messages:
 
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
 
102
                                self.raw = True
 
103
                        self.sequence.setdefault(sequence,[]).append(announcement)
 
104
                self.update_sequence()
 
105
                return self
 
106
 
 
107
        def update_sequence (self):
 
108
                keys = sorted(list(self.sequence))
 
109
                if keys:
 
110
                        key = keys[0]
 
111
                        self.messages = self.sequence[key]
 
112
                        self.step = key
 
113
                        del self.sequence[key]
 
114
                        return True
 
115
                return False
 
116
 
 
117
        def read_message (self):
 
118
                header = ''
 
119
                while len(header) != 19:
 
120
                        try:
 
121
                                left = 19-len(header)
 
122
                                header += self.recv(left)
 
123
                                if left == 19-len(header):  # ugly
 
124
                                        # the TCP session is gone.
 
125
                                        return None,None
 
126
                        except socket.error,e:
 
127
                                if e.args[0] in (errno.EWOULDBLOCK,errno.EAGAIN):
 
128
                                        continue
 
129
                                raise e
 
130
 
 
131
                length = unpack('!H',header[16:18])[0] - 19
 
132
 
 
133
                body = ''
 
134
                while len(body) != length:
 
135
                        try:
 
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):
 
140
                                        continue
 
141
                                raise e
 
142
 
 
143
                return header,body
 
144
 
 
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:]
 
150
                self.send(o)
 
151
                self.send(self.keepalive)
 
152
                self.handle_read = self.handle_keepalive
 
153
 
 
154
        def handle_keepalive (self):
 
155
                header,body = self.read_message()
 
156
 
 
157
                if header is None:
 
158
                        self.announce('connection closed')
 
159
                        self.close()
 
160
                        return
 
161
 
 
162
                parser = self._decoder.get(self.kind(header),None)
 
163
 
 
164
                if parser:
 
165
                        for parsed,raw in parser(self,body):
 
166
                                announcement = raw if self.raw else parsed
 
167
 
 
168
                                if announcement.startswith('eor:'):  # skip EOR
 
169
                                        self.announce('skip eor',announcement)
 
170
                                        continue
 
171
 
 
172
                                if announcement.startswith('mp:'):  # skip unparsed MP
 
173
                                        self.announce('skip announcement for body :',dump(body))
 
174
                                        continue
 
175
 
 
176
                                if announcement in self.messages:
 
177
                                        self.messages.remove(announcement)
 
178
                                        self.announce('expected announcement received (%s%s):' % (self.letter,self.step),announcement)
 
179
                                else:
 
180
                                        if raw:
 
181
                                                self.announce('\n','unexpected announcement (%s%s):' % (self.letter,self.step),'%s %s' % (bytestream(header),bytestream(body)))
 
182
                                        else:
 
183
                                                self.announce('\n','unexpected announcement:',announcement)
 
184
 
 
185
                                        self.announce('valid options were:',', '.join(self.messages),'\n')
 
186
                                        sys.exit(1)
 
187
 
 
188
                                if not self.messages:
 
189
                                        if not self.update_sequence():
 
190
                                                if self.exit:
 
191
                                                        self.announce('successful')
 
192
                                                        sys.exit(0)
 
193
 
 
194
                self.send(self.keepalive)
 
195
 
 
196
        _decoder= {
 
197
                chr(2) : routes,
 
198
                chr(3) : notification,
 
199
        }
 
200
 
 
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))
 
207
                self.listen(5)
 
208
 
 
209
                self.messages = {}
 
210
 
 
211
                for message in messages:
 
212
                        if message[0].isalpha():
 
213
                                index,content = message[:1].upper(), message[1:]
 
214
                        else:
 
215
                                index,content = 'A',message
 
216
                        self.messages.setdefault(index,[]).append(content)
 
217
 
 
218
        def handle_accept (self):
 
219
                messages = None
 
220
                for number in range(ord('A'),ord('Z')+1):
 
221
                        letter = chr(number)
 
222
                        if letter in self.messages:
 
223
                                messages = self.messages[letter]
 
224
                                del self.messages[letter]
 
225
                                break
 
226
 
 
227
                if not messages:
 
228
                        print 'we used all the test data available, can not handle this new connection'
 
229
                        sys.exit(1)
 
230
                else:
 
231
                        print 'using :\n', '\n'.join(messages), '\n'
 
232
 
 
233
                exit = not len(self.messages.keys())
 
234
 
 
235
                pair = self.accept()
 
236
                if pair is not None:
 
237
                        sock,addr = pair
 
238
                        handler = BGPHandler(sock).setup(*addr,letter=letter,messages=messages,exit=exit)
 
239
 
 
240
def drop ():
 
241
        uid = os.getuid()
 
242
        gid = os.getgid()
 
243
 
 
244
        if uid and gid:
 
245
                return
 
246
 
 
247
        for name in ['nobody',]:
 
248
                try:
 
249
                        user = pwd.getpwnam(name)
 
250
                        nuid = int(user.pw_uid)
 
251
                        ngid = int(user.pw_uid)
 
252
                except KeyError:
 
253
                        pass
 
254
 
 
255
        if not gid:
 
256
                os.setgid(ngid)
 
257
        if not uid:
 
258
                os.setuid(nuid)
 
259
 
 
260
def main ():
 
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'
 
265
                sys.exit(1)
 
266
 
 
267
        try:
 
268
                with open(sys.argv[1]) as content:
 
269
                        messages = [_.strip() for _ in content.readlines() if _.strip() and '#' not in _]
 
270
        except IOError:
 
271
                print 'coud not open file', sys.argv[1]
 
272
 
 
273
        try:
 
274
                BGPServer('localhost',int(os.environ.get('exabgp.tcp.port','179')),messages)
 
275
                drop()
 
276
                asyncore.loop()
 
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')
 
282
                else:
 
283
                        print "failure", str(e)
 
284
 
 
285
if __name__ == '__main__':
 
286
        main()