Serious refactoring of TUN/TAP and tunnel code. Linux udp/gre tunnels not yet functional
[nepi.git] / src / nepi / resources / planetlab / tap.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
5 #    This program is free software: you can redistribute it and/or modify
6 #    it under the terms of the GNU General Public License as published by
7 #    the Free Software Foundation, either version 3 of the License, or
8 #    (at your option) any later version.
9 #
10 #    This program is distributed in the hope that it will be useful,
11 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #    GNU General Public License for more details.
14 #
15 #    You should have received a copy of the GNU General Public License
16 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.execution.attribute import Attribute, Flags, Types
21 from nepi.execution.resource import clsinit_copy, ResourceState 
22 from nepi.resources.linux.tap import LinuxTap
23 from nepi.resources.planetlab.node import PlanetlabNode
24 from nepi.util.timefuncs import tnow, tdiffsec
25
26 import os
27 import time
28
29 PYTHON_VSYS_VERSION = "1.0"
30
31 @clsinit_copy
32 class PlanetlabTap(LinuxTap):
33     _rtype = "planetlab::Tap"
34     _help = "Creates a TAP device on a PlanetLab host"
35     _platform = "planetlab"
36
37     @classmethod
38     def _register_attributes(cls):
39         snat = Attribute("snat", "Set SNAT=1", 
40                 type = Types.Bool,
41                 flags = Flags.Design)
42         
43         cls._register_attribute(snat)
44
45     def __init__(self, ec, guid):
46         super(PlanetlabTap, self).__init__(ec, guid)
47         self._home = "tap-%s" % self.guid
48         self._gre_enabled = False
49
50     @property
51     def node(self):
52         node = self.get_connected(PlanetlabNode.get_rtype())
53         if node: return node[0]
54         raise RuntimeError, "TAP/TUN devices must be connected to Node"
55
56     def upload_sources(self):
57         scripts = []
58
59         # vif-creation python script
60         pl_vif_create = os.path.join(os.path.dirname(__file__), "scripts",
61                 "pl-vif-create.py")
62
63         scripts.append(pl_vif_create)
64         
65         # vif-up python script
66         pl_vif_up = os.path.join(os.path.dirname(__file__), "scripts",
67                 "pl-vif-up.py")
68         
69         scripts.append(pl_vif_up)
70
71         # vif-down python script
72         pl_vif_down = os.path.join(os.path.dirname(__file__), "scripts",
73                 "pl-vif-down.py")
74         
75         scripts.append(pl_vif_down)
76
77         # udp-connect python script
78         udp_connect = os.path.join(os.path.dirname(__file__), 
79                 "..",
80                 "linux",
81                 "scripts",
82                 "linux-udp-connect.py")
83         
84         scripts.append(udp_connect)
85
86         # tunnel creation python script
87         tunchannel = os.path.join(os.path.dirname(__file__), 
88                 "..", 
89                 "linux",
90                 "scripts", 
91                 "tunchannel.py")
92
93         scripts.append(tunchannel)
94
95         # Upload scripts
96         scripts = ";".join(scripts)
97
98         self.node.upload(scripts,
99                 os.path.join(self.node.src_dir),
100                 overwrite = False)
101
102         # upload stop.sh script
103         stop_command = self.replace_paths(self._stop_command)
104
105         self.node.upload_command(stop_command,
106                 shfile = os.path.join(self.app_home, "stop.sh"),
107                 # Overwrite file every time. 
108                 # The stop.sh has the path to the socket, which should change
109                 # on every experiment run.
110                 overwrite = True)
111
112     def upload_start_command(self):
113         super(PlanetlabTap, self).upload_start_command()
114
115         # Planetlab TAPs always add a PI header
116         self.set("pi", True)
117
118         if not self.gre_enabled:
119             # After creating the TAP, the pl-vif-create.py script
120             # will write the name of the TAP to a file. We wait until
121             # we can read the interface name from the file.
122             vif_name = self.wait_vif_name()
123             self.set("deviceName", vif_name) 
124
125     def wait_vif_name(self, exec_run_home = None):
126         """ Waits until the vif_name file for the command is generated, 
127             and returns the vif_name for the device """
128         vif_name = None
129         delay = 0.5
130
131         # The vif_name file will be created in the tap-home, while the
132         # current execution home might be elsewhere to check for errors
133         # (e.g. could be a tunnel-home)
134         if not exec_run_home:
135             exec_run_home = self.run_home
136
137         for i in xrange(20):
138             (out, err), proc = self.node.check_output(self.run_home, "vif_name")
139
140             if proc.poll() > 0:
141                 (out, err), proc = self.node.check_errors(exec_run_home)
142                 
143                 if err.strip():
144                     raise RuntimeError, err
145
146             if out:
147                 vif_name = out.strip()
148                 break
149             else:
150                 time.sleep(delay)
151                 delay = delay * 1.5
152         else:
153             msg = "Couldn't retrieve vif_name"
154             self.error(msg, out, err)
155             raise RuntimeError, msg
156
157         return vif_name
158
159     def gre_connect(self, remote_endpoint, connection_app_home,
160             connection_run_home):
161         super(PlanetlabTap, self).gre_connect(remote_endpoint, 
162                 connection_app_home, connection_run_home)
163          # After creating the TAP, the pl-vif-create.py script
164         # will write the name of the TAP to a file. We wait until
165         # we can read the interface name from the file.
166         vif_name = self.wait_vif_name(exec_run_home = connection_run_home)
167         self.set("deviceName", vif_name) 
168
169         return True
170
171     def _gre_connect_command(self, remote_endpoint, connection_app_home, 
172             connection_run_home): 
173         # Set the remote endpoint, (private) IP of the device
174         self.set("pointopoint", remote_endpoint.get("ip"))
175         # Public IP of the node
176         self.set("greRemote", remote_endpoint.node.get("ip"))
177
178         # Generate GRE connect command
179
180         # Use vif_down command to first kill existing TAP in GRE mode
181         vif_down_command = self._vif_down_command
182
183         # Use pl-vif-up.py script to configure TAP with peer info
184         vif_up_command = self._vif_up_command
185         
186         command = ["("]
187         command.append(vif_down_command)
188         command.append(") ; (")
189         command.append(vif_up_command)
190         command.append(")")
191
192         command = " ".join(command)
193         command = self.replace_paths(command)
194
195         return command
196
197     @property
198     def _start_command(self):
199         if self.gre_enabled:
200             command = []
201         else:
202             command = ["sudo -S python ${SRC}/pl-vif-create.py"]
203             
204             command.append("-t %s" % self.vif_type)
205             command.append("-a %s" % self.get("ip"))
206             command.append("-n %s" % self.get("prefix"))
207             command.append("-f %s " % self.vif_name_file)
208             command.append("-S %s " % self.sock_name)
209
210             if self.get("snat") == True:
211                 command.append("-s")
212
213             if self.get("pointopoint"):
214                 command.append("-p %s" % self.get("pointopoint"))
215             
216             if self.get("txqueuelen"):
217                 command.append("-q %s" % self.get("txqueuelen"))
218
219         return " ".join(command)
220
221     @property
222     def _stop_command(self):
223         if self.gre_enabled:
224             command = self._vif_down_command
225         else:
226             command = ["sudo -S "]
227             command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
228             command.append("python ${SRC}/pl-vif-down.py")
229             command.append("-S %s " % self.sock_name)
230             command = " ".join(command)
231
232         return command
233
234     @property
235     def _vif_up_command(self):
236         if self.gre_enabled:
237             device_name = "%s" % self.guid
238         else:
239             device_name = self.get("deviceName")
240
241         # Use pl-vif-up.py script to configure TAP
242         command = ["sudo -S "]
243         command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
244         command.append("python ${SRC}/pl-vif-up.py")
245         command.append("-u %s" % self.node.get("username"))
246         command.append("-N %s" % device_name)
247         command.append("-t %s" % self.vif_type)
248         command.append("-a %s" % self.get("ip"))
249         command.append("-n %s" % self.get("prefix"))
250
251         if self.get("snat") == True:
252             command.append("-s")
253
254         if self.get("pointopoint"):
255             command.append("-p %s" % self.get("pointopoint"))
256         
257         if self.get("txqueuelen"):
258             command.append("-q %s" % self.get("txqueuelen"))
259
260         if self.gre_enabled:
261             command.append("-g %s" % self.get("greKey"))
262             command.append("-G %s" % self.get("greRemote"))
263         
264         command.append("-f %s " % self.vif_name_file)
265
266         return " ".join(command)
267
268     @property
269     def _vif_down_command(self):
270         if self.gre_enabled:
271             device_name = "%s" % self.guid
272         else:
273             device_name = self.get("deviceName")
274
275         command = ["sudo -S "]
276         command.append("PYTHONPATH=$PYTHONPATH:${SRC}")
277         command.append("python ${SRC}/pl-vif-down.py")
278         command.append("-N %s " % device_name)
279         
280         if self.gre_enabled:
281             command.append("-u %s" % self.node.get("username"))
282             command.append("-t %s" % self.vif_type)
283             command.append("-D")
284
285         return " ".join(command)
286
287     @property
288     def vif_name_file(self):
289         return os.path.join(self.run_home, "vif_name")
290
291     @property
292     def _install(self):
293         # Install python-vsys and python-passfd
294         install_vsys = ( " ( "
295                     "   python -c 'import vsys, os;  vsys.__version__ == \"%(version)s\" or os._exit(1)' "
296                     " ) "
297                     " || "
298                     " ( "
299                     "   cd ${SRC} ; "
300                     "   hg clone http://nepi.inria.fr/code/python-vsys ; "
301                     "   cd python-vsys ; "
302                     "   make all ; "
303                     "   sudo -S make install "
304                     " )" ) % ({
305                         "version": PYTHON_VSYS_VERSION
306                         })
307
308         install_passfd = ( " ( python -c 'import passfd' ) "
309                     " || "
310                     " ( "
311                     "   cd ${SRC} ; "
312                     "   hg clone http://nepi.inria.fr/code/python-passfd ; "
313                     "   cd python-passfd ; "
314                     "   make all ; "
315                     "   sudo -S make install "
316                     " )" )
317
318         return "%s ; %s" % ( install_vsys, install_passfd )
319
320     def valid_connection(self, guid):
321         # TODO: Validate!
322         return True
323