Better support of default route
[re6stnet.git] / demo / demo
1 #!/usr/bin/python
2 import math, nemu, os, signal, socket, subprocess, sys, time, weakref
3 from collections import defaultdict
4 IPTABLES = 'iptables'
5 SCREEN = 'screen'
6 VERBOSE = 4
7 REGISTRY='10.0.0.2'
8
9 #                       registry
10 #                           |.2
11 #                           |10.0.0
12 #                           |.1
13 #        ---------------Internet----------------
14 #        |.1                |.1                |.1
15 #        |10.1.0            |10.2.0            |
16 #        |.2                |.2                |
17 #    gateway1           gateway2           s3:10.0.1
18 #        |.1                |.1            |.2 |.3 |.4
19 #    s1:10.1.1          s2:10.2.1          m6  m7  m8
20 #    |.2     |.3        |.2 |.3 |.4
21 #    m1      m2         m3  m4  m5
22 #
23
24 def disable_signal_on_children(sig):
25     pid = os.getpid()
26     sigint = signal.signal(sig, lambda *x: os.getpid() == pid and sigint(*x))
27 disable_signal_on_children(signal.SIGINT)
28
29 Node__add_interface = nemu.Node._add_interface
30 def _add_interface(node, iface):
31     iface.__dict__['node'] = weakref.proxy(node)
32     return Node__add_interface(node, iface)
33 nemu.Node._add_interface = _add_interface
34
35 # create nodes
36 for name in """internet=I registry=R
37                           gateway1=g1 machine1=1 machine2=2
38                           gateway2=g2 machine3=3 machine4=4 machine5=5
39                           machine6=6 machine7=7 machine8=8
40             """.split():
41     name, short = name.split('=')
42     globals()[name] = node = nemu.Node()
43     node.name = name
44     node.short = short
45     node._screen = node.Popen((SCREEN, '-DmS', name))
46     node.screen = (lambda name: lambda *cmd:
47         subprocess.call([SCREEN, '-r', name, '-X', 'eval'] + map(
48             "screen sh -c '%s; exec $SHELL'".__mod__, cmd)))(name)
49
50 # create switch
51 switch1 = nemu.Switch()
52 switch2 = nemu.Switch()
53 switch3 = nemu.Switch()
54
55 #create interfaces
56 re_if_0, in_if_0 = nemu.P2PInterface.create_pair(registry, internet)
57 in_if_1, g1_if_0 = nemu.P2PInterface.create_pair(internet, gateway1)
58 in_if_2, g2_if_0 = nemu.P2PInterface.create_pair(internet, gateway2)
59
60 g1_if_0_name = g1_if_0.name
61 gateway1.Popen((IPTABLES, '-t', 'nat', '-A', 'POSTROUTING', '-o', g1_if_0_name, '-j', 'MASQUERADE')).wait()
62 gateway1.Popen((IPTABLES, '-t', 'nat', '-N', 'MINIUPNPD')).wait()
63 gateway1.Popen((IPTABLES, '-t', 'nat', '-A', 'PREROUTING', '-i', g1_if_0_name, '-j', 'MINIUPNPD')).wait()
64 gateway1.Popen((IPTABLES, '-N', 'MINIUPNPD')).wait()
65
66 in_if_3 = nemu.NodeInterface(internet)
67 g1_if_1 = nemu.NodeInterface(gateway1)
68 g2_if_1 = nemu.NodeInterface(gateway2)
69 m1_if_0 = nemu.NodeInterface(machine1)
70 m2_if_0 = nemu.NodeInterface(machine2)
71 m3_if_0 = nemu.NodeInterface(machine3)
72 m4_if_0 = nemu.NodeInterface(machine4)
73 m5_if_0 = nemu.NodeInterface(machine5)
74 m6_if_0 = nemu.NodeInterface(machine6)
75 m7_if_0 = nemu.NodeInterface(machine7)
76 m8_if_0 = nemu.NodeInterface(machine8)
77
78 # connect to switch
79 switch1.connect(g1_if_1)
80 switch1.connect(m1_if_0)
81 switch1.connect(m2_if_0)
82
83 switch2.connect(g2_if_1)
84 switch2.connect(m3_if_0)
85 switch2.connect(m4_if_0)
86 switch2.connect(m5_if_0)
87
88 switch3.connect(in_if_3)
89 switch3.connect(m6_if_0)
90 switch3.connect(m7_if_0)
91 switch3.connect(m8_if_0)
92
93 # setting everything up
94 switch1.up = switch2.up = switch3.up = True
95 re_if_0.up = in_if_0.up = in_if_1.up = g1_if_0.up = in_if_2.up = g2_if_0.up = True
96 in_if_3.up = g1_if_1.up = g2_if_1.up = m1_if_0.up = m2_if_0.up = m3_if_0.up = m4_if_0.up = m5_if_0.up = m6_if_0.up = m7_if_0.up = m8_if_0.up = True
97
98 # Add IPv4 addresses
99 re_if_0.add_v4_address(address=REGISTRY, prefix_len=24)
100 in_if_0.add_v4_address(address='10.0.0.1', prefix_len=24)
101 in_if_1.add_v4_address(address='10.1.0.1', prefix_len=24)
102 in_if_2.add_v4_address(address='10.2.0.1', prefix_len=24)
103 in_if_3.add_v4_address(address='10.0.1.1', prefix_len=24)
104 in_if_3.add_v6_address(address='2001:db8::1', prefix_len=48)
105 g1_if_0.add_v4_address(address='10.1.0.2', prefix_len=24)
106 g1_if_1.add_v4_address(address='10.1.1.1', prefix_len=24)
107 g2_if_0.add_v4_address(address='10.2.0.2', prefix_len=24)
108 g2_if_1.add_v4_address(address='10.2.1.1', prefix_len=24)
109 m1_if_0.add_v4_address(address='10.1.1.2', prefix_len=24)
110 m2_if_0.add_v4_address(address='10.1.1.3', prefix_len=24)
111 m3_if_0.add_v4_address(address='10.2.1.2', prefix_len=24)
112 m4_if_0.add_v4_address(address='10.2.1.3', prefix_len=24)
113 m5_if_0.add_v4_address(address='10.2.1.4', prefix_len=24)
114 m6_if_0.add_v4_address(address='10.0.1.2', prefix_len=24)
115 m7_if_0.add_v4_address(address='10.0.1.3', prefix_len=24)
116 m8_if_0.add_v4_address(address='10.0.1.4', prefix_len=24)
117
118 def add_llrtr(iface, peer, dst='default'):
119     for a in peer.get_addresses():
120         a = a['address']
121         if a.startswith('fe80:'):
122             return iface.node.Popen(('ip', 'route', 'add', dst, 'via', a,
123                 'proto', 'static', 'dev', iface.name)).wait()
124
125 # setup routes
126 add_llrtr(re_if_0, in_if_0)
127 add_llrtr(in_if_0, re_if_0, '2001:db8:42::/48')
128 registry.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.0.0.1')
129 internet.add_route(prefix='10.2.0.0', prefix_len=16, nexthop='10.2.0.2')
130 gateway1.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.1.0.1')
131 gateway2.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.2.0.1')
132 for m in machine1, machine2:
133     m.add_route(nexthop='10.1.1.1')
134 for m in machine3, machine4, machine5:
135     m.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.2.1.1')
136 for m in machine6, machine7, machine8:
137     m.add_route(prefix='10.0.0.0', prefix_len=8, nexthop='10.0.1.1')
138
139 # Test connectivity first. Run process, hide output and check
140 # return code
141 null = file(os.devnull, "r+")
142 for ip in '10.1.1.2', '10.1.1.3', '10.2.1.2', '10.2.1.3':
143     if machine1.Popen(('ping', '-c1', ip), stdout=null).wait():
144         print 'Failed to ping %s' % ip
145         break
146 else:
147     print "Connectivity IPv4 OK!"
148
149 nodes = []
150 gateway1.screen('miniupnpd -d -f miniupnpd.conf -P miniupnpd.pid -a 10.1.1.1'
151                 ' -i %s' % g1_if_0_name)
152 if 1:
153     import sqlite3
154     db_path = 'registry/registry.db'
155     registry.screen('../re6st-registry @registry/re6st-registry.conf --db %s'
156         ' --mailhost %s -v%u' % (db_path, os.path.abspath('mbox'), VERBOSE))
157     registry_url = 'http://%s/' % REGISTRY
158     registry.Popen(('python', '-c', """if 1:
159         import socket, time
160         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
161         while True:
162             try:
163                 s.connect(('localhost', 80))
164                 break
165             except socket.error:
166                 time.sleep(.1)
167         """)).wait()
168     db = sqlite3.connect(db_path, isolation_level=None)
169     def re6stnet(node, folder, args='', prefix_len=None, registry=registry_url):
170         nodes.append(node)
171         if not os.path.exists(folder + '/cert.crt'):
172             dh_path = folder + '/dh2048.pem'
173             if not os.path.exists(dh_path):
174                 os.symlink('../dh2048.pem', dh_path)
175             email = node.name + '@example.com'
176             p = node.Popen(('../../re6st-conf', '--registry', registry,
177                 '--email', email), stdin=subprocess.PIPE, cwd=folder)
178             token = None
179             while not token:
180                 time.sleep(.1)
181                 token = db.execute("SELECT token FROM token WHERE email=?",
182                                    (email,)).fetchone()
183             if prefix_len:
184                 db.execute("UPDATE token SET prefix_len=? WHERE token=?",
185                            (prefix_len, token[0]))
186             p.communicate(str(token[0]))
187             os.remove(dh_path)
188             os.remove(folder + '/ca.crt')
189         node.screen('../re6stnet @%s/re6stnet.conf --table 0 -v%u --registry %s %s'
190                     % (folder, VERBOSE, registry, args))
191     re6stnet(registry, 'registry', '--ip ' + REGISTRY, registry='http://localhost/')
192     re6stnet(machine1, 'm1', '-I%s' % m1_if_0.name)
193     re6stnet(machine2, 'm2', prefix_len=80)
194     re6stnet(machine3, 'm3', '-i%s' % m3_if_0.name)
195     re6stnet(machine4, 'm4', '-i%s' % m4_if_0.name)
196     re6stnet(machine5, 'm5', '-i%s' % m5_if_0.name)
197     re6stnet(machine6, 'm6')
198     re6stnet(machine7, 'm7')
199     re6stnet(machine8, 'm8')
200     db.close()
201
202 _ll = {}
203 def node_by_ll(addr):
204     try:
205         return _ll[addr]
206     except KeyError:
207         for n in nodes:
208             for i in n.get_interfaces():
209                 t = isinstance(i, nemu.interface.ImportedNodeInterface)
210                 try:
211                     a = i.get_addresses()
212                 except KeyError:
213                     break
214                 for a in a:
215                     p = a['prefix_len']
216                     a = a['address']
217                     if a.startswith('2001:db8:'):
218                         assert not p % 8
219                         a = socket.inet_ntop(socket.AF_INET6,
220                             socket.inet_pton(socket.AF_INET6,
221                             a)[:p/8].ljust(16, '\0'))
222                     elif not a.startswith('fe80::'):
223                         continue
224                     _ll[a] = n, t
225     return _ll[addr]
226
227 def route_svg(z=4):
228     graph = {}
229     for n in nodes:
230         g = graph[n] = defaultdict(list)
231         for r in n.get_routes():
232             if r.prefix and r.prefix.startswith('2001:db8:'):
233                 try:
234                     g[node_by_ll(r.nexthop)].append(node_by_ll(r.prefix)[0])
235                 except KeyError:
236                     pass
237     gv = ["digraph { splines = true; edge[color=grey, labelangle=0, arrowhead=dot];"]
238     N = len(nodes)
239     a = 2 * math.pi / N
240     edges = set()
241     for i, n in enumerate(nodes):
242         gv.append('%s[pos="%s,%s!"];'
243             % (n.name, z * math.cos(a * i), z * math.sin(a * i)))
244         l = []
245         for p, r in graph[n].iteritems():
246             j = abs(nodes.index(p[0]) - i)
247             l.append((min(j, N - j), p, r))
248         for j, (l, (p, t), r) in enumerate(sorted(l)):
249             l = []
250             for r in sorted(r.short for r in r):
251                 if r == p.short:
252                     r = '<font color="grey">%s</font>' % r
253                 l.append(r)
254             if (n.name, p.name) in edges:
255                 r = 'penwidth=0'
256             else:
257                 edges.add((p.name, n.name))
258                 r = 'style=solid' if t else 'style=dashed'
259             gv.append('%s -> %s [labeldistance=%u, headlabel=<%s>, %s];'
260                 % (p.name, n.name, 1.5 * math.sqrt(j) + 2, ','.join(l), r))
261     gv.append('}\n')
262     return subprocess.Popen(('neato', '-Tsvg'),
263         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
264         ).communicate('\n'.join(gv))[0]
265
266 if len(sys.argv) > 1:
267     import SimpleHTTPServer, SocketServer
268
269     class Handler(SimpleHTTPServer.SimpleHTTPRequestHandler):
270         def do_GET(self):
271             svg = None
272             if self.path == '/route.html':
273                 other = 'tunnel'
274                 svg = route_svg()
275             elif self.path == '/tunnel.html':
276                 other = 'route'
277                 gv = registry.Popen(('python', '-c', r"""if 1:
278                     import math, xmlrpclib
279                     g = xmlrpclib.ServerProxy('http://localhost/').topology()
280                     print 'digraph {'
281                     a = 2 * math.pi / len(g)
282                     z = 4
283                     m2 = '%u/80' % (2 << 64)
284                     title = lambda n: '2|80' if n == m2 else n
285                     g = sorted((title(k), v) for k, v in g.iteritems())
286                     for i, (n, p) in enumerate(g):
287                         print '"%s"[pos="%s,%s!"%s];' % (title(n),
288                             z * math.cos(a * i), z * math.sin(a * i),
289                             ', style=dashed' if p is None else '')
290                         for p in p or ():
291                             print '"%s" -> "%s";' % (n, title(p))
292                     print '}'
293                 """), stdout=subprocess.PIPE).communicate()[0]
294                 if gv:
295                     svg = subprocess.Popen(('neato', '-Tsvg'),
296                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
297                         ).communicate(gv)[0]
298                 if not svg:
299                     self.send_error(500)
300                     return
301             else:
302                 if self.path == '/':
303                     self.send_response(302)
304                     self.send_header('Location', 'route.html')
305                     self.end_headers()
306                 else:
307                     self.send_error(404)
308                 return
309             mt = 'text/html'
310             body = """<html>
311 <head><meta http-equiv="refresh" content="10"/></head>
312 <body><a style="position: absolute" href="%s.html">%ss</a>
313 %s
314 </body>
315 </html>""" % (other, other, svg[svg.find('<svg'):])
316             self.send_response(200)
317             self.send_header('Content-Length', len(body))
318             self.send_header('Content-type', mt + '; charset=utf-8')
319             self.end_headers()
320             self.wfile.write(body)
321
322     class TCPServer(SocketServer.TCPServer):
323         allow_reuse_address = True
324
325     TCPServer(('', int(sys.argv[1])), Handler).serve_forever()
326
327 import pdb; pdb.set_trace()