Browse Source

Improve multiplex

世界 2 years ago
parent
commit
bc32c78d03

+ 10 - 524
common/mux/client.go

@@ -1,535 +1,21 @@
 package mux
 
 import (
-	"context"
-	"encoding/binary"
-	"io"
-	"net"
-	"sync"
-
 	"github.com/sagernet/sing-box/option"
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	"github.com/sagernet/sing/common/bufio"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
+	"github.com/sagernet/sing-mux"
 	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/common/x/list"
 )
 
-var _ N.Dialer = (*Client)(nil)
-
-type Client struct {
-	access         sync.Mutex
-	connections    list.List[abstractSession]
-	ctx            context.Context
-	dialer         N.Dialer
-	protocol       Protocol
-	maxConnections int
-	minStreams     int
-	maxStreams     int
-}
-
-func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
-	return &Client{
-		ctx:            ctx,
-		dialer:         dialer,
-		protocol:       protocol,
-		maxConnections: maxConnections,
-		minStreams:     minStreams,
-		maxStreams:     maxStreams,
-	}
-}
-
-func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
+func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
 	if !options.Enabled {
 		return nil, nil
 	}
-	if options.MaxConnections == 0 && options.MaxStreams == 0 {
-		options.MinStreams = 8
-	}
-	protocol, err := ParseProtocol(options.Protocol)
-	if err != nil {
-		return nil, err
-	}
-	return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
-}
-
-func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
-	switch N.NetworkName(network) {
-	case N.NetworkTCP:
-		stream, err := c.openStream()
-		if err != nil {
-			return nil, err
-		}
-		return &ClientConn{Conn: stream, destination: destination}, nil
-	case N.NetworkUDP:
-		stream, err := c.openStream()
-		if err != nil {
-			return nil, err
-		}
-		return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
-	default:
-		return nil, E.Extend(N.ErrUnknownNetwork, network)
-	}
-}
-
-func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
-	stream, err := c.openStream()
-	if err != nil {
-		return nil, err
-	}
-	return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
-}
-
-func (c *Client) openStream() (net.Conn, error) {
-	var (
-		session abstractSession
-		stream  net.Conn
-		err     error
-	)
-	for attempts := 0; attempts < 2; attempts++ {
-		session, err = c.offer()
-		if err != nil {
-			continue
-		}
-		stream, err = session.Open()
-		if err != nil {
-			continue
-		}
-		break
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &wrapStream{stream}, nil
-}
-
-func (c *Client) offer() (abstractSession, error) {
-	c.access.Lock()
-	defer c.access.Unlock()
-
-	sessions := make([]abstractSession, 0, c.maxConnections)
-	for element := c.connections.Front(); element != nil; {
-		if element.Value.IsClosed() {
-			nextElement := element.Next()
-			c.connections.Remove(element)
-			element = nextElement
-			continue
-		}
-		sessions = append(sessions, element.Value)
-		element = element.Next()
-	}
-	sLen := len(sessions)
-	if sLen == 0 {
-		return c.offerNew()
-	}
-	session := common.MinBy(sessions, abstractSession.NumStreams)
-	numStreams := session.NumStreams()
-	if numStreams == 0 {
-		return session, nil
-	}
-	if c.maxConnections > 0 {
-		if sLen >= c.maxConnections || numStreams < c.minStreams {
-			return session, nil
-		}
-	} else {
-		if c.maxStreams > 0 && numStreams < c.maxStreams {
-			return session, nil
-		}
-	}
-	return c.offerNew()
-}
-
-func (c *Client) offerNew() (abstractSession, error) {
-	conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
-	if err != nil {
-		return nil, err
-	}
-	if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
-		conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
-	} else {
-		conn = &protocolConn{Conn: conn, protocol: c.protocol}
-	}
-	session, err := c.protocol.newClient(conn)
-	if err != nil {
-		return nil, err
-	}
-	c.connections.PushBack(session)
-	return session, nil
-}
-
-func (c *Client) Close() error {
-	c.access.Lock()
-	defer c.access.Unlock()
-	for _, session := range c.connections.Array() {
-		session.Close()
-	}
-	return nil
-}
-
-type ClientConn struct {
-	net.Conn
-	destination  M.Socksaddr
-	requestWrite bool
-	responseRead bool
-}
-
-func (c *ClientConn) readResponse() error {
-	response, err := ReadStreamResponse(c.Conn)
-	if err != nil {
-		return err
-	}
-	if response.Status == statusError {
-		return E.New("remote error: ", response.Message)
-	}
-	return nil
-}
-
-func (c *ClientConn) Read(b []byte) (n int, err error) {
-	if !c.responseRead {
-		err = c.readResponse()
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-	}
-	return c.Conn.Read(b)
-}
-
-func (c *ClientConn) Write(b []byte) (n int, err error) {
-	if c.requestWrite {
-		return c.Conn.Write(b)
-	}
-	request := StreamRequest{
-		Network:     N.NetworkTCP,
-		Destination: c.destination,
-	}
-	_buffer := buf.StackNewSize(requestLen(request) + len(b))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	EncodeStreamRequest(request, buffer)
-	buffer.Write(b)
-	_, err = c.Conn.Write(buffer.Bytes())
-	if err != nil {
-		return
-	}
-	c.requestWrite = true
-	return len(b), nil
-}
-
-func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
-	if !c.requestWrite {
-		return bufio.ReadFrom0(c, r)
-	}
-	return bufio.Copy(c.Conn, r)
-}
-
-func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
-	if !c.responseRead {
-		return bufio.WriteTo0(c, w)
-	}
-	return bufio.Copy(w, c.Conn)
-}
-
-func (c *ClientConn) LocalAddr() net.Addr {
-	return c.Conn.LocalAddr()
-}
-
-func (c *ClientConn) RemoteAddr() net.Addr {
-	return c.destination.TCPAddr()
-}
-
-func (c *ClientConn) ReaderReplaceable() bool {
-	return c.responseRead
-}
-
-func (c *ClientConn) WriterReplaceable() bool {
-	return c.requestWrite
-}
-
-func (c *ClientConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ClientConn) Upstream() any {
-	return c.Conn
-}
-
-type ClientPacketConn struct {
-	N.ExtendedConn
-	destination  M.Socksaddr
-	requestWrite bool
-	responseRead bool
-}
-
-func (c *ClientPacketConn) readResponse() error {
-	response, err := ReadStreamResponse(c.ExtendedConn)
-	if err != nil {
-		return err
-	}
-	if response.Status == statusError {
-		return E.New("remote error: ", response.Message)
-	}
-	return nil
-}
-
-func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
-	if !c.responseRead {
-		err = c.readResponse()
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-	}
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	if cap(b) < int(length) {
-		return 0, io.ErrShortBuffer
-	}
-	return io.ReadFull(c.ExtendedConn, b[:length])
-}
-
-func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
-	request := StreamRequest{
-		Network:     N.NetworkUDP,
-		Destination: c.destination,
-	}
-	rLen := requestLen(request)
-	if len(payload) > 0 {
-		rLen += 2 + len(payload)
-	}
-	_buffer := buf.StackNewSize(rLen)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	EncodeStreamRequest(request, buffer)
-	if len(payload) > 0 {
-		common.Must(
-			binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
-			common.Error(buffer.Write(payload)),
-		)
-	}
-	_, err = c.ExtendedConn.Write(buffer.Bytes())
-	if err != nil {
-		return
-	}
-	c.requestWrite = true
-	return len(payload), nil
-}
-
-func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
-	if !c.requestWrite {
-		return c.writeRequest(b)
-	}
-	err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
-	if err != nil {
-		return
-	}
-	return c.ExtendedConn.Write(b)
-}
-
-func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
-	if !c.responseRead {
-		err = c.readResponse()
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-	}
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
-	return
-}
-
-func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
-	if !c.requestWrite {
-		defer buffer.Release()
-		return common.Error(c.writeRequest(buffer.Bytes()))
-	}
-	bLen := buffer.Len()
-	binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ClientPacketConn) FrontHeadroom() int {
-	return 2
-}
-
-func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
-	err = c.ReadBuffer(buffer)
-	return
-}
-
-func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
-	return c.WriteBuffer(buffer)
-}
-
-func (c *ClientPacketConn) LocalAddr() net.Addr {
-	return c.ExtendedConn.LocalAddr()
-}
-
-func (c *ClientPacketConn) RemoteAddr() net.Addr {
-	return c.destination.UDPAddr()
-}
-
-func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ClientPacketConn) Upstream() any {
-	return c.ExtendedConn
-}
-
-var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
-
-type ClientPacketAddrConn struct {
-	N.ExtendedConn
-	destination  M.Socksaddr
-	requestWrite bool
-	responseRead bool
-}
-
-func (c *ClientPacketAddrConn) readResponse() error {
-	response, err := ReadStreamResponse(c.ExtendedConn)
-	if err != nil {
-		return err
-	}
-	if response.Status == statusError {
-		return E.New("remote error: ", response.Message)
-	}
-	return nil
-}
-
-func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
-	if !c.responseRead {
-		err = c.readResponse()
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-	}
-	destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
-	if err != nil {
-		return
-	}
-	if destination.IsFqdn() {
-		addr = destination
-	} else {
-		addr = destination.UDPAddr()
-	}
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	if cap(p) < int(length) {
-		return 0, nil, io.ErrShortBuffer
-	}
-	n, err = io.ReadFull(c.ExtendedConn, p[:length])
-	return
-}
-
-func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
-	request := StreamRequest{
-		Network:     N.NetworkUDP,
-		Destination: c.destination,
-		PacketAddr:  true,
-	}
-	rLen := requestLen(request)
-	if len(payload) > 0 {
-		rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
-	}
-	_buffer := buf.StackNewSize(rLen)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	EncodeStreamRequest(request, buffer)
-	if len(payload) > 0 {
-		common.Must(
-			M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
-			binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
-			common.Error(buffer.Write(payload)),
-		)
-	}
-	_, err = c.ExtendedConn.Write(buffer.Bytes())
-	if err != nil {
-		return
-	}
-	c.requestWrite = true
-	return len(payload), nil
-}
-
-func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
-	if !c.requestWrite {
-		return c.writeRequest(p, M.SocksaddrFromNet(addr))
-	}
-	err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
-	if err != nil {
-		return
-	}
-	err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
-	if err != nil {
-		return
-	}
-	return c.ExtendedConn.Write(p)
-}
-
-func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
-	if !c.responseRead {
-		err = c.readResponse()
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-	}
-	destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
-	if err != nil {
-		return
-	}
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
-	return
-}
-
-func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
-	if !c.requestWrite {
-		defer buffer.Release()
-		return common.Error(c.writeRequest(buffer.Bytes(), destination))
-	}
-	bLen := buffer.Len()
-	header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
-	common.Must(
-		M.SocksaddrSerializer.WriteAddrPort(header, destination),
-		binary.Write(header, binary.BigEndian, uint16(bLen)),
-	)
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
-	return c.ExtendedConn.LocalAddr()
-}
-
-func (c *ClientPacketAddrConn) FrontHeadroom() int {
-	return 2 + M.MaxSocksaddrLength
-}
-
-func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ClientPacketAddrConn) Upstream() any {
-	return c.ExtendedConn
+	return mux.NewClient(mux.Options{
+		Dialer:         dialer,
+		Protocol:       options.Protocol,
+		MaxConnections: options.MaxConnections,
+		MinStreams:     options.MinStreams,
+		MaxStreams:     options.MaxStreams,
+		Padding:        options.Padding,
+	})
 }

