Skip to content

Commit e35797e

Browse files
0x-0ddc0dewl
andauthored
Tun: add support for Darwin utun interface (#4816)
* Tun: add support for Darwin utun interface Fixes #4049 Darwin creates tunnel interfaces differently than BSD, breaking the existing handling which is based on generic BSD `tun`. This commit adds a new tunnel layer specific to the Darwin utun interface. * replace darwin test tag with osx tag --------- Co-authored-by: wl <wl@mac>
1 parent d73bbc1 commit e35797e

2 files changed

Lines changed: 88 additions & 20 deletions

File tree

scapy/layers/tuntap.py

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,42 @@
1010
These allow Scapy to act as the remote side of a virtual network interface.
1111
"""
1212

13-
1413
import socket
1514
import time
1615
from fcntl import ioctl
1716

18-
from scapy.compat import raw, bytes_encode
17+
from scapy.compat import bytes_encode, raw
1918
from scapy.config import conf
20-
from scapy.consts import BIG_ENDIAN, BSD, LINUX
19+
from scapy.consts import BIG_ENDIAN, BSD, DARWIN, LINUX
2120
from scapy.data import ETHER_TYPES, MTU
22-
from scapy.error import warning, log_runtime
23-
from scapy.fields import Field, FlagsField, StrFixedLenField, XShortEnumField
21+
from scapy.error import log_runtime, warning
22+
from scapy.fields import (
23+
BitField,
24+
Field,
25+
FlagsField,
26+
IntField,
27+
StrFixedLenField,
28+
XShortEnumField,
29+
)
2430
from scapy.interfaces import network_name
2531
from scapy.layers.inet import IP
26-
from scapy.layers.inet6 import IPv46, IPv6
32+
from scapy.layers.inet6 import IPv6, IPv46
2733
from scapy.layers.l2 import Ether
28-
from scapy.packet import Packet
34+
from scapy.packet import Packet, bind_layers
2935
from scapy.supersocket import SimpleSocket
3036

31-
3237
# Linux-specific defines (/usr/include/linux/if_tun.h)
3338
LINUX_TUNSETIFF = 0x400454ca
3439
LINUX_IFF_TUN = 0x0001
3540
LINUX_IFF_TAP = 0x0002
3641
LINUX_IFF_NO_PI = 0x1000
3742
LINUX_IFNAMSIZ = 16
3843

44+
# Darwin-specific defines (net/if_utun.h and sys/kern_control.h)
45+
DARWIN_CTLIOCGINFO = 0xc0644e03
46+
DARWIN_UTUN_CONTROL_NAME = b"com.apple.net.utun_control"
47+
DARWIN_MAX_KCTL_NAME = 96
48+
3949

4050
class NativeShortField(Field):
4151
def __init__(self, name, default):
@@ -61,6 +71,18 @@ class LinuxTunIfReq(Packet):
6171
]
6272

6373

74+
class 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+
6486
class LinuxTunPacketInfo(TunPacketInfo):
6587
"""
6688
Base for TUN packets.
@@ -78,6 +100,12 @@ class LinuxTunPacketInfo(TunPacketInfo):
78100
]
79101

80102

103+
class DarwinUtunPacketInfo(Packet):
104+
fields_desc = [
105+
IntField("addr_family", socket.AF_INET)
106+
]
107+
108+
81109
class TunTapInterface(SimpleSocket):
82110
"""
83111
A socket to act as the host's peer of a tun / tap interface.
@@ -116,7 +144,7 @@ def __init__(self, iface=None, mode_tun=None, default_read_size=MTU,
116144

117145
self.mode_tun = mode_tun
118146
if self.mode_tun is None:
119-
if self.iface.startswith(b"tun"):
147+
if self.iface.startswith(b"tun") or self.iface.startswith(b"utun"):
120148
self.mode_tun = True
121149
elif self.iface.startswith(b"tap"):
122150
self.mode_tun = False
@@ -152,23 +180,38 @@ def __init__(self, iface=None, mode_tun=None, default_read_size=MTU,
152180
warning("Linux interface names are limited to %d bytes, "
153181
"truncating!" % (LINUX_IFNAMSIZ,))
154182
self.iface = self.iface[:LINUX_IFNAMSIZ]
155-
183+
sock = open(devname, "r+b", buffering=0)
156184
elif BSD: # also DARWIN
157-
if not (self.iface.startswith(b"tap") or
158-
self.iface.startswith(b"tun")):
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:
159209
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
210+
"`tap` on BSD and Darwin or `utun` on Darwin")
166211
else:
167212
raise NotImplementedError("TunTapInterface is not supported on "
168213
"this platform!")
169214

170-
sock = open(devname, "r+b", buffering=0)
171-
172215
if LINUX:
173216
if self.mode_tun:
174217
flags = LINUX_IFF_TUN
@@ -241,3 +284,8 @@ def send(self, x):
241284
except socket.error:
242285
log_runtime.error("%s send",
243286
self.__class__.__name__, exc_info=True)
287+
288+
289+
# Bindings #
290+
bind_layers(DarwinUtunPacketInfo, IP, addr_family=socket.AF_INET)
291+
bind_layers(DarwinUtunPacketInfo, IPv6, addr_family=socket.AF_INET6)

test/tuntap.uts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ assert p.type == 0x86dd
2323

2424
assert isinstance(p.payload, IPv6)
2525

26+
#######
27+
+ Test Darwin-specific protocol headers for utun
28+
~ osx utun not_libpcap
29+
30+
= Darwin-specific protocol headers
31+
32+
p = DarwinUtunPacketInfo()/IP()
33+
assert p.addr_family == 2
34+
35+
p = DarwinUtunPacketInfo(raw(p))
36+
assert p.addr_family == 2
37+
assert isinstance(p.payload, IP)
38+
39+
p = DarwinUtunPacketInfo()/IPv6()
40+
assert p.addr_family == 30
41+
42+
p = DarwinUtunPacketInfo(raw(p))
43+
assert p.addr_family == 30
44+
assert isinstance(p.payload, IPv6)
45+
2646
#######
2747
+ Test tun device
2848

0 commit comments

Comments
 (0)