-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutun.py
executable file
·79 lines (60 loc) · 2.64 KB
/
utun.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python3
import socket
import subprocess
from fcntl import ioctl
import struct
CTLIOCGINFO = 3227799043 # _IOWR('N', 3, struct ctl_info)
CTL_INFO_FORMAT = "<I96s" # little-endian uint32_t, then a char[96]
UTUN_OPT_IFNAME = 2 # from net/if_utun.h
IF_NAMESIZE = 16 # from net/if.h
class Utun(socket.socket):
"""
A connection for controlling a utun device.
Use send() and recv() to communicate with programs sending via this virtual NIC.
Anything sent via the utun device can be received for processing by calling recv().
Anything sent from this socket with send() is heard by those listening on the utun device.
"""
def __init__(self, address: str = "fe80::1111", mtu: int = 1500) -> None:
"""
Create a new utun device and a socket connection to its controller.
Calling this requires root privileges. This function will crash if not run on macOS,
since PF_SYSTEM and SYSPROTO_CONTROL are only defined on Darwin.
:param address: the IPv6 address that this device should have on the QR code link
:param mtu: the Maximum Transmission Unit for the virtual interface
"""
super().__init__(socket.PF_SYSTEM, socket.SOCK_DGRAM, socket.SYSPROTO_CONTROL)
self._mtu = mtu
# Make struct ctl_info
ctl_info = struct.pack(CTL_INFO_FORMAT, 0, "com.apple.net.utun_control".encode())
# Exchange controller name for ID + name
ctl_info = ioctl(self, CTLIOCGINFO, ctl_info)
controller_id, controller_name = struct.unpack(CTL_INFO_FORMAT, ctl_info)
# Connect + activate (requires sudo)
self.connect((controller_id, 0)) # 0 means pick the next convenient number
# Generate IPv6 address
subprocess.run(["ifconfig", self.name, "inet6", address, "mtu", str(mtu)], check=True)
# Set as default route for IPv6 address
subprocess.run(["route", "add", "-inet6", "-net", "fe80::1111", "-interface", self.name], check=True)
@property
def mtu(self) -> int:
"""The Maximum Transmission Unit of this utun device."""
return self._mtu
@property
def name(self) -> bytes:
"""
Get the name of a utun device.
:return: the name of the utun device as a bytestring
"""
return self.getsockopt(
socket.SYSPROTO_CONTROL,
UTUN_OPT_IFNAME,
IF_NAMESIZE,
)[:-1] # take off the null terminator
if __name__ == "__main__":
utun = Utun()
print(utun.name)
while True:
# infinite loop to keep the interface open
message = utun.recv(utun.mtu)
print(message)
utun.send(message)