mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 10:01:30 +00:00
Add v2ray QUIC transport
This commit is contained in:
parent
77c98fd042
commit
d4b7e221f0
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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`)
|
||||||
|
|
|
@ -3,4 +3,5 @@ package constant
|
||||||
const (
|
const (
|
||||||
V2RayTransportTypeGRPC = "grpc"
|
V2RayTransportTypeGRPC = "grpc"
|
||||||
V2RayTransportTypeWebsocket = "ws"
|
V2RayTransportTypeWebsocket = "ws"
|
||||||
|
V2RayTransportTypeQUIC = "quic"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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{}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
{
|
{
|
||||||
|
|
|
@ -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{
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
23
transport/v2ray/quic.go
Normal 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
|
||||||
|
}
|
23
transport/v2ray/quic_stub.go
Normal file
23
transport/v2ray/quic_stub.go
Normal 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
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
92
transport/v2rayquic/client.go
Normal file
92
transport/v2rayquic/client.go
Normal 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
|
||||||
|
}
|
95
transport/v2rayquic/server.go
Normal file
95
transport/v2rayquic/server.go
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue