diff --git a/go.mod b/go.mod index 4f21ead..911a68d 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/eycorsican/go-tun2socks v1.16.11 github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 - github.com/sagernet/sing v0.0.0-20220801112236-1bb95f9661fc + github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 gvisor.dev/gvisor v0.0.0-20220801010827-addd1f7b3e97 diff --git a/go.sum b/go.sum index 5f3229b..e59aeee 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805 h1:hE+vtsjBCCPmxkRz9jZA+CicHgVkDT6H+Av5ZzskVxs= github.com/sagernet/netlink v0.0.0-20220803045538-bdac49abf805/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/sing v0.0.0-20220801112236-1bb95f9661fc h1:x7H64IiqyrpxPWl/KrWkknzEK4GmpqgfZeVKFVw6E/M= -github.com/sagernet/sing v0.0.0-20220801112236-1bb95f9661fc/go.mod h1:GbtQfZSpmtD3cXeD1qX2LCMwY8dH+bnnInDTqd92IsM= +github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a h1:EeaiaHqcGiGQdgRPHf8FPIKb17VADrncz1P27Jfli2w= +github.com/sagernet/sing v0.0.0-20220807085721-583e78e0b86a/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= diff --git a/gvisor.go b/gvisor.go index 20d144a..e91810b 100644 --- a/gvisor.go +++ b/gvisor.go @@ -25,14 +25,19 @@ import ( const defaultNIC tcpip.NICID = 1 type GVisor struct { - ctx context.Context - tun GVisorTun - tunMtu uint32 - endpointIndependentNat bool - endpointIndependentNatTimeout int64 - handler Handler - stack *stack.Stack - endpoint stack.LinkEndpoint + ctx context.Context + tun GVisorTun + tunMtu uint32 + endpointIndependentNat bool + udpTimeout int64 + handler Handler + stack *stack.Stack + endpoint stack.LinkEndpoint +} + +type GVisorTun interface { + Tun + NewEndpoint() (stack.LinkEndpoint, error) } func NewGVisor( @@ -40,7 +45,7 @@ func NewGVisor( tun Tun, tunMtu uint32, endpointIndependentNat bool, - endpointIndependentNatTimeout int64, + udpTimeout int64, handler Handler, ) (Stack, error) { gTun, isGTun := tun.(GVisorTun) @@ -49,12 +54,12 @@ func NewGVisor( } return &GVisor{ - ctx: ctx, - tun: gTun, - tunMtu: tunMtu, - endpointIndependentNat: endpointIndependentNat, - endpointIndependentNatTimeout: endpointIndependentNatTimeout, - handler: handler, + ctx: ctx, + tun: gTun, + tunMtu: tunMtu, + endpointIndependentNat: endpointIndependentNat, + udpTimeout: udpTimeout, + handler: handler, }, nil } @@ -166,7 +171,7 @@ func (t *GVisor) Start() error { }() }).HandlePacket) } else { - ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, NewUDPForwarder(t.ctx, ipStack, t.handler, t.endpointIndependentNatTimeout).HandlePacket) + ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, NewUDPForwarder(t.ctx, ipStack, t.handler, t.udpTimeout).HandlePacket) } t.stack = ipStack diff --git a/gvisor_other.go b/gvisor_other.go index 0e60506..29647ab 100644 --- a/gvisor_other.go +++ b/gvisor_other.go @@ -1,4 +1,4 @@ -//go:build no_gvisor && !(linux || windows || darwin) +//go:build !no_gvisor && !(linux || windows || darwin) package tun diff --git a/gvisor_stub.go b/gvisor_stub.go index b1ff449..c2fe71d 100644 --- a/gvisor_stub.go +++ b/gvisor_stub.go @@ -1,4 +1,4 @@ -//go:build no_gvisor && (linux || windows || darwin) +//go:build no_gvisor package tun diff --git a/gvisor_tun.go b/gvisor_tun.go deleted file mode 100644 index 653e693..0000000 --- a/gvisor_tun.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !(no_gvisor || !(linux || windows || darwin)) - -package tun - -import "gvisor.dev/gvisor/pkg/tcpip/stack" - -type GVisorTun interface { - Tun - NewEndpoint() (stack.LinkEndpoint, error) -} diff --git a/gvisor_udp.go b/gvisor_udp.go index 4d9850a..82adce8 100644 --- a/gvisor_udp.go +++ b/gvisor_udp.go @@ -31,7 +31,7 @@ func NewUDPForwarder(ctx context.Context, stack *stack.Stack, handler Handler, u ctx: ctx, stack: stack, handler: handler, - udpNat: udpnat.New[netip.AddrPort](udpTimeout, nopErrorHandler{handler}), + udpNat: udpnat.New[netip.AddrPort](udpTimeout, handler), } } @@ -120,10 +120,3 @@ func (w *UDPBackWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) route.Stats().UDP.PacketsSent.Increment() return nil } - -type nopErrorHandler struct { - Handler -} - -func (h nopErrorHandler) NewError(ctx context.Context, err error) { -} diff --git a/lwip.go b/lwip.go new file mode 100644 index 0000000..2ebd1af --- /dev/null +++ b/lwip.go @@ -0,0 +1,147 @@ +//go:build with_lwip + +package tun + +import ( + "context" + "net" + "net/netip" + "os" + "runtime" + + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat" + + lwip "github.com/eycorsican/go-tun2socks/core" +) + +type LWIP struct { + ctx context.Context + tun Tun + tunMtu uint32 + udpTimeout int64 + handler Handler + stack lwip.LWIPStack + udpNat *udpnat.Service[netip.AddrPort] +} + +func NewLWIP( + ctx context.Context, + tun Tun, + tunMtu uint32, + udpTimeout int64, + handler Handler, +) (Stack, error) { + return &LWIP{ + ctx: ctx, + tun: tun, + tunMtu: tunMtu, + handler: handler, + stack: lwip.NewLWIPStack(), + udpNat: udpnat.New[netip.AddrPort](udpTimeout, handler), + }, nil +} + +func (l *LWIP) Start() error { + lwip.RegisterTCPConnHandler(l) + lwip.RegisterUDPConnHandler(l) + lwip.RegisterOutputFn(l.tun.Write) + go l.loopIn() + return nil +} + +func (l *LWIP) loopIn() { + mtu := int(l.tunMtu) + if runtime.GOOS == "darwin" { + mtu += 4 + } + _buffer := buf.StackNewSize(mtu) + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + data := buffer.FreeBytes() + for { + n, err := l.tun.Read(data) + if err != nil { + return + } + var packet []byte + if runtime.GOOS == "darwin" { + packet = data[4:n] + } else { + packet = data[:n] + } + _, err = l.stack.Write(packet) + if err != nil { + if err.Error() == "stack closed" { + return + } + l.handler.NewError(context.Background(), err) + } + } +} + +func (l *LWIP) Close() error { + lwip.RegisterTCPConnHandler(nil) + lwip.RegisterUDPConnHandler(nil) + lwip.RegisterOutputFn(func(bytes []byte) (int, error) { + return 0, os.ErrClosed + }) + return l.stack.Close() +} + +func (l *LWIP) Handle(conn net.Conn, target *net.TCPAddr) error { + lAddr := conn.LocalAddr() + rAddr := conn.RemoteAddr() + if lAddr == nil || rAddr == nil { + conn.Close() + return nil + } + go func() { + var metadata M.Metadata + metadata.Source = M.SocksaddrFromNet(lAddr) + metadata.Destination = M.SocksaddrFromNet(rAddr) + hErr := l.handler.NewConnection(l.ctx, conn, metadata) + if hErr != nil { + conn.(lwip.TCPConn).Abort() + } + }() + return nil +} + +func (l *LWIP) Connect(conn lwip.UDPConn, target *net.UDPAddr) error { + return nil +} + +func (l *LWIP) ReceiveTo(conn lwip.UDPConn, data []byte, addr *net.UDPAddr) error { + var upstreamMetadata M.Metadata + upstreamMetadata.Source = M.SocksaddrFromNet(conn.LocalAddr()) + upstreamMetadata.Destination = M.SocksaddrFromNet(addr) + + l.udpNat.NewPacket( + l.ctx, + upstreamMetadata.Source.AddrPort(), + buf.As(data).ToOwned(), + upstreamMetadata, + func(natConn N.PacketConn) N.PacketWriter { + return &LWIPUDPBackWriter{conn} + }, + ) + return nil +} + +type LWIPUDPBackWriter struct { + conn lwip.UDPConn +} + +func (w *LWIPUDPBackWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + defer buffer.Release() + return common.Error(w.conn.WriteFrom(buffer.Bytes(), destination.UDPAddr())) +} + +func (w *LWIPUDPBackWriter) Close() error { + return w.conn.Close() +} diff --git a/lwip_stub.go b/lwip_stub.go new file mode 100644 index 0000000..2259057 --- /dev/null +++ b/lwip_stub.go @@ -0,0 +1,15 @@ +//go:build !with_lwip + +package tun + +import "context" + +func NewLWIP( + ctx context.Context, + tun Tun, + tunMtu uint32, + udpTimeout int64, + handler Handler, +) (Stack, error) { + return nil, ErrLWIPNotIncluded +} diff --git a/stack.go b/stack.go index 4b690c2..d4012e7 100644 --- a/stack.go +++ b/stack.go @@ -1,12 +1,37 @@ package tun -import E "github.com/sagernet/sing/common/exceptions" +import ( + "context" + + E "github.com/sagernet/sing/common/exceptions" +) var ( ErrGVisorNotIncluded = E.New("gVisor is disabled in current build, try build without -tags `no_gvisor`") ErrGVisorUnsupported = E.New("gVisor stack is unsupported on current platform") + ErrLWIPNotIncluded = E.New("LWIP stack is disabled in current build, try build with -tags `with_lwip` and CGO_ENABLED=1") ) type Stack interface { + Start() error Close() error } + +func NewStack( + ctx context.Context, + stack string, + tun Tun, + tunMtu uint32, + endpointIndependentNat bool, + udpTimeout int64, + handler Handler, +) (Stack, error) { + switch stack { + case "gvisor", "": + return NewGVisor(ctx, tun, tunMtu, endpointIndependentNat, udpTimeout, handler) + case "lwip": + return NewLWIP(ctx, tun, tunMtu, udpTimeout, handler) + default: + return nil, E.New("unknown stack: ", stack) + } +} diff --git a/tun.go b/tun.go index c1a8225..d32fe96 100644 --- a/tun.go +++ b/tun.go @@ -1,14 +1,19 @@ package tun import ( + "io" + + E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" ) type Handler interface { N.TCPConnectionHandler N.UDPConnectionHandler + E.Handler } type Tun interface { + io.ReadWriter Close() error } diff --git a/tun_darwin.go b/tun_darwin.go index 6107899..a396bd4 100644 --- a/tun_darwin.go +++ b/tun_darwin.go @@ -5,12 +5,15 @@ import ( "net" "net/netip" "os" + "runtime" "syscall" "unsafe" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/rw" + "golang.org/x/net/ipv4" "golang.org/x/net/route" "golang.org/x/sys/unix" ) @@ -41,13 +44,45 @@ func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu return nil, err } - return &NativeTun{ + nativeTun := &NativeTun{ tunFd: uintptr(tunFd), tunFile: os.NewFile(uintptr(tunFd), "utun"), inet4Address: string(inet4Address.Addr().AsSlice()), inet6Address: string(inet6Address.Addr().AsSlice()), mtu: mtu, - }, nil + } + runtime.SetFinalizer(nativeTun.tunFile, nil) + return nativeTun, nil +} + +func (t *NativeTun) Read(p []byte) (n int, err error) { + /*n, err = t.tunFile.Read(p) + if n < 4 { + return 0, err + } + + copy(p[:], p[4:]) + return n - 4, err*/ + return t.tunFile.Write(p) +} + +var ( + packetHeader4 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET} + packetHeader6 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET6} +) + +func (t *NativeTun) Write(p []byte) (n int, err error) { + var packetHeader []byte + if p[0]>>4 == ipv4.Version { + packetHeader = packetHeader4[:] + } else { + packetHeader = packetHeader6[:] + } + _, err = rw.WriteV(t.tunFd, [][]byte{packetHeader, p}) + if err == nil { + n = len(p) + } + return } func (t *NativeTun) Close() error { diff --git a/tun_darwin_gvisor.go b/tun_darwin_gvisor.go index b22d00d..26f957c 100644 --- a/tun_darwin_gvisor.go +++ b/tun_darwin_gvisor.go @@ -7,7 +7,6 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/rw" - "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/bufferv2" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" @@ -113,11 +112,6 @@ func (e *DarwinEndpoint) ARPHardwareType() header.ARPHardwareType { func (e *DarwinEndpoint) AddHeader(buffer *stack.PacketBuffer) { } -var ( - packetHeader4 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET} - packetHeader6 = [4]byte{0x00, 0x00, 0x00, unix.AF_INET6} -) - func (e *DarwinEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) { var n int for _, packet := range packetBufferList.AsSlice() { diff --git a/tun_linux.go b/tun_linux.go index 7bd141a..17b692d 100644 --- a/tun_linux.go +++ b/tun_linux.go @@ -3,6 +3,8 @@ package tun import ( "net" "net/netip" + "os" + "runtime" "unsafe" "github.com/sagernet/netlink" @@ -14,7 +16,8 @@ import ( type NativeTun struct { name string - fd int + tunFd int + tunFile *os.File inet4Address netip.Prefix inet6Address netip.Prefix mtu uint32 @@ -32,12 +35,14 @@ func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu } nativeTun := &NativeTun{ name: name, - fd: tunFd, + tunFd: tunFd, + tunFile: os.NewFile(uintptr(tunFd), "tun"), mtu: mtu, inet4Address: inet4Address, inet6Address: inet6Address, autoRoute: autoRoute, } + runtime.SetFinalizer(nativeTun.tunFile, nil) err = nativeTun.configure(tunLink) if err != nil { return nil, E.Errors(err, unix.Close(tunFd)) @@ -45,6 +50,14 @@ func Open(name string, inet4Address netip.Prefix, inet6Address netip.Prefix, mtu return nativeTun, nil } +func (t *NativeTun) Read(p []byte) (n int, err error) { + return t.tunFile.Read(p) +} + +func (t *NativeTun) Write(p []byte) (n int, err error) { + return t.tunFile.Write(p) +} + var controlPath string func init() { @@ -131,8 +144,7 @@ func (t *NativeTun) Close() error { if t.autoRoute { errors = append(errors, t.unsetRoute()) } - errors = append(errors, unix.Close(t.fd)) - return E.Errors(errors...) + return E.Errors(append(errors, t.tunFile.Close())...) } const tunTableIndex = 2022 diff --git a/tun_linux_gvisor.go b/tun_linux_gvisor.go index 63f6324..cfd0be1 100644 --- a/tun_linux_gvisor.go +++ b/tun_linux_gvisor.go @@ -11,7 +11,7 @@ var _ GVisorTun = (*NativeTun)(nil) func (t *NativeTun) NewEndpoint() (stack.LinkEndpoint, error) { return fdbased.New(&fdbased.Options{ - FDs: []int{t.fd}, + FDs: []int{t.tunFd}, MTU: t.mtu, }) } diff --git a/tun_other.go b/tun_other.go index 27488cf..d3c147d 100644 --- a/tun_other.go +++ b/tun_other.go @@ -1,4 +1,4 @@ -//go:build no_gvisor || !(linux || windows || darwin) +//go:build !(linux || windows || darwin) package tun diff --git a/tun_windows.go b/tun_windows.go index 8c80113..9badbe2 100644 --- a/tun_windows.go +++ b/tun_windows.go @@ -172,7 +172,29 @@ retry: } } -func (t *NativeTun) Write(packetElementList [][]byte) (n int, err error) { +func (t *NativeTun) Write(p []byte) (n int, err error) { + t.running.Add(1) + defer t.running.Done() + if atomic.LoadInt32(&t.close) == 1 { + return 0, os.ErrClosed + } + t.rate.update(uint64(len(p))) + packet, err := t.session.AllocateSendPacket(len(p)) + copy(packet, p) + if err == nil { + t.session.SendPacket(packet) + return len(p), nil + } + switch err { + case windows.ERROR_HANDLE_EOF: + return 0, os.ErrClosed + case windows.ERROR_BUFFER_OVERFLOW: + return 0, nil // Dropping when ring is full. + } + return 0, fmt.Errorf("write failed: %w", err) +} + +func (t *NativeTun) write(packetElementList [][]byte) (n int, err error) { t.running.Add(1) defer t.running.Done() if atomic.LoadInt32(&t.close) == 1 { diff --git a/tun_windows_gvisor.go b/tun_windows_gvisor.go index 53754dc..6915053 100644 --- a/tun_windows_gvisor.go +++ b/tun_windows_gvisor.go @@ -77,7 +77,7 @@ func (e *WintunEndpoint) dispatchLoop() { case header.IPv6Version: networkProtocol = header.IPv6ProtocolNumber default: - e.tun.Write([][]byte{packet}) + e.tun.Write(packet) continue } pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ @@ -111,7 +111,7 @@ func (e *WintunEndpoint) AddHeader(buffer *stack.PacketBuffer) { func (e *WintunEndpoint) WritePackets(packetBufferList stack.PacketBufferList) (int, tcpip.Error) { var n int for _, packet := range packetBufferList.AsSlice() { - _, err := e.tun.Write(packet.AsSlices()) + _, err := e.tun.write(packet.AsSlices()) if err != nil { return n, &tcpip.ErrAborted{} }