mirror of
				https://git.zx2c4.com/wireguard-go
				synced 2025-10-31 20:02:37 +08:00 
			
		
		
		
	 eae5e0f3a3
			
		
	
	eae5e0f3a3
	
	
	
		
			
			At these points, the socket file descriptor is not yet wrapped in an *os.File, so it needs to be closed explicitly on error. Signed-off-by: Tobias Klauser <tklauser@distanz.ch> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
		
			
				
	
	
		
			323 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /* SPDX-License-Identifier: MIT
 | |
|  *
 | |
|  * Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
 | |
|  */
 | |
| 
 | |
| package tun
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 	"time"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"golang.org/x/net/ipv6"
 | |
| 	"golang.org/x/sys/unix"
 | |
| )
 | |
| 
 | |
| const utunControlName = "com.apple.net.utun_control"
 | |
| 
 | |
| type NativeTun struct {
 | |
| 	name        string
 | |
| 	tunFile     *os.File
 | |
| 	events      chan Event
 | |
| 	errors      chan error
 | |
| 	routeSocket int
 | |
| 	closeOnce   sync.Once
 | |
| }
 | |
| 
 | |
| func retryInterfaceByIndex(index int) (iface *net.Interface, err error) {
 | |
| 	for i := 0; i < 20; i++ {
 | |
| 		iface, err = net.InterfaceByIndex(index)
 | |
| 		if err != nil && errors.Is(err, syscall.ENOMEM) {
 | |
| 			time.Sleep(time.Duration(i) * time.Second / 3)
 | |
| 			continue
 | |
| 		}
 | |
| 		return iface, err
 | |
| 	}
 | |
| 	return nil, err
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) routineRouteListener(tunIfindex int) {
 | |
| 	var (
 | |
| 		statusUp  bool
 | |
| 		statusMTU int
 | |
| 	)
 | |
| 
 | |
| 	defer close(tun.events)
 | |
| 
 | |
| 	data := make([]byte, os.Getpagesize())
 | |
| 	for {
 | |
| 	retry:
 | |
| 		n, err := unix.Read(tun.routeSocket, data)
 | |
| 		if err != nil {
 | |
| 			if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
 | |
| 				goto retry
 | |
| 			}
 | |
| 			tun.errors <- err
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if n < 14 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if data[3 /* type */] != unix.RTM_IFINFO {
 | |
| 			continue
 | |
| 		}
 | |
| 		ifindex := int(*(*uint16)(unsafe.Pointer(&data[12 /* ifindex */])))
 | |
| 		if ifindex != tunIfindex {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		iface, err := retryInterfaceByIndex(ifindex)
 | |
| 		if err != nil {
 | |
| 			tun.errors <- err
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		// Up / Down event
 | |
| 		up := (iface.Flags & net.FlagUp) != 0
 | |
| 		if up != statusUp && up {
 | |
| 			tun.events <- EventUp
 | |
| 		}
 | |
| 		if up != statusUp && !up {
 | |
| 			tun.events <- EventDown
 | |
| 		}
 | |
| 		statusUp = up
 | |
| 
 | |
| 		// MTU changes
 | |
| 		if iface.MTU != statusMTU {
 | |
| 			tun.events <- EventMTUUpdate
 | |
| 		}
 | |
| 		statusMTU = iface.MTU
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func CreateTUN(name string, mtu int) (Device, error) {
 | |
| 	ifIndex := -1
 | |
| 	if name != "utun" {
 | |
| 		_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
 | |
| 		if err != nil || ifIndex < 0 {
 | |
| 			return nil, fmt.Errorf("Interface name must be utun[0-9]*")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	ctlInfo := &unix.CtlInfo{}
 | |
| 	copy(ctlInfo.Name[:], []byte(utunControlName))
 | |
| 	err = unix.IoctlCtlInfo(fd, ctlInfo)
 | |
| 	if err != nil {
 | |
| 		unix.Close(fd)
 | |
| 		return nil, fmt.Errorf("IoctlGetCtlInfo: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	sc := &unix.SockaddrCtl{
 | |
| 		ID:   ctlInfo.Id,
 | |
| 		Unit: uint32(ifIndex) + 1,
 | |
| 	}
 | |
| 
 | |
| 	err = unix.Connect(fd, sc)
 | |
| 	if err != nil {
 | |
| 		unix.Close(fd)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	err = unix.SetNonblock(fd, true)
 | |
| 	if err != nil {
 | |
| 		unix.Close(fd)
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu)
 | |
| 
 | |
| 	if err == nil && name == "utun" {
 | |
| 		fname := os.Getenv("WG_TUN_NAME_FILE")
 | |
| 		if fname != "" {
 | |
| 			os.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return tun, err
 | |
| }
 | |
| 
 | |
| func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
 | |
| 	tun := &NativeTun{
 | |
| 		tunFile: file,
 | |
| 		events:  make(chan Event, 10),
 | |
| 		errors:  make(chan error, 5),
 | |
| 	}
 | |
| 
 | |
| 	name, err := tun.Name()
 | |
| 	if err != nil {
 | |
| 		tun.tunFile.Close()
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	tunIfindex, err := func() (int, error) {
 | |
| 		iface, err := net.InterfaceByName(name)
 | |
| 		if err != nil {
 | |
| 			return -1, err
 | |
| 		}
 | |
| 		return iface.Index, nil
 | |
| 	}()
 | |
| 	if err != nil {
 | |
| 		tun.tunFile.Close()
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
 | |
| 	if err != nil {
 | |
| 		tun.tunFile.Close()
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	go tun.routineRouteListener(tunIfindex)
 | |
| 
 | |
| 	if mtu > 0 {
 | |
| 		err = tun.setMTU(mtu)
 | |
| 		if err != nil {
 | |
| 			tun.Close()
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return tun, nil
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Name() (string, error) {
 | |
| 	var err error
 | |
| 	tun.operateOnFd(func(fd uintptr) {
 | |
| 		tun.name, err = unix.GetsockoptString(
 | |
| 			int(fd),
 | |
| 			2, /* #define SYSPROTO_CONTROL 2 */
 | |
| 			2, /* #define UTUN_OPT_IFNAME 2 */
 | |
| 		)
 | |
| 	})
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return "", fmt.Errorf("GetSockoptString: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return tun.name, nil
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) File() *os.File {
 | |
| 	return tun.tunFile
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Events() chan Event {
 | |
| 	return tun.events
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
 | |
| 	select {
 | |
| 	case err := <-tun.errors:
 | |
| 		return 0, err
 | |
| 	default:
 | |
| 		buff := buff[offset-4:]
 | |
| 		n, err := tun.tunFile.Read(buff[:])
 | |
| 		if n < 4 {
 | |
| 			return 0, err
 | |
| 		}
 | |
| 		return n - 4, err
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
 | |
| 
 | |
| 	// reserve space for header
 | |
| 
 | |
| 	buff = buff[offset-4:]
 | |
| 
 | |
| 	// add packet information header
 | |
| 
 | |
| 	buff[0] = 0x00
 | |
| 	buff[1] = 0x00
 | |
| 	buff[2] = 0x00
 | |
| 
 | |
| 	if buff[4]>>4 == ipv6.Version {
 | |
| 		buff[3] = unix.AF_INET6
 | |
| 	} else {
 | |
| 		buff[3] = unix.AF_INET
 | |
| 	}
 | |
| 
 | |
| 	// write
 | |
| 
 | |
| 	return tun.tunFile.Write(buff)
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Flush() error {
 | |
| 	// TODO: can flushing be implemented by buffering and using sendmmsg?
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) Close() error {
 | |
| 	var err1, err2 error
 | |
| 	tun.closeOnce.Do(func() {
 | |
| 		err1 = tun.tunFile.Close()
 | |
| 		if tun.routeSocket != -1 {
 | |
| 			unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
 | |
| 			err2 = unix.Close(tun.routeSocket)
 | |
| 		} else if tun.events != nil {
 | |
| 			close(tun.events)
 | |
| 		}
 | |
| 	})
 | |
| 	if err1 != nil {
 | |
| 		return err1
 | |
| 	}
 | |
| 	return err2
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) setMTU(n int) error {
 | |
| 	fd, err := unix.Socket(
 | |
| 		unix.AF_INET,
 | |
| 		unix.SOCK_DGRAM,
 | |
| 		0,
 | |
| 	)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	defer unix.Close(fd)
 | |
| 
 | |
| 	var ifr unix.IfreqMTU
 | |
| 	copy(ifr.Name[:], tun.name)
 | |
| 	ifr.MTU = int32(n)
 | |
| 	err = unix.IoctlSetIfreqMTU(fd, &ifr)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to set MTU on %s: %w", tun.name, err)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (tun *NativeTun) MTU() (int, error) {
 | |
| 	fd, err := unix.Socket(
 | |
| 		unix.AF_INET,
 | |
| 		unix.SOCK_DGRAM,
 | |
| 		0,
 | |
| 	)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	defer unix.Close(fd)
 | |
| 
 | |
| 	ifr, err := unix.IoctlGetIfreqMTU(fd, tun.name)
 | |
| 	if err != nil {
 | |
| 		return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, err)
 | |
| 	}
 | |
| 
 | |
| 	return int(ifr.MTU), nil
 | |
| }
 |