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