Added PlanetlabTAP & PlanetlabTUN
[nepi.git] / src / nepi / resources / linux / interface.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, Types, Flags
21 from nepi.execution.resource import ResourceManager, clsinit, ResourceState, \
22         reschedule_delay
23 from nepi.resources.linux.node import LinuxNode
24 from nepi.resources.linux.channel import LinuxChannel
25
26 import collections
27 import os
28 import random
29 import re
30 import tempfile
31 import time
32
33 # TODO: UP, MTU attributes!
34
35
36 @clsinit
37 class LinuxInterface(ResourceManager):
38     _rtype = "LinuxInterface"
39
40     @classmethod
41     def _register_attributes(cls):
42         ip4 = Attribute("ip4", "IPv4 Address",
43               flags = Flags.ExecReadOnly)
44
45         ip6 = Attribute("ip6", "IPv6 Address",
46                 flags = Flags.ExecReadOnly)
47
48         mac = Attribute("mac", "MAC Address",
49                 flags = Flags.ExecReadOnly)
50
51         mask4 = Attribute("mask4", "IPv4 network mask",
52                 flags = Flags.ExecReadOnly)
53
54         mask6 = Attribute("mask6", "IPv6 network mask",
55                 type = Types.Integer,
56                 flags = Flags.ExecReadOnly)
57
58         mtu = Attribute("mtu", "Maximum transmition unit for device",
59                 type = Types.Integer)
60
61         devname = Attribute("deviceName", 
62                 "Name of the network interface (e.g. eth0, wlan0, etc)",
63                 flags = Flags.ExecReadOnly)
64
65         up = Attribute("up", "Link up", type = Types.Bool)
66
67         tear_down = Attribute("tearDown", "Bash script to be executed before " + \
68                 "releasing the resource",
69                 flags = Flags.ExecReadOnly)
70
71         cls._register_attribute(ip4)
72         cls._register_attribute(ip6)
73         cls._register_attribute(mac)
74         cls._register_attribute(mask4)
75         cls._register_attribute(mask6)
76         cls._register_attribute(mtu)
77         cls._register_attribute(devname)
78         cls._register_attribute(up)
79         cls._register_attribute(tear_down)
80
81     def __init__(self, ec, guid):
82         super(LinuxInterface, self).__init__(ec, guid)
83         self._configured = False
84         
85         self.add_set_hooks()
86
87     def log_message(self, msg):
88         return " guid %d - host %s - %s " % (self.guid, 
89                 self.node.get("hostname"), msg)
90
91     @property
92     def node(self):
93         node = self.get_connected(LinuxNode)
94         if node: return node[0]
95         return None
96
97     @property
98     def channel(self):
99         chan = self.get_connected(LinuxChannel)
100         if chan: return chan[0]
101         return None
102
103     def discover(self):
104         devname = self.get("deviceName")
105         ip4 = self.get("ip4")
106         ip6 = self.get("ip4")
107         mac = self.get("mac")
108         mask4 = self.get("mask4")
109         mask6 = self.get("mask6")
110         mtu = self.get("mtu")
111
112         # Get current interfaces information
113         (out, err), proc = self.node.execute("ifconfig", sudo = True, tty = True)
114
115         if err and proc.poll():
116             msg = " Error retrieving interface information "
117             self.error(msg, out, err)
118             raise RuntimeError, "%s - %s - %s" % (msg, out, err)
119         
120         # Check if an interface is found matching the RM attributes
121         ifaces = out.split("\n\n")
122
123         for i in ifaces:
124             m = re.findall("(\w+)\s+Link\s+encap:\w+(\s+HWaddr\s+(([0-9a-fA-F]{2}:?){6}))?(\s+inet\s+addr:((\d+\.?){4}).+Mask:(\d+\.\d+\.\d+\.\d+))?(.+inet6\s+addr:\s+([0-9a-fA-F:.]+)/(\d+))?(.+(UP))?(.+MTU:(\d+))?", i, re.DOTALL)
125             
126             m = m[0]
127             dn = m[0]
128             mc = m[2]
129             i4 = m[5]
130             msk4 = m[7]
131             i6 = m[9]
132             msk6 = m[10]
133             up = True if m[12] else False
134             mu = m[14]
135
136             self.debug("Found interface %(devname)s with MAC %(mac)s,"
137                     "IPv4 %(ipv4)s %(mask4)s IPv6 %(ipv6)s/%(mask6)s %(up)s %(mtu)s" % ({
138                 'devname': dn,
139                 'mac': mc,
140                 'ipv4': i4,
141                 'mask4': msk4,
142                 'ipv6': i6,
143                 'mask6': msk6,
144                 'up': up,
145                 'mtu': mu
146                 }) )
147
148             # If the user didn't provide information we take the first 
149             # interface that is UP
150             if not devname and not ip4 and not ip6 and up:
151                 self._configured = True
152                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
153                 break
154
155             # If the user provided ipv4 or ipv6 matching that of an interface
156             # load the interface info
157             if (ip4 and ip4 == i4) or (ip6 and ip6 == i6):
158                 self._configured = True
159                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
160                 break
161
162             # If the user provided the device name we load the associated info
163             if devname and devname == dn:
164                 if ((ip4 and ip4 == i4) and (ipv6 and ip6 == i6)) or \
165                         not (ip4 or ip6):
166                     self._configured = True
167                
168                 # If the user gave a different ip than the existing, asume ip 
169                 # needs to be changed
170                 i4 = ip4 or i4
171                 i6 = ip6 or i6
172                 mu = mtu or mu 
173
174                 self.load_configuration(dn, mc, i4, msk4, i6, msk6, mu, up)
175                 break
176        
177         if not self.get("deviceName"):
178             msg = "Unable to resolve interface "
179             self.error(msg)
180             raise RuntimeError, msg
181
182         super(LinuxInterface, self).discover()
183
184     def provision(self):
185         devname = self.get("deviceName")
186         ip4 = self.get("ip4")
187         ip6 = self.get("ip4")
188         mac = self.get("mac")
189         mask4 = self.get("mask4")
190         mask6 = self.get("mask6")
191         mtu = self.get("mtu")
192
193         # Must configure interface if configuration is required
194         if not self._configured:
195             cmd = "ifconfig %s" % devname
196
197             if ip4 and mask4:
198                 cmd += " %(ip4)s netmask %(mask4)s broadcast %(bcast)s up" % ({
199                     'ip4': ip4,
200                     'mask4': mask4,
201                     'bcast': bcast})
202             if mtu:
203                 cmd += " mtu %d " % mtu
204
205             (out, err), proc = self.node.execute(cmd, sudo = True)
206
207             if err and proc.poll():
208                 msg = "Error configuring interface with command '%s'" % cmd
209                 self.error(msg, out, err)
210                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
211
212             if ip6 and mask6:
213                 cmd = "ifconfig %(devname)s inet6 add %(ip6)s/%(mask6)d" % ({
214                         'devname': devname,
215                         'ip6': ip6,
216                         'mask6': mask6})
217
218             (out, err), proc = self.node.execute(cmd, sudo = True)
219
220             if err and proc.poll():
221                 msg = "Error seting ipv6 for interface using command '%s' " % cmd
222                 self.error(msg, out, err)
223                 raise RuntimeError, "%s - %s - %s" % (msg, out, err)
224
225         super(LinuxInterface, self).provision()
226
227     def deploy(self):
228         # Wait until node is provisioned
229         node = self.node
230         chan = self.channel
231
232         if not node or node.state < ResourceState.PROVISIONED:
233             self.ec.schedule(reschedule_delay, self.deploy)
234         elif not chan or chan.state < ResourceState.READY:
235             self.ec.schedule(reschedule_delay, self.deploy)
236         else:
237             # Verify if the interface exists in node. If not, configue
238             # if yes, load existing configuration
239             try:
240                 self.discover()
241                 self.provision()
242             except:
243                 self.fail()
244                 raise
245
246             super(LinuxInterface, self).deploy()
247
248     def release(self):
249         tear_down = self.get("tearDown")
250         if tear_down:
251             self.execute(tear_down)
252
253         super(LinuxInterface, self).release()
254
255     def valid_connection(self, guid):
256         # TODO: Validate!
257         return True
258
259     def load_configuration(self, devname, mac, ip4, mask4, ip6, mask6, mtu, up):
260         self.set("deviceName", devname)
261         self.set("mac", mac)
262         self.set("ip4", ip4)
263         self.set("mask4", mask4)
264         self.set("ip6", ip6)
265         self.set("mask6", mask6)
266
267         # set the following without validating or triggering hooks
268         attr = self._attrs["up"]
269         attr._value = up
270         attr = self._attrs["mtu"]
271
272     def add_set_hooks(self):
273         attrup = self._attrs["up"]
274         attrup.set_hook = self.set_hook_up
275
276         attrmtu = self._attrs["mtu"]
277         attrmtu.set_hook = self.set_hook_mtu
278
279     def set_hook_up(self, oldval, newval):
280         if oldval == newval:
281             return oldval
282
283         # configure interface up
284         if newval == True:
285             cmd = "ifup %s" % self.get("deviceName")
286         elif newval == False:
287             cmd = "ifdown %s" % self.get("deviceName")
288
289         (out, err), proc = self.node.execute(cmd, sudo = True)
290
291         if err and proc.poll():
292             msg = "Error setting interface up/down using command '%s' " % cmd
293             self.error(msg, err, out)
294             return oldval
295         
296         return newval
297
298     def set_hook_mtu(self, oldval, newval):
299         if oldval == newval:
300             return oldval
301
302         cmd = "ifconfig %s mtu %d" % (self.get("deviceName"), newval)
303
304         (out, err), proc = self.node.execute(cmd, sudo = True)
305
306         if err and proc.poll():
307             msg = "Error setting interface MTU using command '%s' " % cmd
308             self.error(msg, err, out)
309             return  oldval
310         
311         return newval
312