浏览代码

Add v2ray QUIC transport

世界 3 年之前
父节点
当前提交
d4b7e221f0

+ 2 - 0
adapter/v2ray.go

@@ -6,7 +6,9 @@ import (
 )
 
 type V2RayServerTransport interface {
+	Network() []string
 	Serve(listener net.Listener) error
+	ServePacket(listener net.PacketConn) error
 	Close() error
 }
 

+ 4 - 0
constant/quic_stub.go

@@ -2,4 +2,8 @@
 
 package constant
 
+import E "github.com/sagernet/sing/common/exceptions"
+
 const QUIC_AVAILABLE = false
+
+var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)

+ 1 - 0
constant/v2ray.go

@@ -3,4 +3,5 @@ package constant
 const (
 	V2RayTransportTypeGRPC      = "grpc"
 	V2RayTransportTypeWebsocket = "ws"
+	V2RayTransportTypeQUIC      = "quic"
 )

+ 12 - 0
inbound/default.go

@@ -129,6 +129,18 @@ func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
 	return tcpListener, err
 }
 
+func (a *myInboundAdapter) ListenUDP() (*net.UDPConn, error) {
+	bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
+	udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
+	if err != nil {
+		return nil, err
+	}
+	a.udpConn = udpConn
+	a.udpAddr = bindAddr
+	a.logger.Info("udp server started at ", udpConn.LocalAddr())
+	return udpConn, err
+}
+
 func (a *myInboundAdapter) Close() error {
 	var err error
 	if a.clearSystemProxy != nil {

+ 2 - 2
inbound/hysteria_stub.go

@@ -6,11 +6,11 @@ import (
 	"context"
 
 	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
-	E "github.com/sagernet/sing/common/exceptions"
 )
 
 func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
-	return nil, E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
+	return nil, C.ErrQUICNotIncluded
 }

+ 4 - 2
inbound/naive_quic_stub.go

@@ -2,8 +2,10 @@
 
 package inbound
 
-import E "github.com/sagernet/sing/common/exceptions"
+import (
+	C "github.com/sagernet/sing-box/constant"
+)
 
 func (n *Naive) configureHTTP3Listener(listenAddr string) error {
-	return E.New("QUIC is not included in this build, rebuild with -tags with_quic")
+	return C.ErrQUICNotIncluded
 }

+ 22 - 8
inbound/vmess.go

@@ -83,16 +83,30 @@ func (h *VMess) Start() error {
 	if h.transport == nil {
 		return h.myInboundAdapter.Start()
 	}
-	tcpListener, err := h.myInboundAdapter.ListenTCP()
-	if err != nil {
-		return err
+	if common.Contains(h.transport.Network(), N.NetworkTCP) {
+		tcpListener, err := h.myInboundAdapter.ListenTCP()
+		if err != nil {
+			return err
+		}
+		go func() {
+			sErr := h.transport.Serve(tcpListener)
+			if sErr != nil && !E.IsClosed(sErr) {
+				h.logger.Error("transport serve error: ", sErr)
+			}
+		}()
 	}
-	go func() {
-		sErr := h.transport.Serve(tcpListener)
-		if sErr != nil && !E.IsClosed(sErr) {
-			h.logger.Error("transport serve error: ", sErr)
+	if common.Contains(h.transport.Network(), N.NetworkUDP) {
+		udpConn, err := h.myInboundAdapter.ListenUDP()
+		if err != nil {
+			return err
 		}
-	}()
+		go func() {
+			sErr := h.transport.ServePacket(udpConn)
+			if sErr != nil && !E.IsClosed(sErr) {
+				h.logger.Error("transport serve error: ", sErr)
+			}
+		}()
+	}
 	return nil
 }
 

+ 9 - 0
option/v2ray_transport.go

@@ -10,6 +10,7 @@ type _V2RayTransportOptions struct {
 	Type             string                `json:"type,omitempty"`
 	GRPCOptions      V2RayGRPCOptions      `json:"-"`
 	WebsocketOptions V2RayWebsocketOptions `json:"-"`
+	QUICOptions      V2RayQUICOptions      `json:"-"`
 }
 
 type V2RayTransportOptions _V2RayTransportOptions
@@ -23,6 +24,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
 		v = o.GRPCOptions
 	case C.V2RayTransportTypeWebsocket:
 		v = o.WebsocketOptions
+	case C.V2RayTransportTypeQUIC:
+		v = o.QUICOptions
 	default:
 		return nil, E.New("unknown transport type: " + o.Type)
 	}
@@ -38,6 +41,10 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
 	switch o.Type {
 	case C.V2RayTransportTypeGRPC:
 		v = &o.GRPCOptions
+	case C.V2RayTransportTypeWebsocket:
+		v = &o.WebsocketOptions
+	case C.V2RayTransportTypeQUIC:
+		v = &o.QUICOptions
 	default:
 		return E.New("unknown transport type: " + o.Type)
 	}
@@ -102,3 +109,5 @@ type V2RayWebsocketOptions struct {
 	MaxEarlyData        uint32            `json:"max_early_data,omitempty"`
 	EarlyDataHeaderName string            `json:"early_data_header_name,omitempty"`
 }
+
+type V2RayQUICOptions struct{}

+ 2 - 2
outbound/hysteria_stub.go

@@ -6,11 +6,11 @@ import (
 	"context"
 
 	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
-	E "github.com/sagernet/sing/common/exceptions"
 )
 
 func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
-	return nil, E.New(`QUIC is not included in this build, rebuild with -tags with_quic`)
+	return nil, C.ErrQUICNotIncluded
 }

+ 1 - 1
test/box_test.go

@@ -59,7 +59,7 @@ func testTCP(t *testing.T, clientPort uint16, testPort uint16) {
 	require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
 }
 
-func testSuitHy(t *testing.T, clientPort uint16, testPort uint16) {
+func testSuitQUIC(t *testing.T, clientPort uint16, testPort uint16) {
 	dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
 	dialTCP := func() (net.Conn, error) {
 		return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", testPort))

+ 2 - 2
test/hysteria_test.go

@@ -83,7 +83,7 @@ func TestHysteriaSelf(t *testing.T) {
 			},
 		},
 	})
-	testSuitHy(t, clientPort, testPort)
+	testSuitQUIC(t, clientPort, testPort)
 }
 
 func TestHysteriaInbound(t *testing.T) {
@@ -180,5 +180,5 @@ func TestHysteriaOutbound(t *testing.T) {
 			},
 		},
 	})
