diff --git a/adapter/router.go b/adapter/router.go index b4bdd143..5828ab35 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -17,6 +17,7 @@ import ( type Router interface { Service + PreStarter PostStarter Outbounds() []Outbound diff --git a/box.go b/box.go index 72189d94..89392d14 100644 --- a/box.go +++ b/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() } diff --git a/common/badtls/badtls.go b/common/badtls/badtls.go deleted file mode 100644 index c5c55e3c..00000000 --- a/common/badtls/badtls.go +++ /dev/null @@ -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() -} diff --git a/common/badtls/badtls_stub.go b/common/badtls/badtls_stub.go deleted file mode 100644 index 2f0028f6..00000000 --- a/common/badtls/badtls_stub.go +++ /dev/null @@ -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 -} diff --git a/common/badtls/link.go b/common/badtls/link.go deleted file mode 100644 index b8d5f4bd..00000000 --- a/common/badtls/link.go +++ /dev/null @@ -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 diff --git a/common/badtls/read_wait.go b/common/badtls/read_wait.go new file mode 100644 index 00000000..4657bc5b --- /dev/null +++ b/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 diff --git a/common/badtls/read_wait_stub.go b/common/badtls/read_wait_stub.go new file mode 100644 index 00000000..c5c9946f --- /dev/null +++ b/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 +} diff --git a/common/dialer/default.go b/common/dialer/default.go index 9fbd1d8e..0234b1b9 100644 --- a/common/dialer/default.go +++ b/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 diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go index b059e38e..bbb4b3a9 100644 --- a/common/dialer/dialer.go +++ b/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 diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go new file mode 100644 index 00000000..195133c6 --- /dev/null +++ b/common/dialer/wireguard.go @@ -0,0 +1,9 @@ +package dialer + +import ( + "net" +) + +type WireGuardListener interface { + ListenPacketCompat(network, address string) (net.PacketConn, error) +} diff --git a/common/dialer/wireguard_control.go b/common/dialer/wireguard_control.go new file mode 100644 index 00000000..def86411 --- /dev/null +++ b/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 diff --git a/common/dialer/wiregurad_stub.go b/common/dialer/wiregurad_stub.go new file mode 100644 index 00000000..d30c223a --- /dev/null +++ b/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 diff --git a/common/tls/client.go b/common/tls/client.go index d1c9475a..4d6b0c54 100644 --- a/common/tls/client.go +++ b/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 { diff --git a/common/tls/server.go b/common/tls/server.go index ac6d0a2e..6afd89d6 100644 --- a/common/tls/server.go +++ b/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 } diff --git a/docs/clients/android/features.md b/docs/clients/android/features.md index 2702e6fa..8fe84add 100644 --- a/docs/clients/android/features.md +++ b/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: | / | diff --git a/docs/clients/apple/features.md b/docs/clients/apple/features.md index 95143d98..7d419103 100644 --- a/docs/clients/apple/features.md +++ b/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 | |-----------------------|------------------|-----------------------| diff --git a/docs/configuration/inbound/tun.md b/docs/configuration/inbound/tun.md index 00c5e050..de977e76 100644 --- a/docs/configuration/inbound/tun.md +++ b/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": [ @@ -98,6 +108,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. @@ -160,18 +180,19 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes). #### stack +!!! quote "Changes in sing-box 1.8.0" + + :material-delete-alert: The legacy LWIP stack has been deprecated and removed. + TCP/IP stack. -| 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 | +| 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 | -!!! warning "" - - 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 @@ -217,10 +238,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 diff --git a/docs/configuration/inbound/tun.zh.md b/docs/configuration/inbound/tun.zh.md index d22009c2..89dd2409 100644 --- a/docs/configuration/inbound/tun.zh.md +++ b/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": [ @@ -98,6 +108,16 @@ tun 接口的 IPv6 前缀。 最大传输单元。 +#### gso + +!!! question "自 sing-box 1.8.0 起" + +!!! quote "" + + 仅支持 Linux。 + +启用通用分段卸载。 + #### auto_route 设置到 Tun 的默认路由。 @@ -157,17 +177,19 @@ UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。 #### stack +!!! quote "sing-box 1.8.0 中的更改" + + :material-delete-alert: 旧的 LWIP 栈已被弃用并移除。 + TCP/IP 栈。 -| 栈 | 描述 | 状态 | -|-------------|--------------------------------------------------------------------------|-------| -| system (默认) | 有时性能更好 | 推荐 | -| gVisor | 兼容性较好,基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 | -| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 | +| 栈 | 描述 | +|--------|------------------------------------------------------------------| +| system | 基于系统网络栈执行 L3 到 L4 转换 | +| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 | +| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 | -!!! warning "" - - 默认安装不包含 LWIP 栈,参阅 [安装](/zh/installation/build-from-source/#_5)。 +默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。 #### include_interface @@ -214,8 +236,8 @@ TCP/IP 栈。 限制被路由的 Android 用户。 | 常用用户 | ID | -|--|-----| -| 您 | 0 | +|------|----| +| 您 | 0 | | 工作资料 | 10 | #### include_package diff --git a/docs/configuration/outbound/wireguard.md b/docs/configuration/outbound/wireguard.md index 3fefb7a4..0d7ab63e 100644 --- a/docs/configuration/outbound/wireguard.md +++ b/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 diff --git a/docs/configuration/outbound/wireguard.zh.md b/docs/configuration/outbound/wireguard.zh.md index 150dda6d..e853d72e 100644 --- a/docs/configuration/outbound/wireguard.zh.md +++ b/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 diff --git a/docs/installation/build-from-source.md b/docs/installation/build-from-source.md index 3b098317..f30e69ad 100644 --- a/docs/installation/build-from-source.md +++ b/docs/installation/build-from-source.md @@ -45,19 +45,18 @@ 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. diff --git a/docs/installation/build-from-source.zh.md b/docs/installation/build-from-source.zh.md index 69e41925..3e76c2b1 100644 --- a/docs/installation/build-from-source.zh.md +++ b/docs/installation/build-from-source.zh.md @@ -43,21 +43,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). | 除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。 diff --git a/go.mod b/go.mod index 96143844..6f3fde0b 100644 --- a/go.mod +++ b/go.mod @@ -23,22 +23,22 @@ require ( github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950 - github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930 + github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e github.com/sagernet/quic-go v0.40.0 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 - github.com/sagernet/sing v0.3.0-beta.1 + github.com/sagernet/sing v0.3.0-beta.6 github.com/sagernet/sing-dns v0.1.11 - github.com/sagernet/sing-mux v0.1.5 - github.com/sagernet/sing-quic v0.1.5 + github.com/sagernet/sing-mux v0.1.6-beta.1 + github.com/sagernet/sing-quic v0.1.6-beta.1 github.com/sagernet/sing-shadowsocks v0.2.6 - github.com/sagernet/sing-shadowsocks2 v0.1.5 + github.com/sagernet/sing-shadowsocks2 v0.1.6-beta.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-beta.4 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-20230816093905-5a5c285d44a6 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 - 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 @@ -78,7 +78,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 @@ -86,10 +85,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-20231127185646-65229373498e // indirect + golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // 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-20230822172742-b8732ec3820d // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index da3f0e3d..f3853024 100644 --- a/go.sum +++ b/go.sum @@ -96,46 +96,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-20230829051644-4a68352d0c4a h1:wZHruBxZCsQLXHAozWpnJBL3wJ/XufDpz0qKtgpSnA4= github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI= -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.0.0-20230915142329-c6740b6d2950 h1:hUz/2mJLgi7l2H36JGpDY+jou9FmI6kAm0ZkU+xPpgE= github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU= -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/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-beta.1 h1:v9VTg1DtNEPqJEWRc+QcQ/KXr3naUyXN+sKU7NkIVbw= -github.com/sagernet/sing v0.3.0-beta.1/go.mod h1:Ce5LNojQOgOiWhiD8pPD6E9H7e2KgtOe3Zxx4Ou5u80= +github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= +github.com/sagernet/sing v0.3.0-beta.6 h1:MvCJO2sBKr+k/jt6WfJ/YbohDujEnlaGqA4YQ3/Vh4U= +github.com/sagernet/sing v0.3.0-beta.6/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= github.com/sagernet/sing-dns v0.1.11 h1:PPrMCVVrAeR3f5X23I+cmvacXJ+kzuyAsBiWyUKhGSE= github.com/sagernet/sing-dns v0.1.11/go.mod h1:zJ/YjnYB61SYE+ubMcMqVdpaSvsyQ2iShQGO3vuLvvE= -github.com/sagernet/sing-mux v0.1.5 h1:jUbYth9QQd1wsDmU8Ush+fKce7lNo9TMv2dp8PJtSOY= -github.com/sagernet/sing-mux v0.1.5/go.mod h1:MoH6Soz1R+CYZcCeIXZWx6fkZa6hQc9o3HZu9G6CDTw= -github.com/sagernet/sing-quic v0.1.5 h1:PIQzE4cGrry+JkkMEJH/EH3wRkv/QgD48+ScNr/2oig= -github.com/sagernet/sing-quic v0.1.5/go.mod h1:n2mXukpubasyV4SlWyyW0+LCdAn7DZ8/brAkUxZujrw= +github.com/sagernet/sing-mux v0.1.6-beta.1 h1:ADs1TgiMfA628Y2qfv21tEvePDZjBRRYddwtNFZiwe8= +github.com/sagernet/sing-mux v0.1.6-beta.1/go.mod h1:WWtRmrwCDgb+g+7Da6o62I9WiMNB0a3w6BJhEpNQlNA= +github.com/sagernet/sing-quic v0.1.6-beta.1 h1:OhNk0jxp7yZSvG/2Pg/gRLrhApqM1gFLB8LG2S4wSCc= +github.com/sagernet/sing-quic v0.1.6-beta.1/go.mod h1:+OLsIVPmgHzCqcF3lGBVngc7ItYM6v3T0rn6Qtd4T1g= 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-beta.1 h1:0vbf4XA6QybVXhUqlmbceYWYpH96s4RRt6J+K+Xtr0A= +github.com/sagernet/sing-shadowsocks2 v0.1.6-beta.1/go.mod h1:H7p/X9U15oLBpdAHPk+n3GMEuJiimImj1B3GfwY2igE= 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-beta.4 h1:KcblUxYmG9/Qyn38AP1uFf/a/fh1kmFrybmF0eYlfrQ= +github.com/sagernet/sing-tun v0.2.0-beta.4/go.mod h1:bV5YMmTun6g8AavxZDLu9S69MLVlu3Y9isBr3z0ujW4= 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-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q= github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= -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= @@ -171,8 +168,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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No= -golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= +golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/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= @@ -185,10 +182,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= @@ -197,8 +194,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= diff --git a/inbound/http.go b/inbound/http.go index fa6c3d58..b4663319 100644 --- a/inbound/http.go +++ b/inbound/http.go @@ -26,7 +26,7 @@ var ( type HTTP struct { myInboundAdapter - authenticator auth.Authenticator + authenticator *auth.Authenticator tlsConfig tls.ServerConfig } diff --git a/inbound/mixed.go b/inbound/mixed.go index 8c3bf3b4..982842ef 100644 --- a/inbound/mixed.go +++ b/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 { diff --git a/inbound/naive.go b/inbound/naive.go index 1ff159d1..36bda492 100644 --- a/inbound/naive.go +++ b/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 diff --git a/inbound/socks.go b/inbound/socks.go index 65dfb1e6..7c5183c9 100644 --- a/inbound/socks.go +++ b/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 { diff --git a/inbound/tun.go b/inbound/tun.go index f37e0825..cb04a572 100644 --- a/inbound/tun.go +++ b/inbound/tun.go @@ -74,6 +74,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, @@ -167,10 +168,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, diff --git a/option/outbound.go b/option/outbound.go index 40f8a160..501b5dbe 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -132,20 +132,21 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error { } 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:"-"` } type ServerOptions struct { diff --git a/option/tun.go b/option/tun.go index 306d4541..c700251b 100644 --- a/option/tun.go +++ b/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"` diff --git a/option/wireguard.go b/option/wireguard.go index 5ede7a61..65bfad20 100644 --- a/option/wireguard.go +++ b/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"` diff --git a/outbound/dns.go b/outbound/dns.go index 74adb3ae..fcb67d45 100644 --- a/outbound/dns.go +++ b/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 } diff --git a/outbound/proxy.go b/outbound/proxy.go index dae0d9c5..6127f0f2 100644 --- a/outbound/proxy.go +++ b/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 { diff --git a/outbound/wireguard.go b/outbound/wireguard.go index e645f056..5eb48450 100644 --- a/outbound/wireguard.go +++ b/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{ - Verbosef: func(format string, args ...interface{}) { - logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) - }, - Errorf: func(format string, args ...interface{}) { - logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) - }, - }, options.Workers) - if debug.Enabled { - logger.Trace("created wireguard ipc conf: \n", ipcConf) - } - err = wgDevice.IpcSet(ipcConf) - if err != nil { - return nil, E.Cause(err, "setup wireguard") - } - outbound.device = wgDevice 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{}) { + w.logger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) + }, + Errorf: func(format string, args ...interface{}) { + w.logger.Error(fmt.Sprintf(strings.ToLower(format), args...)) + }, + }, w.workers) + ipcConf := w.ipcConf + for _, peer := range w.peers { + ipcConf += peer.GenerateIpcLines() + } + err = wgDevice.IpcSet(ipcConf) + if err != nil { + return E.Cause(err, "setup wireguard: \n", ipcConf) + } + 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 -} diff --git a/route/router.go b/route/router.go index 0e863a24..97f5e787 100644 --- a/route/router.go +++ b/route/router.go @@ -418,6 +418,35 @@ func (r *Router) Outbounds() []adapter.Outbound { return r.outbounds } +func (r *Router) PreStart() error { + monitor := taskmonitor.New(r.logger, C.DefaultStartTimeout) + if r.interfaceMonitor != nil { + monitor.Start("initialize interface monitor") + err := r.interfaceMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + if r.networkMonitor != nil { + monitor.Start("initialize network monitor") + err := r.networkMonitor.Start() + monitor.Finish() + if err != nil { + return err + } + } + if r.fakeIPStore != nil { + monitor.Start("initialize fakeip store") + err := r.fakeIPStore.Start() + monitor.Finish() + if err != nil { + return err + } + } + return nil +} + func (r *Router) Start() error { monitor := taskmonitor.New(r.logger, C.DefaultStartTimeout) if r.needGeoIPDatabase { @@ -436,22 +465,6 @@ func (r *Router) Start() error { return err } } - if r.interfaceMonitor != nil { - monitor.Start("initialize interface monitor") - err := r.interfaceMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } - if r.networkMonitor != nil { - monitor.Start("initialize network monitor") - err := r.networkMonitor.Start() - monitor.Finish() - if err != nil { - return err - } - } if r.needGeositeDatabase { for _, rule := range r.rules { err := rule.UpdateGeosite() @@ -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) diff --git a/route/rule_set_remote.go b/route/rule_set_remote.go index ac939a45..4e687e21 100644 --- a/route/rule_set_remote.go +++ b/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 decoder := json.NewDecoder(json.NewCommentFilter(bytes.NewReader(content))) decoder.DisallowUnknownFields() diff --git a/transport/fakeip/packet_wait.go b/transport/fakeip/packet_wait.go index 3e3fd89f..9fa4a5bd 100644 --- a/transport/fakeip/packet_wait.go +++ b/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, diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index 77324000..13ac1e83 100644 --- a/transport/trojan/mux.go +++ b/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) } diff --git a/transport/trojan/protocol.go b/transport/trojan/protocol.go index 09e18782..394ba291 100644 --- a/transport/trojan/protocol.go +++ b/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 { diff --git a/transport/trojan/protocol_wait.go b/transport/trojan/protocol_wait.go new file mode 100644 index 00000000..c6b4ec06 --- /dev/null +++ b/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 +} diff --git a/transport/trojan/service.go b/transport/trojan/service.go index de6bd7e8..9078276c 100644 --- a/transport/trojan/service.go +++ b/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) { diff --git a/transport/trojan/service_wait.go b/transport/trojan/service_wait.go new file mode 100644 index 00000000..5ec082fe --- /dev/null +++ b/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 +} diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 2b56f73a..4d391205 100644 --- a/transport/wireguard/client_bind.go +++ b/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 diff --git a/transport/wireguard/device_stack.go b/transport/wireguard/device_stack.go index 4ddaffe1..9d9b4549 100644 --- a/transport/wireguard/device_stack.go +++ b/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) { diff --git a/transport/wireguard/device_system.go b/transport/wireguard/device_system.go index 98626404..49acc5b9 100644 --- a/transport/wireguard/device_system.go +++ b/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 } diff --git a/transport/wireguard/endpoint.go b/transport/wireguard/endpoint.go index dd2b7dbc..3c3ec7db 100644 --- a/transport/wireguard/endpoint.go +++ b/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 { diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go new file mode 100644 index 00000000..5b4124d2 --- /dev/null +++ b/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 +}