Skip to content

Commit 2a6b7f4

Browse files
committed
Proxy: TUN: Enhance Darwin interface support.
- reduce number of actions done to create/configure the interface in the system - assign synthetic static link-local ip address to the interface, that is required by the OS to just be available for routing - make tun_darwin_endpoint be implemented significantly more similar to tun_windows_enpoint, preparing them for potential unification
1 parent 0a42dba commit 2a6b7f4

File tree

4 files changed

+190
-140
lines changed

4 files changed

+190
-140
lines changed

proxy/tun/README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ TUN interface support bridges the gap between network layer 3 and layer 7, intro
44

55
This functionality is targeted to assist applications/end devices that don't have proxy support, or can't run external applications (like Smart TV's). Making it possible to run Xray proxy right on network edge devices (routers) with support to route raw network traffic. \
66
Primary targets are Linux based router devices. Like most popular OpenWRT option. \
7-
Although support for Windows is also implemented (see below).
7+
Although support for Windows/MacOSX is also implemented (see below).
88

99
## PLEASE READ FOLLOWING CAREFULLY
1010

@@ -172,3 +172,21 @@ route add 1.1.1.1 mask 255.0.0.0 0.0.0.0 if 47
172172
Note on ipv6 support. \
173173
Despite Windows also giving the adapter autoconfigured ipv6 address, the ipv6 is not possible until the interface has any _routable_ ipv6 address (given link-local address will not accept traffic from external addresses). \
174174
So everything applicable for ipv4 above also works for ipv6, you only need to give the interface some address manually, e.g. anything private like fc00::a:b:c:d/64 will do just fine
175+
176+
## MAC OS X SUPPORT
177+
178+
Darwin (Mac OS X) support of the same functionality is implemented through utun (userspace tunnel).
179+
180+
Interface name in the configuration must comply to the scheme "utunN", where N is some number. \
181+
Most running OS'es create some amount of utun interfaces in advance for own needs. Please either check the interfaces you already have occupied by issuing following command:
182+
```
183+
ifconfig
184+
```
185+
Produced list will have all system interfaces listed, from which you will see how many "utun" ones already exists.
186+
It's not required to select next available number, e.g. if you have utun1-utun7 interfaces, it's not required to have "utun8" in the config. You can choose any available name, even utun20, to get surely available interface number.
187+
188+
To attach routing to the interface, route command like following can be executed:
189+
```
190+
sudo route add -net 1.1.1.0/24 -iface utun10
191+
```
192+
Important to remember that everything written above about Linux routing concept, also apply to Mac OS X. If you simply route default route through utun interface, that will result network loop and immediate network failure.

proxy/tun/tun_darwin.go

Lines changed: 89 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ package tun
55
import (
66
"errors"
77
"fmt"
8-
"strings"
8+
"net"
9+
"net/netip"
10+
"os"
11+
"syscall"
912
"unsafe"
1013

1114
"golang.org/x/sys/unix"
@@ -14,154 +17,163 @@ import (
1417

1518
const (
1619
utunControlName = "com.apple.net.utun_control"
17-
utunOptIfName = 2
1820
sysprotoControl = 2
21+
gateway = "169.254.10.1/30"
1922
)
2023

2124
type DarwinTun struct {
22-
tunFd int
23-
name string
25+
tunFile *os.File
2426
options TunOptions
2527
}
2628

2729
var _ Tun = (*DarwinTun)(nil)
2830
var _ GVisorTun = (*DarwinTun)(nil)
2931

3032
func NewTun(options TunOptions) (Tun, error) {
31-
tunFd, name, err := openUTun(options.Name)
33+
tunFile, err := open(options.Name)
3234
if err != nil {
3335
return nil, err
3436
}
3537

38+
err = setup(options.Name, options.MTU)
39+
if err != nil {
40+
_ = tunFile.Close()
41+
return nil, err
42+
}
43+
3644
return &DarwinTun{
37-
tunFd: tunFd,
38-
name: name,
45+
tunFile: tunFile,
3946
options: options,
4047
}, nil
4148
}
4249

4350
func (t *DarwinTun) Start() error {
44-
if t.options.MTU > 0 {
45-
if err := setMTU(t.name, int(t.options.MTU)); err != nil {
46-
return err
47-
}
48-
}
49-
return setState(t.name, true)
51+
return nil
5052
}
5153

5254
func (t *DarwinTun) Close() error {
53-
_ = setState(t.name, false)
54-
return unix.Close(t.tunFd)
55+
return t.tunFile.Close()
5556
}
5657

5758
func (t *DarwinTun) newEndpoint() (stack.LinkEndpoint, error) {
58-
return newDarwinEndpoint(t.tunFd, t.options.MTU), nil
59+
return &DarwinEndpoint{tun: t}, nil
5960
}
6061

61-
func openUTun(name string) (int, string, error) {
62+
// open the interface, by creating new utunN if in the system and returning its file descriptor
63+
func open(name string) (*os.File, error) {
64+
ifIndex := -1
65+
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
66+
if err != nil || ifIndex < 0 {
67+
return nil, errors.New("interface name must be utunN, where N is a number, e.g. utun9, utun11 and so on")
68+
}
69+
6270
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysprotoControl)
6371
if err != nil {
64-
return -1, "", err
72+
return nil, err
6573
}
6674

6775
ctlInfo := &unix.CtlInfo{}
6876
copy(ctlInfo.Name[:], utunControlName)
6977
if err := unix.IoctlCtlInfo(fd, ctlInfo); err != nil {
7078
_ = unix.Close(fd)
71-
return -1, "", err
79+
return nil, err
7280
}
7381

7482
sockaddr := &unix.SockaddrCtl{
7583
ID: ctlInfo.Id,
76-
Unit: parseUTunUnit(name),
84+
Unit: uint32(ifIndex) + 1,
7785
}
78-
7986
if err := unix.Connect(fd, sockaddr); err != nil {
8087
_ = unix.Close(fd)
81-
return -1, "", err
88+
return nil, err
8289
}
8390

8491
if err := unix.SetNonblock(fd, true); err != nil {
8592
_ = unix.Close(fd)
86-
return -1, "", err
87-
}
88-
89-
tunName, err := unix.GetsockoptString(fd, sysprotoControl, utunOptIfName)
90-
if err != nil {
91-
_ = unix.Close(fd)
92-
return -1, "", err
93-
}
94-
95-
tunName = strings.TrimRight(tunName, "\x00")
96-
if tunName == "" {
97-
_ = unix.Close(fd)
98-
return -1, "", errors.New("empty utun name")
93+
return nil, err
9994
}
10095

101-
return fd, tunName, nil
96+
return os.NewFile(uintptr(fd), name), nil
10297
}
10398

104-
func parseUTunUnit(name string) uint32 {
105-
var unit uint32
106-
if _, err := fmt.Sscanf(name, "utun%d", &unit); err != nil {
107-
return 0
108-
}
109-
return unit + 1
110-
}
111-
112-
type ifreqMTU struct {
113-
Name [unix.IFNAMSIZ]byte
114-
MTU int32
115-
_ [12]byte
116-
}
117-
118-
type ifreqFlags struct {
119-
Name [unix.IFNAMSIZ]byte
120-
Flags int16
121-
_ [14]byte
122-
}
123-
124-
func setMTU(name string, mtu int) error {
125-
if mtu <= 0 {
126-
return nil
99+
// setup the interface by name
100+
func setup(name string, MTU uint32) error {
101+
if err := setMTU(name, MTU); err != nil {
102+
return err
127103
}
128104

129-
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
130-
if err != nil {
105+
/*
106+
* Darwin routing require tunnel type interface to have local and remote address, to be routable.
107+
* To simplify inevitable task, assign the interface static ip address, which in current implementation
108+
* is just some random ip from link-local pool, allowing to not bother about existing routing intersection.
109+
*/
110+
syntheticIP, _ := netip.ParsePrefix(gateway)
111+
if err := setIPAddress(name, syntheticIP); err != nil {
131112
return err
132113
}
133-
defer func() { _ = unix.Close(fd) }()
134114

135-
ifr := ifreqMTU{MTU: int32(mtu)}
136-
copy(ifr.Name[:], name)
137-
return ioctlPtr(fd, unix.SIOCSIFMTU, unsafe.Pointer(&ifr))
115+
return nil
138116
}
139117

140-
func setState(name string, up bool) error {
141-
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
118+
// setMTU sets MTU on the interface by given name
119+
func setMTU(name string, mtu uint32) error {
120+
socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
142121
if err != nil {
143122
return err
144123
}
145-
defer func() { _ = unix.Close(fd) }()
124+
defer unix.Close(socket)
146125

147-
ifr := ifreqFlags{}
126+
ifr := unix.IfreqMTU{MTU: int32(mtu)}
148127
copy(ifr.Name[:], name)
128+
return unix.IoctlSetIfreqMTU(socket, &ifr)
129+
}
149130

150-
if err := ioctlPtr(fd, unix.SIOCGIFFLAGS, unsafe.Pointer(&ifr)); err != nil {
131+
type ifAliasReq struct {
132+
Name [unix.IFNAMSIZ]byte
133+
Addr unix.RawSockaddrInet4
134+
Dstaddr unix.RawSockaddrInet4
135+
Mask unix.RawSockaddrInet4
136+
}
137+
138+
// setIPAddress sets local and remote ends addresses on the tunnel interface
139+
func setIPAddress(name string, gateway netip.Prefix) error {
140+
socket, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
141+
if err != nil {
151142
return err
152143
}
153-
154-
if up {
155-
ifr.Flags |= unix.IFF_UP
156-
} else {
157-
ifr.Flags &^= unix.IFF_UP
144+
defer unix.Close(socket)
145+
146+
// assume local ip address is next one from the remote address
147+
local := gateway.Addr().As4()
148+
local[3]++
149+
// fill the configuration
150+
ifReq := ifAliasReq{
151+
Addr: unix.RawSockaddrInet4{
152+
Len: unix.SizeofSockaddrInet4,
153+
Family: unix.AF_INET,
154+
Addr: local,
155+
},
156+
Dstaddr: unix.RawSockaddrInet4{
157+
Len: unix.SizeofSockaddrInet4,
158+
Family: unix.AF_INET,
159+
Addr: gateway.Addr().As4(),
160+
},
161+
Mask: unix.RawSockaddrInet4{
162+
Len: unix.SizeofSockaddrInet4,
163+
Family: unix.AF_INET,
164+
Addr: netip.MustParseAddr(net.IP(net.CIDRMask(gateway.Bits(), 32)).String()).As4(),
165+
},
166+
}
167+
copy(ifReq.Name[:], name)
168+
if err = ioctlPtr(socket, unix.SIOCAIFADDR, unsafe.Pointer(&ifReq)); err != nil {
169+
return os.NewSyscallError("SIOCAIFADDR", err)
158170
}
159171

160-
return ioctlPtr(fd, unix.SIOCSIFFLAGS, unsafe.Pointer(&ifr))
172+
return nil
161173
}
162174

163175
func ioctlPtr(fd int, req uint, arg unsafe.Pointer) error {
164-
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
176+
_, _, errno := unix.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
165177
if errno != 0 {
166178
return errno
167179
}

0 commit comments

Comments
 (0)