-	testSuitHy(t, clientPort, testPort)
+	testSuitQUIC(t, clientPort, testPort)
 }

+ 1 - 2
test/trojan_test.go

@@ -59,8 +59,7 @@ func TestTrojanSelf(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
 		Log: &option.LogOptions{
-			Level:  "error",
-			Output: "stderr",
+			Level: "error",
 		},
 		Inbounds: []option.Inbound{
 			{

+ 1 - 2
test/vmess_test.go

@@ -229,8 +229,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, g
 func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, globalPadding bool, authenticatedLength bool) {
 	startInstance(t, option.Options{
 		Log: &option.LogOptions{
-			Level:  "error",
-			Output: "stderr",
+			Level: "error",
 		},
 		Inbounds: []option.Inbound{
 			{

+ 8 - 3
test/vmess_transport_test.go

@@ -45,14 +45,19 @@ func TestVMessWebscoketSelf(t *testing.T) {
 	})
 }
 
+func TestVMessQUICSelf(t *testing.T) {
+	testVMessWebscoketSelf(t, &option.V2RayTransportOptions{
+		Type: C.V2RayTransportTypeQUIC,
+	})
+}
+
 func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOptions) {
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
 		Log: &option.LogOptions{
-			Level:  "error",
-			Output: "stderr",
+			Level: "error",
 		},
 		Inbounds: []option.Inbound{
 			{
@@ -122,5 +127,5 @@ func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOption
 			},
 		},
 	})
-	testSuit(t, clientPort, testPort)
+	testSuitQUIC(t, clientPort, testPort)
 }

+ 9 - 0
transport/hysteria/wrap.go

@@ -34,9 +34,18 @@ func (c *PacketConnWrapper) Upstream() any {
 }
 
 type StreamWrapper struct {
+	Conn quic.Connection
 	quic.Stream
 }
 
+func (s *StreamWrapper) LocalAddr() net.Addr {
+	return s.Conn.LocalAddr()
+}
+
+func (s *StreamWrapper) RemoteAddr() net.Addr {
+	return s.Conn.RemoteAddr()
+}
+
 func (s *StreamWrapper) Upstream() any {
 	return s.Stream
 }

+ 23 - 0
transport/v2ray/quic.go

@@ -0,0 +1,23 @@
+//go:build with_quic
+
+package v2ray
+
+import (
+	"context"
+	"crypto/tls"
+
+	"github.com/sagernet/sing-box/adapter"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-box/transport/v2rayquic"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+)
+
+func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig *tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
+	return v2rayquic.NewServer(ctx, options, tlsConfig, handler, errorHandler), nil
+}
+
+func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig *tls.Config) (adapter.V2RayClientTransport, error) {
+	return v2rayquic.NewClient(ctx, dialer, serverAddr, options, tlsConfig), nil
+}

+ 23 - 0
transport/v2ray/quic_stub.go

@@ -0,0 +1,23 @@
+//go:build !with_quic
+
+package v2ray
+
+import (
+	"context"
+	"crypto/tls"
+
+	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+)
+
+func NewQUICServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig *tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) (adapter.V2RayServerTransport, error) {
+	return nil, C.ErrQUICNotIncluded
+}
+
+func NewQUICClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig *tls.Config) (adapter.V2RayClientTransport, error) {
+	return nil, C.ErrQUICNotIncluded
+}

