Skip to content

Commit 338bd59

Browse files
committed
add license, readme and example
1 parent ae55236 commit 338bd59

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2009 Phil Mayers
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
This code is a simple python BGP implementation. It only speaks the protocol;
2+
there is no code for building or maintaining a RIB, or doing kernel-layer
3+
stuff such as inserting or deleting routes.
4+
5+
Whilst I'm not opposed to someone else adding those things, I have no use
6+
for them, so please don't ask me to write them ;o)
7+
8+
The intended use is for things such as anycast load-balancing, as with DNS
9+
or similar services. There is a script in "examples" which is very similar
10+
to the code we use.
11+
12+
There are a few tests; there could and should be more.
13+
14+
The code as written requires Python 2.5 struct.unpack_from. It would be pretty
15+
easy to either remove this requirement or make a compatibility layer for older
16+
python versions.
17+
18+
The project is run, somewhat against my better judgement, in Launchpad:
19+
20+
https://launchpad.net/pybgp

examples/dnsanycast

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/python2.5
2+
3+
from twisted.internet import reactor, protocol
4+
5+
from pybgp import speaker, pathattr, proto
6+
7+
import tcpcheck
8+
9+
# This script is a simple example of the pybgp library. The setup below
10+
# assumes the following config:
11+
#
12+
# router 192.168.1.1 AS# 65000
13+
# |
14+
# us 192.168.1.2 AS# 65001
15+
#
16+
# ...and virtual IPs bound to the loopback interface, on which a DNS
17+
# server will be listening - e.g. under linux:
18+
#
19+
# ip addr add 192.168.2.1/32 dev lo
20+
# ip addr add 192.168.2.2/32 dev lo
21+
# rndc reload
22+
#
23+
# As long as the virtual IPs continue to answer TCP port 53 connects the
24+
# VIPs will be advertised by eBGP to the router
25+
26+
class Checker(tcpcheck.Checker):
27+
def change(self, old, new):
28+
if new=='up':
29+
self.bgp.advertise('%s/32' % (self.host,))
30+
else:
31+
self.bgp.withdraw('%s/32' % (self.host,))
32+
33+
class BGPp(speaker.BGP):
34+
def send(self, msg):
35+
print "sending", msg
36+
speaker.BGP.send(self, msg)
37+
38+
class BGPd:
39+
# This is all a bit hacky. Proper support for both listening and
40+
# connecting, the BGP state machine, disconnects and so forth are
41+
# all needed...
42+
43+
def __init__(self, asnum, bgpid):
44+
self.cc = protocol.ClientCreator(reactor, BGPp)
45+
self.asnum = asnum
46+
self.bgpid = bgpid
47+
self.peers = {}
48+
49+
def peer(self, ip, remoteas):
50+
if ip in self.peers:
51+
raise Exception('connection already established')
52+
53+
self.peers[ip] = {'remoteas': remoteas, 'state': 'opening', 'proto': None, 'queue': []}
54+
55+
d = self.cc.connectTCP(ip, 179)
56+
d.addCallbacks(self.ok, self.err, (ip,), {}, (ip,), {})
57+
58+
def ok(self, proto, ip):
59+
print "connection to", ip, "up"
60+
self.peers[ip]['proto'] = proto
61+
self.peers[ip]['state'] = 'open'
62+
63+
# send our Open message
64+
proto.open(self.asnum, self.bgpid)
65+
proto.handle_msg = lambda x: self.msg(ip, x)
66+
67+
def msg(self, ip, msg):
68+
if not ip in self.peers:
69+
return
70+
proto = self.peers[ip]['proto']
71+
72+
if msg.kind=='open':
73+
proto.start_timer(msg.holdtime)
74+
75+
q = self.peers[ip]['queue']
76+
while q:
77+
action, data = q.pop(0)
78+
if action=='update':
79+
proto.send(data)
80+
81+
elif msg.kind=='notification':
82+
print "notification from", ip, msg
83+
proto.transport.loseConnection()
84+
del self.peers[ip]
85+
86+
def err(self, reason, ip):
87+
if not ip in self.peers:
88+
return
89+
print "connection to", ip, "failed", reason
90+
del self.peers[ip]
91+
92+
def advertise(self, prefix):
93+
up = proto.Update(
94+
pathattr.Origin('incomplete'),
95+
pathattr.Med(0),
96+
pathattr.AsPath([[self.asnum]]),
97+
pathattr.NextHop('155.198.62.12'),
98+
nlri=[prefix],
99+
)
100+
for p, info in self.peers.items():
101+
if info['state']=='open':
102+
info['proto'].send(up)
103+
else:
104+
info['queue'].append(('update', up))
105+
106+
def withdraw(self, prefix):
107+
up = proto.Update(
108+
withdraw=[prefix],
109+
)
110+
for p, info in self.peers.items():
111+
if info['state']=='open':
112+
info['proto'].send(up)
113+
else:
114+
info['queue'].append(('update', up))
115+
116+
117+
def main():
118+
b = BGPd(65001, '192.168.1.2')
119+
b.peer('192.168.1.1', 65000)
120+
121+
for ip in ('192.168.2.1', '192.168.2.2'):
122+
c = Checker(ip, 53, '127.0.0.1')
123+
c.bgp = b
124+
c.start(5)
125+
126+
if __name__=='__main__':
127+
reactor.callWhenRunning(main)
128+
reactor.run()

examples/tcpcheck.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/python2.5
2+
3+
from twisted.internet import reactor, protocol, task
4+
5+
class TcpProbeProt(protocol.Protocol):
6+
def connectionMade(self):
7+
self.factory.ok()
8+
self.transport.loseConnection()
9+
10+
class TcpProbe(protocol.ClientFactory):
11+
protocol = TcpProbeProt
12+
ok = 0
13+
14+
def clientConnectionFailed(self, connector, reason):
15+
self.callback(False)
16+
17+
def ok(self):
18+
self.ok = 1
19+
20+
def clientConnectionLost(self, connector, reason):
21+
if self.ok:
22+
self.callback(True)
23+
else:
24+
self.callback(False)
25+
26+
class Checker:
27+
def __init__(self, host, port, localif=''):
28+
self.state = 'down'
29+
self.host = host
30+
self.port = port
31+
self.localif = localif
32+
self.timeout = 2
33+
34+
def start(self, interval):
35+
self.task = task.LoopingCall(self.check_tcp)
36+
self.task.start(interval)
37+
38+
def check_tcp(self):
39+
f = TcpProbe()
40+
f.callback = self.callback
41+
reactor.connectTCP(self.host, self.port, f, timeout=self.timeout, bindAddress=(self.localif,0))
42+
43+
def callback(self, up):
44+
print "state", up
45+
if up:
46+
if self.state=='down':
47+
self.change('down', 'up')
48+
self.state = 'up'
49+
else:
50+
if self.state=='up':
51+
self.change('up', 'down')
52+
self.state = 'down'
53+
54+
def change(self, old, new):
55+
print "state for %s:%s (from %s) changes from %s to %s" % (self.host, self.port, self.localif, old, new)

0 commit comments

Comments
 (0)