mirror of
				https://github.com/SagerNet/sing-tun.git
				synced 2025-10-31 03:46:26 +08:00 
			
		
		
		
	Add darwin support
This commit is contained in:
		| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| Simple transparent proxy library. | ||||
|  | ||||
| Currently only for linux and windows. | ||||
| For Linux, Windows and macOS. | ||||
|  | ||||
| ## License | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"net/netip" | ||||
| 	"os" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/sagernet/sing/common" | ||||
| 	E "github.com/sagernet/sing/common/exceptions" | ||||
| @@ -13,7 +14,6 @@ import ( | ||||
|  | ||||
| 	"golang.org/x/net/route" | ||||
| 	"golang.org/x/sys/unix" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| type networkUpdateMonitor struct { | ||||
| @@ -47,7 +47,7 @@ func (m *networkUpdateMonitor) Start() error { | ||||
| func (m *networkUpdateMonitor) loopUpdate() { | ||||
| 	rawConn, err := m.routeSocket.SyscallConn() | ||||
| 	if err != nil { | ||||
| 		m.errorHandler.NewError(context.Background(), err) | ||||
| 		m.errorHandler.NewError(context.Background(), E.Cause(err, "create raw route connection")) | ||||
| 		return | ||||
| 	} | ||||
| 	for { | ||||
| @@ -66,7 +66,7 @@ func (m *networkUpdateMonitor) loopUpdate() { | ||||
| 		m.emit() | ||||
| 	} | ||||
| 	if err != syscall.EAGAIN { | ||||
| 		m.errorHandler.NewError(context.Background(), err) | ||||
| 		m.errorHandler.NewError(context.Background(), E.Cause(err, "read route message")) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										392
									
								
								tun_darwin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								tun_darwin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,392 @@ | ||||
| package tun | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/netip" | ||||
| 	"os" | ||||
| 	"syscall" | ||||
| 	"unsafe" | ||||
|  | ||||
| 	"github.com/sagernet/sing/common" | ||||
| 	"github.com/sagernet/sing/common/buf" | ||||
| 	E "github.com/sagernet/sing/common/exceptions" | ||||
| 	"github.com/sagernet/sing/common/rw" | ||||
|  | ||||
| 	"golang.org/x/net/route" | ||||
| 	"golang.org/x/sys/unix" | ||||
| 	gBuffer "gvisor.dev/gvisor/pkg/buffer" | ||||
| 	"gvisor.dev/gvisor/pkg/tcpip" | ||||
| 	"gvisor.dev/gvisor/pkg/tcpip/header" | ||||
| 	"gvisor.dev/gvisor/pkg/tcpip/stack" | ||||
| ) | ||||
|  | ||||
| type NativeTun struct { | ||||
| 	tunFd        uintptr | ||||
| 	tunFile      *os.File | ||||
| 	inet4Address tcpip.Address | ||||
| 	inet6Address tcpip.Address | ||||
| 	mtu          uint32 | ||||
| } | ||||
|  | ||||
| func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) (Tun, error) { | ||||
| 	ifIndex := -1 | ||||
| 	_, err := fmt.Sscanf(name, "utun%d", &ifIndex) | ||||
| 	if err != nil { | ||||
| 		return nil, E.New("bad tun name: ", name) | ||||
| 	} | ||||
|  | ||||
| 	tunFd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	err = configure(tunFd, ifIndex, name, inet4Address, inet6Address, mtu, autoRoute) | ||||
| 	if err != nil { | ||||
| 		unix.Close(tunFd) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &NativeTun{ | ||||
| 		tunFd:        uintptr(tunFd), | ||||
| 		tunFile:      os.NewFile(uintptr(tunFd), "utun"), | ||||
| 		inet4Address: tcpip.Address(inet4Address.Addr().AsSlice()), | ||||
| 		inet6Address: tcpip.Address(inet6Address.Addr().AsSlice()), | ||||
| 		mtu:          mtu, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) { | ||||
| 	return &DarwinEndpoint{tun: t}, nil | ||||
| } | ||||
|  | ||||
| func (t *NativeTun) Close() error { | ||||
| 	return t.tunFile.Close() | ||||
| } | ||||
|  | ||||
| var _ stack.LinkEndpoint = (*DarwinEndpoint)(nil) | ||||
|  | ||||
| type DarwinEndpoint struct { | ||||
| 	tun        *NativeTun | ||||
| 	dispatcher stack.NetworkDispatcher | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) MTU() uint32 { | ||||
| 	return e.tun.mtu | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) MaxHeaderLength() uint16 { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) LinkAddress() tcpip.LinkAddress { | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) Capabilities() stack.LinkEndpointCapabilities { | ||||
| 	return stack.CapabilityNone | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) Attach(dispatcher stack.NetworkDispatcher) { | ||||
| 	if dispatcher == nil && e.dispatcher != nil { | ||||
| 		e.dispatcher = nil | ||||
| 		return | ||||
| 	} | ||||
| 	if dispatcher != nil && e.dispatcher == nil { | ||||
| 		e.dispatcher = dispatcher | ||||
| 		go e.dispatchLoop() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) dispatchLoop() { | ||||
| 	_buffer := buf.StackNewSize(int(e.tun.mtu) + 4) | ||||
| 	defer common.KeepAlive(_buffer) | ||||
| 	buffer := common.Dup(_buffer) | ||||
| 	defer buffer.Release() | ||||
| 	data := buffer.FreeBytes() | ||||
| 	for { | ||||
| 		n, err := e.tun.tunFile.Read(data) | ||||
| 		if err != nil { | ||||
| 			break | ||||
| 		} | ||||
| 		packet := buf.NewSize(n - 4) | ||||
| 		common.Must1(packet.Write(data[4:n])) | ||||
| 		pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ | ||||
| 			Payload:           gBuffer.NewWithData(packet.Bytes()), | ||||
| 			IsForwardedPacket: true, | ||||
| 			OnRelease:         packet.Release, | ||||
| 		}) | ||||
| 		var p tcpip.NetworkProtocolNumber | ||||
| 		ipHeader, ok := pkt.Data().PullUp(1) | ||||
| 		if !ok { | ||||
| 			pkt.DecRef() | ||||
| 			continue | ||||
| 		} | ||||
| 		switch header.IPVersion(ipHeader) { | ||||
| 		case header.IPv4Version: | ||||
| 			p = header.IPv4ProtocolNumber | ||||
| 			if header.IPv4(packet.Bytes()).DestinationAddress() == e.tun.inet4Address { | ||||
| 				_, err = e.tun.tunFile.Write(data[:n]) | ||||
| 				continue | ||||
| 			} | ||||
| 		case header.IPv6Version: | ||||
| 			p = header.IPv6ProtocolNumber | ||||
| 			if header.IPv6(packet.Bytes()).DestinationAddress() == e.tun.inet6Address { | ||||
| 				_, err = e.tun.tunFile.Write(data[:n]) | ||||
| 				continue | ||||
| 			} | ||||
| 		default: | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		dispatcher := e.dispatcher | ||||
| 		if dispatcher == nil { | ||||
| 			pkt.DecRef() | ||||
| 			return | ||||
| 		} | ||||
| 		dispatcher.DeliverNetworkPacket(p, pkt) | ||||
| 		pkt.DecRef() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) IsAttached() bool { | ||||
| 	return e.dispatcher != nil | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) Wait() { | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType { | ||||
| 	return header.ARPHardwareNone | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) { | ||||
| } | ||||
|  | ||||
| func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) { | ||||
| 	_packetHeader := buf.StackNewSize(4) | ||||
| 	defer common.KeepAlive(_packetHeader) | ||||
| 	packetHeader := common.Dup(_packetHeader) | ||||
| 	defer packetHeader.Release() | ||||
| 	var n int | ||||
| 	for _, packet := range packetBufferList.AsSlice() { | ||||
| 		packetHeader.FullReset() | ||||
| 		packetHeader.WriteZeroN(3) | ||||
| 		switch packet.NetworkProtocolNumber { | ||||
| 		case header.IPv4ProtocolNumber: | ||||
| 			packetHeader.WriteByte(unix.AF_INET) | ||||
| 		case header.IPv6ProtocolNumber: | ||||
| 			packetHeader.WriteByte(unix.AF_INET6) | ||||
| 		} | ||||
| 		_, err := rw.WriteV(e.tun.tunFd, append([][]byte{packetHeader.Bytes()}, packet.Slices()...)) | ||||
| 		if err != nil { | ||||
| 			return n, &tcpip.ErrAborted{} | ||||
| 		} | ||||
| 		n++ | ||||
| 	} | ||||
| 	return n, nil | ||||
| } | ||||
|  | ||||
| const utunControlName = "com.apple.net.utun_control" | ||||
|  | ||||
| const ( | ||||
| 	SIOCAIFADDR_IN6       = 2155899162 // netinet6/in6_var.h | ||||
| 	IN6_IFF_NODAD         = 0x0020     // netinet6/in6_var.h | ||||
| 	IN6_IFF_SECURED       = 0x0400     // netinet6/in6_var.h | ||||
| 	ND6_INFINITE_LIFETIME = 0xFFFFFFFF // netinet6/nd6.h | ||||
| ) | ||||
|  | ||||
| type ifAliasReq struct { | ||||
| 	Name    [unix.IFNAMSIZ]byte | ||||
| 	Addr    unix.RawSockaddrInet4 | ||||
| 	Dstaddr unix.RawSockaddrInet4 | ||||
| 	Mask    unix.RawSockaddrInet4 | ||||
| } | ||||
|  | ||||
| type ifAliasReq6 struct { | ||||
| 	Name     [16]byte | ||||
| 	Addr     unix.RawSockaddrInet6 | ||||
| 	Dstaddr  unix.RawSockaddrInet6 | ||||
| 	Mask     unix.RawSockaddrInet6 | ||||
| 	Flags    uint32 | ||||
| 	Lifetime addrLifetime6 | ||||
| } | ||||
|  | ||||
| type addrLifetime6 struct { | ||||
| 	Expire    float64 | ||||
| 	Preferred float64 | ||||
| 	Vltime    uint32 | ||||
| 	Pltime    uint32 | ||||
| } | ||||
|  | ||||
| func configure(tunFd int, ifIndex int, name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu uint32, autoRoute bool) error { | ||||
| 	ctlInfo := &unix.CtlInfo{} | ||||
| 	copy(ctlInfo.Name[:], utunControlName) | ||||
| 	err := unix.IoctlCtlInfo(tunFd, ctlInfo) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = unix.Connect(tunFd, &unix.SockaddrCtl{ | ||||
| 		ID:   ctlInfo.Id, | ||||
| 		Unit: uint32(ifIndex) + 1, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = unix.SetNonblock(tunFd, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error { | ||||
| 		var ifr unix.IfreqMTU | ||||
| 		copy(ifr.Name[:], name) | ||||
| 		ifr.MTU = int32(mtu) | ||||
| 		return unix.IoctlSetIfreqMTU(socketFd, &ifr) | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if inet4Address.IsValid() { | ||||
| 		ifReq := ifAliasReq{ | ||||
| 			Addr: unix.RawSockaddrInet4{ | ||||
| 				Len:    unix.SizeofSockaddrInet4, | ||||
| 				Family: unix.AF_INET, | ||||
| 				Addr:   inet4Address.Addr().As4(), | ||||
| 			}, | ||||
| 			Dstaddr: unix.RawSockaddrInet4{ | ||||
| 				Len:    unix.SizeofSockaddrInet4, | ||||
| 				Family: unix.AF_INET, | ||||
| 				Addr:   inet4Address.Addr().As4(), | ||||
| 			}, | ||||
| 			Mask: unix.RawSockaddrInet4{ | ||||
| 				Len:    unix.SizeofSockaddrInet4, | ||||
| 				Family: unix.AF_INET, | ||||
| 				Addr:   netip.MustParseAddr(net.IP(net.CIDRMask(inet4Address.Bits(), 32)).String()).As4(), | ||||
| 			}, | ||||
| 		} | ||||
| 		copy(ifReq.Name[:], name) | ||||
| 		err = useSocket(unix.AF_INET, unix.SOCK_DGRAM, 0, func(socketFd int) error { | ||||
| 			if _, _, errno := unix.Syscall( | ||||
| 				syscall.SYS_IOCTL, | ||||
| 				uintptr(socketFd), | ||||
| 				uintptr(unix.SIOCAIFADDR), | ||||
| 				uintptr(unsafe.Pointer(&ifReq)), | ||||
| 			); errno != 0 { | ||||
| 				return os.NewSyscallError("SIOCAIFADDR", errno) | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if inet6Address.IsValid() { | ||||
| 		ifReq6 := ifAliasReq6{ | ||||
| 			Addr: unix.RawSockaddrInet6{ | ||||
| 				Len:    unix.SizeofSockaddrInet6, | ||||
| 				Family: unix.AF_INET6, | ||||
| 				Addr:   inet6Address.Addr().As16(), | ||||
| 			}, | ||||
| 			Mask: unix.RawSockaddrInet6{ | ||||
| 				Len:    unix.SizeofSockaddrInet6, | ||||
| 				Family: unix.AF_INET6, | ||||
| 				Addr:   netip.MustParseAddr(net.IP(net.CIDRMask(inet6Address.Bits(), 128)).String()).As16(), | ||||
| 			}, | ||||
| 			Flags: IN6_IFF_NODAD | IN6_IFF_SECURED, | ||||
| 			Lifetime: addrLifetime6{ | ||||
| 				Vltime: ND6_INFINITE_LIFETIME, | ||||
| 				Pltime: ND6_INFINITE_LIFETIME, | ||||
| 			}, | ||||
| 		} | ||||
| 		if inet6Address.Bits() == 128 { | ||||
| 			ifReq6.Dstaddr = unix.RawSockaddrInet6{ | ||||
| 				Len:    unix.SizeofSockaddrInet6, | ||||
| 				Family: unix.AF_INET6, | ||||
| 				Addr:   inet6Address.Addr().Next().As16(), | ||||
| 			} | ||||
| 		} | ||||
| 		copy(ifReq6.Name[:], name) | ||||
| 		err = useSocket(unix.AF_INET6, unix.SOCK_DGRAM, 0, func(socketFd int) error { | ||||
| 			if _, _, errno := unix.Syscall( | ||||
| 				syscall.SYS_IOCTL, | ||||
| 				uintptr(socketFd), | ||||
| 				uintptr(SIOCAIFADDR_IN6), | ||||
| 				uintptr(unsafe.Pointer(&ifReq6)), | ||||
| 			); errno != 0 { | ||||
| 				return os.NewSyscallError("SIOCAIFADDR_IN6", errno) | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	if autoRoute { | ||||
| 		if inet4Address.IsValid() { | ||||
| 			for _, subnet := range []netip.Prefix{ | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 0, 0, 0}), 8), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{2, 0, 0, 0}), 7), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{4, 0, 0, 0}), 6), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{8, 0, 0, 0}), 5), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{16, 0, 0, 0}), 4), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{32, 0, 0, 0}), 3), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{64, 0, 0, 0}), 2), | ||||
| 				netip.PrefixFrom(netip.AddrFrom4([4]byte{128, 0, 0, 0}), 1), | ||||
| 			} { | ||||
| 				err = addRoute(subnet, inet4Address.Addr()) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if inet6Address.IsValid() { | ||||
| 			subnet := netip.PrefixFrom(netip.AddrFrom16([16]byte{32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}), 3) | ||||
| 			err = addRoute(subnet, inet6Address.Addr()) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func useSocket(domain, typ, proto int, block func(socketFd int) error) error { | ||||
| 	socketFd, err := unix.Socket(domain, typ, proto) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer unix.Close(socketFd) | ||||
| 	return block(socketFd) | ||||
| } | ||||
|  | ||||
| func addRoute(destination netip.Prefix, gateway netip.Addr) error { | ||||
| 	routeMessage := route.RouteMessage{ | ||||
| 		Type:    unix.RTM_ADD, | ||||
| 		Flags:   unix.RTF_UP | unix.RTF_STATIC | unix.RTF_GATEWAY, | ||||
| 		Version: unix.RTM_VERSION, | ||||
| 		Seq:     1, | ||||
| 	} | ||||
| 	if gateway.Is4() { | ||||
| 		routeMessage.Addrs = []route.Addr{ | ||||
| 			syscall.RTAX_DST:     &route.Inet4Addr{IP: destination.Addr().As4()}, | ||||
| 			syscall.RTAX_NETMASK: &route.Inet4Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 32)).String()).As4()}, | ||||
| 			syscall.RTAX_GATEWAY: &route.Inet4Addr{IP: gateway.As4()}, | ||||
| 		} | ||||
| 	} else { | ||||
| 		routeMessage.Addrs = []route.Addr{ | ||||
| 			syscall.RTAX_DST:     &route.Inet6Addr{IP: destination.Addr().As16()}, | ||||
| 			syscall.RTAX_NETMASK: &route.Inet6Addr{IP: netip.MustParseAddr(net.IP(net.CIDRMask(destination.Bits(), 128)).String()).As16()}, | ||||
| 			syscall.RTAX_GATEWAY: &route.Inet6Addr{IP: gateway.As16()}, | ||||
| 		} | ||||
| 	} | ||||
| 	request, err := routeMessage.Marshal() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return useSocket(unix.AF_ROUTE, unix.SOCK_RAW, 0, func(socketFd int) error { | ||||
| 		return common.Error(unix.Write(socketFd, request)) | ||||
| 	}) | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| //go:build no_gvisor || !(linux || windows) | ||||
| //go:build no_gvisor || !(linux || windows || darwin) | ||||
|  | ||||
| package tun | ||||
|  | ||||
|   | ||||
| @@ -308,7 +308,7 @@ func (e *WintunEndpoint) Attach(dispatcher stack.NetworkDispatcher) { | ||||
| } | ||||
|  | ||||
| func (e *WintunEndpoint) dispatchLoop() { | ||||
| 	_buffer := buf.StackNewPacket() | ||||
| 	_buffer := buf.StackNewSize(int(e.tun.mtu)) | ||||
| 	defer common.KeepAlive(_buffer) | ||||
| 	buffer := common.Dup(_buffer) | ||||
| 	defer buffer.Release() | ||||
| @@ -339,7 +339,12 @@ func (e *WintunEndpoint) dispatchLoop() { | ||||
| 		default: | ||||
| 			continue | ||||
| 		} | ||||
| 		e.dispatcher.DeliverNetworkPacket(p, pkt) | ||||
| 		dispatcher := e.dispatcher | ||||
| 		if dispatcher == nil { | ||||
| 			pkt.DecRef() | ||||
| 			return | ||||
| 		} | ||||
| 		dispatcher.DeliverNetworkPacket(p, pkt) | ||||
| 		pkt.DecRef() | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 世界
					世界