1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import fcntl
6import os
7import struct
8import socket
9
10from lansim import pyiftun
11from lansim import tools
12
13
14# Export some constants used by callers to pass to the |mode| argument while
15# create a TunTap() object.
16from lansim.pyiftun import IFF_TAP, IFF_TUN
17
18
19class TunTapError(Exception):
20    """TunTap specific error."""
21
22
23ETHERNET_HEADER_SIZE = 18
24
25
26STRUCT_IFREQ_FMT = {
27    "ifr_flags": "h", # short ifr_flags
28    "ifr_mtu": "i", # int ifr_flags
29    "ifr_addr": "HH12s", # struct sockaddr_in ifr_addr
30    "ifr_hwaddr": "H14s", # struct sockaddr ifru_hwaddr
31}
32
33
34IFNAMSIZ_FMT = str(pyiftun.IFNAMSIZ) + 's'
35
36
37def pack_struct_ifreq(if_name, argname, *args):
38    """Packs a binary string representing a struct ifreq.
39
40    The struct ifreq is used to call ioctl() on network devices. The argument
41    type and size depends on the operation performed and is represented as the
42    union of different types. This function packs the struct according to the
43    provided |argname| which defines the type of |arg|. See netdevice(7) for a
44    list of possible arguments and ioctl() commands.
45
46    @param if_name: The interface name.
47    @param argname: The name of the member used for the union in struct ifreq.
48    @param args: The values used to pack the requested |argname|.
49    @raises ValueError: if |argname| isn't a supported union's member name.
50    """
51    if argname not in STRUCT_IFREQ_FMT:
52      raise ValueError()
53    return struct.pack(IFNAMSIZ_FMT + STRUCT_IFREQ_FMT[argname], if_name, *args)
54
55
56def unpack_struct_ifreq(data, argname):
57    """Returns a tuple with the interpreted contents of the a struct ifreq.
58
59    The result returned from a ioctl() on network devices has the same format
60    than the passed struct ifreq request. This function decodes this result into
61    a python tuple, which depends on the |argname| passed to
62
63    @param data: The packed representation of the struct ifreq.
64    @param argname: The name of the member used for the union in struct ifreq.
65    @raises ValueError: if |argname| isn't a supported union's member name.
66    """
67    if argname not in STRUCT_IFREQ_FMT:
68      raise ValueError()
69    return struct.unpack(IFNAMSIZ_FMT + STRUCT_IFREQ_FMT[argname], data)
70
71
72class TunTap(object):
73    """TUN/TAP network interface manipulation class."""
74
75
76    DEFAULT_DEV_NAME = {
77        IFF_TUN: "tun%d",
78        IFF_TAP: "tap%d",
79    }
80
81
82    def __init__(self, mode=pyiftun.IFF_TUN, name=None, tundev='/dev/net/tun'):
83        """Creates or re-opens a TUN/TAP interface.
84
85        @param mode: This argument is passed to the TUNSETIFF ioctl() to create
86        the interface. It says whether the interface created is a TAP (IFF_TAP)
87        or TUN (IFF_TUN) interface and some related constant flags found on
88        pyiftun.IFF_*.
89        @param name: The name of the created interface. If the name ends in '%d'
90        that value will be replaced by the kernel with a given number, otherwise
91        the name will be appended with '%d'.
92        @param tundev: The path to the kerner interface to the tun driver which
93        defaults to the standard '/dev/net/tun' if not specified.
94        """
95        tun_type = mode & pyiftun.TUN_TYPE_MASK
96        if tun_type not in self.DEFAULT_DEV_NAME:
97            raise TunTapError("mode (%r) not supported" % mode)
98
99        self.mode = mode
100
101        # The interface name can have a "%d" that the kernel will replace with
102        # a number.
103        if name is None:
104            name = self.DEFAULT_DEV_NAME[tun_type]
105        elif not name.endswith('%d'):
106            name += "%d"
107
108        # Create the TUN/TAP interface.
109        fd = os.open(tundev, os.O_RDWR)
110        self._fd = fd
111
112        ifs = fcntl.ioctl(fd, pyiftun.TUNSETIFF,
113            pack_struct_ifreq(name, 'ifr_flags', mode))
114        ifs_name, ifs_mode = struct.unpack(IFNAMSIZ_FMT + "H", ifs)
115        self.name = ifs_name.rstrip('\0')
116
117        # Socket used for ioctl() operations over the network device.
118        self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
119
120        self.mtu = self._get_mtu()
121
122
123    def __del__(self):
124        if hasattr(self, '_fd'):
125            os.close(self._fd)
126
127
128    def _get_mtu(self):
129        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCGIFMTU,
130            pack_struct_ifreq(self.name, 'ifr_mtu', 0))
131        ifr_name, ifr_mtu = unpack_struct_ifreq(ifs, 'ifr_mtu')
132        return ifr_mtu
133
134
135    def _get_flags(self):
136        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCGIFFLAGS,
137            pack_struct_ifreq(self.name, 'ifr_flags', 0))
138        ifr_name, ifr_flags = unpack_struct_ifreq(ifs, 'ifr_flags')
139        return ifr_flags
140
141
142    def _set_flags(self, flags):
143        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCSIFFLAGS,
144            pack_struct_ifreq(self.name, 'ifr_flags', flags))
145        ifr_name, ifr_flags = unpack_struct_ifreq(ifs, 'ifr_flags')
146        return ifr_flags
147
148
149    def get_addr(self):
150        """Return the address of the interface.
151
152        @param string addr: The IPv4 address for the interface.
153        """
154        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCGIFADDR,
155            pack_struct_ifreq(self.name, 'ifr_addr', socket.AF_INET, 0, ''))
156        ifr_name, ifr_family, ifr_type, ifr_addr = unpack_struct_ifreq(
157                ifs, 'ifr_addr')
158        if ifr_type != 0:
159            return None
160        # ifr_addr contains up to 12 bytes (see STRUCT_IFREQ_FMT).
161        return socket.inet_ntoa(ifr_addr[:4])
162
163
164    def set_addr(self, addr, mask=None):
165        """Sets the address and network mask of the interface.
166
167        @param string addr: The IPv4 address for the interface.
168        """
169        str_addr = socket.inet_aton(addr)
170        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCSIFADDR,
171            pack_struct_ifreq(self.name, 'ifr_addr',
172                socket.AF_INET, 0, str_addr))
173
174        if mask != None:
175          net_mask = (1 << 32) - (1 << (32 - mask))
176          str_mask = struct.pack('!I', net_mask)
177          ifs = fcntl.ioctl(self._sock, pyiftun.SIOCSIFNETMASK,
178              pack_struct_ifreq(self.name, 'ifr_addr',
179                  socket.AF_INET, 0, str_mask))
180
181
182    """The interface IPv4 address in plain text as in '192.168.0.1'."""
183    addr = property(get_addr, set_addr)
184
185
186    def get_hwaddr(self):
187        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCGIFHWADDR,
188            pack_struct_ifreq(self.name, 'ifr_hwaddr', 0, ''))
189        ifr_name, ifr_family, ifr_hwaddr = unpack_struct_ifreq(
190            ifs, 'ifr_hwaddr')
191        return (ifr_family, tools.inet_ntohw(ifr_hwaddr[:6]))
192
193
194    def set_hwaddr(self, hwaddr):
195        """Sets the hardware ethernet address of the interface.
196
197        The interface needs to be down in order to set this hardware (MAC)
198        address.
199
200        @param string hwaddr: The address in hex format: 'aa:bb:cc:DD:EE:FF'.
201        """
202        ifs = fcntl.ioctl(self._sock, pyiftun.SIOCSIFHWADDR,
203            pack_struct_ifreq(self.name, 'ifr_hwaddr', 1, # 1 for Ethernet
204                              tools.inet_hwton(hwaddr)))
205        ifr_name, ifr_family, ifr_hwaddr = unpack_struct_ifreq(
206            ifs, 'ifr_hwaddr')
207        return (ifr_family, tools.inet_ntohw(ifr_hwaddr[:6]))
208
209
210    """The interface Ethernet address as in '00:11:22:AA:BB:CC'."""
211    hwaddr = property(get_hwaddr, set_hwaddr)
212
213
214    def up(self):
215        """Brings up the interface."""
216        self._set_flags(self._get_flags() | pyiftun.IFF_UP)
217
218
219    def down(self):
220        """Brings down the interface."""
221        self._set_flags(self._get_flags() & ~pyiftun.IFF_UP)
222
223
224    def is_up(self):
225        """Returns whether the interface is up."""
226        return (self._get_flags() & pyiftun.IFF_UP) != 0
227
228
229    def read(self):
230        """Reads a 'sent' frame from the interface.
231
232        The frame format depends on the interface type: Ethernet frame for TAP
233        interfaces and IP frame for TUN interfaces. This function blocks until
234        a new frame is available.
235
236        @return string: A single frame sent to the interface.
237        """
238        return os.read(self._fd, self.mtu + ETHERNET_HEADER_SIZE)
239
240
241    def write(self, data):
242        """Write a 'received' frame from the interface.
243
244        The frame format depends on the interface type: Ethernet frame for TAP
245        interfaces and IP frame for TUN interfaces. This function does not
246        block.
247
248        @param data: A single frame received from the interface.
249        """
250        os.write(self._fd, data)
251
252
253    def fileno(self):
254        """Returns a file descriptor suitable to be used with select()."""
255        return self._fd
256