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