applied the except and raise fixers to the master branch to close the gap with 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) as 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     # xxx : Thierry : not quite sure where this gets closed
144     fd = os.open("/dev/net/tun", os.O_RDWR)
145  
146     ifreq = struct.pack("16sH", vif_name, flags)
147     ifr = fcntl.ioctl(fd, TUNSETIFF, ifreq)
148     if ifr < 0:
149         os.close(fd)
150         raise RuntimeError("Could not configure device %s" % vif_name)
151
152     return fd
153
154 def passfd_action(fd, args):
155     """ Sends the file descriptor associated to the TAP device 
156     to another process through a unix socket.
157     """
158     address = args.pop(0)
159     print(address)
160     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
161     sock.connect(address)
162     passfd.sendfd(sock, fd, '0')
163
164     return "PASSFD-ACK"
165
166 def down_action(fd, vif_name):
167     updown_action(fd, vif_name, False)
168     return "STOP-ACK"
169
170 def get_options():
171     usage = ("usage: %prog -t <vif-type> -a <ip-address> -n <prefix> "
172         "-N <device-name> -p <pi> -S <socket-name>")
173     
174     parser = OptionParser(usage = usage)
175
176     parser.add_option("-t", "--vif-type", dest="vif_type",
177         help = "Virtual interface type. Either IFF_TAP or IFF_TUN. "
178             "Defaults to IFF_TAP. ", type="str")
179
180     parser.add_option("-a", "--ip-address", dest="ip_address",
181         help = "IPv4 address to assign to interface.",
182         type="str")
183
184     parser.add_option("-n", "--prefix", dest="prefix",
185         help = "IPv4 network prefix for the interface. ",
186         type="int")
187
188     parser.add_option("-N", "--vif-name", dest="vif_name",
189         help="The name of the virtual interface", type="str")
190
191     parser.add_option("-p", "--pi", dest="pi", action="store_true", 
192             default=False, help="Enable PI header")
193
194     parser.add_option("-S", "--socket-name", dest="socket_name",
195         help = "Name for the unix socket used to interact with this process", 
196         type="str")
197
198     (options, args) = parser.parse_args()
199     
200     vif_type = IFF_TAP
201     if options.vif_type and options.vif_type == "IFF_TUN":
202         vif_type = IFF_TUN
203
204     return (vif_type, options.vif_name, options.ip_address, options.prefix, 
205             options.pi, options.socket_name)
206
207 if __name__ == '__main__':
208
209     (vif_type, vif_name, ip_address, prefix, pi, socket_name) = get_options()
210
211     # create and configure the virtual device 
212     fd = create_tap(vif_name, vif_type, pi)
213     set_ifaddr(vif_name, ip_address, prefix)
214     set_ifupdown(vif_name, True)
215
216     # create unix socket to receive instructions
217     sock = create_socket(socket_name)
218     sock.listen(0)
219
220     # wait for messages to arrive and process them
221     stop = False
222
223     while not stop:
224         conn, addr = sock.accept()
225         conn.settimeout(5)
226
227         while not stop:
228             try:
229                 (msg, args) = recv_msg(conn)
230             except socket.timeout as e:
231                 # Ingore time-out
232                 continue
233
234             if not msg:
235                 # Ignore - connection lost
236                 break
237
238             if msg == STOP_MSG:
239                 stop = True
240                 reply = stop_action(vif_name)
241             elif msg == PASSFD_MSG:
242                 reply = passfd_action(fd, args)
243
244             try:
245                 send_reply(conn, reply)
246             except socket.error:
247                 break
248