瀏覽代碼

Improve read wait interface &
Refactor Authenticator interface to struct &
Update smux &
Update gVisor to 20231204.0 &
Update quic-go to v0.40.1 &
Update wireguard-go &
Add GSO support for TUN/WireGuard &
Fix router pre-start &
Fix bind forwarder to interface for systems stack

世界 1 年之前
父節點
當前提交
89c723e3e4
共有 48 個文件被更改,包括 893 次插入649 次删除
  1. 1 0
      adapter/router.go
  2. 5 4
      box.go
  3. 0 233
      common/badtls/badtls.go
  4. 0 14
      common/badtls/badtls_stub.go
  5. 0 22
      common/badtls/link.go
  6. 115 0
      common/badtls/read_wait.go
  7. 13 0
      common/badtls/read_wait_stub.go
  8. 20 7
      common/dialer/default.go
  9. 3 5
      common/dialer/dialer.go
  10. 9 0
      common/dialer/wireguard.go
  11. 11 0
      common/dialer/wireguard_control.go
  12. 9 0
      common/dialer/wiregurad_stub.go
  13. 12 1
      common/tls/client.go
  14. 13 1
      common/tls/server.go
  15. 1 0
      docs/clients/android/features.md
  16. 23 22
      docs/clients/apple/features.md
  17. 34 13
      docs/configuration/inbound/tun.md
  18. 32 10
      docs/configuration/inbound/tun.zh.md
  19. 22 3
      docs/configuration/outbound/wireguard.md
  20. 22 3
      docs/configuration/outbound/wireguard.zh.md
  21. 12 13
      docs/installation/build-from-source.md
  22. 14 16
      docs/installation/build-from-source.zh.md
  23. 11 12
      go.mod
  24. 24 27
      go.sum
  25. 1 1
      inbound/http.go
  26. 1 1
      inbound/mixed.go
  27. 1 1
      inbound/naive.go
  28. 1 1
      inbound/socks.go
  29. 2 4
      inbound/tun.go
  30. 15 14
      option/outbound.go
  31. 1 0
      option/tun.go
  32. 1 0
      option/wireguard.go
  33. 10 10
      outbound/dns.go
  34. 1 1
      outbound/proxy.go
  35. 93 121
      outbound/wireguard.go
  36. 34 21
      route/router.go
  37. 1 1
      route/rule_set_remote.go
  38. 6 6
      transport/fakeip/packet_wait.go
  39. 1 1
      transport/trojan/mux.go
  40. 4 3
      transport/trojan/protocol.go
  41. 45 0
      transport/trojan/protocol_wait.go
  42. 2 1
      transport/trojan/service.go
  43. 45 0
      transport/trojan/service_wait.go
  44. 21 28
      transport/wireguard/client_bind.go
  45. 1 1
      transport/wireguard/device_stack.go
  46. 48 22
      transport/wireguard/device_system.go
  47. 4 5
      transport/wireguard/endpoint.go
  48. 148 0
      transport/wireguard/resolve.go

+ 1 - 0
adapter/router.go

@@ -17,6 +17,7 @@ import (
 
 type Router interface {
 	Service
+	PreStarter
 	PostStarter
 
 	Outbounds() []Outbound

+ 5 - 4
box.go

@@ -259,6 +259,10 @@ func (s *Box) preStart() error {
 			}
 		}
 	}
+	err = s.router.PreStart()
+	if err != nil {
+		return E.Cause(err, "pre-start router")
+	}
 	err = s.startOutbounds()
 	if err != nil {
 		return err
@@ -313,10 +317,7 @@ func (s *Box) postStart() error {
 			}
 		}
 	}
-	err := s.router.PostStart()
-	if err != nil {
-		return E.Cause(err, "post-start router")
-	}
+
 	return s.router.PostStart()
 }
 

+ 0 - 233
common/badtls/badtls.go

@@ -1,233 +0,0 @@
-//go:build go1.20 && !go1.21
-
-package badtls
-
-import (
-	"crypto/cipher"
-	"crypto/rand"
-	"crypto/tls"
-	"encoding/binary"
-	"io"
-	"net"
-	"reflect"
-	"sync"
-	"sync/atomic"
-	"unsafe"
-
-	"github.com/sagernet/sing-box/log"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	"github.com/sagernet/sing/common/bufio"
-	E "github.com/sagernet/sing/common/exceptions"
-	N "github.com/sagernet/sing/common/network"
-	aTLS "github.com/sagernet/sing/common/tls"
-)
-
-type Conn struct {
-	*tls.Conn
-	writer              N.ExtendedWriter
-	isHandshakeComplete *atomic.Bool
-	activeCall          *atomic.Int32
-	closeNotifySent     *bool
-	version             *uint16
-	rand                io.Reader
-	halfAccess          *sync.Mutex
-	halfError           *error
-	cipher              cipher.AEAD
-	explicitNonceLen    int
-	halfPtr             uintptr
-	halfSeq             []byte
-	halfScratchBuf      []byte
-}
-
-func TryCreate(conn aTLS.Conn) aTLS.Conn {
-	tlsConn, ok := conn.(*tls.Conn)
-	if !ok {
-		return conn
-	}
-	badConn, err := Create(tlsConn)
-	if err != nil {
-		log.Warn("initialize badtls: ", err)
-		return conn
-	}
-	return badConn
-}
-
-func Create(conn *tls.Conn) (aTLS.Conn, error) {
-	rawConn := reflect.Indirect(reflect.ValueOf(conn))
-	rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
-	if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
-		return nil, E.New("badtls: invalid isHandshakeComplete")
-	}
-	isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
-	if !isHandshakeComplete.Load() {
-		return nil, E.New("handshake not finished")
-	}
-	rawActiveCall := rawConn.FieldByName("activeCall")
-	if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
-		return nil, E.New("badtls: invalid active call")
-	}
-	activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
-	rawHalfConn := rawConn.FieldByName("out")
-	if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
-		return nil, E.New("badtls: invalid half conn")
-	}
-	rawVersion := rawConn.FieldByName("vers")
-	if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
-		return nil, E.New("badtls: invalid version")
-	}
-	version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
-	rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
-	if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
-		return nil, E.New("badtls: invalid notify")
-	}
-	closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
-	rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
-	if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
-		return nil, E.New("badtls: bad config")
-	}
-	config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
-	randReader := config.Rand
-	if randReader == nil {
-		randReader = rand.Reader
-	}
-	rawHalfMutex := rawHalfConn.FieldByName("Mutex")
-	if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
-		return nil, E.New("badtls: invalid half mutex")
-	}
-	halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
-	rawHalfError := rawHalfConn.FieldByName("err")
-	if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
-		return nil, E.New("badtls: invalid half error")
-	}
-	halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
-	rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
-	if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
-		return nil, E.New("badtls: invalid cipher interface")
-	}
-	rawHalfCipher := rawHalfCipherInterface.Elem()
-	aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
-	if !loaded {
-		return nil, E.New("badtls: invalid AEAD cipher")
-	}
-	var explicitNonceLen int
-	switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
-	case "tls.prefixNonceAEAD":
-		explicitNonceLen = aeadCipher.NonceSize()
-	case "tls.xorNonceAEAD":
-	default:
-		return nil, E.New("badtls: unknown cipher type: ", cipherName)
-	}
-	rawHalfSeq := rawHalfConn.FieldByName("seq")
-	if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
-		return nil, E.New("badtls: invalid seq")
-	}
-	halfSeq := rawHalfSeq.Bytes()
-	rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
-	if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
-		return nil, E.New("badtls: invalid scratchBuf")
-	}
-	halfScratchBuf := rawHalfScratchBuf.Bytes()
-	return &Conn{
-		Conn:                conn,
-		writer:              bufio.NewExtendedWriter(conn.NetConn()),
-		isHandshakeComplete: isHandshakeComplete,
-		activeCall:          activeCall,
-		closeNotifySent:     closeNotifySent,
-		version:             version,
-		halfAccess:          halfAccess,
-		halfError:           halfError,
-		cipher:              aeadCipher,
-		explicitNonceLen:    explicitNonceLen,
-		rand:                randReader,
-		halfPtr:             rawHalfConn.UnsafeAddr(),
-		halfSeq:             halfSeq,
-		halfScratchBuf:      halfScratchBuf,
-	}, nil
-}
-
-func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
-	if buffer.Len() > maxPlaintext {
-		defer buffer.Release()
-		return common.Error(c.Write(buffer.Bytes()))
-	}
-	for {
-		x := c.activeCall.Load()
-		if x&1 != 0 {
-			return net.ErrClosed
-		}
-		if c.activeCall.CompareAndSwap(x, x+2) {
-			break
-		}
-	}
-	defer c.activeCall.Add(-2)
-	c.halfAccess.Lock()
-	defer c.halfAccess.Unlock()
-	if err := *c.halfError; err != nil {
-		return err
-	}
-	if *c.closeNotifySent {
-		return errShutdown
-	}
-	dataLen := buffer.Len()
-	dataBytes := buffer.Bytes()
-	outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
-	outBuf[0] = 23
-	version := *c.version
-	if version == 0 {
-		version = tls.VersionTLS10
-	} else if version == tls.VersionTLS13 {
-		version = tls.VersionTLS12
-	}
-	binary.BigEndian.PutUint16(outBuf[1:], version)
-	var nonce []byte
-	if c.explicitNonceLen > 0 {
-		nonce = outBuf[5 : 5+c.explicitNonceLen]
-		if c.explicitNonceLen < 16 {
-			copy(nonce, c.halfSeq)
-		} else {
-			if _, err := io.ReadFull(c.rand, nonce); err != nil {
-				return err
-			}
-		}
-	}
-	if len(nonce) == 0 {
-		nonce = c.halfSeq
-	}
-	if *c.version == tls.VersionTLS13 {
-		buffer.FreeBytes()[0] = 23
-		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
-		c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
-		buffer.Extend(1 + c.cipher.Overhead())
-	} else {
-		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
-		additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
-		additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
-		c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
-		buffer.Extend(c.cipher.Overhead())
-		binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
-	}
-	incSeq(c.halfPtr)
-	log.Trace("badtls write ", buffer.Len())
-	return c.writer.WriteBuffer(buffer)
-}
-
-func (c *Conn) FrontHeadroom() int {
-	return recordHeaderLen + c.explicitNonceLen
-}
-
-func (c *Conn) RearHeadroom() int {
-	return 1 + c.cipher.Overhead()
-}
-
-func (c *Conn) WriterMTU() int {
-	return maxPlaintext
-}
-
-func (c *Conn) Upstream() any {
-	return c.Conn
-}
-
-func (c *Conn) UpstreamWriter() any {
-	return c.NetConn()
-}