+ 4 - 0
transport/v2ray/transport.go

@@ -22,6 +22,8 @@ func NewServerTransport(ctx context.Context, options option.V2RayTransportOption
 		return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler)
 	case C.V2RayTransportTypeWebsocket:
 		return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler, errorHandler), nil
+	case C.V2RayTransportTypeQUIC:
+		return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler, errorHandler)
 	default:
 		return nil, E.New("unknown transport type: " + options.Type)
 	}
@@ -36,6 +38,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
 		return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
 	case C.V2RayTransportTypeWebsocket:
 		return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
+	case C.V2RayTransportTypeQUIC:
+		return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)
 	default:
 		return nil, E.New("unknown transport type: " + options.Type)
 	}

+ 9 - 0
transport/v2raygrpc/server.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"crypto/tls"
 	"net"
+	"os"
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/option"
@@ -44,10 +45,18 @@ func (s *Server) Tun(server GunService_TunServer) error {
 func (s *Server) mustEmbedUnimplementedGunServiceServer() {
 }
 
+func (s *Server) Network() []string {
+	return []string{N.NetworkTCP}
+}
+
 func (s *Server) Serve(listener net.Listener) error {
 	return s.server.Serve(listener)
 }
 
+func (s *Server) ServePacket(listener net.PacketConn) error {
+	return os.ErrInvalid
+}
+
 func (s *Server) Close() error {
 	s.server.Stop()
 	return nil

+ 92 - 0
transport/v2rayquic/client.go

@@ -0,0 +1,92 @@
+package v2rayquic
+
+import (
+	"context"
+	"crypto/tls"
+	"net"
+	"sync"
+
+	"github.com/sagernet/quic-go"
+	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-box/transport/hysteria"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/bufio"
+	M "github.com/sagernet/sing/common/metadata"
+	N "github.com/sagernet/sing/common/network"
+)
+
+var _ adapter.V2RayClientTransport = (*Client)(nil)
+
+type Client struct {
+	ctx        context.Context
+	dialer     N.Dialer
+	serverAddr M.Socksaddr
+	tlsConfig  *tls.Config
+	quicConfig *quic.Config
+	conn       quic.Connection
+	connAccess sync.Mutex
+}
+
+func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig *tls.Config) adapter.V2RayClientTransport {
+	quicConfig := &quic.Config{
+		DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
+	}
+	if len(tlsConfig.NextProtos) == 0 {
+		tlsConfig.NextProtos = []string{"h2", "http/1.1"}
+	}
+	return &Client{
+		ctx:        ctx,
+		dialer:     dialer,
+		serverAddr: serverAddr,
+		tlsConfig:  tlsConfig,
+		quicConfig: quicConfig,
+	}
+}
+
+func (c *Client) offer() (quic.Connection, error) {
+	conn := c.conn
+	if conn != nil && !common.Done(conn.Context()) {
+		return conn, nil
+	}
+	c.connAccess.Lock()
+	defer c.connAccess.Unlock()
+	conn = c.conn
+	if conn != nil && !common.Done(conn.Context()) {
+		return conn, nil
+	}
+	conn, err := c.offerNew()
+	if err != nil {
+		return nil, err
+	}
+	c.conn = conn
+	return conn, nil
+}
+
+func (c *Client) offerNew() (quic.Connection, error) {
+	udpConn, err := c.dialer.DialContext(c.ctx, "udp", c.serverAddr)
+	if err != nil {
+		return nil, err
+	}
+	var packetConn net.PacketConn
+	packetConn = bufio.NewUnbindPacketConn(udpConn)
+	quicConn, err := quic.Dial(packetConn, udpConn.RemoteAddr(), c.serverAddr.AddrString(), c.tlsConfig, c.quicConfig)
+	if err != nil {
+		packetConn.Close()
+		return nil, err
+	}
+	return quicConn, nil
+}
+
+func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
+	conn, err := c.offer()
+	if err != nil {
+		return nil, err
+	}
+	stream, err := conn.OpenStream()
+	if err != nil {
+		return nil, err
+	}
+	return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
+}

