Skip to content

Commit 5fa7699

Browse files
committed
add exception hierarchy, refactor parsing code, add tests for speaker code
1 parent f63e148 commit 5fa7699

File tree

5 files changed

+327
-32
lines changed

5 files changed

+327
-32
lines changed

pybgp/exceptions.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import struct
2+
3+
class BgpExc(Exception):
4+
data = None
5+
6+
send_error = True
7+
8+
def __str__(self):
9+
return '<%s %r>' % (self.__class__.__name__, self.data)
10+
11+
class NotSync(BgpExc):
12+
code = 1
13+
subcode = 1
14+
15+
16+
class BadLen(BgpExc):
17+
code = 1
18+
subcode = 2
19+
20+
def __init__(self, msg, len):
21+
self.msg = msg
22+
self.len = len
23+
self.data = struct.pack('!H', len)
24+
25+
def __str__(self):
26+
return '<BadLen %d msgtype=%d>' % (self.len,self.msg)
27+
28+
class BadMsg(BgpExc):
29+
code = 1
30+
subcode = 3
31+
32+
def __init__(self, msg):
33+
self.msg = msg
34+
self.data = struct.pack('B', msg)
35+
36+
def __str__(self):
37+
return '<BadMsg %d>' % (self.msg,)
38+

pybgp/proto.py

+44-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from odict import OrderedDict as OD
66

77

8-
from pybgp import nlri, pathattr
8+
from pybgp import nlri, pathattr, exceptions
99

1010
class Open:
1111
kind = 'open'
@@ -28,8 +28,6 @@ def from_bytes(cls, bytes):
2828

2929
self.version, self.asnum, self.holdtime, bgpid, paramlen = struct.unpack_from('!BHH4sB', bytes)
3030
self.bgpid = socket.inet_ntoa(bgpid)
31-
if paramlen==0:
32-
return
3331

3432
assert len(bytes) == 10 + paramlen, "message too short?"
3533

@@ -133,6 +131,11 @@ class Notification:
133131
kind = 'notification'
134132
number = 3
135133

134+
def __init__(self, code, subcode, data=''):
135+
self.code = code
136+
self.subcode = subcode
137+
self.data = data
138+
136139
REASONS = {
137140
(1,1): 'not synchronised',
138141
(1,2): 'bad message len',
@@ -157,12 +160,10 @@ class Notification:
157160
}
158161

159162
def from_bytes(cls, bytes):
160-
self = cls()
163+
code, subcode = struct.unpack_from('BB', bytes)
164+
data = bytes[2:]
161165

162-
self.code, self.subcode = struct.unpack_from('BB', bytes)
163-
self.data = bytes[2:]
164-
165-
return self
166+
return cls(code, subcode, data)
166167
from_bytes = classmethod(from_bytes)
167168

168169
def __str__(self):
@@ -171,6 +172,13 @@ def __str__(self):
171172
return 'Notification "%s" params %r' % (self.REASONS[c,s], self.data)
172173
return 'Notification message code=%d subcode=%d params=%r' % (self.code, self.subcode, self.data)
173174

175+
def encode(self):
176+
v = struct.pack('BB', self.code, self.subcode)
177+
if self.data:
178+
v += self.data
179+
return v
180+
181+
174182
class Update:
175183
kind = 'update'
176184
number = 2
@@ -266,3 +274,31 @@ def encode(self):
266274

267275
return v
268276

277+
278+
class ProtoBase:
279+
def parse_payload(self, type, payload):
280+
length = len(payload)
281+
totlen = length + 19
282+
283+
if type==1:
284+
if length<10:
285+
raise exceptions.BadLen(type, totlen)
286+
return Open.from_bytes(payload)
287+
288+
elif type==2:
289+
if length<4:
290+
raise exceptions.BadLen(type, totlen)
291+
return Update.from_bytes(payload)
292+
293+
elif type==3:
294+
if length<2:
295+
raise exceptions.BadLen(type, totlen)
296+
return Notification.from_bytes(payload)
297+
298+
elif type==4:
299+
if length:
300+
print "keepalive with data? %r" % (payload,)
301+
return Keepalive.from_bytes(payload)
302+
303+
raise exceptions.BadMsg(type)
304+

pybgp/speaker.py

+21-23
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
from twisted.internet import reactor, protocol, task
55
from twisted.python import log
66

7-
from pybgp import nlri, pathattr, proto
7+
from pybgp import nlri, pathattr, proto, exceptions
88

9-
class BGP(protocol.Protocol):
9+
class BGP(protocol.Protocol, proto.ProtoBase):
1010

1111
def connectionMade(self):
1212
self.buffer = ''
@@ -37,35 +37,33 @@ def dataReceived(self, data):
3737
if len(self.buffer) < 19:
3838
return
3939
auth, length, type = struct.unpack('!16sHB', self.buffer[:19])
40+
41+
if auth!='\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff':
42+
return self.notify(exceptions.NotSync())
43+
44+
if length < 19 or length > 4096:
45+
return self.notify(exceptions.BadLen(type, length))
46+
4047
if len(self.buffer) < length:
4148
return
4249

43-
msg = self.buffer[:length]
50+
payload = self.buffer[19:length]
4451
self.buffer = self.buffer[length:]
4552

46-
msg = self.parse_msg(msg)
53+
try:
54+
msg = self.parse_payload(type, payload)
55+
except exceptions.BgpExc, ex:
56+
return self.notify(ex)
4757

4858
self._handle_msg(msg)
4959

50-
def parse_msg(self, msg):
51-
auth, length, type = struct.unpack('!16sHB', msg[:19])
52-
payload = msg[19:]
53-
del msg
54-
55-
if type==1:
56-
return proto.Open.from_bytes(payload)
57-
58-
elif type==2:
59-
return proto.Update.from_bytes(payload)
60-
61-
elif type==3:
62-
return proto.Notification.from_bytes(payload)
63-
64-
elif type==4:
65-
return proto.Keepalive.from_bytes(payload)
66-
67-
else:
68-
raise Exception('invalid message')
60+
def notify(self, ex):
61+
if ex.send_error:
62+
log.msg("sending notify", ex)
63+
notify = proto.Notification(ex.code, ex.subcode, ex.data)
64+
self.send(notify)
65+
log.msg("disconnecting")
66+
self.transport.loseConnection()
6967

7068
def send(self, msg):
7169
body = msg.encode()

pybgp/test/test_proto.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import unittest
44

5-
from pybgp import proto, pathattr, nlri
5+
from pybgp import proto, pathattr, nlri, exceptions
66

77
class TestOpen(unittest.TestCase):
88
def test_decode(self):

0 commit comments

Comments
 (0)