tun_darwin.go raw
1 /* SPDX-License-Identifier: MIT
2 *
3 * Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
4 */
5
6 package tun
7
8 import (
9 "fmt"
10 "io"
11 "net"
12 "os"
13 "sync"
14 "syscall"
15 "unsafe"
16
17 "golang.org/x/sys/unix"
18 )
19
20 const utunControlName = "com.apple.net.utun_control"
21
22 type NativeTun struct {
23 name string
24 tunFile *os.File
25 events chan Event
26 errors chan error
27 routeSocket int
28 closeOnce sync.Once
29 }
30
31 func (tun *NativeTun) routineRouteListener(tunIfindex int) {
32 var (
33 statusUp bool
34 statusMTU int
35 )
36
37 defer close(tun.events)
38
39 data := make([]byte, os.Getpagesize())
40 for {
41 retry:
42 n, err := unix.Read(tun.routeSocket, data)
43 if err != nil {
44 if errno, ok := err.(unix.Errno); ok && errno == unix.EINTR {
45 goto retry
46 }
47 tun.errors <- err
48 return
49 }
50
51 if n < 28 {
52 continue
53 }
54
55 if data[3 /* ifm_type */] != unix.RTM_IFINFO {
56 continue
57 }
58 ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifm_index */])))
59 if ifindex != tunIfindex {
60 continue
61 }
62
63 flags := int(*(*uint32)(unsafe.Pointer(&data[8 /* ifm_flags */])))
64
65 // Up / Down event
66 up := (flags & syscall.IFF_UP) != 0
67 if up != statusUp && up {
68 tun.events <- EventUp
69 }
70 if up != statusUp && !up {
71 tun.events <- EventDown
72 }
73 statusUp = up
74
75 mtu := int(*(*uint32)(unsafe.Pointer(&data[24 /* ifm_data.ifi_mtu */])))
76
77 // MTU changes
78 if mtu != statusMTU {
79 tun.events <- EventMTUUpdate
80 }
81 statusMTU = mtu
82 }
83 }
84
85 func CreateTUN(name string, mtu int) (Device, error) {
86 ifIndex := -1
87 if name != "utun" {
88 _, err := fmt.Sscanf(name, "utun%d", &ifIndex)
89 if err != nil || ifIndex < 0 {
90 return nil, fmt.Errorf("Interface name must be utun[0-9]*")
91 }
92 }
93
94 fd, err := socketCloexec(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
95 if err != nil {
96 return nil, err
97 }
98
99 ctlInfo := &unix.CtlInfo{}
100 copy(ctlInfo.Name[:], []byte(utunControlName))
101 err = unix.IoctlCtlInfo(fd, ctlInfo)
102 if err != nil {
103 unix.Close(fd)
104 return nil, fmt.Errorf("IoctlGetCtlInfo: %w", err)
105 }
106
107 sc := &unix.SockaddrCtl{
108 ID: ctlInfo.Id,
109 Unit: uint32(ifIndex) + 1,
110 }
111
112 err = unix.Connect(fd, sc)
113 if err != nil {
114 unix.Close(fd)
115 return nil, err
116 }
117
118 err = unix.SetNonblock(fd, true)
119 if err != nil {
120 unix.Close(fd)
121 return nil, err
122 }
123 tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu)
124
125 if err == nil && name == "utun" {
126 fname := os.Getenv("WG_TUN_NAME_FILE")
127 if fname != "" {
128 os.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0o400)
129 }
130 }
131
132 return tun, err
133 }
134
135 func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
136 tun := &NativeTun{
137 tunFile: file,
138 events: make(chan Event, 10),
139 errors: make(chan error, 5),
140 }
141
142 name, err := tun.Name()
143 if err != nil {
144 tun.tunFile.Close()
145 return nil, err
146 }
147
148 tunIfindex, err := func() (int, error) {
149 iface, err := net.InterfaceByName(name)
150 if err != nil {
151 return -1, err
152 }
153 return iface.Index, nil
154 }()
155 if err != nil {
156 tun.tunFile.Close()
157 return nil, err
158 }
159
160 tun.routeSocket, err = socketCloexec(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
161 if err != nil {
162 tun.tunFile.Close()
163 return nil, err
164 }
165
166 go tun.routineRouteListener(tunIfindex)
167
168 if mtu > 0 {
169 err = tun.setMTU(mtu)
170 if err != nil {
171 tun.Close()
172 return nil, err
173 }
174 }
175
176 return tun, nil
177 }
178
179 func (tun *NativeTun) Name() (string, error) {
180 var err error
181 tun.operateOnFd(func(fd uintptr) {
182 tun.name, err = unix.GetsockoptString(
183 int(fd),
184 2, /* #define SYSPROTO_CONTROL 2 */
185 2, /* #define UTUN_OPT_IFNAME 2 */
186 )
187 })
188
189 if err != nil {
190 return "", fmt.Errorf("GetSockoptString: %w", err)
191 }
192
193 return tun.name, nil
194 }
195
196 func (tun *NativeTun) File() *os.File {
197 return tun.tunFile
198 }
199
200 func (tun *NativeTun) Events() <-chan Event {
201 return tun.events
202 }
203
204 func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
205 // TODO: the BSDs look very similar in Read() and Write(). They should be
206 // collapsed, with platform-specific files containing the varying parts of
207 // their implementations.
208 select {
209 case err := <-tun.errors:
210 return 0, err
211 default:
212 buf := bufs[0][offset-4:]
213 n, err := tun.tunFile.Read(buf[:])
214 if n < 4 {
215 return 0, err
216 }
217 sizes[0] = n - 4
218 return 1, err
219 }
220 }
221
222 func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
223 if offset < 4 {
224 return 0, io.ErrShortBuffer
225 }
226 for i, buf := range bufs {
227 buf = buf[offset-4:]
228 buf[0] = 0x00
229 buf[1] = 0x00
230 buf[2] = 0x00
231 switch buf[4] >> 4 {
232 case 4:
233 buf[3] = unix.AF_INET
234 case 6:
235 buf[3] = unix.AF_INET6
236 default:
237 return i, unix.EAFNOSUPPORT
238 }
239 if _, err := tun.tunFile.Write(buf); err != nil {
240 return i, err
241 }
242 }
243 return len(bufs), nil
244 }
245
246 func (tun *NativeTun) Close() error {
247 var err1, err2 error
248 tun.closeOnce.Do(func() {
249 err1 = tun.tunFile.Close()
250 if tun.routeSocket != -1 {
251 unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
252 err2 = unix.Close(tun.routeSocket)
253 } else if tun.events != nil {
254 close(tun.events)
255 }
256 })
257 if err1 != nil {
258 return err1
259 }
260 return err2
261 }
262
263 func (tun *NativeTun) setMTU(n int) error {
264 fd, err := socketCloexec(
265 unix.AF_INET,
266 unix.SOCK_DGRAM,
267 0,
268 )
269 if err != nil {
270 return err
271 }
272
273 defer unix.Close(fd)
274
275 var ifr unix.IfreqMTU
276 copy(ifr.Name[:], tun.name)
277 ifr.MTU = int32(n)
278 err = unix.IoctlSetIfreqMTU(fd, &ifr)
279 if err != nil {
280 return fmt.Errorf("failed to set MTU on %s: %w", tun.name, err)
281 }
282
283 return nil
284 }
285
286 func (tun *NativeTun) MTU() (int, error) {
287 fd, err := socketCloexec(
288 unix.AF_INET,
289 unix.SOCK_DGRAM,
290 0,
291 )
292 if err != nil {
293 return 0, err
294 }
295
296 defer unix.Close(fd)
297
298 ifr, err := unix.IoctlGetIfreqMTU(fd, tun.name)
299 if err != nil {
300 return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, err)
301 }
302
303 return int(ifr.MTU), nil
304 }
305
306 func (tun *NativeTun) BatchSize() int {
307 return 1
308 }
309
310 func socketCloexec(family, sotype, proto int) (fd int, err error) {
311 // See go/src/net/sys_cloexec.go for background.
312 syscall.ForkLock.RLock()
313 defer syscall.ForkLock.RUnlock()
314
315 fd, err = unix.Socket(family, sotype, proto)
316 if err == nil {
317 unix.CloseOnExec(fd)
318 }
319 return
320 }
321