+ 6 - 232
common/mux/protocol.go

@@ -1,240 +1,14 @@
 package mux
 
 import (
-	"encoding/binary"
-	"io"
-	"net"
-
-	C "github.com/sagernet/sing-box/constant"
-	"github.com/sagernet/sing/common"
-	"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"
-	"github.com/sagernet/smux"
-
-	"github.com/hashicorp/yamux"
+	"github.com/sagernet/sing-mux"
 )
 
-var Destination = M.Socksaddr{
-	Fqdn: "sp.mux.sing-box.arpa",
-	Port: 444,
-}
-
-const (
-	ProtocolSMux Protocol = iota
-	ProtocolYAMux
+type (
+	Client = mux.Client
 )
 
-type Protocol byte
-
-func ParseProtocol(name string) (Protocol, error) {
-	switch name {
-	case "", "smux":
-		return ProtocolSMux, nil
-	case "yamux":
-		return ProtocolYAMux, nil
-	default:
-		return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
-	}
-}
-
-func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
-	switch p {
-	case ProtocolSMux:
-		session, err := smux.Server(conn, smuxConfig())
-		if err != nil {
-			return nil, err
-		}
-		return &smuxSession{session}, nil
-	case ProtocolYAMux:
-		return yamux.Server(conn, yaMuxConfig())
-	default:
-		panic("unknown protocol")
-	}
-}
-
-func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
-	switch p {
-	case ProtocolSMux:
-		session, err := smux.Client(conn, smuxConfig())
-		if err != nil {
-			return nil, err
-		}
-		return &smuxSession{session}, nil
-	case ProtocolYAMux:
-		return yamux.Client(conn, yaMuxConfig())
-	default:
-		panic("unknown protocol")
-	}
-}
-
-func smuxConfig() *smux.Config {
-	config := smux.DefaultConfig()
-	config.KeepAliveDisabled = true
-	return config
-}
-
-func yaMuxConfig() *yamux.Config {
-	config := yamux.DefaultConfig()
-	config.LogOutput = io.Discard
-	config.StreamCloseTimeout = C.TCPTimeout
-	config.StreamOpenTimeout = C.TCPTimeout
-	return config
-}
-
-func (p Protocol) String() string {
-	switch p {
-	case ProtocolSMux:
-		return "smux"
-	case ProtocolYAMux:
-		return "yamux"
-	default:
-		return "unknown"
-	}
-}
-
-const (
-	version0 = 0
+var (
+	Destination      = mux.Destination
+	HandleConnection = mux.HandleConnection
 )
-
-type Request struct {
-	Protocol Protocol
-}
-
-func ReadRequest(reader io.Reader) (*Request, error) {
-	version, err := rw.ReadByte(reader)
-	if err != nil {
-		return nil, err
-	}
-	if version != version0 {
-		return nil, E.New("unsupported version: ", version)
-	}
-	protocol, err := rw.ReadByte(reader)
-	if err != nil {
-		return nil, err
-	}
-	if protocol > byte(ProtocolYAMux) {
-		return nil, E.New("unsupported protocol: ", protocol)
-	}
-	return &Request{Protocol: Protocol(protocol)}, nil
-}
-
-func EncodeRequest(buffer *buf.Buffer, request Request) {
-	buffer.WriteByte(version0)
-	buffer.WriteByte(byte(request.Protocol))
-}
-
-const (
-	flagUDP       = 1
-	flagAddr      = 2
-	statusSuccess = 0
-	statusError   = 1
-)
-
-type StreamRequest struct {
-	Network     string
-	Destination M.Socksaddr
-	PacketAddr  bool
-}
-
-func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
-	var flags uint16
-	err := binary.Read(reader, binary.BigEndian, &flags)
-	if err != nil {
-		return nil, err
-	}
-	destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
-	if err != nil {
-		return nil, err
-	}
-	var network string
-	var udpAddr bool
-	if flags&flagUDP == 0 {
-		network = N.NetworkTCP
-	} else {
-		network = N.NetworkUDP
-		udpAddr = flags&flagAddr != 0
-	}
-	return &StreamRequest{network, destination, udpAddr}, nil
-}
-
-func requestLen(request StreamRequest) int {
-	var rLen int
-	rLen += 1 // version
-	rLen += 2 // flags
-	rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
-	return rLen
-}
-
-func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
-	destination := request.Destination
-	var flags uint16
-	if request.Network == N.NetworkUDP {
-		flags |= flagUDP
-	}
-	if request.PacketAddr {
-		flags |= flagAddr
-		if !destination.IsValid() {
-			destination = Destination
-		}
-	}
-	common.Must(
-		binary.Write(buffer, binary.BigEndian, flags),
-		M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
-	)
-}
-
-type StreamResponse struct {
-	Status  uint8
-	Message string
-}
-
-func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
-	var response StreamResponse
-	status, err := rw.ReadByte(reader)
-	if err != nil {
-		return nil, err
-	}
-	response.Status = status
-	if status == statusError {
-		response.Message, err = rw.ReadVString(reader)
-		if err != nil {
-			return nil, err
-		}
-	}
-	return &response, nil
-}
-
-type wrapStream struct {
-	net.Conn
-}
-
-func (w *wrapStream) Read(p []byte) (n int, err error) {
-	n, err = w.Conn.Read(p)
-	err = wrapError(err)
-	return
-}
-
-func (w *wrapStream) Write(p []byte) (n int, err error) {
-	n, err = w.Conn.Write(p)
-	err = wrapError(err)
-	return
-}
-
-func (w *wrapStream) WriteIsThreadUnsafe() {
-}
-
-func (w *wrapStream) Upstream() any {
-	return w.Conn
-}
-
-func wrapError(err error) error {
-	switch err {
-	case yamux.ErrStreamClosed:
-		return io.EOF
-	default:
-		return err
-	}
-}

