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