- TAP interfaces
[nepi.git] / src / nepi / testbeds / planetlab / tunproto.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import weakref
5 import os
6 import os.path
7 import rspawn
8 import subprocess
9 import threading
10
11 from nepi.util import server
12
13 class TunProtoBase(object):
14     def __init__(self, local, peer, home_path, key):
15         # Weak references, since ifaces do have a reference to the
16         # tunneling protocol implementation - we don't want strong
17         # circular references.
18         self.peer = weakref.ref(peer)
19         self.local = weakref.ref(local)
20         
21         self.port = 15000
22         self.mode = 'pl-tun'
23         self.key = key
24         
25         self.home_path = home_path
26         
27         self._launcher = None
28         self._started = False
29         self._pid = None
30         self._ppid = None
31
32     def _make_home(self):
33         local = self.local()
34         
35         if not local:
36             raise RuntimeError, "Lost reference to peering interfaces before launching"
37         if not local.node:
38             raise RuntimeError, "Unconnected TUN - missing node"
39         
40         # Make sure all the paths are created where 
41         # they have to be created for deployment
42         cmd = "mkdir -p %s" % (server.shell_escape(self.home_path),)
43         (out,err),proc = server.popen_ssh_command(
44             cmd,
45             host = local.node.hostname,
46             port = None,
47             user = local.node.slicename,
48             agent = None,
49             ident_key = local.node.ident_path,
50             server_key = local.node.server_key
51             )
52         
53         if proc.wait():
54             raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
55         
56     
57     def _install_scripts(self):
58         local = self.local()
59         
60         if not local:
61             raise RuntimeError, "Lost reference to peering interfaces before launching"
62         if not local.node:
63             raise RuntimeError, "Unconnected TUN - missing node"
64         
65         # Install the tun_connect script and tunalloc utility
66         sources = [
67             os.path.join(os.path.dirname(__file__), 'scripts', 'tun_connect.py'),
68             os.path.join(os.path.dirname(__file__), 'scripts', 'tunalloc.c'),
69         ]
70         dest = "%s@%s:%s" % (
71             local.node.slicename, local.node.hostname, 
72             os.path.join(self.home_path,'.'),)
73         (out,err),proc = server.popen_scp(
74             sources,
75             dest,
76             ident_key = local.node.ident_path,
77             server_key = local.node.server_key
78             )
79     
80         if proc.wait():
81             raise RuntimeError, "Failed upload TUN connect script %r: %s %s" % (source, out,err,)
82
83         cmd = "cd %s && gcc -shared tunalloc.c -o tunalloc.so" % (server.shell_escape(self.home_path),)
84         (out,err),proc = server.popen_ssh_command(
85             cmd,
86             host = local.node.hostname,
87             port = None,
88             user = local.node.slicename,
89             agent = None,
90             ident_key = local.node.ident_path,
91             server_key = local.node.server_key
92             )
93         
94         if proc.wait():
95             raise RuntimeError, "Failed to set up TUN forwarder: %s %s" % (out,err,)
96         
97     def launch(self, check_proto, listen, extra_args=[]):
98         peer = self.peer()
99         local = self.local()
100         
101         if not peer or not local:
102             raise RuntimeError, "Lost reference to peering interfaces before launching"
103         
104         peer_port = peer.tun_port
105         peer_addr = peer.tun_addr
106         peer_proto= peer.tun_proto
107         
108         local_port = self.port
109         local_cap  = local.capture
110         local_addr = local.address
111         local_mask = local.netprefix
112         local_snat = local.snat
113         local_txq  = local.txqueuelen
114         
115         if check_proto != peer_proto:
116             raise RuntimeError, "Peering protocol mismatch: %s != %s" % (check_proto, peer_proto)
117         
118         if not listen and (not peer_port or not peer_addr):
119             raise RuntimeError, "Misconfigured peer: %s" % (peer,)
120         
121         if listen and (not local_port or not local_addr or not local_mask):
122             raise RuntimeError, "Misconfigured TUN: %s" % (local,)
123         
124         args = ["python", "tun_connect.py", 
125             "-m", str(self.mode),
126             "-p", str(local_port if listen else peer_port),
127             "-A", str(local_addr),
128             "-M", str(local_mask),
129             "-k", str(self.key)]
130         
131         if local_snat:
132             args.append("-S")
133         if local_txq:
134             args.extend(("-Q",str(local_txq)))
135         if extra_args:
136             args.extend(map(str,extra_args))
137         if not listen:
138             args.append(str(peer_addr))
139         
140         self._make_home()
141         self._install_scripts()
142         
143         # Start process in a "daemonized" way, using nohup and heavy
144         # stdin/out redirection to avoid connection issues
145         (out,err),proc = rspawn.remote_spawn(
146             " ".join(args),
147             
148             pidfile = './pid',
149             home = self.home_path,
150             stdin = '/dev/null',
151             stdout = 'capture' if local_cap else '/dev/null',
152             stderr = rspawn.STDOUT,
153             sudo = True,
154             
155             host = local.node.hostname,
156             port = None,
157             user = local.node.slicename,
158             agent = None,
159             ident_key = local.node.ident_path,
160             server_key = local.node.server_key
161             )
162         
163         if proc.wait():
164             raise RuntimeError, "Failed to set up TUN: %s %s" % (out,err,)
165
166         self._started = True
167     
168     def async_launch(self, check_proto, listen, extra_args=[]):
169         if not self._launcher:
170             self._launcher = threading.Thread(
171                 target = self.launch,
172                 args = (check_proto, listen, extra_args))
173             self._launcher.start()
174     
175     def async_launch_wait(self):
176         if not self._started:
177             if self._launcher:
178                 self._launcher.join()
179                 if not self._started:
180                     raise RuntimeError, "Failed to launch TUN forwarder"
181             else:
182                 self.launch()
183
184     def checkpid(self):            
185         local = self.local()
186         
187         if not local:
188             raise RuntimeError, "Lost reference to local interface"
189         
190         # Get PID/PPID
191         # NOTE: wait a bit for the pidfile to be created
192         if self._started and not self._pid or not self._ppid:
193             pidtuple = rspawn.remote_check_pid(
194                 os.path.join(self.home_path,'pid'),
195                 host = local.node.hostname,
196                 port = None,
197                 user = local.node.slicename,
198                 agent = None,
199                 ident_key = local.node.ident_path,
200                 server_key = local.node.server_key
201                 )
202             
203             if pidtuple:
204                 self._pid, self._ppid = pidtuple
205     
206     def status(self):
207         local = self.local()
208         
209         if not local:
210             raise RuntimeError, "Lost reference to local interface"
211         
212         self.checkpid()
213         if not self._started:
214             return rspawn.NOT_STARTED
215         elif not self._pid or not self._ppid:
216             return rspawn.NOT_STARTED
217         else:
218             status = rspawn.remote_status(
219                 self._pid, self._ppid,
220                 host = local.node.hostname,
221                 port = None,
222                 user = local.node.slicename,
223                 agent = None,
224                 ident_key = local.node.ident_path
225                 )
226             return status
227     
228     def kill(self):
229         local = self.local()
230         
231         if not local:
232             raise RuntimeError, "Lost reference to local interface"
233         
234         status = self.status()
235         if status == rspawn.RUNNING:
236             # kill by ppid+pid - SIGTERM first, then try SIGKILL
237             rspawn.remote_kill(
238                 self._pid, self._ppid,
239                 host = local.node.hostname,
240                 port = None,
241                 user = local.node.slicename,
242                 agent = None,
243                 ident_key = local.node.ident_path,
244                 server_key = local.node.server_key,
245                 sudo = True
246                 )
247         
248     def sync_trace(self, local_dir, whichtrace):
249         if whichtrace != 'packets':
250             return None
251         
252         local = self.local()
253         
254         if not local:
255             return None
256         
257         local_path = os.path.join(local_dir, 'capture')
258         
259         # create parent local folders
260         proc = subprocess.Popen(
261             ["mkdir", "-p", os.path.dirname(local_path)],
262             stdout = open("/dev/null","w"),
263             stdin = open("/dev/null","r"))
264
265         if proc.wait():
266             raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
267         
268         # sync files
269         (out,err),proc = server.popen_scp(
270             '%s@%s:%s' % (local.node.slicename, local.node.hostname, 
271                 os.path.join(self.home_path, 'capture')),
272             local_path,
273             port = None,
274             agent = None,
275             ident_key = local.node.ident_path,
276             server_key = local.node.server_key
277             )
278         
279         if proc.wait():
280             raise RuntimeError, "Failed to synchronize trace: %s %s" % (out,err,)
281         
282         return local_path
283         
284         
285     def prepare(self):
286         """
287         First-phase setup
288         
289         eg: set up listening ports
290         """
291         raise NotImplementedError
292     
293     def setup(self):
294         """
295         Second-phase setup
296         
297         eg: connect to peer
298         """
299         raise NotImplementedError
300     
301     def shutdown(self):
302         """
303         Cleanup
304         """
305         raise NotImplementedError
306         
307
308 class TunProtoUDP(TunProtoBase):
309     def __init__(self, local, peer, home_path, key, listening):
310         super(TunProtoUDP, self).__init__(local, peer, home_path, key)
311         self.listening = listening
312     
313     def prepare(self):
314         pass
315     
316     def setup(self):
317         self.async_launch('udp', False, ("-u",str(self.port)))
318     
319     def shutdown(self):
320         self.kill()
321
322 class TunProtoTCP(TunProtoBase):
323     def __init__(self, local, peer, home_path, key, listening):
324         super(TunProtoTCP, self).__init__(local, peer, home_path, key)
325         self.listening = listening
326     
327     def prepare(self):
328         if self.listening:
329             self.async_launch('tcp', True)
330     
331     def setup(self):
332         if not self.listening:
333             # make sure our peer is ready
334             peer = self.peer()
335             if peer and peer.peer_proto_impl:
336                 peer.peer_proto_impl.async_launch_wait()
337             
338             self.launch('tcp', False)
339         else:
340             # make sure WE are ready
341             self.async_launch_wait()
342         
343         self.checkpid()
344     
345     def shutdown(self):
346         self.kill()
347
348 class TapProtoUDP(TunProtoUDP):
349     def __init__(self, local, peer, home_path, key, listening):
350         super(TapProtoUDP, self).__init__(local, peer, home_path, key, listening)
351         self.mode = 'pl-tap'
352
353 class TapProtoTCP(TunProtoTCP):
354     def __init__(self, local, peer, home_path, key, listening):
355         super(TapProtoTCP, self).__init__(local, peer, home_path, key, listening)
356         self.mode = 'pl-tap'
357
358
359
360 TUN_PROTO_MAP = {
361     'tcp' : TunProtoTCP,
362     'udp' : TunProtoUDP,
363 }
364
365 TAP_PROTO_MAP = {
366     'tcp' : TapProtoTCP,
367     'udp' : TapProtoUDP,
368 }
369
370