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