+ 0 - 14
common/badtls/badtls_stub.go

@@ -1,14 +0,0 @@
-//go:build !go1.19 || go1.21
-
-package badtls
-
-import (
-	"crypto/tls"
-	"os"
-
-	aTLS "github.com/sagernet/sing/common/tls"
-)
-
-func Create(conn *tls.Conn) (aTLS.Conn, error) {
-	return nil, os.ErrInvalid
-}

+ 0 - 22
common/badtls/link.go

@@ -1,22 +0,0 @@
-//go:build go1.20 && !go.1.21
-
-package badtls
-
-import (
-	"reflect"
-	_ "unsafe"
-)
-
-const (
-	maxPlaintext    = 16384 // maximum plaintext payload length
-	recordHeaderLen = 5     // record header length
-)
-
-//go:linkname errShutdown crypto/tls.errShutdown
-var errShutdown error
-
-//go:linkname incSeq crypto/tls.(*halfConn).incSeq
-func incSeq(conn uintptr)
-
-//go:linkname valueInterface reflect.valueInterface
-func valueInterface(v reflect.Value, safe bool) any

+ 115 - 0
common/badtls/read_wait.go

@@ -0,0 +1,115 @@
+//go:build go1.21 && !without_badtls
+
+package badtls
+
+import (
+	"bytes"
+	"os"
+	"reflect"
+	"sync"
+	"unsafe"
+
+	"github.com/sagernet/sing/common/buf"
+	E "github.com/sagernet/sing/common/exceptions"
+	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/tls"
+)
+
+var _ N.ReadWaiter = (*ReadWaitConn)(nil)
+
+type ReadWaitConn struct {
+	*tls.STDConn
+	halfAccess      *sync.Mutex
+	rawInput        *bytes.Buffer
+	input           *bytes.Reader
+	hand            *bytes.Buffer
+	readWaitOptions N.ReadWaitOptions
+}
+
+func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
+	stdConn, isSTDConn := conn.(*tls.STDConn)
+	if !isSTDConn {
+		return nil, os.ErrInvalid
+	}
+	rawConn := reflect.Indirect(reflect.ValueOf(stdConn))
+	rawHalfConn := rawConn.FieldByName("in")
+	if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid half conn")
+	}
+	rawHalfMutex := rawHalfConn.FieldByName("Mutex")
+	if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid half mutex")
+	}
+	halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
+	rawRawInput := rawConn.FieldByName("rawInput")
+	if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid raw input")
+	}
+	rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
+	rawInput0 := rawConn.FieldByName("input")
+	if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid input")
+	}
+	input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
+	rawHand := rawConn.FieldByName("hand")
+	if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
+		return nil, E.New("badtls: invalid hand")
+	}
+	hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
+	return &ReadWaitConn{
+		STDConn:    stdConn,
+		halfAccess: halfAccess,
+		rawInput:   rawInput,
+		input:      input,
+		hand:       hand,
+	}, nil
+}
+
+func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
+	c.readWaitOptions = options
+	return false
+}
+
+func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
+	err = c.Handshake()
+	if err != nil {
+		return
+	}
+	c.halfAccess.Lock()
+	defer c.halfAccess.Unlock()
+	for c.input.Len() == 0 {
+		err = tlsReadRecord(c.STDConn)
+		if err != nil {
+			return
+		}
+		for c.hand.Len() > 0 {
+			err = tlsHandlePostHandshakeMessage(c.STDConn)
+			if err != nil {
+				return
+			}
+		}
+	}
+	buffer = c.readWaitOptions.NewBuffer()
+	n, err := c.input.Read(buffer.FreeBytes())
+	if err != nil {
+		buffer.Release()
+		return
+	}
+	buffer.Truncate(n)
+
+	if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
+		// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
+		c.rawInput.Bytes()[0] == 21 {
+		_ = tlsReadRecord(c.STDConn)
+		// return n, err // will be io.EOF on closeNotify
+	}
+
+	c.readWaitOptions.PostReturn(buffer)
+	return
+}
+
+//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord
+func tlsReadRecord(c *tls.STDConn) error
+
+//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
+func tlsHandlePostHandshakeMessage(c *tls.STDConn) error

+ 13 - 0
common/badtls/read_wait_stub.go

@@ -0,0 +1,13 @@
+//go:build !go1.21 || without_badtls
+
+package badtls
+
+import (
+	"os"
+
+	"github.com/sagernet/sing/common/tls"
+)
+
+func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
+	return nil, os.ErrInvalid
+}

+ 20 - 7
common/dialer/default.go

@@ -15,14 +15,17 @@ import (
 	N "github.com/sagernet/sing/common/network"
 )
 
+var _ WireGuardListener = (*DefaultDialer)(nil)
+
 type DefaultDialer struct {
-	dialer4     tcpDialer
-	dialer6     tcpDialer
-	udpDialer4  net.Dialer
-	udpDialer6  net.Dialer
-	udpListener net.ListenConfig
-	udpAddr4    string
-	udpAddr6    string
+	dialer4             tcpDialer
+	dialer6             tcpDialer
+	udpDialer4          net.Dialer
+	udpDialer6          net.Dialer
+	udpListener         net.ListenConfig
+	udpAddr4            string
+	udpAddr6            string
+	isWireGuardListener bool
 }
 
 func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
@@ -98,6 +101,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
 		}
 		setMultiPathTCP(&dialer4)
 	}
+	if options.IsWireGuardListener {
+		for _, controlFn := range wgControlFns {
+			listener.Control = control.Append(listener.Control, controlFn)
+		}
+	}
 	tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
 	if err != nil {
 		return nil, err
@@ -114,6 +122,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
 		listener,
 		udpAddr4,
 		udpAddr6,
+		options.IsWireGuardListener,
 	}, nil
 }
 
@@ -146,6 +155,10 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
 	}
 }
 
