1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Philippe Biondi <phil@secdev.org>
5# Copyright (C) Michael Farrell <micolous+git@gmail.com>
6
7"""
8Implementation of TUN/TAP interfaces.
9
10These allow Scapy to act as the remote side of a virtual network interface.
11"""
12
13import socket
14import time
15from fcntl import ioctl
16
17from scapy.compat import bytes_encode, raw
18from scapy.config import conf
19from scapy.consts import BIG_ENDIAN, BSD, DARWIN, LINUX
20from scapy.data import ETHER_TYPES, MTU
21from scapy.error import log_runtime, warning
22from scapy.fields import (
23 BitField,
24 Field,
25 FlagsField,
26 IntField,
27 StrFixedLenField,
28 XShortEnumField,
29)
30from scapy.interfaces import network_name
31from scapy.layers.inet import IP
32from scapy.layers.inet6 import IPv6, IPv46
33from scapy.layers.l2 import Ether
34from scapy.packet import Packet, bind_layers
35from scapy.supersocket import SimpleSocket
36
37# Linux-specific defines (/usr/include/linux/if_tun.h)
38LINUX_TUNSETIFF = 0x400454ca
39LINUX_IFF_TUN = 0x0001
40LINUX_IFF_TAP = 0x0002
41LINUX_IFF_NO_PI = 0x1000
42LINUX_IFNAMSIZ = 16
43
44# Darwin-specific defines (net/if_utun.h and sys/kern_control.h)
45DARWIN_CTLIOCGINFO = 0xc0644e03
46DARWIN_UTUN_CONTROL_NAME = b"com.apple.net.utun_control"
47DARWIN_MAX_KCTL_NAME = 96
48
49
50class NativeShortField(Field):
51 def __init__(self, name, default):
52 Field.__init__(self, name, default, "@H")
53
54
55class TunPacketInfo(Packet):
56 aliastypes = [Ether]
57
58
59class LinuxTunIfReq(Packet):
60 """
61 Structure to request a specific device name for a tun/tap
62 Linux ``struct ifreq``.
63
64 See linux/if.h (struct ifreq) and tuntap.txt for reference.
65 """
66 fields_desc = [
67 # union ifr_ifrn
68 StrFixedLenField("ifrn_name", b"", 16),
69 # union ifr_ifru
70 NativeShortField("ifru_flags", 0),
71 ]
72
73
74class DarwinUtunIfReq(Packet):
75 """
76 Structure for issuing Darwin ioctl commands (``struct ctl_info``).
77
78 See net/if_utun.h and sys/kern_control.h for reference.
79 """
80 fields_desc = [
81 BitField("ctl_id", 0, -32),
82 StrFixedLenField("ctl_name", DARWIN_UTUN_CONTROL_NAME, DARWIN_MAX_KCTL_NAME)
83 ]
84
85
86class LinuxTunPacketInfo(TunPacketInfo):
87 """
88 Base for TUN packets.
89
90 See linux/if_tun.h (struct tun_pi) for reference.
91 """
92 fields_desc = [
93 # This is native byte order
94 FlagsField("flags", 0,
95 (lambda _: 16 if BIG_ENDIAN else -16),
96 ["TUN_VNET_HDR"] +
97 ["reserved%d" % x for x in range(1, 16)]),
98 # This is always network byte order
99 XShortEnumField("type", 0x9000, ETHER_TYPES),
100 ]
101
102
103class DarwinUtunPacketInfo(Packet):
104 fields_desc = [
105 IntField("addr_family", socket.AF_INET)
106 ]
107
108
109class TunTapInterface(SimpleSocket):
110 """
111 A socket to act as the host's peer of a tun / tap interface.
112
113 This implements kernel interfaces for tun and tap devices.
114
115 :param iface: The name of the interface to use, eg: 'tun0'
116 :param mode_tun: If True, create as TUN interface (layer 3).
117 If False, creates a TAP interface (layer 2).
118 If not supplied, attempts to detect from the ``iface``
119 name.
120 :type mode_tun: bool
121 :param strip_packet_info: If True (default), strips any TunPacketInfo from
122 the packet. If False, leaves it in tact. Some
123 operating systems and tunnel types don't include
124 this sort of data.
125 :type strip_packet_info: bool
126
127 FreeBSD references:
128
129 * tap(4): https://www.freebsd.org/cgi/man.cgi?query=tap&sektion=4
130 * tun(4): https://www.freebsd.org/cgi/man.cgi?query=tun&sektion=4
131
132 Linux references:
133
134 * https://www.kernel.org/doc/Documentation/networking/tuntap.txt
135
136 """
137 desc = "Act as the host's peer of a tun / tap interface"
138
139 def __init__(self, iface=None, mode_tun=None, default_read_size=MTU,
140 strip_packet_info=True, *args, **kwargs):
141 self.iface = bytes_encode(
142 network_name(conf.iface) if iface is None else iface
143 )
144
145 self.mode_tun = mode_tun
146 if self.mode_tun is None:
147 if self.iface.startswith(b"tun") or self.iface.startswith(b"utun"):
148 self.mode_tun = True
149 elif self.iface.startswith(b"tap"):
150 self.mode_tun = False
151 else:
152 raise ValueError(
153 "Could not determine interface type for %r; set "
154 "`mode_tun` explicitly." % (self.iface,))
155
156 self.strip_packet_info = bool(strip_packet_info)
157
158 # This is non-zero when there is some kernel-specific packet info.
159 # We add this to any MTU value passed to recv(), and use it to
160 # remove leading bytes when strip_packet_info=True.
161 self.mtu_overhead = 0
162
163 # The TUN packet specification sends raw IP at us, and doesn't specify
164 # which version.
165 self.kernel_packet_class = IPv46 if self.mode_tun else Ether
166
167 if LINUX:
168 devname = b"/dev/net/tun"
169
170 # Having an EtherType always helps on Linux, then we don't need
171 # to use auto-detection of IP version.
172 if self.mode_tun:
173 self.kernel_packet_class = LinuxTunPacketInfo
174 self.mtu_overhead = 4 # len(LinuxTunPacketInfo)
175 else:
176 warning("tap devices on Linux do not include packet info!")
177 self.strip_packet_info = True
178
179 if len(self.iface) > LINUX_IFNAMSIZ:
180 warning("Linux interface names are limited to %d bytes, "
181 "truncating!" % (LINUX_IFNAMSIZ,))
182 self.iface = self.iface[:LINUX_IFNAMSIZ]
183 sock = open(devname, "r+b", buffering=0)
184 elif BSD: # also DARWIN
185 if self.iface.startswith(b"utun"): # allowed for Darwin
186 if not DARWIN:
187 raise ValueError('`utun` iface prefix is only allowed for Darwin')
188 self.kernel_packet_class = DarwinUtunPacketInfo
189 self.mtu_overhead = 4
190 interface_num = int(self.iface[4:])
191
192 utun_socket = socket.socket(
193 socket.PF_SYSTEM, socket.SOCK_DGRAM, socket.SYSPROTO_CONTROL)
194 ctl_info = ioctl(utun_socket, DARWIN_CTLIOCGINFO,
195 raw(DarwinUtunIfReq()))
196 utun_socket.connect(
197 (DarwinUtunIfReq(ctl_info).getfieldval("ctl_id"), interface_num + 1)
198 )
199
200 sock = utun_socket.makefile(mode="rwb", buffering=0)
201 elif self.iface.startswith(b"tap") or self.iface.startswith(b"tun"):
202 devname = b"/dev/" + self.iface
203 if not self.strip_packet_info:
204 warning("tun/tap devices on BSD and Darwin never include "
205 "packet info!")
206 self.strip_packet_info = True
207 sock = open(devname, "r+b", buffering=0)
208 else:
209 raise ValueError("Interface names must start with `tun` or "
210 "`tap` on BSD and Darwin or `utun` on Darwin")
211 else:
212 raise NotImplementedError("TunTapInterface is not supported on "
213 "this platform!")
214
215 if LINUX:
216 if self.mode_tun:
217 flags = LINUX_IFF_TUN
218 else:
219 # Linux can send us LinuxTunPacketInfo for TAP interfaces, but
220 # the kernel sends the wrong information!
221 #
222 # Instead of type=1 (Ether), it sends that of the payload
223 # (eg: 0x800 for IPv4 or 0x86dd for IPv6).
224 #
225 # tap interfaces always send Ether frames, which include a
226 # type parameter for the IPv4/v6/etc. payload, so we set
227 # IFF_NO_PI.
228 flags = LINUX_IFF_TAP | LINUX_IFF_NO_PI
229
230 tsetiff = raw(LinuxTunIfReq(
231 ifrn_name=self.iface,
232 ifru_flags=flags))
233
234 ioctl(sock, LINUX_TUNSETIFF, tsetiff)
235
236 self.closed = False
237 self.default_read_size = default_read_size
238 super(TunTapInterface, self).__init__(sock)
239
240 def __call__(self, *arg, **karg):
241 """Needed when using an instantiated TunTapInterface object for
242 conf.L2listen, conf.L2socket or conf.L3socket.
243
244 """
245 return self
246
247 def recv_raw(self, x=None):
248 if x is None:
249 x = self.default_read_size
250
251 x += self.mtu_overhead
252
253 dat = self.ins.read(x)
254 r = self.kernel_packet_class, dat, time.time()
255 if self.mtu_overhead > 0 and self.strip_packet_info:
256 # Get the packed class of the payload, without triggering a full
257 # decode of the payload data.
258 cls = r[0](r[1][:self.mtu_overhead]).guess_payload_class(b'')
259
260 # Return the payload data only
261 return cls, r[1][self.mtu_overhead:], r[2]
262 else:
263 return r
264
265 def send(self, x):
266 # type: (Packet) -> int
267 if hasattr(x, "sent_time"):
268 x.sent_time = time.time()
269
270 if self.kernel_packet_class == IPv46:
271 # IPv46 is an auto-detection wrapper; we should just push through
272 # packets normally if we got IP or IPv6.
273 if not isinstance(x, (IP, IPv6)):
274 x = IP() / x
275 elif not isinstance(x, self.kernel_packet_class):
276 x = self.kernel_packet_class() / x
277
278 sx = raw(x)
279
280 try:
281 r = self.outs.write(sx)
282 self.outs.flush()
283 return r
284 except socket.error:
285 log_runtime.error("%s send",
286 self.__class__.__name__, exc_info=True)
287
288
289# Bindings #
290bind_layers(DarwinUtunPacketInfo, IP, addr_family=socket.AF_INET)
291bind_layers(DarwinUtunPacketInfo, IPv6, addr_family=socket.AF_INET6)