Add v2ray QUIC transport

This commit is contained in:
世界 2022-08-22 21:20:05 +08:00
parent 77c98fd042
commit d4b7e221f0
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
22 changed files with 336 additions and 25 deletions

View file

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

View file

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

View file

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

View file

@ -129,6 +129,18 @@ func (a *myInboundAdapter) ListenTCP() (*net.TCPListener, error) {
return tcpListener, err 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 { func (a *myInboundAdapter) Close() error {
var err error var err error
if a.clearSystemProxy != nil { if a.clearSystemProxy != nil {

View file

@ -6,11 +6,11 @@ import (
"context" "context"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "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) { 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
} }

View file

@ -2,8 +2,10 @@
package inbound 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 { 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
} }

View file

@ -83,16 +83,30 @@ func (h *VMess) Start() error {
if h.transport == nil { if h.transport == nil {
return h.myInboundAdapter.Start() return h.myInboundAdapter.Start()
} }
tcpListener, err := h.myInboundAdapter.ListenTCP() if common.Contains(h.transport.Network(), N.NetworkTCP) {
if err != nil { tcpListener, err := h.myInboundAdapter.ListenTCP()
return err 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 return nil
} }

View file

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

View file

@ -6,11 +6,11 @@ import (
"context" "context"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "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) { 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
} }

View file

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

View file

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

View file

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

View file

@ -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) { func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, alterId int, globalPadding bool, authenticatedLength bool) {
startInstance(t, option.Options{ startInstance(t, option.Options{
Log: &option.LogOptions{ Log: &option.LogOptions{
Level: "error", Level: "error",
Output: "stderr",
}, },
Inbounds: []option.Inbound{ Inbounds: []option.Inbound{
{ {

View file

@ -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) { func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOptions) {
user, err := uuid.DefaultGenerator.NewV4() user, err := uuid.DefaultGenerator.NewV4()
require.NoError(t, err) require.NoError(t, err)
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org") _, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
startInstance(t, option.Options{ startInstance(t, option.Options{
Log: &option.LogOptions{ Log: &option.LogOptions{
Level: "error", Level: "error",
Output: "stderr",
}, },
Inbounds: []option.Inbound{ Inbounds: []option.Inbound{
{ {
@ -122,5 +127,5 @@ func testVMessWebscoketSelf(t *testing.T, transport *option.V2RayTransportOption
}, },
}, },
}) })
testSuit(t, clientPort, testPort) testSuitQUIC(t, clientPort, testPort)
} }

View file

@ -34,9 +34,18 @@ func (c *PacketConnWrapper) Upstream() any {
} }
type StreamWrapper struct { type StreamWrapper struct {
Conn quic.Connection
quic.Stream 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 { func (s *StreamWrapper) Upstream() any {
return s.Stream return s.Stream
} }

23
transport/v2ray/quic.go Normal file
View file

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

View file

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

View file

@ -22,6 +22,8 @@ func NewServerTransport(ctx context.Context, options option.V2RayTransportOption
return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler) return NewGRPCServer(ctx, options.GRPCOptions, tlsConfig, handler)
case C.V2RayTransportTypeWebsocket: case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler, errorHandler), nil return v2raywebsocket.NewServer(ctx, options.WebsocketOptions, tlsConfig, handler, errorHandler), nil
case C.V2RayTransportTypeQUIC:
return NewQUICServer(ctx, options.QUICOptions, tlsConfig, handler, errorHandler)
default: default:
return nil, E.New("unknown transport type: " + options.Type) 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) return NewGRPCClient(ctx, dialer, serverAddr, options.GRPCOptions, tlsConfig)
case C.V2RayTransportTypeWebsocket: case C.V2RayTransportTypeWebsocket:
return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil return v2raywebsocket.NewClient(ctx, dialer, serverAddr, options.WebsocketOptions, tlsConfig), nil
case C.V2RayTransportTypeQUIC:
return NewQUICClient(ctx, dialer, serverAddr, options.QUICOptions, tlsConfig)
default: default:
return nil, E.New("unknown transport type: " + options.Type) return nil, E.New("unknown transport type: " + options.Type)
} }

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"os"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "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)) 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 { func (s *Server) Serve(listener net.Listener) error {
if s.httpServer.TLSConfig == nil { if s.httpServer.TLSConfig == nil {
return s.httpServer.Serve(listener) 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 { func (s *Server) Close() error {
return common.Close(s.httpServer) return common.Close(s.httpServer)
} }