+ 0 - 269
common/mux/service.go

@@ -1,269 +0,0 @@
-package mux
-
-import (
-	"context"
-	"encoding/binary"
-	"net"
-
-	"github.com/sagernet/sing-box/adapter"
-	"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"
-	M "github.com/sagernet/sing/common/metadata"
-	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/sing/common/rw"
-	"github.com/sagernet/sing/common/task"
-)
-
-func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
-	request, err := ReadRequest(conn)
-	if err != nil {
-		return err
-	}
-	session, err := request.Protocol.newServer(conn)
-	if err != nil {
-		return err
-	}
-	var group task.Group
-	group.Append0(func(ctx context.Context) error {
-		var stream net.Conn
-		for {
-			stream, err = session.Accept()
-			if err != nil {
-				return err
-			}
-			go newConnection(ctx, router, errorHandler, logger, stream, metadata)
-		}
-	})
-	group.Cleanup(func() {
-		session.Close()
-	})
-	return group.Run(ctx)
-}
-
-func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
-	stream = &wrapStream{stream}
-	request, err := ReadStreamRequest(stream)
-	if err != nil {
-		logger.ErrorContext(ctx, err)
-		return
-	}
-	metadata.Destination = request.Destination
-	if request.Network == N.NetworkTCP {
-		logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
-		hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
-		stream.Close()
-		if hErr != nil {
-			errorHandler.NewError(ctx, hErr)
-		}
-	} else {
-		var packetConn N.PacketConn
-		if !request.PacketAddr {
-			logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
-			packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
-		} else {
-			logger.InfoContext(ctx, "inbound multiplex packet connection")
-			packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
-		}
-		hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
-		stream.Close()
-		if hErr != nil {
-			errorHandler.NewError(ctx, hErr)
-		}
-	}
-}
-
-var _ N.HandshakeConn = (*ServerConn)(nil)
-
-type ServerConn struct {
-	N.ExtendedConn
-	responseWrite bool
-}
-
-func (c *ServerConn) HandshakeFailure(err error) error {
-	errMessage := err.Error()
-	_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	common.Must(
-		buffer.WriteByte(statusError),
-		rw.WriteVString(_buffer, errMessage),
-	)
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerConn) Write(b []byte) (n int, err error) {
-	if c.responseWrite {
-		return c.ExtendedConn.Write(b)
-	}
-	_buffer := buf.StackNewSize(1 + len(b))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	common.Must(
-		buffer.WriteByte(statusSuccess),
-		common.Error(buffer.Write(b)),
-	)
-	_, err = c.ExtendedConn.Write(buffer.Bytes())
-	if err != nil {
-		return
-	}
-	c.responseWrite = true
-	return len(b), nil
-}
-
-func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
-	if c.responseWrite {
-		return c.ExtendedConn.WriteBuffer(buffer)
-	}
-	buffer.ExtendHeader(1)[0] = statusSuccess
-	c.responseWrite = true
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerConn) FrontHeadroom() int {
-	if !c.responseWrite {
-		return 1
-	}
-	return 0
-}
-
-func (c *ServerConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ServerConn) Upstream() any {
-	return c.ExtendedConn
-}
-
-var (
-	_ N.HandshakeConn = (*ServerPacketConn)(nil)
-	_ N.PacketConn    = (*ServerPacketConn)(nil)
-)
-
-type ServerPacketConn struct {
-	N.ExtendedConn
-	destination   M.Socksaddr
-	responseWrite bool
-}
-
-func (c *ServerPacketConn) HandshakeFailure(err error) error {
-	errMessage := err.Error()
-	_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	common.Must(
-		buffer.WriteByte(statusError),
-		rw.WriteVString(_buffer, errMessage),
-	)
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
-	if err != nil {
-		return
-	}
-	destination = c.destination
-	return
-}
-
-func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
-	pLen := buffer.Len()
-	common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
-	if !c.responseWrite {
-		buffer.ExtendHeader(1)[0] = statusSuccess
-		c.responseWrite = true
-	}
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ServerPacketConn) Upstream() any {
-	return c.ExtendedConn
-}
-
-func (c *ServerPacketConn) FrontHeadroom() int {
-	if !c.responseWrite {
-		return 3
-	}
-	return 2
-}
-
-var (
-	_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
-	_ N.PacketConn    = (*ServerPacketAddrConn)(nil)
-)
-
-type ServerPacketAddrConn struct {
-	N.ExtendedConn
-	responseWrite bool
-}
-
-func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
-	errMessage := err.Error()
-	_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	common.Must(
-		buffer.WriteByte(statusError),
-		rw.WriteVString(_buffer, errMessage),
-	)
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
-	destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
-	if err != nil {
-		return
-	}
-	var length uint16
-	err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
-	if err != nil {
-		return
-	}
-	return
-}
-
-func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
-	pLen := buffer.Len()
-	common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
-	common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
-	if !c.responseWrite {
-		buffer.ExtendHeader(1)[0] = statusSuccess
-		c.responseWrite = true
-	}
-	return c.ExtendedConn.WriteBuffer(buffer)
-}
-
-func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
-	return true
-}
-
-func (c *ServerPacketAddrConn) Upstream() any {
-	return c.ExtendedConn
-}
-
-func (c *ServerPacketAddrConn) FrontHeadroom() int {
-	if !c.responseWrite {
-		return 3 + M.MaxSocksaddrLength
-	}
-	return 2 + M.MaxSocksaddrLength
-}