+func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
+	return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
+}
+
 func trackConn(conn net.Conn, err error) (net.Conn, error) {
 	if !conntrack.Enabled || err != nil {
 		return conn, err

+ 3 - 5
common/dialer/dialer.go

@@ -6,15 +6,13 @@ import (
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-dns"
-	"github.com/sagernet/sing/common"
 	N "github.com/sagernet/sing/common/network"
 )
 
-func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
-	return common.Must1(New(router, options))
-}
-
 func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
+	if options.IsWireGuardListener {
+		return NewDefault(router, options)
+	}
 	var (
 		dialer N.Dialer
 		err    error

+ 9 - 0
common/dialer/wireguard.go

@@ -0,0 +1,9 @@
+package dialer
+
+import (
+	"net"
+)
+
+type WireGuardListener interface {
+	ListenPacketCompat(network, address string) (net.PacketConn, error)
+}

+ 11 - 0
common/dialer/wireguard_control.go

@@ -0,0 +1,11 @@
+//go:build with_wireguard
+
+package dialer
+
+import (
+	"github.com/sagernet/wireguard-go/conn"
+)
+
+var _ WireGuardListener = (conn.Listener)(nil)
+
+var wgControlFns = conn.ControlFns

+ 9 - 0
common/dialer/wiregurad_stub.go

@@ -0,0 +1,9 @@
+//go:build !with_wireguard
+
+package dialer
+
+import (
+	"github.com/sagernet/sing/common/control"
+)
+
+var wgControlFns []control.Func

+ 12 - 1
common/tls/client.go

@@ -6,6 +6,7 @@ import (
 	"os"
 
 	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/common/badtls"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	M "github.com/sagernet/sing/common/metadata"
@@ -42,7 +43,17 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
 func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
 	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
 	defer cancel()
-	return aTLS.ClientHandshake(ctx, conn, config)
+	tlsConn, err := aTLS.ClientHandshake(ctx, conn, config)
+	if err != nil {
+		return nil, err
+	}
+	readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
+	if err == nil {
+		return readWaitConn, nil
+	} else if err != os.ErrInvalid {
+		return nil, err
+	}
+	return tlsConn, nil
 }
 
 type Dialer struct {

+ 13 - 1
common/tls/server.go

@@ -3,7 +3,9 @@ package tls
 import (
 	"context"
 	"net"
+	"os"
 
+	"github.com/sagernet/sing-box/common/badtls"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
@@ -26,5 +28,15 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
 func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
 	ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
 	defer cancel()
-	return aTLS.ServerHandshake(ctx, conn, config)
+	tlsConn, err := aTLS.ServerHandshake(ctx, conn, config)
+	if err != nil {
+		return nil, err
+	}
+	readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
+	if err == nil {
+		return readWaitConn, nil
+	} else if err != os.ErrInvalid {
+		return nil, err
+	}
+	return tlsConn, nil
 }

+ 1 - 0
docs/clients/android/features.md

@@ -18,6 +18,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
 | `inet4_address`               | :material-check: | /                  |
 | `inet6_address`               | :material-check: | /                  |
 | `mtu`                         | :material-check: | /                  |
+| `gso`                         | :material-close: | No permission      |
 | `auto_route`                  | :material-check: | /                  |
 | `strict_route`                | :material-close: | Not implemented    |
 | `inet4_route_address`         | :material-check: | /                  |

+ 23 - 22
docs/clients/apple/features.md

@@ -14,28 +14,29 @@ SFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application
 
 SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.
 
-| TUN inbound option            | Available | Note              |
-|-------------------------------|-----------|-------------------|
-| `interface_name`              | ✖️        | Managed by Darwin |
-| `inet4_address`               | ✔️        | /                 |
-| `inet6_address`               | ✔️        | /                 |
-| `mtu`                         | ✔️        | /                 |
-| `auto_route`                  | ✔️        | /                 |
-| `strict_route`                | ✖️        | Not implemented   |
-| `inet4_route_address`         | ✔️        | /                 |
-| `inet6_route_address`         | ✔️        | /                 |
-| `inet4_route_exclude_address` | ✔️        | /                 |
-| `inet6_route_exclude_address` | ✔️        | /                 |
-| `endpoint_independent_nat`    | ✔️        | /                 |
-| `stack`                       | ✔️        | /                 |
-| `include_interface`           | ✖️        | Not implemented   |
-| `exclude_interface`           | ✖️        | Not implemented   |
-| `include_uid`                 | ✖️        | Not implemented   |
-| `exclude_uid`                 | ✖️        | Not implemented   |
-| `include_android_user`        | ✖️        | Not implemented   |
-| `include_package`             | ✖️        | Not implemented   |
-| `exclude_package`             | ✖️        | Not implemented   |
-| `platform`                    | ✔️        | /                 |
+| TUN inbound option            | Available         | Note              |
+|-------------------------------|-------------------|-------------------|
+| `interface_name`              | :material-close:️ | Managed by Darwin |
+| `inet4_address`               | :material-check:  | /                 |
+| `inet6_address`               | :material-check:  | /                 |
+| `mtu`                         | :material-check:  | /                 |
+| `gso`                         | :material-close:  | Not implemented   |
+| `auto_route`                  | :material-check:  | /                 |
+| `strict_route`                | :material-close:️ | Not implemented   |
+| `inet4_route_address`         | :material-check:  | /                 |
+| `inet6_route_address`         | :material-check:  | /                 |
+| `inet4_route_exclude_address` | :material-check:  | /                 |
+| `inet6_route_exclude_address` | :material-check:  | /                 |
+| `endpoint_independent_nat`    | :material-check:  | /                 |
+| `stack`                       | :material-check:  | /                 |
+| `include_interface`           | :material-close:️ | Not implemented   |
+| `exclude_interface`           | :material-close:️ | Not implemented   |
+| `include_uid`                 | :material-close:️ | Not implemented   |
+| `exclude_uid`                 | :material-close:️ | Not implemented   |
+| `include_android_user`        | :material-close:️ | Not implemented   |
+| `include_package`             | :material-close:️ | Not implemented   |
+| `exclude_package`             | :material-close:️ | Not implemented   |
+| `platform`                    | :material-check:  | /                 |
 
 | Route/DNS rule option | Available        | Note                  |
 |-----------------------|------------------|-----------------------|

+ 34 - 13
docs/configuration/inbound/tun.md

@@ -1,3 +1,12 @@
+---
+icon: material/alert-decagram
+---
+
+!!! quote "Changes in sing-box 1.8.0"
+
+    :material-plus: [gso](#gso)  
+    :material-alert-decagram: [stack](#stack)
+
 !!! quote ""
 
     Only supported on Linux, Windows and macOS.
@@ -12,6 +21,7 @@
   "inet4_address": "172.19.0.1/30",
   "inet6_address": "fdfe:dcba:9876::1/126",
   "mtu": 9000,
+  "gso": false,
   "auto_route": true,
   "strict_route": true,
   "inet4_route_address": [
@@ -99,6 +109,16 @@ IPv6 prefix for the tun interface.
 
 The maximum transmission unit.
 
+#### gso
+
+!!! question "Since sing-box 1.8.0"
+
+!!! quote ""
+
+    Only supported on Linux.
+
+Enable generic segmentation offload.
+
 #### auto_route
 
 Set the default route to the Tun.
@@ -161,18 +181,19 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
 
 #### stack
 
-TCP/IP stack.
+!!! quote "Changes in sing-box 1.8.0"
 
-| Stack  | Description                                                                      | Status            |
-|--------|----------------------------------------------------------------------------------|-------------------|
-| system | Sometimes better performance                                                     | recommended       |
-| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended       |
-| mixed  | Mixed `system` TCP stack and `gVisor` UDP stack                                  | recommended       |
-| LWIP   | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks)   | upstream archived |
+    :material-delete-alert: The legacy LWIP stack has been deprecated and removed.
 
-!!! warning ""
+TCP/IP stack.
+
+| Stack    | Description                                                                                           | 
+|----------|-------------------------------------------------------------------------------------------------------|
+| `system` | Perform L3 to L4 translation using the system network stack                                           |
+| `gvisor` | Perform L3 to L4 translation using [gVisor](https://github.com/google/gvisor)'s virtual network stack |
+| `mixed`  | Mixed `system` TCP stack and `gvisor` UDP stack                                                       |
 
-    LWIP stacks is not included by default, see [Installation](/installation/build-from-source/#build-tags).
+Defaults to the `mixed` stack if the gVisor build tag is enabled, otherwise defaults to the `system` stack.
 
 #### include_interface
 
@@ -218,10 +239,10 @@ Exclude users in route, but in range.
 
 Limit android users in route.
 
-| Common user  | ID  |
-|--------------|-----|
-| Main         | 0   |
-| Work Profile | 10  |
+| Common user  | ID |
+|--------------|----|
+| Main         | 0  |
+| Work Profile | 10 |
 
 #### include_package
 

+ 32 - 10
docs/configuration/inbound/tun.zh.md

@@ -1,3 +1,12 @@
+---
+icon: material/alert-decagram
+---
+
+!!! quote "sing-box 1.8.0 中的更改"
+
+    :material-plus: [gso](#gso)  
+    :material-alert-decagram: [stack](#stack)
+
 !!! quote ""
 
     仅支持 Linux、Windows 和 macOS。
@@ -12,6 +21,7 @@
   "inet4_address": "172.19.0.1/30",
   "inet6_address": "fdfe:dcba:9876::1/126",
   "mtu": 9000,
+  "gso": false,
   "auto_route": true,
   "strict_route": true,
   "inet4_route_address": [
@@ -99,6 +109,16 @@ tun 接口的 IPv6 前缀。
 
 最大传输单元。
 
+#### gso
+
+!!! question "自 sing-box 1.8.0 起"
+
+!!! quote ""
+
+    仅支持 Linux。
+
+启用通用分段卸载。
+
 #### auto_route
 
 设置到 Tun 的默认路由。
@@ -158,17 +178,19 @@ UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
 
 #### stack
 
-TCP/IP 栈。
+!!! quote "sing-box 1.8.0 中的更改"
 
-| 栈           | 描述                                                                       | 状态    |
-|-------------|--------------------------------------------------------------------------|-------|
-| system (默认) | 有时性能更好                                                                   | 推荐    |
-| gVisor      | 兼容性较好,基于 [google/gvisor](https://github.com/google/gvisor)               | 推荐    |
-| LWIP        | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 |
+    :material-delete-alert: 旧的 LWIP 栈已被弃用并移除。
 
-!!! warning ""
+TCP/IP 栈。
+
+| 栈      | 描述                                                               |
+|--------|------------------------------------------------------------------|
+| system | 基于系统网络栈执行 L3 到 L4 转换                                             |
+| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
+| mixed  | 混合 `system` TCP 栈与 `gvisor` UDP 栈                                |
 
-    默认安装不包含 LWIP 栈,参阅 [安装](/zh/installation/build-from-source/#_5)。
+默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈
 
 #### include_interface
 
@@ -215,8 +237,8 @@ TCP/IP 栈。
 限制被路由的 Android 用户。
 
 | 常用用户 | ID |
-|--|-----|
-| 您 | 0 |
+|------|----|
+| 您    | 0  |
 | 工作资料 | 10 |
 
 #### include_package

+ 22 - 3
docs/configuration/outbound/wireguard.md

@@ -1,3 +1,11 @@
+---
+icon: material/new-box
+---
+
+!!! quote "Changes in sing-box 1.8.0"
+    
+    :material-plus: [gso](#gso)  
+
 ### Structure
 
 ```json
@@ -8,6 +16,7 @@
   "server": "127.0.0.1",
   "server_port": 1080,
   "system_interface": false,
+  "gso": false,
   "interface_name": "wg0",
   "local_address": [
     "10.0.0.2/32"
@@ -52,15 +61,25 @@ The server port.
 
 #### system_interface
 
-Use system tun support.
+Use system interface.
 
-Requires privilege and cannot conflict with system interfaces.
+Requires privilege and cannot conflict with exists system interfaces.
 
 Forced if gVisor not included in the build.
 
 #### interface_name
 
-Custom device name when `system_interface` enabled.
+Custom interface name for system interface.
+
+#### gso
+
+!!! question "Since sing-box 1.8.0"
+
+!!! quote ""
+
+    Only supported on Linux.
+
+Try to enable generic segmentation offload.
 
 #### local_address
 

+ 22 - 3
docs/configuration/outbound/wireguard.zh.md

@@ -1,3 +1,11 @@
+---
+icon: material/new-box
+---
+
+!!! quote "sing-box 1.8.0 中的更改"
+
+    :material-plus: [gso](#gso)  
+
 ### 结构
 
 ```json
@@ -8,6 +16,7 @@
   "server": "127.0.0.1",
   "server_port": 1080,
   "system_interface": false,
+  "gso": false,
   "interface_name": "wg0",
   "local_address": [
     "10.0.0.2/32"
@@ -40,15 +49,25 @@
 
 #### system_interface
 
-使用系统 tun 支持
+使用系统设备
 
-需要特权且不能与系统接口冲突。
+需要特权且不能与已有系统接口冲突。
 
 如果 gVisor 未包含在构建中,则强制执行。
 
 #### interface_name
 
-启用 `system_interface` 时的自定义设备名称。
+为系统接口自定义设备名称。
+
+#### gso
+
+!!! question "自 sing-box 1.8.0 起"
+
+!!! quote ""
+
+    仅支持 Linux。
+
+尝试启用通用分段卸载。
 
 #### local_address
 

+ 12 - 13
docs/installation/build-from-source.md

@@ -55,18 +55,17 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
 
 | Build Tag                          | Enabled by default | Description                                                                                                                                                                                                                                                                                                                    |
 |------------------------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `with_quic`                        | ✔                  | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
-| `with_grpc`                        | ✖️                 | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                      |
-| `with_dhcp`                        | ✔                  | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/).                                                                                                                                                                                                                                                 |
-| `with_wireguard`                   | ✔                  | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/).                                                                                                                                                                                                                                    |
-| `with_ech`                         | ✔                  | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech).                                                                                                                                                                                                                               |
-| `with_utls`                        | ✔                  | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls).                                                                                                                                                                                          |
-| `with_reality_server`              | ✔                  | Build with reality TLS server support,  see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                                 |
-| `with_acme`                        | ✔                  | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                         |
-| `with_clash_api`                   | ✔                  | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                                |
-| `with_v2ray_api`                   | ✖️                 | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                                |
-| `with_gvisor`                      | ✔                  | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface).                                                                                                                                                                   |
-| `with_embedded_tor` (CGO required) | ✖️                 | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/).                                                                                                                                                                                                                                             |
-| `with_lwip` (CGO required)         | ✖️                 | Build with LWIP Tun stack support, see [Tun inbound](/configuration/inbound/tun#stack).                                                                                                                                                                                                                                        |
+| `with_quic`                        | :material-check:   | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
+| `with_grpc`                        | :material-close:️                 | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                      |
+| `with_dhcp`                        | :material-check:   | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/).                                                                                                                                                                                                                                                 |
+| `with_wireguard`                   | :material-check:   | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/).                                                                                                                                                                                                                                    |
+| `with_ech`                         | :material-check:                  | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech).                                                                                                                                                                                                                               |
+| `with_utls`                        | :material-check:                  | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls).                                                                                                                                                                                          |
+| `with_reality_server`              | :material-check:   | Build with reality TLS server support,  see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                                 |
+| `with_acme`                        | :material-check:   | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                         |
+| `with_clash_api`                   | :material-check:                  | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                                |
+| `with_v2ray_api`                   | :material-close:️                 | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                                |
+| `with_gvisor`                      | :material-check:                  | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface).                                                                                                                                                                   |
+| `with_embedded_tor` (CGO required) | :material-close:️  | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/).                                                                                                                                                                                                                                             |
 
 It is not recommended to change the default build tag list unless you really know what you are adding.

+ 14 - 16
docs/installation/build-from-source.zh.md

@@ -53,21 +53,19 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
 
 ## :material-folder-settings: 构建标记
 
-| 构建标记                               | 默认启动 | 说明                                                                                                                                                                                                                                                                                                                         |
-|------------------------------------|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `with_quic`                        | ✔    | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
-| `with_grpc`                        | ✖️   | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                  |
-| `with_dhcp`                        | ✔    | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/).                                                                                                                                                                                                                                              |
-| `with_wireguard`                   | ✔    | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/).                                                                                                                                                                                                                                 |
-| `with_ech`                         | ✔    | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech).                                                                                                                                                                                                                           |
-| `with_utls`                        | ✔    | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls).                                                                                                                                                                                      |
-| `with_reality_server`              | ✔    | Build with reality TLS server support,  see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                              |
-| `with_acme`                        | ✔    | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                      |
-| `with_clash_api`                   | ✔    | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                            |
-| `with_v2ray_api`                   | ✖️   | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                            |
-| `with_gvisor`                      | ✔    | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface).                                                                                                                                                               |
-| `with_embedded_tor` (CGO required) | ✖️   | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/).                                                                                                                                                                                                                                          |
-| `with_lwip` (CGO required)         | ✖️   | Build with LWIP Tun stack support, see [Tun inbound](/configuration/inbound/tun#stack).                                                                                                                                                                                                                                    |
-
+| 构建标记                               | 默认启动              | 说明                                                                                                                                                                                                                                                                                                                         |
+|------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `with_quic`                        | :material-check:  | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server/), [Naive inbound](/configuration/inbound/naive/), [Hysteria Inbound](/configuration/inbound/hysteria/), [Hysteria Outbound](/configuration/outbound/hysteria/) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
+| `with_grpc`                        | :material-close:️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc).                                                                                                                                                                                                                  |
+| `with_dhcp`                        | :material-check:  | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server/).                                                                                                                                                                                                                                              |
+| `with_wireguard`                   | :material-check:  | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard/).                                                                                                                                                                                                                                 |
+| `with_ech`                         | :material-check:  | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech).                                                                                                                                                                                                                           |
+| `with_utls`                        | :material-check:  | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls).                                                                                                                                                                                      |
+| `with_reality_server`              | :material-check:  | Build with reality TLS server support,  see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                              |
+| `with_acme`                        | :material-check:  | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls/).                                                                                                                                                                                                                                      |
+| `with_clash_api`                   | :material-check:  | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields).                                                                                                                                                                                                                            |
+| `with_v2ray_api`                   | :material-close:️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields).                                                                                                                                                                                                                            |
+| `with_gvisor`                      | :material-check:  | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface).                                                                                                                                                               |
+| `with_embedded_tor` (CGO required) | :material-close:️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor/).                                                                                                                                                                                                                                          |
 
 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。

