8da93115aa77d20e44427ac24764bc9b6922b88f
[nepi.git] / src / nepi / resources / linux / scripts / linux-tap-create.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 version 2 as
7 #    published by the Free Software Foundation;
8 #
9 #    This program is distributed in the hope that it will be useful,
10 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #    GNU General Public License for more details.
13 #
14 #    You should have received a copy of the GNU General Public License
15 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
18
19 import base64
20 import fcntl
21 import errno
22 import passfd
23 import os
24 import socket
25 import subprocess
26 import struct
27 from optparse import OptionParser
28
29 STOP_MSG = "STOP"
30 PASSFD_MSG = "PASSFD"
31
32 IFF_NO_PI       = 0x1000
33 IFF_TUN         = 0x0001
34 IFF_TAP         = 0x0002
35 IFF_UP          = 0x1
36 IFF_RUNNING     = 0x40
37 TUNSETIFF       = 0x400454ca
38 SIOCGIFFLAGS    =  0x8913
39 SIOCSIFFLAGS    =  0x8914
40 SIOCGIFADDR     = 0x8915
41 SIOCSIFADDR     = 0x8916
42 SIOCGIFNETMASK  = 0x891B
43 SIOCSIFNETMASK  = 0x891C
44
45
46 def create_socket(socket_name):
47     sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
48     sock.bind(socket_name)
49     return sock
50
51 def recv_msg(conn):
52     msg = []
53     chunk = ''
54
55     while '\n' not in chunk:
56         try:
57             chunk = conn.recv(1024)
58         except (OSError, socket.error), e:
59             if e[0] != errno.EINTR:
60                 raise
61             # Ignore eintr errors
62             continue
63
64         if chunk:
65             msg.append(chunk)
66         else:
67             # empty chunk = EOF
68             break
69
70     msg = ''.join(msg).split('\n')[0]
71     # The message might have arguments that will be appended
72     # as a '|' separated list after the message type
73     args = msg.split("|")
74     msg = args.pop(0)
75
76     dmsg = base64.b64decode(msg)
77     dargs = []
78     for arg in args:
79         darg = base64.b64decode(arg)
80         dargs.append(darg.rstrip())
81
82     return (dmsg.rstrip(), dargs)
83
84 def send_reply(conn, reply):
85     encoded = base64.b64encode(reply)
86     conn.send("%s\n" % encoded)
87
88 def set_ifupdown(vif_name, up):
89     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
90     
91     # get flags
92     ifreq = struct.pack("16sH", vif_name, 0)
93     ifr = fcntl.ioctl(sock.fileno(), SIOCGIFFLAGS, ifreq)
94
95     if ifr < 0:
96         os.close(sock)
97         raise RuntimeError("Could not set device %s UP" % vif_name)
98
99     name, flags = struct.unpack("16sH", ifr)
100     if up:
101         flags = flags | IFF_UP | IFF_RUNNING
102     else:
103         flags = flags & ~IFF_UP & ~IFF_RUNNING
104     
105     # set flags
106     ifreq = struct.pack("16sH", vif_name, flags)
107     ifr = fcntl.ioctl(sock.fileno(), SIOCSIFFLAGS, ifreq)
108
109     if ifr < 0:
110         os.close(sock)
111         raise RuntimeError("Could not set device %s UP" % vif_name)
112
113 def set_ifaddr(vif_name, ip, prefix):
114     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
115
116     # configure IP
117     ifreq = struct.pack("16sH2s4s8s", vif_name, socket.AF_INET, "\x00"*2, 
118             socket.inet_aton(ip), "\x00"*8)
119     ifr = fcntl.ioctl(sock.fileno(), SIOCSIFADDR, ifreq)
120
121     if ifr < 0:
122         os.close(sock)
123         raise RuntimeError("Could not set IP address for device %s" % vif_name)
124
125     netmask = socket.inet_ntoa(struct.pack(">I", (0xffffffff << (32 - prefix)) & 0xffffffff))
126     ifreq = struct.pack("16sH2s4s8s", vif_name, socket.AF_INET, "\x00"*2,
127             socket.inet_aton(netmask), "\x00"*8)
128     ifr = fcntl.ioctl(sock.fileno(), SIOCSIFNETMASK, ifreq)
129
130     if ifr < 0:
131         os.close(sock)
132         raise RuntimeError("Could not set IP mask for device %s" % vif_name)
133
134 def create_tap(vif_name, vif_type, pi):
135     flags = 0
136     flags |= vif_type
137  
138     if not pi:
139         flags |= IFF_NO_PI
140  
141     fd = os.open("/dev/net/tun", os.O_RDWR)
142  
143     ifreq = struct.pack("16sH", vif_name, flags)
144     ifr = fcntl.ioctl(fd, TUNSETIFF, ifreq)
145     if ifr < 0:
146         os.close(fd)
147         raise RuntimeError("Could not configure device %s" % vif_name)
148
149     return fd
150
151 def passfd_action(fd, args):
152     """ Sends the file descriptor associated to the TAP device 
153     to another process through a unix socket.
154     """
155     address = args.pop(0)
156     print address
157     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
158     sock.connect(address)
159     passfd.sendfd(sock, fd, '0')
160
161     return "PASSFD-ACK"
162
163 def down_action(fd, vif_name):
164     updown_action(fd, vif_name, False)
165     return "STOP-ACK"
166
167 def get_options():
168     usage = ("usage: %prog -t <vif-type> -a <ip-address> -n <prefix> "
169         "-N <device-name> -p <pi> -S <socket-name>")
170     
171     parser = OptionParser(usage = usage)
172
173     parser.add_option("-t", "--vif-type", dest="vif_type",
174         help = "Virtual interface type. Either IFF_TAP or IFF_TUN. "
175             "Defaults to IFF_TAP. ", type="str")
176
177     parser.add_option("-a", "--ip-address", dest="ip_address",
178         help = "IPv4 address to assign to interface.",
179         type="str")
180
181     parser.add_option("-n", "--prefix", dest="prefix",
182         help = "IPv4 network prefix for the interface. ",
183         type="int")
184
185     parser.add_option("-N", "--vif-name", dest="vif_name",
186         help="The name of the virtual interface", type="str")
187
188     parser.add_option("-p", "--pi", dest="pi", action="store_true", 
189             default=False, help="Enable PI header")
190
191     parser.add_option("-S", "--socket-name", dest="socket_name",
192         help = "Name for the unix socket used to interact with this process", 
193         type="str")
194
195     (options, args) = parser.parse_args()
196     
197     vif_type = IFF_TAP
198     if options.vif_type and options.vif_type == "IFF_TUN":
199         vif_type = IFF_TUN
200
201     return (vif_type, options.vif_name, options.ip_address, options.prefix, 
202             options.pi, options.socket_name)
203
204 if __name__ == '__main__':
205
206     (vif_type, vif_name, ip_address, prefix, pi, socket_name) = get_options()
207
208     # create and configure the virtual device 
209     fd = create_tap(vif_name, vif_type, pi)
210     set_ifaddr(vif_name, ip_address, prefix)
211     set_ifupdown(vif_name, True)
212
213     # create unix socket to receive instructions
214     sock = create_socket(socket_name)
215     sock.listen(0)
216
217     # wait for messages to arrive and process them
218     stop = False
219
220     while not stop:
221         conn, addr = sock.accept()
222         conn.settimeout(5)
223
224         while not stop:
225             try:
226                 (msg, args) = recv_msg(conn)
227             except socket.timeout, e:
228                 # Ingore time-out
229                 continue
230
231             if not msg:
232                 # Ignore - connection lost
233                 break
234
235             if msg == STOP_MSG:
236                 stop = True
237                 reply = stop_action(vif_name)
238             elif msg == PASSFD_MSG:
239                 reply = passfd_action(fd, args)
240
241             try:
242                 send_reply(conn, reply)
243             except socket.error:
244                 break
245