+ 0 - 91
common/mux/session.go

@@ -1,91 +0,0 @@
-package mux
-
-import (
-	"io"
-	"net"
-
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	"github.com/sagernet/sing/common/bufio"
-	N "github.com/sagernet/sing/common/network"
-	"github.com/sagernet/smux"
-)
-
-type abstractSession interface {
-	Open() (net.Conn, error)
-	Accept() (net.Conn, error)
-	NumStreams() int
-	Close() error
-	IsClosed() bool
-}
-
-var _ abstractSession = (*smuxSession)(nil)
-
-type smuxSession struct {
-	*smux.Session
-}
-
-func (s *smuxSession) Open() (net.Conn, error) {
-	return s.OpenStream()
-}
-
-func (s *smuxSession) Accept() (net.Conn, error) {
-	return s.AcceptStream()
-}
-
-type protocolConn struct {
-	net.Conn
-	protocol        Protocol
-	protocolWritten bool
-}
-
-func (c *protocolConn) Write(p []byte) (n int, err error) {
-	if c.protocolWritten {
-		return c.Conn.Write(p)
-	}
-	_buffer := buf.StackNewSize(2 + len(p))
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	EncodeRequest(buffer, Request{
-		Protocol: c.protocol,
-	})
-	common.Must(common.Error(buffer.Write(p)))
-	n, err = c.Conn.Write(buffer.Bytes())
-	if err == nil {
-		n--
-	}
-	c.protocolWritten = true
-	return n, err
-}
-
-func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
-	if !c.protocolWritten {
-		return bufio.ReadFrom0(c, r)
-	}
-	return bufio.Copy(c.Conn, r)
-}
-
-func (c *protocolConn) Upstream() any {
-	return c.Conn
-}
-
-type vectorisedProtocolConn struct {
-	protocolConn
-	N.VectorisedWriter
-}
-
-func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
-	if c.protocolWritten {
-		return c.VectorisedWriter.WriteVectorised(buffers)
-	}
-	c.protocolWritten = true
-	_buffer := buf.StackNewSize(2)
-	defer common.KeepAlive(_buffer)
-	buffer := common.Dup(_buffer)
-	defer buffer.Release()
-	EncodeRequest(buffer, Request{
-		Protocol: c.protocol,
-	})
-	return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
-}