+ 11 - 12
go.mod

@@ -23,22 +23,22 @@ require (
 	github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
 	github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
 	github.com/sagernet/gomobile v0.1.1
-	github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930
-	github.com/sagernet/quic-go v0.40.0
+	github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e
+	github.com/sagernet/quic-go v0.40.1-beta.2
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
-	github.com/sagernet/sing v0.3.0-rc.4
+	github.com/sagernet/sing v0.3.0-rc.7
 	github.com/sagernet/sing-dns v0.1.12
-	github.com/sagernet/sing-mux v0.1.7
-	github.com/sagernet/sing-quic v0.1.6
+	github.com/sagernet/sing-mux v0.1.8-rc.1
+	github.com/sagernet/sing-quic v0.1.7-rc.2
 	github.com/sagernet/sing-shadowsocks v0.2.6
-	github.com/sagernet/sing-shadowsocks2 v0.1.5
+	github.com/sagernet/sing-shadowsocks2 v0.1.6-rc.1
 	github.com/sagernet/sing-shadowtls v0.1.4
-	github.com/sagernet/sing-tun v0.1.24
+	github.com/sagernet/sing-tun v0.2.0-rc.1
 	github.com/sagernet/sing-vmess v0.1.8
-	github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
+	github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
 	github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
 	github.com/sagernet/utls v1.5.4
-	github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f
+	github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8
 	github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
 	github.com/spf13/cobra v1.8.0
 	github.com/stretchr/testify v1.8.4
@@ -79,7 +79,6 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/quic-go/qpack v0.4.0 // indirect
 	github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
-	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -87,10 +86,10 @@ require (
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect
+	golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect
 	golang.org/x/mod v0.14.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.4.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.16.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect

+ 24 - 27
go.sum

@@ -98,46 +98,43 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
-github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
-github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
 github.com/sagernet/gomobile v0.1.1 h1:3vihRGyUfFTToHMeeak0UK6/ldt2MV2bcWKFi2VyECU=
 github.com/sagernet/gomobile v0.1.1/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
-github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930 h1:dSPgjIw0CT6ISLeEh8Q20dZMBMFCcEceo23+LncRcNQ=
-github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930/go.mod h1:JpKHkOYgh4wLwrX2BhH3ZIvCvazCkTnPeEcmigZJfHY=
+github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE=
+github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
-github.com/sagernet/quic-go v0.40.0 h1:DvQNPb72lzvNQDe9tcUyHTw8eRv6PLtM2mNYmdlzUMo=
-github.com/sagernet/quic-go v0.40.0/go.mod h1:VqtdhlbkeeG5Okhb3eDMb/9o0EoglReHunNT9ukrJAI=
+github.com/sagernet/quic-go v0.40.1-beta.2 h1:USRwm36XuAFdcrmv4vDRD+YUOO08DfvLNruXThrVHZU=
+github.com/sagernet/quic-go v0.40.1-beta.2/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4DwGbNl9UQrU=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
-github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
-github.com/sagernet/sing v0.3.0-rc.4 h1:1Til9jN0AnTPB9iiX/MbFrocbRCOXDsdZ/io1IjVWkg=
-github.com/sagernet/sing v0.3.0-rc.4/go.mod h1:Ce5LNojQOgOiWhiD8pPD6E9H7e2KgtOe3Zxx4Ou5u80=
+github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
+github.com/sagernet/sing v0.3.0-rc.7 h1:FmnzFRYC6usVgWf112cUxiexwvL+iAurKmCL4Axa9+A=
+github.com/sagernet/sing v0.3.0-rc.7/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g=
 github.com/sagernet/sing-dns v0.1.12 h1:1HqZ+ln+Rezx/aJMStaS0d7oPeX2EobSV1NT537kyj4=
 github.com/sagernet/sing-dns v0.1.12/go.mod h1:rx/DTOisneQpCgNQ4jbFU/JNEtnz0lYcHXenlVzpjEU=
-github.com/sagernet/sing-mux v0.1.7 h1:+48spVReBwIrv6ZdUujiRFCCnblZFwxmbPgrs5zezlI=
-github.com/sagernet/sing-mux v0.1.7/go.mod h1:UmcVSPrVjsOGe95jDXmGgOyKKIXOcjz6FKbFy+0LeDU=
-github.com/sagernet/sing-quic v0.1.6 h1:yNkZiNOlmEGpS+A7I4/Zavhe/fRrLz7yCO/dVMZzt+k=
-github.com/sagernet/sing-quic v0.1.6/go.mod h1:g1Ogcy2KSwKvC7eDXEUu9AnHbjotC+2xsSP+A1i/VOA=
+github.com/sagernet/sing-mux v0.1.8-rc.1 h1:5dsZgWmNr9W6JzQj4fb3xX2pMP0OyJH6kVtlqc2kFKA=
+github.com/sagernet/sing-mux v0.1.8-rc.1/go.mod h1:KK5zCbNujj5kn36G+wLFROOXyJhaaXLyaZWY2w7kBNQ=
+github.com/sagernet/sing-quic v0.1.7-rc.2 h1:rCWhtvzQwgkWbX4sVHYdNwzyPweoUPEgBCBatywHjMs=
+github.com/sagernet/sing-quic v0.1.7-rc.2/go.mod h1:IbKCPWXP13zd3cdu0rirtYjkMlquc5zWtc3avfSUGAw=
 github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
 github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
-github.com/sagernet/sing-shadowsocks2 v0.1.5 h1:JDeAJ4ZWlYZ7F6qEVdDKPhQEangxKw/JtmU+i/YfCYE=
-github.com/sagernet/sing-shadowsocks2 v0.1.5/go.mod h1:KF65y8lI5PGHyMgRZGYXYsH9ilgRc/yr+NYbSNGuBm4=
+github.com/sagernet/sing-shadowsocks2 v0.1.6-rc.1 h1:E+8OyyVg0YfFNUmxMx9jYBEhjLYMQSAMzJrUmE934bo=
+github.com/sagernet/sing-shadowsocks2 v0.1.6-rc.1/go.mod h1:wFkU7sKxyZADS/idtJqBhtc+QBf5iwX9nZO7ymcn6MM=
 github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
 github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
-github.com/sagernet/sing-tun v0.1.24 h1:cxn8lr8uHMLB1tLU0SzBPE1Q04pG0Fb71GyeeCuic5Q=
-github.com/sagernet/sing-tun v0.1.24/go.mod h1:Mnd7+8iGNb9uGnMAh3bp0ZA+nPFBZNaMHZPMEGdAQJM=
+github.com/sagernet/sing-tun v0.2.0-rc.1 h1:CnlxRgrJKAMKYNuJOcKie6TjRz8wremEq1wndLup7cA=
+github.com/sagernet/sing-tun v0.2.0-rc.1/go.mod h1:hpbL9jNAbYT9G2EHCpCXVIgSrM/2Wgnrm/Hped+8zdY=
 github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
 github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
-github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
-github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
+github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
+github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
 github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI=
 github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY=
 github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
 github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
-github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
-github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
+github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs=
+github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=
 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
@@ -173,8 +170,8 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
-golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
+golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
+golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -187,10 +184,10 @@ golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
 golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -199,8 +196,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
-golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
 golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=

+ 1 - 1
inbound/http.go

@@ -26,7 +26,7 @@ var (
 
 type HTTP struct {
 	myInboundAdapter
-	authenticator auth.Authenticator
+	authenticator *auth.Authenticator
 	tlsConfig     tls.ServerConfig
 }
 

+ 1 - 1
inbound/mixed.go

@@ -29,7 +29,7 @@ var (
 
 type Mixed struct {
 	myInboundAdapter
-	authenticator auth.Authenticator
+	authenticator *auth.Authenticator
 }
 
 func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed {

+ 1 - 1
inbound/naive.go

@@ -32,7 +32,7 @@ var _ adapter.Inbound = (*Naive)(nil)
 
 type Naive struct {
 	myInboundAdapter
-	authenticator auth.Authenticator
+	authenticator *auth.Authenticator
 	tlsConfig     tls.ServerConfig
 	httpServer    *http.Server
 	h3Server      any

+ 1 - 1
inbound/socks.go

@@ -22,7 +22,7 @@ var (
 
 type Socks struct {
 	myInboundAdapter
-	authenticator auth.Authenticator
+	authenticator *auth.Authenticator
 }
 
 func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks {

+ 2 - 4
inbound/tun.go

@@ -75,6 +75,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
 		tunOptions: tun.Options{
 			Name:                     options.InterfaceName,
 			MTU:                      tunMTU,
+			GSO:                      options.GSO,
 			Inet4Address:             options.Inet4Address,
 			Inet6Address:             options.Inet6Address,
 			AutoRoute:                options.AutoRoute,
@@ -168,10 +169,7 @@ func (t *Tun) Start() error {
 	t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
 		Context:                t.ctx,
 		Tun:                    tunInterface,
-		MTU:                    t.tunOptions.MTU,
-		Name:                   t.tunOptions.Name,
-		Inet4Address:           t.tunOptions.Inet4Address,
-		Inet6Address:           t.tunOptions.Inet6Address,
+		TunOptions:             t.tunOptions,
 		EndpointIndependentNat: t.endpointIndependentNat,
 		UDPTimeout:             t.udpTimeout,
 		Handler:                t,

+ 15 - 14
option/outbound.go

@@ -108,20 +108,21 @@ type DialerOptionsWrapper interface {
 }
 
 type DialerOptions struct {
-	Detour             string         `json:"detour,omitempty"`
-	BindInterface      string         `json:"bind_interface,omitempty"`
-	Inet4BindAddress   *ListenAddress `json:"inet4_bind_address,omitempty"`
-	Inet6BindAddress   *ListenAddress `json:"inet6_bind_address,omitempty"`
-	ProtectPath        string         `json:"protect_path,omitempty"`
-	RoutingMark        int            `json:"routing_mark,omitempty"`
-	ReuseAddr          bool           `json:"reuse_addr,omitempty"`
-	ConnectTimeout     Duration       `json:"connect_timeout,omitempty"`
-	TCPFastOpen        bool           `json:"tcp_fast_open,omitempty"`
-	TCPMultiPath       bool           `json:"tcp_multi_path,omitempty"`
-	UDPFragment        *bool          `json:"udp_fragment,omitempty"`
-	UDPFragmentDefault bool           `json:"-"`
-	DomainStrategy     DomainStrategy `json:"domain_strategy,omitempty"`
-	FallbackDelay      Duration       `json:"fallback_delay,omitempty"`
+	Detour              string         `json:"detour,omitempty"`
+	BindInterface       string         `json:"bind_interface,omitempty"`
+	Inet4BindAddress    *ListenAddress `json:"inet4_bind_address,omitempty"`
+	Inet6BindAddress    *ListenAddress `json:"inet6_bind_address,omitempty"`
+	ProtectPath         string         `json:"protect_path,omitempty"`
+	RoutingMark         int            `json:"routing_mark,omitempty"`
+	ReuseAddr           bool           `json:"reuse_addr,omitempty"`
+	ConnectTimeout      Duration       `json:"connect_timeout,omitempty"`
+	TCPFastOpen         bool           `json:"tcp_fast_open,omitempty"`
+	TCPMultiPath        bool           `json:"tcp_multi_path,omitempty"`
+	UDPFragment         *bool          `json:"udp_fragment,omitempty"`
+	UDPFragmentDefault  bool           `json:"-"`
+	DomainStrategy      DomainStrategy `json:"domain_strategy,omitempty"`
+	FallbackDelay       Duration       `json:"fallback_delay,omitempty"`
+	IsWireGuardListener bool           `json:"-"`
 }
 
 func (o *DialerOptions) TakeDialerOptions() DialerOptions {

+ 1 - 0
option/tun.go

@@ -5,6 +5,7 @@ import "net/netip"
 type TunInboundOptions struct {
 	InterfaceName            string                 `json:"interface_name,omitempty"`
 	MTU                      uint32                 `json:"mtu,omitempty"`
+	GSO                      bool                   `json:"gso,omitempty"`
 	Inet4Address             Listable[netip.Prefix] `json:"inet4_address,omitempty"`
 	Inet6Address             Listable[netip.Prefix] `json:"inet6_address,omitempty"`
 	AutoRoute                bool                   `json:"auto_route,omitempty"`

+ 1 - 0
option/wireguard.go

@@ -5,6 +5,7 @@ import "net/netip"
 type WireGuardOutboundOptions struct {
 	DialerOptions
 	SystemInterface bool                   `json:"system_interface,omitempty"`
+	GSO             bool                   `json:"gso,omitempty"`
 	InterfaceName   string                 `json:"interface_name,omitempty"`
 	LocalAddress    Listable[netip.Prefix] `json:"local_address"`
 	PrivateKey      string                 `json:"private_key"`

+ 10 - 10
outbound/dns.go

@@ -111,6 +111,9 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
 			}
 		}
 		if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created {
+			readWaiter.InitializeReadWaiter(N.ReadWaitOptions{
+				MTU: dns.FixedPacketSize,
+			})
 			return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata)
 		}
 		break
@@ -193,15 +196,13 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa
 	timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
 	var group task.Group
 	group.Append0(func(ctx context.Context) error {
-		var buffer *buf.Buffer
-		readWaiter.InitializeReadWaiter(func() *buf.Buffer {
-			return buf.NewSize(dns.FixedPacketSize)
-		})
-		defer readWaiter.InitializeReadWaiter(nil)
 		for {
-			var message mDNS.Msg
-			var destination M.Socksaddr
-			var err error
+			var (
+				message     mDNS.Msg
+				destination M.Socksaddr
+				err         error
+				buffer      *buf.Buffer
+			)
 			if len(cached) > 0 {
 				packet := cached[0]
 				cached = cached[1:]
@@ -216,9 +217,8 @@ func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWa
 				}
 				destination = packet.Destination
 			} else {
-				destination, err = readWaiter.WaitReadPacket()
+				buffer, destination, err = readWaiter.WaitReadPacket()
 				if err != nil {
-					buffer.Release()
 					cancel(err)
 					return err
 				}

+ 1 - 1
outbound/proxy.go

@@ -30,7 +30,7 @@ type ProxyListener struct {
 	tcpListener   *net.TCPListener
 	username      string
 	password      string
-	authenticator auth.Authenticator
+	authenticator *auth.Authenticator
 }
 
 func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Dialer) *ProxyListener {

+ 93 - 121
outbound/wireguard.go

@@ -8,6 +8,7 @@ import (
 	"encoding/hex"
 	"fmt"
 	"net"
+	"net/netip"
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -18,10 +19,12 @@ import (
 	"github.com/sagernet/sing-box/transport/wireguard"
 	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing-tun"
-	"github.com/sagernet/sing/common/debug"
 	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/x/list"
+	"github.com/sagernet/sing/service/pause"
+	"github.com/sagernet/wireguard-go/conn"
 	"github.com/sagernet/wireguard-go/device"
 )
 
@@ -32,9 +35,18 @@ var (
 
 type WireGuard struct {
 	myOutboundAdapter
-	bind      *wireguard.ClientBind
-	device    *device.Device
-	tunDevice wireguard.Device
+	ctx           context.Context
+	workers       int
+	peers         []wireguard.PeerConfig
+	useStdNetBind bool
+	listener      N.Dialer
+	ipcConf       string
+
+	pauseManager  pause.Manager
+	pauseCallback *list.Element[pause.Callback]
+	bind          conn.Bind
+	device        *device.Device
+	tunDevice     wireguard.Device
 }
 
 func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) {
@@ -47,32 +59,30 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
 			tag:          tag,
 			dependencies: withDialerDependency(options.DialerOptions),
 		},
+		ctx:          ctx,
+		workers:      options.Workers,
+		pauseManager: pause.ManagerFromContext(ctx),
 	}
-	var reserved [3]uint8
-	if len(options.Reserved) > 0 {
-		if len(options.Reserved) != 3 {
-			return nil, E.New("invalid reserved value, required 3 bytes, got ", len(options.Reserved))
-		}
-		copy(reserved[:], options.Reserved)
-	}
-	var isConnect bool
-	var connectAddr M.Socksaddr
-	if len(options.Peers) < 2 {
-		isConnect = true
-		if len(options.Peers) == 1 {
-			connectAddr = options.Peers[0].ServerOptions.Build()
-		} else {
-			connectAddr = options.ServerOptions.Build()
-		}
-	}
-	outboundDialer, err := dialer.New(router, options.DialerOptions)
+	peers, err := wireguard.ParsePeers(options)
 	if err != nil {
 		return nil, err
 	}
-	outbound.bind = wireguard.NewClientBind(ctx, outbound, outboundDialer, isConnect, connectAddr, reserved)
+	outbound.peers = peers
 	if len(options.LocalAddress) == 0 {
 		return nil, E.New("missing local address")
 	}
+	if options.GSO {
+		if options.GSO && options.Detour != "" {
+			return nil, E.New("gso is conflict with detour")
+		}
+		options.IsWireGuardListener = true
+		outbound.useStdNetBind = true
+	}
+	listener, err := dialer.New(router, options.DialerOptions)
+	if err != nil {
+		return nil, err
+	}
+	outbound.listener = listener
 	var privateKey string
 	{
 		bytes, err := base64.StdEncoding.DecodeString(options.PrivateKey)
@@ -81,80 +91,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
 		}
 		privateKey = hex.EncodeToString(bytes)
 	}
-	ipcConf := "private_key=" + privateKey
-	if len(options.Peers) > 0 {
-		for i, peer := range options.Peers {
-			var peerPublicKey, preSharedKey string
-			{
-				bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
-				if err != nil {
-					return nil, E.Cause(err, "decode public key for peer ", i)
-				}
-				peerPublicKey = hex.EncodeToString(bytes)
-			}
-			if peer.PreSharedKey != "" {
-				bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
-				if err != nil {
-					return nil, E.Cause(err, "decode pre shared key for peer ", i)
-				}
-				preSharedKey = hex.EncodeToString(bytes)
-			}
-			destination := peer.ServerOptions.Build()
-			ipcConf += "\npublic_key=" + peerPublicKey
-			ipcConf += "\nendpoint=" + destination.String()
-			if preSharedKey != "" {
-				ipcConf += "\npreshared_key=" + preSharedKey
-			}
-			if len(peer.AllowedIPs) == 0 {
-				return nil, E.New("missing allowed_ips for peer ", i)
-			}
-			for _, allowedIP := range peer.AllowedIPs {
-				ipcConf += "\nallowed_ip=" + allowedIP
-			}
-			if len(peer.Reserved) > 0 {
-				if len(peer.Reserved) != 3 {
-					return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
-				}
-				copy(reserved[:], options.Reserved)
-				outbound.bind.SetReservedForEndpoint(destination, reserved)
-			}
-		}
-	} else {
-		var peerPublicKey, preSharedKey string
-		{
-			bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey)
-			if err != nil {
-				return nil, E.Cause(err, "decode peer public key")
-			}
-			peerPublicKey = hex.EncodeToString(bytes)
-		}
-		if options.PreSharedKey != "" {
-			bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey)
-			if err != nil {
-				return nil, E.Cause(err, "decode pre shared key")
-			}
-			preSharedKey = hex.EncodeToString(bytes)
-		}
-		ipcConf += "\npublic_key=" + peerPublicKey
-		ipcConf += "\nendpoint=" + options.ServerOptions.Build().String()
-		if preSharedKey != "" {
-			ipcConf += "\npreshared_key=" + preSharedKey
-		}
-		var has4, has6 bool
-		for _, address := range options.LocalAddress {
-			if address.Addr().Is4() {
-				has4 = true
-			} else {
-				has6 = true
-			}
-		}
-		if has4 {
-			ipcConf += "\nallowed_ip=0.0.0.0/0"
-		}
-		if has6 {
-			ipcConf += "\nallowed_ip=::/0"
-		}
-	}
+	outbound.ipcConf = "private_key=" + privateKey
 	mtu := options.MTU
 	if mtu == 0 {
 		mtu = 1408
@@ -163,36 +100,83 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
 	if !options.SystemInterface && tun.WithGVisor {
 		wireTunDevice, err = wireguard.NewStackDevice(options.LocalAddress, mtu)
 	} else {
-		wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu)
+		wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, options.LocalAddress, mtu, options.GSO)
 	}
 	if err != nil {
 		return nil, E.Cause(err, "create WireGuard device")
 	}
-	wgDevice := device.NewDevice(ctx, wireTunDevice, outbound.bind, &device.Logger{
+	outbound.tunDevice = wireTunDevice
+	return outbound, nil
+}
+
+func (w *WireGuard) Start() error {
+	err := wireguard.ResolvePeers(w.ctx, w.router, w.peers)
+	if err != nil {
+		return err
+	}
+	var bind conn.Bind
+	if w.useStdNetBind {
+		bind = conn.NewStdNetBind(w.listener.(dialer.WireGuardListener))
+	} else {
+		var (
+			isConnect   bool
+			connectAddr netip.AddrPort
+			reserved    [3]uint8
+		)
+		peerLen := len(w.peers)
+		if peerLen == 1 {
+			isConnect = true
+			connectAddr = w.peers[0].Endpoint
+			reserved = w.peers[0].Reserved
+		}
+		bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved)
+	}
+	wgDevice := device.NewDevice(w.tunDevice, bind, &device.Logger{
 		Verbosef: func(format string, args ...interface{}) {
-			logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
+			w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
 		},
 		Errorf: func(format string, args ...interface{}) {
-			logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
+			w.logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
 		},
-	}, options.Workers)
-	if debug.Enabled {
-		logger.Trace("created wireguard ipc conf: \n", ipcConf)
+	}, w.workers)
+	ipcConf := w.ipcConf
+	for _, peer := range w.peers {
+		ipcConf += peer.GenerateIpcLines()
 	}
 	err = wgDevice.IpcSet(ipcConf)
 	if err != nil {
-		return nil, E.Cause(err, "setup wireguard")
+		return E.Cause(err, "setup wireguard: \n", ipcConf)
 	}
-	outbound.device = wgDevice
-	outbound.tunDevice = wireTunDevice
-	return outbound, nil
+	w.device = wgDevice
+	w.pauseCallback = w.pauseManager.RegisterCallback(w.onPauseUpdated)
+	return w.tunDevice.Start()
+}
+
+func (w *WireGuard) Close() error {
+	if w.device != nil {
+		w.device.Close()
+	}
+	if w.pauseCallback != nil {
+		w.pauseManager.UnregisterCallback(w.pauseCallback)
+	}
+	w.tunDevice.Close()
+	return nil
 }
 
 func (w *WireGuard) InterfaceUpdated() {
-	w.bind.Reset()
+	w.device.BindUpdate()
 	return
 }
 
+func (w *WireGuard) onPauseUpdated(event int) {
+	switch event {
+	case pause.EventDevicePaused:
+		w.device.Down()
+	case pause.EventDeviceWake:
+		w.device.Up()
+	}
+}
+
 func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
 	switch network {
 	case N.NetworkTCP:
@@ -233,15 +217,3 @@ func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata a
 func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
 	return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
 }
-
-func (w *WireGuard) Start() error {
-	return w.tunDevice.Start()
-}
-
-func (w *WireGuard) Close() error {
-	if w.device != nil {
-		w.device.Close()
-	}
-	w.tunDevice.Close()
-	return nil
-}

+ 34 - 21
route/router.go

@@ -418,35 +418,48 @@ func (r *Router) Outbounds() []adapter.Outbound {
 	return r.outbounds
 }
 
-func (r *Router) Start() error {
+func (r *Router) PreStart() error {
 	monitor := taskmonitor.New(r.logger, C.DefaultStartTimeout)
-	if r.needGeoIPDatabase {
-		monitor.Start("initialize geoip database")
-		err := r.prepareGeoIPDatabase()
+	if r.interfaceMonitor != nil {
+		monitor.Start("initialize interface monitor")
+		err := r.interfaceMonitor.Start()
 		monitor.Finish()
 		if err != nil {
 			return err
 		}
 	}
-	if r.needGeositeDatabase {
-		monitor.Start("initialize geosite database")
-		err := r.prepareGeositeDatabase()
+	if r.networkMonitor != nil {
+		monitor.Start("initialize network monitor")
+		err := r.networkMonitor.Start()
 		monitor.Finish()
 		if err != nil {
 			return err
 		}
 	}
-	if r.interfaceMonitor != nil {
-		monitor.Start("initialize interface monitor")
-		err := r.interfaceMonitor.Start()
+	if r.fakeIPStore != nil {
+		monitor.Start("initialize fakeip store")
+		err := r.fakeIPStore.Start()
 		monitor.Finish()
 		if err != nil {
 			return err
 		}
 	}
-	if r.networkMonitor != nil {
-		monitor.Start("initialize network monitor")
-		err := r.networkMonitor.Start()
+	return nil
+}
+
+func (r *Router) Start() error {
+	monitor := taskmonitor.New(r.logger, C.DefaultStartTimeout)
+	if r.needGeoIPDatabase {
+		monitor.Start("initialize geoip database")
+		err := r.prepareGeoIPDatabase()
+		monitor.Finish()
+		if err != nil {
+			return err
+		}
+	}
+	if r.needGeositeDatabase {
+		monitor.Start("initialize geosite database")
+		err := r.prepareGeositeDatabase()
 		monitor.Finish()
 		if err != nil {
 			return err
@@ -472,14 +485,7 @@ func (r *Router) Start() error {
 		r.geositeCache = nil
 		r.geositeReader = nil
 	}
-	if r.fakeIPStore != nil {
-		monitor.Start("initialize fakeip store")
-		err := r.fakeIPStore.Start()
-		monitor.Finish()
-		if err != nil {
-			return err
-		}
-	}
+
 	if len(r.ruleSets) > 0 {
 		monitor.Start("initialize rule-set")
 		ruleSetStartContext := NewRuleSetStartContext()
@@ -708,6 +714,10 @@ func (r *Router) RuleSet(tag string) (adapter.RuleSet, bool) {
 }
 
 func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	if r.pauseManager.IsDevicePaused() {
+		return E.New("reject connection to ", metadata.Destination, " while device paused")
+	}
+
 	if metadata.InboundDetour != "" {
 		if metadata.LastInbound == metadata.InboundDetour {
 			return E.New("routing loop on detour: ", metadata.InboundDetour)
@@ -832,6 +842,9 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 }
 
 func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
+	if r.pauseManager.IsDevicePaused() {
+		return E.New("reject packet connection to ", metadata.Destination, " while device paused")
+	}
 	if metadata.InboundDetour != "" {
 		if metadata.LastInbound == metadata.InboundDetour {
 			return E.New("routing loop on detour: ", metadata.InboundDetour)

+ 1 - 1
route/rule_set_remote.go

@@ -126,7 +126,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
 		err          error
 	)
 	switch s.options.Format {
-	case C.RuleSetFormatSource, "":
+	case C.RuleSetFormatSource:
 		var compat option.PlainRuleSetCompat
 		compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
 		if err != nil {

+ 6 - 6
transport/fakeip/packet_wait.go

@@ -17,16 +17,16 @@ func (c *NATPacketConn) CreatePacketReadWaiter() (N.PacketReadWaiter, bool) {
 
 type waitNATPacketConn struct {
 	*NATPacketConn
-	waiter N.PacketReadWaiter
+	readWaiter N.PacketReadWaiter
 }
 
-func (c *waitNATPacketConn) InitializeReadWaiter(newBuffer func() *buf.Buffer) {
-	c.waiter.InitializeReadWaiter(newBuffer)
+func (c *waitNATPacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
+	return c.readWaiter.InitializeReadWaiter(options)
 }
 
-func (c *waitNATPacketConn) WaitReadPacket() (destination M.Socksaddr, err error) {
-	destination, err = c.waiter.WaitReadPacket()
-	if socksaddrWithoutPort(destination) == c.origin {
+func (c *waitNATPacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
+	buffer, destination, err = c.readWaiter.WaitReadPacket()
+	if err == nil && socksaddrWithoutPort(destination) == c.origin {
 		destination = M.Socksaddr{
 			Addr: c.destination.Addr,
 			Fqdn: c.destination.Fqdn,

+ 1 - 1
transport/trojan/mux.go

@@ -53,7 +53,7 @@ func newMuxConnection0(ctx context.Context, stream net.Conn, metadata M.Metadata
 	case CommandTCP:
 		return handler.NewConnection(ctx, stream, metadata)
 	case CommandUDP:
-		return handler.NewPacketConnection(ctx, &PacketConn{stream}, metadata)
+		return handler.NewPacketConnection(ctx, &PacketConn{Conn: stream}, metadata)
 	default:
 		return E.New("unknown command ", command)
 	}

+ 4 - 3
transport/trojan/protocol.go

@@ -85,9 +85,10 @@ func (c *ClientConn) Upstream() any {
 
 type ClientPacketConn struct {
 	net.Conn
-	access        sync.Mutex
-	key           [KeyLength]byte
-	headerWritten bool
+	access          sync.Mutex
+	key             [KeyLength]byte
+	headerWritten   bool
+	readWaitOptions N.ReadWaitOptions
 }
 
 func NewClientPacketConn(conn net.Conn, key [KeyLength]byte) *ClientPacketConn {

+ 45 - 0
transport/trojan/protocol_wait.go

@@ -0,0 +1,45 @@
+package trojan
+
+import (
+	"encoding/binary"
+
+	"github.com/sagernet/sing/common/buf"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/rw"
+)
+
+var _ N.PacketReadWaiter = (*ClientPacketConn)(nil)
+
+func (c *ClientPacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
+	c.readWaitOptions = options
+	return false
+}
+
+func (c *ClientPacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
+	destination, err = M.SocksaddrSerializer.ReadAddrPort(c.Conn)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "read destination")
+	}
+
+	var length uint16
+	err = binary.Read(c.Conn, binary.BigEndian, &length)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "read chunk length")
+	}
+
+	err = rw.SkipN(c.Conn, 2)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "skip crlf")
+	}
+
+	buffer = c.readWaitOptions.NewPacketBuffer()
+	_, err = buffer.ReadFullFrom(c.Conn, int(length))
+	if err != nil {
+		buffer.Release()
+		return
+	}
+	c.readWaitOptions.PostReturn(buffer)
+	return
+}

+ 2 - 1
transport/trojan/service.go

@@ -105,7 +105,7 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata
 	case CommandTCP:
 		return s.handler.NewConnection(ctx, conn, metadata)
 	case CommandUDP:
-		return s.handler.NewPacketConnection(ctx, &PacketConn{conn}, metadata)
+		return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata)
 	// case CommandMux:
 	default:
 		return HandleMuxConnection(ctx, conn, metadata, s.handler)
@@ -122,6 +122,7 @@ func (s *Service[K]) fallback(ctx context.Context, conn net.Conn, metadata M.Met
 
 type PacketConn struct {
 	net.Conn
+	readWaitOptions N.ReadWaitOptions
 }
 
 func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {

+ 45 - 0
transport/trojan/service_wait.go

@@ -0,0 +1,45 @@
+package trojan
+
+import (
+	"encoding/binary"
+
+	"github.com/sagernet/sing/common/buf"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+	"github.com/sagernet/sing/common/rw"
+)
+
+var _ N.PacketReadWaiter = (*PacketConn)(nil)
+
+func (c *PacketConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
+	c.readWaitOptions = options
+	return false
+}
+
+func (c *PacketConn) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
+	destination, err = M.SocksaddrSerializer.ReadAddrPort(c.Conn)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "read destination")
+	}
+
+	var length uint16
+	err = binary.Read(c.Conn, binary.BigEndian, &length)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "read chunk length")
+	}
+
+	err = rw.SkipN(c.Conn, 2)
+	if err != nil {
+		return nil, M.Socksaddr{}, E.Cause(err, "skip crlf")
+	}
+
+	buffer = c.readWaitOptions.NewPacketBuffer()
+	_, err = buffer.ReadFullFrom(c.Conn, int(length))
+	if err != nil {
+		buffer.Release()
+		return
+	}
+	c.readWaitOptions.PostReturn(buffer)
+	return
+}

+ 21 - 28
transport/wireguard/client_bind.go

@@ -12,7 +12,6 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/service/pause"
 	"github.com/sagernet/wireguard-go/conn"
 )
 
@@ -22,33 +21,27 @@ type ClientBind struct {
 	ctx                 context.Context
 	errorHandler        E.Handler
 	dialer              N.Dialer
-	reservedForEndpoint map[M.Socksaddr][3]uint8
+	reservedForEndpoint map[netip.AddrPort][3]uint8
 	connAccess          sync.Mutex
 	conn                *wireConn
 	done                chan struct{}
 	isConnect           bool
-	connectAddr         M.Socksaddr
+	connectAddr         netip.AddrPort
 	reserved            [3]uint8
-	pauseManager        pause.Manager
 }
 
-func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr M.Socksaddr, reserved [3]uint8) *ClientBind {
+func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind {
 	return &ClientBind{
 		ctx:                 ctx,
 		errorHandler:        errorHandler,
 		dialer:              dialer,
-		reservedForEndpoint: make(map[M.Socksaddr][3]uint8),
+		reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
 		isConnect:           isConnect,
 		connectAddr:         connectAddr,
 		reserved:            reserved,
-		pauseManager:        pause.ManagerFromContext(ctx),
 	}
 }
 
-func (c *ClientBind) SetReservedForEndpoint(destination M.Socksaddr, reserved [3]byte) {
-	c.reservedForEndpoint[destination] = reserved
-}
-
 func (c *ClientBind) connect() (*wireConn, error) {
 	serverConn := c.conn
 	if serverConn != nil {
@@ -71,16 +64,13 @@ func (c *ClientBind) connect() (*wireConn, error) {
 		}
 	}
 	if c.isConnect {
-		udpConn, err := c.dialer.DialContext(c.ctx, N.NetworkUDP, c.connectAddr)
+		udpConn, err := c.dialer.DialContext(c.ctx, N.NetworkUDP, M.SocksaddrFromNetIP(c.connectAddr))
 		if err != nil {
 			return nil, err
 		}
 		c.conn = &wireConn{
-			PacketConn: &bufio.UnbindPacketConn{
-				ExtendedConn: bufio.NewExtendedConn(udpConn),
-				Addr:         c.connectAddr,
-			},
-			done: make(chan struct{}),
+			PacketConn: bufio.NewUnbindPacketConn(udpConn),
+			done:       make(chan struct{}),
 		}
 	} else {
 		udpConn, err := c.dialer.ListenPacket(c.ctx, M.Socksaddr{Addr: netip.IPv4Unspecified()})
@@ -116,7 +106,6 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
 		c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server"))
 		err = nil
 		time.Sleep(time.Second)
-		c.pauseManager.WaitActive()
 		return
 	}
 	n, addr, err := udpConn.ReadFrom(packets[0])
@@ -133,11 +122,9 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
 	sizes[0] = n
 	if n > 3 {
 		b := packets[0]
-		b[1] = 0
-		b[2] = 0
-		b[3] = 0
+		common.ClearArray(b[1:4])
 	}
-	eps[0] = Endpoint(M.SocksaddrFromNet(addr))
+	eps[0] = Endpoint(M.AddrPortFromNet(addr))
 	count = 1
 	return
 }
@@ -170,18 +157,16 @@ func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error {
 	if err != nil {
 		return err
 	}
-	destination := M.Socksaddr(ep.(Endpoint))
+	destination := netip.AddrPort(ep.(Endpoint))
 	for _, b := range bufs {
 		if len(b) > 3 {
 			reserved, loaded := c.reservedForEndpoint[destination]
 			if !loaded {
 				reserved = c.reserved
 			}
-			b[1] = reserved[0]
-			b[2] = reserved[1]
-			b[3] = reserved[2]
+			copy(b[1:4], reserved[:])
 		}
-		_, err = udpConn.WriteTo(b, destination)
+		_, err = udpConn.WriteTo(b, M.SocksaddrFromNetIP(destination))
 		if err != nil {
 			udpConn.Close()
 			return err
@@ -191,13 +176,21 @@ func (c *ClientBind) Send(bufs [][]byte, ep conn.Endpoint) error {
 }
 
 func (c *ClientBind) ParseEndpoint(s string) (conn.Endpoint, error) {
-	return Endpoint(M.ParseSocksaddr(s)), nil
+	ap, err := netip.ParseAddrPort(s)
+	if err != nil {
+		return nil, err
+	}
+	return Endpoint(ap), nil
 }
 
 func (c *ClientBind) BatchSize() int {
 	return 1
 }
 
+func (c *ClientBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) {
+	c.reservedForEndpoint[destination] = reserved
+}
+
 type wireConn struct {
 	net.PacketConn
 	access sync.Mutex

+ 1 - 1
transport/wireguard/device_stack.go

@@ -265,7 +265,7 @@ func (ep *wireEndpoint) LinkAddress() tcpip.LinkAddress {
 }
 
 func (ep *wireEndpoint) Capabilities() stack.LinkEndpointCapabilities {
-	return stack.CapabilityNone
+	return stack.CapabilityRXChecksumOffload
 }
 
 func (ep *wireEndpoint) Attach(dispatcher stack.NetworkDispatcher) {

+ 48 - 22
transport/wireguard/device_system.go

@@ -2,6 +2,7 @@ package wireguard
 
 import (
 	"context"
+	"errors"
 	"net"
 	"net/netip"
 	"os"
@@ -11,6 +12,7 @@ import (
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common"
+	E "github.com/sagernet/sing/common/exceptions"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 	wgTun "github.com/sagernet/wireguard-go/tun"
@@ -19,16 +21,17 @@ import (
 var _ Device = (*SystemDevice)(nil)
 
 type SystemDevice struct {
-	dialer N.Dialer
-	device tun.Tun
-	name   string
-	mtu    int
-	events chan wgTun.Event
-	addr4  netip.Addr
-	addr6  netip.Addr
+	dialer      N.Dialer
+	device      tun.Tun
+	batchDevice tun.LinuxTUN
+	name        string
+	mtu         int
+	events      chan wgTun.Event
+	addr4       netip.Addr
+	addr6       netip.Addr
 }
 
-func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32) (*SystemDevice, error) {
+func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes []netip.Prefix, mtu uint32, gso bool) (*SystemDevice, error) {
 	var inet4Addresses []netip.Prefix
 	var inet6Addresses []netip.Prefix
 	for _, prefixes := range localPrefixes {
@@ -46,6 +49,7 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
 		Inet4Address: inet4Addresses,
 		Inet6Address: inet6Addresses,
 		MTU:          mtu,
+		GSO:          gso,
 	})
 	if err != nil {
 		return nil, err
@@ -58,16 +62,25 @@ func NewSystemDevice(router adapter.Router, interfaceName string, localPrefixes
 	if len(inet6Addresses) > 0 {
 		inet6Address = inet6Addresses[0].Addr()
 	}
+	var batchDevice tun.LinuxTUN
+	if gso {
+		batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)
+		if !isBatchTUN {
+			return nil, E.New("GSO is not supported on current platform")
+		}
+		batchDevice = batchTUN
+	}
 	return &SystemDevice{
 		dialer: common.Must1(dialer.NewDefault(router, option.DialerOptions{
 			BindInterface: interfaceName,
 		})),
-		device: tunInterface,
-		name:   interfaceName,
-		mtu:    int(mtu),
-		events: make(chan wgTun.Event),
-		addr4:  inet4Address,
-		addr6:  inet6Address,
+		device:      tunInterface,
+		batchDevice: batchDevice,
+		name:        interfaceName,
+		mtu:         int(mtu),
+		events:      make(chan wgTun.Event),
+		addr4:       inet4Address,
+		addr6:       inet6Address,
 	}, nil
 }
 
@@ -97,21 +110,31 @@ func (w *SystemDevice) File() *os.File {
 }
 
 func (w *SystemDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
-	sizes[0], err = w.device.Read(bufs[0][offset-tun.PacketOffset:])
-	if err == nil {
-		count = 1
+	if w.batchDevice != nil {
+		count, err = w.batchDevice.BatchRead(bufs, offset, sizes)
+	} else {
+		sizes[0], err = w.device.Read(bufs[0][offset:])
+		if err == nil {
+			count = 1
+		} else if errors.Is(err, tun.ErrTooManySegments) {
+			err = wgTun.ErrTooManySegments
+		}
 	}
 	return
 }
 
 func (w *SystemDevice) Write(bufs [][]byte, offset int) (count int, err error) {
-	for _, b := range bufs {
-		_, err = w.device.Write(b[offset:])
-		if err != nil {
-			return
+	if w.batchDevice != nil {
+		return 0, w.batchDevice.BatchWrite(bufs, offset)
+	} else {
+		for _, b := range bufs {
+			_, err = w.device.Write(b[offset:])
+			if err != nil {
+				return
+			}
 		}
-		count++
 	}
+	// WireGuard will not read count
 	return
 }
 
@@ -136,5 +159,8 @@ func (w *SystemDevice) Close() error {
 }
 
 func (w *SystemDevice) BatchSize() int {
+	if w.batchDevice != nil {
+		return w.batchDevice.BatchSize()
+	}
 	return 1
 }

+ 4 - 5
transport/wireguard/endpoint.go

@@ -3,13 +3,12 @@ package wireguard
 import (
 	"net/netip"
 
-	M "github.com/sagernet/sing/common/metadata"
 	"github.com/sagernet/wireguard-go/conn"
 )
 
 var _ conn.Endpoint = (*Endpoint)(nil)
 
-type Endpoint M.Socksaddr
+type Endpoint netip.AddrPort
 
 func (e Endpoint) ClearSrc() {
 }
@@ -19,16 +18,16 @@ func (e Endpoint) SrcToString() string {
 }
 
 func (e Endpoint) DstToString() string {
-	return (M.Socksaddr)(e).String()
+	return (netip.AddrPort)(e).String()
 }
 
 func (e Endpoint) DstToBytes() []byte {
-	b, _ := (M.Socksaddr)(e).AddrPort().MarshalBinary()
+	b, _ := (netip.AddrPort)(e).MarshalBinary()
 	return b
 }
 
 func (e Endpoint) DstIP() netip.Addr {
-	return (M.Socksaddr)(e).Addr
+	return (netip.AddrPort)(e).Addr()
 }
 
 func (e Endpoint) SrcIP() netip.Addr {

+ 148 - 0
transport/wireguard/resolve.go

@@ -0,0 +1,148 @@
+package wireguard
+
+import (
+	"context"
+	"encoding/base64"
+	"encoding/hex"
+	"net/netip"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/option"
+	dns "github.com/sagernet/sing-dns"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+)
+
+type PeerConfig struct {
+	destination    M.Socksaddr
+	domainStrategy dns.DomainStrategy
+	Endpoint       netip.AddrPort
+	PublicKey      string
+	PreSharedKey   string
+	AllowedIPs     []string
+	Reserved       [3]uint8
+}
+
+func (c PeerConfig) GenerateIpcLines() string {
+	ipcLines := "\npublic_key=" + c.PublicKey
+	ipcLines += "\nendpoint=" + c.Endpoint.String()
+	if c.PreSharedKey != "" {
+		ipcLines += "\npreshared_key=" + c.PreSharedKey
+	}
+	for _, allowedIP := range c.AllowedIPs {
+		ipcLines += "\nallowed_ip=" + allowedIP
+	}
+	return ipcLines
+}
+
+func ParsePeers(options option.WireGuardOutboundOptions) ([]PeerConfig, error) {
+	var peers []PeerConfig
+	if len(options.Peers) > 0 {
+		for peerIndex, rawPeer := range options.Peers {
+			peer := PeerConfig{
+				AllowedIPs: rawPeer.AllowedIPs,
+			}
+			destination := rawPeer.ServerOptions.Build()
+			if destination.IsFqdn() {
+				peer.destination = destination
+				peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy)
+			} else {
+				peer.Endpoint = destination.AddrPort()
+			}
+			{
+				bytes, err := base64.StdEncoding.DecodeString(rawPeer.PublicKey)
+				if err != nil {
+					return nil, E.Cause(err, "decode public key for peer ", peerIndex)
+				}
+				peer.PublicKey = hex.EncodeToString(bytes)
+			}
+			if rawPeer.PreSharedKey != "" {
+				bytes, err := base64.StdEncoding.DecodeString(rawPeer.PreSharedKey)
+				if err != nil {
+					return nil, E.Cause(err, "decode pre shared key for peer ", peerIndex)
+				}
+				peer.PreSharedKey = hex.EncodeToString(bytes)
+			}
+			if len(rawPeer.AllowedIPs) == 0 {
+				return nil, E.New("missing allowed_ips for peer ", peerIndex)
+			}
+			if len(rawPeer.Reserved) > 0 {
+				if len(rawPeer.Reserved) != 3 {
+					return nil, E.New("invalid reserved value for peer ", peerIndex, ", required 3 bytes, got ", len(peer.Reserved))
+				}
+				copy(peer.Reserved[:], options.Reserved)
+			}
+			peers = append(peers, peer)
+		}
+	} else {
+		peer := PeerConfig{}
+		var (
+			addressHas4 bool
+			addressHas6 bool
+		)
+		for _, localAddress := range options.LocalAddress {
+			if localAddress.Addr().Is4() {
+				addressHas4 = true
+			} else {
+				addressHas6 = true
+			}
+		}
+		if addressHas4 {
+			peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv4Unspecified(), 0).String())
+		}
+		if addressHas6 {
+			peer.AllowedIPs = append(peer.AllowedIPs, netip.PrefixFrom(netip.IPv6Unspecified(), 0).String())
+		}
+		destination := options.ServerOptions.Build()
+		if destination.IsFqdn() {
+			peer.destination = destination
+			peer.domainStrategy = dns.DomainStrategy(options.DomainStrategy)
+		} else {
+			peer.Endpoint = destination.AddrPort()
+		}
+		{
+			bytes, err := base64.StdEncoding.DecodeString(options.PeerPublicKey)
+			if err != nil {
+				return nil, E.Cause(err, "decode peer public key")
+			}
+			peer.PublicKey = hex.EncodeToString(bytes)
+		}
+		if options.PreSharedKey != "" {
+			bytes, err := base64.StdEncoding.DecodeString(options.PreSharedKey)
+			if err != nil {
+				return nil, E.Cause(err, "decode pre shared key")
+			}
+			peer.PreSharedKey = hex.EncodeToString(bytes)
+		}
+		if len(options.Reserved) > 0 {
+			if len(options.Reserved) != 3 {
+				return nil, E.New("invalid reserved value, required 3 bytes, got ", len(peer.Reserved))
+			}
+			copy(peer.Reserved[:], options.Reserved)
+		}
+		peers = append(peers, peer)
+	}
+	return peers, nil
+}
+
+func ResolvePeers(ctx context.Context, router adapter.Router, peers []PeerConfig) error {
+	for peerIndex, peer := range peers {
+		if peer.Endpoint.IsValid() {
+			continue
+		}
+		destinationAddresses, err := router.Lookup(ctx, peer.destination.Fqdn, peer.domainStrategy)
+		if err != nil {
+			if len(peers) == 1 {
+				return E.Cause(err, "resolve endpoint domain")
+			} else {
+				return E.Cause(err, "resolve endpoint domain for peer ", peerIndex)
+			}
+		}
+		if len(destinationAddresses) == 0 {
+			return E.New("no addresses found for endpoint domain: ", peer.destination.Fqdn)
+		}
+		peers[peerIndex].Endpoint = netip.AddrPortFrom(destinationAddresses[0], peer.destination.Port)
+
+	}
+	return nil
+}