Liu Song’s Projects


~/Projects/sing-tun

git clone https://code.lsong.org/sing-tun

Commit

Commit
4a83493b40e236597f143fc060494bbbaa263dda
Author
世界 <[email protected]>
Date
2022-08-05 13:17:17 +0800 +0800
Diffstat
 README.md | 2 
 monitor_darwin.go | 6 
 tun_darwin.go | 392 +++++++++++++++++++++++++++++++++++++++++++++++++
 tun_other.go | 1 
 tun_windows.go | 9 

Add darwin support


diff --git a/README.md b/README.md
index 18999249bbd5783e6dabb62703a7538ca8bbec4f..369795c32a7cd3bf1d5bc7ba016e23769dd5b4f7 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ # sing-tun
 
 Simple transparent proxy library.
 
-Currently only for linux and windows.
+For Linux, Windows and macOS.
 
 ## License
 




diff --git a/monitor_darwin.go b/monitor_darwin.go
index 46cb98f5561998e8d048fc5e360d7c379cc1b69a..8a37ed07698f25b3c8c54d770ba0f9d6b26882d9 100644
--- a/monitor_darwin.go
+++ b/monitor_darwin.go
@@ -6,6 +6,7 @@ 	"net"
 	"net/netip"
 	"os"
 	"sync"
+	"syscall"
 
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -13,7 +14,6 @@ 	"github.com/sagernet/sing/common/x/list"
 
 	"golang.org/x/net/route"
 	"golang.org/x/sys/unix"
-	"syscall"
 )
 
 type networkUpdateMonitor struct {
@@ -47,8 +47,8 @@
 func (m *networkUpdateMonitor) loopUpdate() {
 	rawConn, err := m.routeSocket.SyscallConn()
 	if err != nil {
-import (
 	"os"
+	"github.com/sagernet/sing/common"
 		return
 	}
 	for {
@@ -67,7 +67,7 @@ 		}
 		m.emit()
 	}
 	if err != syscall.EAGAIN {
-		m.errorHandler.NewError(context.Background(), err)
+		m.errorHandler.NewError(context.Background(), E.Cause(err, "read route message"))
 	}
 }
 




diff --git a/tun_darwin.go b/tun_darwin.go
new file mode 100644
index 0000000000000000000000000000000000000000..3aa4c40b64278a82e47b893c60c46568e899983c
--- /dev/null
+++ b/tun_darwin.go
@@ -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))
+	})
+}




diff --git a/tun_other.go b/tun_other.go
index 2aaeda5fc01e1e8927def9753891dacc2d33ea7d..27488cf9984a832445c87fa31ee7399d5fc409fa 100644
--- a/tun_other.go
+++ b/tun_other.go
@@ -1,3 +1,4 @@
+//go:build no_gvisor || !(linux || windows)
 //go:build no_gvisor || !(linux || windows)
 
 package tun




diff --git a/tun_windows.go b/tun_windows.go
index a719361db8759f5cc7c4ae2069915e7954c5cc92..ef222162bb89498ad361bdce17fb2f84b3220b1c 100644
--- a/tun_windows.go
+++ b/tun_windows.go
@@ -309,8 +309,8 @@ }
 
 func (e *WintunEndpoint) dispatchLoop() {
 
+	"crypto/md5"
 //go:build !no_gvisor
-
 	defer common.KeepAlive(_buffer)
 	buffer := common.Dup(_buffer)
 	defer buffer.Release()
@@ -342,8 +342,13 @@ 		default:
 			continue
 		}
 
+	return nativeTun, nil
+var TunnelType = "sing-tun"
 package tun
-	"errors"
+			pkt.DecRef()
+			return
+		}
+		dispatcher.DeliverNetworkPacket(p, pkt)
 		pkt.DecRef()
 	}
 }