+ 13 - 2
docs/configuration/shared/multiplex.md

@@ -10,7 +10,8 @@
   "protocol": "smux",
   "max_connections": 4,
   "min_streams": 4,
-  "max_streams": 0
+  "max_streams": 0,
+  "padding": false
 }
 ```
 
@@ -28,8 +29,9 @@ Multiplex protocol.
 |----------|------------------------------------|
 | smux     | https://github.com/xtaci/smux      |
 | yamux    | https://github.com/hashicorp/yamux |
+| h2mux    | https://golang.org/x/net/http2     |
 
-SMux is used by default.
+h2mux is used by default.
 
 #### max_connections
 
@@ -48,3 +50,12 @@ Conflict with `max_streams`.
 Maximum multiplexed streams in a connection before opening a new connection.
 
 Conflict with `max_connections` and `min_streams`.
+
+#### padding
+
+!!! info
+
+    Requires sing-box server version 1.3-beta9 or later.
+
+Enable padding.
+

+ 12 - 2
docs/configuration/shared/multiplex.zh.md

@@ -28,8 +28,9 @@
 |-------|------------------------------------|
 | smux  | https://github.com/xtaci/smux      |
 | yamux | https://github.com/hashicorp/yamux |
+| h2mux | https://golang.org/x/net/http2     |
 
-默认使用 SMux。
+默认使用 h2mux。
 
 #### max_connections
 
@@ -47,4 +48,13 @@
 
 在打开新连接之前,连接中的最大多路复用流数量。
 
-与 `max_connections` 和 `min_streams` 冲突。
+与 `max_connections` 和 `min_streams` 冲突。
+
+#### padding
+
+!!! info
+
+    需要 sing-box 服务器版本 1.3-beta9 或更高。
+
+启用填充。
+

+ 3 - 2
go.mod

@@ -13,7 +13,6 @@ require (
 	github.com/go-chi/cors v1.2.1
 	github.com/go-chi/render v1.0.2
 	github.com/gofrs/uuid/v5 v5.0.0
-	github.com/hashicorp/yamux v0.1.1
 	github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
 	github.com/logrusorgru/aurora v2.0.3+incompatible
 	github.com/mholt/acmez v1.1.1
@@ -25,8 +24,9 @@ require (
 	github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
 	github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
-	github.com/sagernet/sing v0.2.4
+	github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207
 	github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
+	github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646
 	github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
 	github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
 	github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b
@@ -63,6 +63,7 @@ require (
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/btree v1.0.1 // indirect
 	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+	github.com/hashicorp/yamux v0.1.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/josharian/native v1.1.0 // indirect
 	github.com/klauspost/compress v1.15.15 // indirect

+ 4 - 2
go.sum

@@ -111,10 +111,12 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
 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.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
-github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
+github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207 h1:+dDVjW20IT+e8maKryaDeRY2+RFmTFdrQeIzqE2WOss=
+github.com/sagernet/sing v0.2.5-0.20230423085534-0902e6216207/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
 github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
 github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
+github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc=
+github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
 github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
 github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
 github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=

+ 1 - 0
option/outbound.go

@@ -150,4 +150,5 @@ type MultiplexOptions struct {
 	MaxConnections int    `json:"max_connections,omitempty"`
 	MinStreams     int    `json:"min_streams,omitempty"`
 	MaxStreams     int    `json:"max_streams,omitempty"`
+	Padding        bool   `json:"padding,omitempty"`
 }

+ 10 - 3
outbound/shadowsocks.go

@@ -30,7 +30,7 @@ type Shadowsocks struct {
 	serverAddr      M.Socksaddr
 	plugin          sip003.Plugin
 	uotClient       *uot.Client
-	multiplexDialer N.Dialer
+	multiplexDialer *mux.Client
 }
 
 func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
@@ -58,7 +58,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
 	}
 	uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
 	if !uotOptions.Enabled {
-		outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
+		outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
 		if err != nil {
 			return nil, err
 		}
@@ -127,8 +127,15 @@ func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn
 	return NewPacketConnection(ctx, h, conn, metadata)
 }
 
+func (h *Shadowsocks) InterfaceUpdated() error {
+	if h.multiplexDialer != nil {
+		h.multiplexDialer.Reset()
+	}
+	return nil
+}
+
 func (h *Shadowsocks) Close() error {
-	return common.Close(h.multiplexDialer)
+	return common.Close(common.PtrOrNil(h.multiplexDialer))
 }
 
 var _ N.Dialer = (*shadowsocksDialer)(nil)

+ 10 - 3
outbound/trojan.go

@@ -27,7 +27,7 @@ type Trojan struct {
 	dialer          N.Dialer
 	serverAddr      M.Socksaddr
 	key             [56]byte
-	multiplexDialer N.Dialer
+	multiplexDialer *mux.Client
 	tlsConfig       tls.Config
 	transport       adapter.V2RayClientTransport
 }
@@ -58,7 +58,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
 			return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
 		}
 	}
-	outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
+	outbound.multiplexDialer, err = mux.NewClientWithOptions((*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
 	if err != nil {
 		return nil, err
 	}
@@ -103,8 +103,15 @@ func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, met
 	return NewPacketConnection(ctx, h, conn, metadata)
 }
 
+func (h *Trojan) InterfaceUpdated() error {
+	if h.multiplexDialer != nil {
+		h.multiplexDialer.Reset()
+	}
+	return nil
+}
+
 func (h *Trojan) Close() error {
-	return common.Close(h.multiplexDialer, h.transport)
+	return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
 }
 
 type trojanDialer Trojan

+ 10 - 3
outbound/vmess.go

@@ -27,7 +27,7 @@ type VMess struct {
 	dialer          N.Dialer
 	client          *vmess.Client
 	serverAddr      M.Socksaddr
-	multiplexDialer N.Dialer
+	multiplexDialer *mux.Client
 	tlsConfig       tls.Config
 	transport       adapter.V2RayClientTransport
 	packetAddr      bool
@@ -59,7 +59,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
 			return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
 		}
 	}
-	outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
+	outbound.multiplexDialer, err = mux.NewClientWithOptions((*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
 	if err != nil {
 		return nil, err
 	}
@@ -97,8 +97,15 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
 	return outbound, nil
 }
 
+func (h *VMess) InterfaceUpdated() error {
+	if h.multiplexDialer != nil {
+		h.multiplexDialer.Reset()
+	}
+	return nil
+}
+
 func (h *VMess) Close() error {
-	return common.Close(h.multiplexDialer, h.transport)
+	return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
 }
 
 func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {

+ 2 - 1
route/router.go

@@ -589,7 +589,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
 	switch metadata.Destination.Fqdn {
 	case mux.Destination.Fqdn:
 		r.logger.InfoContext(ctx, "inbound multiplex connection")
-		return mux.NewConnection(ctx, r, r, r.logger, conn, metadata)
+		handler := adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)
+		return mux.HandleConnection(ctx, handler, r.logger, conn, adapter.UpstreamMetadata(metadata))
 	case vmess.MuxDestination.Fqdn:
 		r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
 		return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))

+ 12 - 16
test/go.mod

@@ -9,9 +9,9 @@ replace github.com/sagernet/sing-box => ../
 require (
 	github.com/docker/docker v20.10.18+incompatible
 	github.com/docker/go-connections v0.4.0
-	github.com/gofrs/uuid v4.4.0+incompatible
-	github.com/sagernet/sing v0.2.4-0.20230418025125-f196b4303e31
-	github.com/sagernet/sing-shadowsocks v0.2.2-0.20230418025154-6114beeeba6d
+	github.com/gofrs/uuid/v5 v5.0.0
+	github.com/sagernet/sing v0.2.4
+	github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
 	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.2
 	go.uber.org/goleak v1.2.0
@@ -21,7 +21,7 @@ require (
 require (
 	berty.tech/go-libtor v1.0.385 // indirect
 	github.com/Dreamacro/clash v1.15.0 // indirect
-	github.com/Microsoft/go-winio v0.5.1 // indirect
+	github.com/Microsoft/go-winio v0.6.0 // indirect
 	github.com/ajg/form v1.5.1 // indirect
 	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/caddyserver/certmagic v0.17.2 // indirect
@@ -36,7 +36,6 @@ require (
 	github.com/go-chi/cors v1.2.1 // indirect
 	github.com/go-chi/render v1.0.2 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/gofrs/uuid/v5 v5.0.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/mock v1.6.0 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
@@ -51,7 +50,7 @@ require (
 	github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
 	github.com/mholt/acmez v1.1.0 // indirect
 	github.com/miekg/dns v1.1.53 // indirect
-	github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
+	github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
 	github.com/ooni/go-libtor v1.1.7 // indirect
@@ -71,15 +70,15 @@ require (
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 // indirect
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
-	github.com/sagernet/sing-dns v0.1.5-0.20230418025317-8a132998b322 // indirect
-	github.com/sagernet/sing-shadowtls v0.1.0 // indirect
-	github.com/sagernet/sing-tun v0.1.4-0.20230419061614-d744d03d9302 // indirect
-	github.com/sagernet/sing-vmess v0.1.3 // indirect
+	github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc // indirect
+	github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b // indirect
+	github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b // indirect
+	github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 // indirect
 	github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
 	github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
 	github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
-	github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
+	github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
@@ -93,15 +92,12 @@ require (
 	golang.org/x/mod v0.8.0 // indirect
 	golang.org/x/sys v0.7.0 // indirect
 	golang.org/x/text v0.9.0 // indirect
-	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
+	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
 	golang.org/x/tools v0.6.0 // indirect
 	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
 	google.golang.org/grpc v1.54.0 // indirect
 	google.golang.org/protobuf v1.30.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
-	gotest.tools/v3 v3.4.0 // indirect
-	gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
+	gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523 // indirect
 	lukechampine.com/blake3 v1.1.7 // indirect
 )
-
-//replace github.com/sagernet/sing => ../../sing

+ 22 - 31
test/go.sum

@@ -3,8 +3,8 @@ berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+f
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Dreamacro/clash v1.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
 github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
-github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
-github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
+github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
 github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
@@ -43,8 +43,6 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
 github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
-github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
-github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
 github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -84,8 +82,8 @@ github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
 github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
 github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
 github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
-github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c h1:RC8WMpjonrBfyAh6VN/POIPtYD5tRAq0qMqCRjQNK+g=
-github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c/go.mod h1:9OcmHNQQUTbk4XCffrLgN1NEKc2mh5u++biHVrvHsSU=
+github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
+github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -128,18 +126,18 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
 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.2.4-0.20230418025125-f196b4303e31 h1:qgq8jeY/rbnY9NwYXByO//AP0ByIxnsKUxQx1tOB3W0=
-github.com/sagernet/sing v0.2.4-0.20230418025125-f196b4303e31/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
-github.com/sagernet/sing-dns v0.1.5-0.20230418025317-8a132998b322 h1:UDSeJZ2xB3dj1lySnM5LpF48dGlphGstw2BqtkJwcZI=
-github.com/sagernet/sing-dns v0.1.5-0.20230418025317-8a132998b322/go.mod h1:2wjxSr1Gbecq9A0ESA9cnR399tQTcpCZEOGytekb+qI=
-github.com/sagernet/sing-shadowsocks v0.2.2-0.20230418025154-6114beeeba6d h1:UUxtLujzp5jmtOXqXpSOGvHwHSZcBveKVDzRJ4GlnFU=
-github.com/sagernet/sing-shadowsocks v0.2.2-0.20230418025154-6114beeeba6d/go.mod h1:Co3PJXcaZoLwHGBfT0rbSnn9C7ywc41zVYWtDeoeI/Q=
-github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
-github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
-github.com/sagernet/sing-tun v0.1.4-0.20230419061614-d744d03d9302 h1:aPb0T2HQRTG2t7fEwLvFLZSXmhmnBh+SMs2NufhmrsI=
-github.com/sagernet/sing-tun v0.1.4-0.20230419061614-d744d03d9302/go.mod h1:bvcVzlf9q9dgxt8qKluW+zOXCFoN1+SpBG3sHTq8/9Q=
-github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
-github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
+github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
+github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
+github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
+github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
+github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
+github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
+github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
+github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
+github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b h1:9NsciSJGwzdkXwVvT2c2g+RvkTVkANeBLr2l+soJ7LM=
+github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b/go.mod h1:DD7Ce2Gt0GFc6I/1+Uw4D/aUlBsGqrQsC52CMK/V818=
+github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
+github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
 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/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
@@ -148,9 +146,8 @@ github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfI
 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
-github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
-github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
+github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
@@ -158,7 +155,6 @@ github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzy
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -218,13 +214,10 @@ golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -243,13 +236,12 @@ 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@@ -272,8 +264,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
-gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
-gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
+gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523 h1:zUQYeyyPLnSR6yMvLSOmLH37xDWCZ7BqlpE69fE5K3Q=
+gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
 lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

+ 32 - 16
test/mux_test.go

@@ -18,18 +18,40 @@ var muxProtocols = []mux.Protocol{
 }
 
 func TestVMessSMux(t *testing.T) {
-	testVMessMux(t, mux.ProtocolSMux.String())
+	testVMessMux(t, option.MultiplexOptions{
+		Enabled:  true,
+		Protocol: mux.ProtocolSMux.String(),
+	})
 }
 
 func TestShadowsocksMux(t *testing.T) {
 	for _, protocol := range muxProtocols {
 		t.Run(protocol.String(), func(t *testing.T) {
-			testShadowsocksMux(t, protocol.String())
+			testShadowsocksMux(t, option.MultiplexOptions{
+				Enabled:  true,
+				Protocol: protocol.String(),
+			})
 		})
 	}
 }
 
-func testShadowsocksMux(t *testing.T, protocol string) {
+func TestShadowsockH2Mux(t *testing.T) {
+	testShadowsocksMux(t, option.MultiplexOptions{
+		Enabled:  true,
+		Protocol: mux.ProtocolH2Mux.String(),
+		Padding:  true,
+	})
+}
+
+func TestShadowsockSMuxPadding(t *testing.T) {
+	testShadowsocksMux(t, option.MultiplexOptions{
+		Enabled:  true,
+		Protocol: mux.ProtocolSMux.String(),
+		Padding:  true,
+	})
+}
+
+func testShadowsocksMux(t *testing.T, options option.MultiplexOptions) {
 	method := shadowaead_2022.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
@@ -68,12 +90,9 @@ func testShadowsocksMux(t *testing.T, protocol string) {
 						Server:     "127.0.0.1",
 						ServerPort: serverPort,
 					},
-					Method:   method,
-					Password: password,
-					MultiplexOptions: &option.MultiplexOptions{
-						Enabled:  true,
-						Protocol: protocol,
-					},
+					Method:           method,
+					Password:         password,
+					MultiplexOptions: &options,
 				},
 			},
 		},
@@ -91,7 +110,7 @@ func testShadowsocksMux(t *testing.T, protocol string) {
 	testSuit(t, clientPort, testPort)
 }
 
-func testVMessMux(t *testing.T, protocol string) {
+func testVMessMux(t *testing.T, options option.MultiplexOptions) {
 	user, _ := uuid.NewV4()
 	startInstance(t, option.Options{
 		Inbounds: []option.Inbound{
@@ -132,12 +151,9 @@ func testVMessMux(t *testing.T, protocol string) {
 						Server:     "127.0.0.1",
 						ServerPort: serverPort,
 					},
-					Security: "auto",
-					UUID:     user.String(),
-					Multiplex: &option.MultiplexOptions{
-						Enabled:  true,
-						Protocol: protocol,
-					},
+					Security:  "auto",
+					UUID:      user.String(),
+					Multiplex: &options,
 				},
 			},
 		},

+ 1 - 1
test/udpnat_test.go

@@ -19,7 +19,7 @@ func TestUDPNatClose(t *testing.T) {
 	defer cancel()
 	connCtx, connCancel := common.ContextWithCancelCause(context.Background())
 	defer connCancel(net.ErrClosed)
-	service := udpnat.New[int](ctx, 1, &testUDPNatCloseHandler{connCancel})
+	service := udpnat.New[int](1, &testUDPNatCloseHandler{connCancel})
 	service.NewPacket(ctx, 0, buf.As([]byte("Hello")), M.Metadata{}, func(natConn N.PacketConn) N.PacketWriter {
 		return &testPacketWriter{}
 	})

+ 7 - 2
transport/v2rayhttp/conn.go

@@ -199,13 +199,13 @@ func (c *HTTP2Conn) NeedAdditionalReadDeadline() bool {
 
 type ServerHTTPConn struct {
 	HTTP2Conn
-	flusher http.Flusher
+	Flusher http.Flusher
 }
 
 func (c *ServerHTTPConn) Write(b []byte) (n int, err error) {
 	n, err = c.writer.Write(b)
 	if err == nil {
-		c.flusher.Flush()
+		c.Flusher.Flush()
 	}
 	return
 }
@@ -246,6 +246,11 @@ func (w *HTTP2ConnWrapper) CloseWrapper() {
 	w.closed = true
 }
 
+func (w *HTTP2ConnWrapper) Close() error {
+	w.CloseWrapper()
+	return w.ExtendedConn.Close()
+}
+
 func (w *HTTP2ConnWrapper) Upstream() any {
 	return w.ExtendedConn
 }