Add optional LWIP stack support

This commit is contained in:
世界 2022-08-07 17:15:29 +08:00
parent a937ff2d8d
commit 0fd822f913
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
18 changed files with 300 additions and 57 deletions

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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

View file

@ -1,4 +1,4 @@
//go:build no_gvisor && !(linux || windows || darwin)
//go:build !no_gvisor && !(linux || windows || darwin)
package tun

View file

@ -1,4 +1,4 @@
//go:build no_gvisor && (linux || windows || darwin)
//go:build no_gvisor
package tun

View file

@ -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)
}

View file

@ -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) {
}

147
lwip.go Normal file
View file

@ -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()
}

15
lwip_stub.go Normal file
View file

@ -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
}

View file

@ -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)
}
}

5
tun.go
View file

@ -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
}

View file

@ -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 {

View file

@ -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() {

View file

@ -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

View file

@ -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,
})
}

View file

@ -1,4 +1,4 @@
//go:build no_gvisor || !(linux || windows || darwin)
//go:build !(linux || windows || darwin)
package tun

View file

@ -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 {

View file

@ -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{}
}