+ 95 - 0
transport/v2rayquic/server.go

@@ -0,0 +1,95 @@
+package v2rayquic
+
+import (
+	"context"
+	"crypto/tls"
+	"net"
+	"os"
+
+	"github.com/sagernet/quic-go"
+	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-box/transport/hysteria"
+	"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"
+)
+
+var _ adapter.V2RayServerTransport = (*Server)(nil)
+
+type Server struct {
+	ctx          context.Context
+	tlsConfig    *tls.Config
+	quicConfig   *quic.Config
+	handler      N.TCPConnectionHandler
+	errorHandler E.Handler
+	udpListener  net.PacketConn
+	quicListener quic.Listener
+}
+
+func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig *tls.Config, handler N.TCPConnectionHandler, errorHandler E.Handler) *Server {
+	quicConfig := &quic.Config{
+		DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
+	}
+	if len(tlsConfig.NextProtos) == 0 {
+		tlsConfig.NextProtos = []string{"h2", "http/1.1"}
+	}
+	server := &Server{
+		ctx:          ctx,
+		tlsConfig:    tlsConfig,
+		quicConfig:   quicConfig,
+		handler:      handler,
+		errorHandler: errorHandler,
+	}
+	return server
+}
+
+func (s *Server) Network() []string {
+	return []string{N.NetworkUDP}
+}
+
+func (s *Server) Serve(listener net.Listener) error {
+	return os.ErrInvalid
+}
+
+func (s *Server) ServePacket(listener net.PacketConn) error {
+	quicListener, err := quic.Listen(listener, s.tlsConfig, s.quicConfig)
+	if err != nil {
+		return err
+	}
+	s.udpListener = listener
+	s.quicListener = quicListener
+	go s.acceptLoop()
+	return nil
+}
+
+func (s *Server) acceptLoop() {
+	for {
+		conn, err := s.quicListener.Accept(s.ctx)
+		if err != nil {
+			return
+		}
+		go func() {
+			hErr := s.streamAcceptLoop(conn)
+			if hErr != nil {
+				s.errorHandler.NewError(conn.Context(), hErr)
+			}
+		}()
+	}
+}
+
+func (s *Server) streamAcceptLoop(conn quic.Connection) error {
+	for {
+		stream, err := conn.AcceptStream(s.ctx)
+		if err != nil {
+			return err
+		}
+		go s.handler.NewConnection(conn.Context(), &hysteria.StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{})
+	}
+}
+
+func (s *Server) Close() error {
+	return common.Close(s.udpListener, s.quicListener)
+}

+ 9 - 0
transport/v2raywebsocket/server.go

@@ -7,6 +7,7 @@ import (
 	"net"
 	"net/http"
 	"net/netip"
+	"os"
 	"strings"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -125,6 +126,10 @@ func (s *Server) badRequest(request *http.Request, err error) {
 	s.errorHandler.NewError(request.Context(), E.Cause(err, "process connection from ", request.RemoteAddr))
 }
 
+func (s *Server) Network() []string {
+	return []string{N.NetworkTCP}
+}
+
 func (s *Server) Serve(listener net.Listener) error {
 	if s.httpServer.TLSConfig == nil {
 		return s.httpServer.Serve(listener)
@@ -133,6 +138,10 @@ func (s *Server) Serve(listener net.Listener) error {
 	}
 }
 
+func (s *Server) ServePacket(listener net.PacketConn) error {
+	return os.ErrInvalid
+}
+
 func (s *Server) Close() error {
 	return common.Close(s.httpServer)
 }