github.com/sagernet/sing-tun@v0.3.0-beta.5/tun_darwin.go (about)

     1  package tun
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/netip"
     7  	"os"
     8  	"syscall"
     9  	"unsafe"
    10  
    11  	"github.com/sagernet/sing/common"
    12  	"github.com/sagernet/sing/common/buf"
    13  	"github.com/sagernet/sing/common/bufio"
    14  	E "github.com/sagernet/sing/common/exceptions"
    15  	N "github.com/sagernet/sing/common/network"
    16  	"github.com/sagernet/sing/common/shell"
    17  
    18  	"golang.org/x/net/route"
    19  	"golang.org/x/sys/unix"
    20  )
    21  
    22  const PacketOffset = 4
    23  
    24  type NativeTun struct {
    25  	tunFile      *os.File
    26  	tunWriter    N.VectorisedWriter
    27  	mtu          uint32
    28  	inet4Address [4]byte
    29  	inet6Address [16]byte
    30  }
    31  
    32  func New(options Options) (Tun, error) {
    33  	var tunFd int
    34  	if options.FileDescriptor == 0 {
    35  		ifIndex := -1
    36  		_, err := fmt.Sscanf(options.Name, "utun%d", &ifIndex)
    37  		if err != nil {
    38  			return nil, E.New("bad tun name: ", options.Name)
    39  		}
    40  
    41  		tunFd, err = unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  
    46  		err = configure(tunFd, ifIndex, options.Name, options)
    47  		if err != nil {
    48  			unix.Close(tunFd)
    49  			return nil, err
    50  		}
    51  	} else {
    52  		tunFd = options.FileDescriptor
    53  	}
    54  
    55  	nativeTun := &NativeTun{
    56  		tunFile: os.NewFile(uintptr(tunFd), "utun"),
    57  		mtu:     options.MTU,
    58  	}
    59  	if len(options.Inet4Address) > 0 {
    60  		nativeTun.inet4Address = options.Inet4Address[0].Addr().As4()
    61  	}
    62  	if len(options.Inet6Address) > 0 {
    63  		nativeTun.inet6Address = options.Inet6Address[0].Addr().As16()
    64  	}
    65  	var ok bool
    66  	nativeTun.tunWriter, ok = bufio.CreateVectorisedWriter(nativeTun.tunFile)
    67  	if !ok {
    68  		panic("create vectorised writer")
    69  	}
    70  	return nativeTun, nil
    71  }
    72  
    73  func (t *NativeTun) Read(p []byte) (n int, err error) {
    74  	return t.tunFile.Read(p)
    75  }
    76  
    77  func (t *NativeTun) Write(p []byte) (n int, err error) {
    78  	return t.tunFile.Write(p)
    79  }
    80  
    81  var (
    82  	packetHeader4 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET}
    83  	packetHeader6 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET6}
    84  )
    85  
    86  func (t *NativeTun) WriteVectorised(buffers []*buf.Buffer) error {
    87  	var packetHeader []byte
    88  	if buffers[0].Byte(0)>>4 == 4 {
    89  		packetHeader = packetHeader4[:]
    90  	} else {
    91  		packetHeader = packetHeader6[:]
    92  	}
    93  	return t.tunWriter.WriteVectorised(append([]*buf.Buffer{buf.As(packetHeader)}, buffers...))
    94  }
    95  
    96  func (t *NativeTun) Close() error {
    97  	flushDNSCache()
    98  	return t.tunFile.Close()
    99  }
   100  
   101  const utunControlName = "com.apple.net.utun_control"
   102  
   103  const (
   104  	SIOCAIFADDR_IN6       = 2155899162 // netinet6/in6_var.h
   105  	IN6_IFF_NODAD         = 0x0020     // netinet6/in6_var.h
   106  	IN6_IFF_SECURED       = 0x0400     // netinet6/in6_var.h
   107  	ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h
   108  )
   109  
   110  type ifAliasReq struct {
   111  	Name    [unix.IFNAMSIZ]byte
   112  	Addr    unix.RawSockaddrInet4
   113  	Dstaddr unix.RawSockaddrInet4
   114  	Mask    unix.RawSockaddrInet4
   115  }
   116  
   117  type ifAliasReq6 struct {
   118  	Name     [16]byte
   119  	Addr     unix.RawSockaddrInet6
   120  	Dstaddr  unix.RawSockaddrInet6
   121  	Mask     unix.RawSockaddrInet6
   122  	Flags    uint32
   123  	Lifetime addrLifetime6
   124  }
   125  
   126  type addrLifetime6 struct {
   127  	Expire    float64
   128  	Preferred float64
   129  	Vltime    uint32
   130  	Pltime    uint32
   131  }
   132  
   133  func configure(tunFd int, ifIndex int, name string, options Options) error {
   134  	ctlInfo := &unix.CtlInfo{}
   135  	copy(ctlInfo.Name[:], utunControlName)
   136  	err := unix.IoctlCtlInfo(tunFd, ctlInfo)
   137  	if err != nil {
   138  		return os.NewSyscallError("IoctlCtlInfo", err)
   139  	}
   140  
   141  	err = unix.Connect(tunFd, &unix.SockaddrCtl{
   142  		ID:   ctlInfo.Id,
   143  		Unit: uint32(ifIndex) + 1,
   144  	})
   145  	if err != nil {
   146  		return os.NewSyscallError("Connect", err)
   147  	}
   148  
   149  	err = unix.SetNonblock(tunFd, true)
   150  	if err != nil {
   151  		return os.NewSyscallError("SetNonblock", err)
   152  	}
   153  
   154  	err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error {
   155  		var ifr unix.IfreqMTU
   156  		copy(ifr.Name[:], name)
   157  		ifr.MTU = int32(options.MTU)
   158  		return unix.IoctlSetIfreqMTU(socketFd, &ifr)
   159  	})
   160  	if err != nil {
   161  		return os.NewSyscallError("IoctlSetIfreqMTU", err)
   162  	}
   163  	if len(options.Inet4Address) > 0 {
   164  		for _, address := range options.Inet4Address {
   165  			ifReq := ifAliasReq{
   166  				Addr: unix.RawSockaddrInet4{
   167  					Len:    unix.SizeofSockaddrInet4,
   168  					Family: unix.AF_INET,
   169  					Addr:   address.Addr().As4(),
   170  				},
   171  				Dstaddr: unix.RawSockaddrInet4{
   172  					Len:    unix.SizeofSockaddrInet4,
   173  					Family: unix.AF_INET,
   174  					Addr:   address.Addr().As4(),
   175  				},
   176  				Mask: unix.RawSockaddrInet4{
   177  					Len:    unix.SizeofSockaddrInet4,
   178  					Family: unix.AF_INET,
   179  					Addr:   netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), 32)).String()).As4(),
   180  				},
   181  			}
   182  			copy(ifReq.Name[:], name)
   183  			err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error {
   184  				if _, _, errno := unix.Syscall(
   185  					syscall.SYS_IOCTL,
   186  					uintptr(socketFd),
   187  					uintptr(unix.SIOCAIFADDR),
   188  					uintptr(unsafe.Pointer(&ifReq)),
   189  				); errno != 0 {
   190  					return os.NewSyscallError("SIOCAIFADDR", errno)
   191  				}
   192  				return nil
   193  			})
   194  			if err != nil {
   195  				return err
   196  			}
   197  		}
   198  	}
   199  	if len(options.Inet6Address) > 0 {
   200  		for _, address := range options.Inet6Address {
   201  			ifReq6 := ifAliasReq6{
   202  				Addr: unix.RawSockaddrInet6{
   203  					Len:    unix.SizeofSockaddrInet6,
   204  					Family: unix.AF_INET6,
   205  					Addr:   address.Addr().As16(),
   206  				},
   207  				Mask: unix.RawSockaddrInet6{
   208  					Len:    unix.SizeofSockaddrInet6,
   209  					Family: unix.AF_INET6,
   210  					Addr:   netip.MustParseAddr(net.IP(net.CIDRMask(address.Bits(), 128)).String()).As16(),
   211  				},
   212  				Flags: IN6_IFF_NODAD | IN6_IFF_SECURED,
   213  				Lifetime: addrLifetime6{
   214  					Vltime: ND6_INFINITE_LIFETIME,
   215  					Pltime: ND6_INFINITE_LIFETIME,
   216  				},
   217  			}
   218  			if address.Bits() == 128 {
   219  				ifReq6.Dstaddr = unix.RawSockaddrInet6{
   220  					Len:    unix.SizeofSockaddrInet6,
   221  					Family: unix.AF_INET6,
   222  					Addr:   address.Addr().Next().As16(),
   223  				}
   224  			}
   225  			copy(ifReq6.Name[:], name)
   226  			err = useSocket(unix.AF_INET6, unix.SOCK_DGRAM, 0, func(socketFd int) error {
   227  				if _, _, errno := unix.Syscall(
   228  					syscall.SYS_IOCTL,
   229  					uintptr(socketFd),
   230  					uintptr(SIOCAIFADDR_IN6),
   231  					uintptr(unsafe.Pointer(&ifReq6)),
   232  				); errno != 0 {
   233  					return os.NewSyscallError("SIOCAIFADDR_IN6", errno)
   234  				}
   235  				return nil
   236  			})
   237  			if err != nil {
   238  				return err
   239  			}
   240  		}
   241  	}
   242  	if options.AutoRoute {
   243  		var routeRanges []netip.Prefix
   244  		routeRanges, err = options.BuildAutoRouteRanges(false)
   245  		for _, routeRange := range routeRanges {
   246  			if routeRange.Addr().Is4() {
   247  				err = addRoute(routeRange, options.Inet4Address[0].Addr())
   248  			} else {
   249  				err = addRoute(routeRange, options.Inet6Address[0].Addr())
   250  			}
   251  			if err != nil {
   252  				return E.Cause(err, "add route: ", routeRange)
   253  			}
   254  		}
   255  		flushDNSCache()
   256  	}
   257  	return nil
   258  }
   259  
   260  func useSocket(domain, typ, proto int, block func(socketFd int) error) error {
   261  	socketFd, err := unix.Socket(domain, typ, proto)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	defer unix.Close(socketFd)
   266  	return block(socketFd)
   267  }
   268  
   269  func addRoute(destination netip.Prefix, gateway netip.Addr) error {
   270  	routeMessage := route.RouteMessage{
   271  		Type:    unix.RTM_ADD,
   272  		Flags:   unix.RTF_UP | unix.RTF_STATIC | unix.RTF_GATEWAY,
   273  		Version: unix.RTM_VERSION,
   274  		Seq:     1,
   275  	}
   276  	if gateway.Is4() {
   277  		routeMessage.Addrs = []route.Addr{
   278  			syscall.RTAX_DST:     &route.Inet4Addr{IP: destination.Addr().As4()},
   279  			syscall.RTAX_NETMASK: &route.Inet4Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 32)).String()).As4()},
   280  			syscall.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.As4()},
   281  		}
   282  	} else {
   283  		routeMessage.Addrs = []route.Addr{
   284  			syscall.RTAX_DST:     &route.Inet6Addr{IP: destination.Addr().As16()},
   285  			syscall.RTAX_NETMASK: &route.Inet6Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 128)).String()).As16()},
   286  			syscall.RTAX_GATEWAY: &route.Inet6Addr{IP: gateway.As16()},
   287  		}
   288  	}
   289  	request, err := routeMessage.Marshal()
   290  	if err != nil {
   291  		return err
   292  	}
   293  	return useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error {
   294  		return common.Error(unix.Write(socketFd, request))
   295  	})
   296  }
   297  
   298  func flushDNSCache() {
   299  	shell.Exec("dscacheutil", "-flushcache").Start()
   300  }