mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
Add ECH support for QUIC based protocols
This commit is contained in:
parent
533fca9fa3
commit
4c050d7f4b
120
common/qtls/wrapper.go
Normal file
120
common/qtls/wrapper.go
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
package qtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QUICConfig interface {
|
||||||
|
Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error)
|
||||||
|
DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error)
|
||||||
|
CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICServerConfig interface {
|
||||||
|
Listen(conn net.PacketConn, config *quic.Config) (QUICListener, error)
|
||||||
|
ListenEarly(conn net.PacketConn, config *quic.Config) (QUICEarlyListener, error)
|
||||||
|
ConfigureHTTP3()
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICListener interface {
|
||||||
|
Accept(ctx context.Context) (quic.Connection, error)
|
||||||
|
Close() error
|
||||||
|
Addr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type QUICEarlyListener interface {
|
||||||
|
Accept(ctx context.Context) (quic.EarlyConnection, error)
|
||||||
|
Close() error
|
||||||
|
Addr() net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config aTLS.Config, quicConfig *quic.Config) (quic.Connection, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.Dial(ctx, conn, addr, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.Dial(ctx, conn, addr, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config aTLS.Config, quicConfig *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.DialEarly(ctx, conn, addr, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.DialEarly(ctx, conn, addr, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, config aTLS.Config, quicConfig *quic.Config, enableDatagrams bool) (http.RoundTripper, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.CreateTransport(conn, quicConnPtr, serverAddr, quicConfig, enableDatagrams), nil
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &http3.RoundTripper{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
EnableDatagrams: enableDatagrams,
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*quicConnPtr = quicConn
|
||||||
|
return quicConn, nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Listen(conn net.PacketConn, config aTLS.ServerConfig, quicConfig *quic.Config) (QUICListener, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.Listen(conn, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.Listen(conn, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenEarly(conn net.PacketConn, config aTLS.ServerConfig, quicConfig *quic.Config) (QUICEarlyListener, error) {
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
return quicTLSConfig.ListenEarly(conn, quicConfig)
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return quic.ListenEarly(conn, tlsConfig, quicConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConfigureHTTP3(config aTLS.ServerConfig) error {
|
||||||
|
if len(config.NextProtos()) == 0 {
|
||||||
|
config.SetNextProtos([]string{http3.NextProtoH3})
|
||||||
|
}
|
||||||
|
if quicTLSConfig, isQUICConfig := config.(QUICServerConfig); isQUICConfig {
|
||||||
|
quicTLSConfig.ConfigureHTTP3()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tlsConfig, err := config.Config()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
http3.ConfigureTLSConfig(tlsConfig)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -23,37 +23,37 @@ import (
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type echClientConfig struct {
|
||||||
config *cftls.Config
|
config *cftls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) ServerName() string {
|
func (c *echClientConfig) ServerName() string {
|
||||||
return e.config.ServerName
|
return c.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetServerName(serverName string) {
|
func (c *echClientConfig) SetServerName(serverName string) {
|
||||||
e.config.ServerName = serverName
|
c.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) NextProtos() []string {
|
func (c *echClientConfig) NextProtos() []string {
|
||||||
return e.config.NextProtos
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
|
func (c *echClientConfig) SetNextProtos(nextProto []string) {
|
||||||
e.config.NextProtos = nextProto
|
c.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
func (c *echClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (c *echClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
return &echConnWrapper{cftls.Client(conn, c.config)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (c *echClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{
|
return &echClientConfig{
|
||||||
config: e.config.Clone(),
|
config: c.config.Clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,8 +171,20 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
||||||
tlsConfig.ECHEnabled = true
|
tlsConfig.ECHEnabled = true
|
||||||
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
|
||||||
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
|
||||||
|
|
||||||
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
block, rest := pem.Decode([]byte(strings.Join(options.ECH.Config, "\n")))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
|
} else if options.ECH.ConfigPath != "" {
|
||||||
|
content, err := os.ReadFile(options.ECH.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read ECH config")
|
||||||
|
}
|
||||||
|
echConfig = content
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(echConfig) > 0 {
|
||||||
|
block, rest := pem.Decode(echConfig)
|
||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
|
@ -184,7 +196,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
||||||
} else {
|
} else {
|
||||||
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx)
|
tlsConfig.GetClientECHConfigs = fetchECHClientConfig(ctx)
|
||||||
}
|
}
|
||||||
return &ECHClientConfig{&tlsConfig}, nil
|
return &echClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverName string) ([]cftls.ECHConfig, error) {
|
||||||
|
|
56
common/tls/ech_quic.go
Normal file
56
common/tls/ech_quic.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
//go:build with_quic && with_ech
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/cloudflare-tls"
|
||||||
|
"github.com/sagernet/quic-go/ech"
|
||||||
|
"github.com/sagernet/quic-go/http3_ech"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ qtls.QUICConfig = (*echClientConfig)(nil)
|
||||||
|
_ qtls.QUICServerConfig = (*echServerConfig)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *echClientConfig) Dial(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.Connection, error) {
|
||||||
|
return quic.Dial(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, addr net.Addr, config *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
return quic.DialEarly(ctx, conn, addr, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper {
|
||||||
|
return &http3.RoundTripper{
|
||||||
|
TLSClientConfig: c.config,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
EnableDatagrams: enableDatagrams,
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*quicConnPtr = quicConn
|
||||||
|
return quicConn, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) Listen(conn net.PacketConn, config *quic.Config) (qtls.QUICListener, error) {
|
||||||
|
return quic.Listen(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ListenEarly(conn net.PacketConn, config *quic.Config) (qtls.QUICEarlyListener, error) {
|
||||||
|
return quic.ListenEarly(conn, c.config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *echServerConfig) ConfigureHTTP3() {
|
||||||
|
http3.ConfigureTLSConfig(c.config)
|
||||||
|
}
|
|
@ -159,7 +159,7 @@ func (c *echServerConfig) startECHWatcher() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.watcher = watcher
|
c.echWatcher = watcher
|
||||||
go c.loopECHUpdate()
|
go c.loopECHUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ func (c *echServerConfig) loopECHUpdate() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload ECH key"))
|
c.logger.Error(E.Cause(err, "reload ECH key"))
|
||||||
}
|
}
|
||||||
case err, ok := <-c.watcher.Errors:
|
case err, ok := <-c.echWatcher.Errors:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||||
certificate = content
|
certificate = content
|
||||||
}
|
}
|
||||||
if len(options.Key) > 0 {
|
if len(options.Key) > 0 {
|
||||||
key = []byte(strings.Join(options.Key, ""))
|
key = []byte(strings.Join(options.Key, "\n"))
|
||||||
} else if options.KeyPath != "" {
|
} else if options.KeyPath != "" {
|
||||||
content, err := os.ReadFile(options.KeyPath)
|
content, err := os.ReadFile(options.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -298,7 +298,20 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []cftls.Certificate{keyPair}
|
tlsConfig.Certificates = []cftls.Certificate{keyPair}
|
||||||
|
|
||||||
block, rest := pem.Decode([]byte(strings.Join(options.ECH.Key, "\n")))
|
var echKey []byte
|
||||||
|
if len(options.ECH.Key) > 0 {
|
||||||
|
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||||
|
} else if options.KeyPath != "" {
|
||||||
|
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read ECH key")
|
||||||
|
}
|
||||||
|
echKey = content
|
||||||
|
} else {
|
||||||
|
return nil, E.New("missing ECH key")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, rest := pem.Decode(echKey)
|
||||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH keys pem")
|
return nil, E.New("invalid ECH keys pem")
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,12 @@ The server private key line array, in PEM format.
|
||||||
|
|
||||||
The path to the server private key, in PEM format.
|
The path to the server private key, in PEM format.
|
||||||
|
|
||||||
|
## Custom TLS support
|
||||||
|
|
||||||
|
!!! info "QUIC support"
|
||||||
|
|
||||||
|
Only ECH is supported in QUIC.
|
||||||
|
|
||||||
#### utls
|
#### utls
|
||||||
|
|
||||||
==Client only==
|
==Client only==
|
||||||
|
@ -217,7 +223,7 @@ Available fingerprint values:
|
||||||
|
|
||||||
Chrome fingerprint will be used if empty.
|
Chrome fingerprint will be used if empty.
|
||||||
|
|
||||||
## ECH Fields
|
### ECH Fields
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/quic-go/congestion"
|
"github.com/sagernet/quic-go/congestion"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
@ -35,7 +36,7 @@ type Hysteria struct {
|
||||||
xplusKey []byte
|
xplusKey []byte
|
||||||
sendBPS uint64
|
sendBPS uint64
|
||||||
recvBPS uint64
|
recvBPS uint64
|
||||||
listener *quic.Listener
|
listener qtls.QUICListener
|
||||||
udpAccess sync.RWMutex
|
udpAccess sync.RWMutex
|
||||||
udpSessionId uint32
|
udpSessionId uint32
|
||||||
udpSessions map[uint32]chan *hysteria.UDPMessage
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
|
@ -147,11 +148,7 @@ func (h *Hysteria) Start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rawConfig, err := h.tlsConfig.Config()
|
listener, err := qtls.Listen(packetConn, h.tlsConfig, h.quicConfig)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
listener, err := quic.Listen(packetConn, rawConfig, h.quicConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -333,7 +330,7 @@ func (h *Hysteria) Close() error {
|
||||||
h.udpAccess.Unlock()
|
h.udpAccess.Unlock()
|
||||||
return common.Close(
|
return common.Close(
|
||||||
&h.myInboundAdapter,
|
&h.myInboundAdapter,
|
||||||
common.PtrOrNil(h.listener),
|
h.listener,
|
||||||
h.tlsConfig,
|
h.tlsConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,28 +3,39 @@
|
||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/quic-go/http3"
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *Naive) configureHTTP3Listener() error {
|
func (n *Naive) configureHTTP3Listener() error {
|
||||||
tlsConfig, err := n.tlsConfig.Config()
|
err := qtls.ConfigureHTTP3(n.tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h3Server := &http3.Server{
|
|
||||||
Port: int(n.listenOptions.ListenPort),
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
Handler: n,
|
|
||||||
}
|
|
||||||
|
|
||||||
udpConn, err := n.ListenUDP()
|
udpConn, err := n.ListenUDP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{
|
||||||
|
MaxIncomingStreams: 1 << 60,
|
||||||
|
Allow0RTT: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
udpConn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h3Server := &http3.Server{
|
||||||
|
Port: int(n.listenOptions.ListenPort),
|
||||||
|
Handler: n,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sErr := h3Server.Serve(udpConn)
|
sErr := h3Server.ServeListener(quicListener)
|
||||||
udpConn.Close()
|
udpConn.Close()
|
||||||
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
||||||
n.logger.Error("http3 server serve error: ", sErr)
|
n.logger.Error("http3 server serve error: ", sErr)
|
||||||
|
|
|
@ -38,10 +38,6 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rawConfig, err := tlsConfig.Config()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var users []tuic.User
|
var users []tuic.User
|
||||||
for index, user := range options.Users {
|
for index, user := range options.Users {
|
||||||
if user.UUID == "" {
|
if user.UUID == "" {
|
||||||
|
@ -67,7 +63,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||||
server, err := tuic.NewServer(tuic.ServerOptions{
|
server, err := tuic.NewServer(tuic.ServerOptions{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
TLSConfig: rawConfig,
|
TLSConfig: tlsConfig,
|
||||||
Users: users,
|
Users: users,
|
||||||
CongestionControl: options.CongestionControl,
|
CongestionControl: options.CongestionControl,
|
||||||
AuthTimeout: time.Duration(options.AuthTimeout),
|
AuthTimeout: time.Duration(options.AuthTimeout),
|
||||||
|
|
|
@ -50,8 +50,8 @@ type InboundECHOptions struct {
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
|
||||||
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
|
||||||
Key Listable[string] `json:"ech_keys,omitempty"`
|
Key Listable[string] `json:"key,omitempty"`
|
||||||
KeyPath string `json:"ech_keys_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundECHOptions struct {
|
type OutboundECHOptions struct {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/sagernet/quic-go/congestion"
|
"github.com/sagernet/quic-go/congestion"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
@ -33,7 +34,7 @@ type Hysteria struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig *tls.STDConfig
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
authKey []byte
|
authKey []byte
|
||||||
xplusKey []byte
|
xplusKey []byte
|
||||||
|
@ -52,17 +53,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
abstractTLSConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig, err := abstractTLSConfig.Config()
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
if err != nil {
|
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig.MinVersion = tls.VersionTLS13
|
|
||||||
if len(tlsConfig.NextProtos) == 0 {
|
|
||||||
tlsConfig.NextProtos = []string{hysteria.DefaultALPN}
|
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
||||||
|
@ -182,7 +178,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
|
||||||
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
}
|
}
|
||||||
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
quicConn, err := quic.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
|
quicConn, err := qtls.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -41,11 +41,7 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
abstractTLSConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConfig, err := abstractTLSConfig.Config()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestECH(t *testing.T) {
|
func TestECH(t *testing.T) {
|
||||||
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
echConfig, echKey := common.Must2(tls.ECHKeygenDefault("example.org", false))
|
echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false))
|
||||||
startInstance(t, option.Options{
|
startInstance(t, option.Options{
|
||||||
Inbounds: []option.Inbound{
|
Inbounds: []option.Inbound{
|
||||||
{
|
{
|
||||||
|
@ -89,3 +91,80 @@ func TestECH(t *testing.T) {
|
||||||
})
|
})
|
||||||
testSuit(t, clientPort, testPort)
|
testSuit(t, clientPort, testPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestECHQUIC(t *testing.T) {
|
||||||
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false))
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeMixed,
|
||||||
|
Tag: "mixed-in",
|
||||||
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: clientPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeTUIC,
|
||||||
|
TUICOptions: option.TUICInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []option.TUICUser{{
|
||||||
|
UUID: uuid.Nil.String(),
|
||||||
|
}},
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
ECH: &option.InboundECHOptions{
|
||||||
|
Enabled: true,
|
||||||
|
Key: []string{echKey},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeDirect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: C.TypeTUIC,
|
||||||
|
Tag: "tuic-out",
|
||||||
|
TUICOptions: option.TUICOutboundOptions{
|
||||||
|
ServerOptions: option.ServerOptions{
|
||||||
|
Server: "127.0.0.1",
|
||||||
|
ServerPort: serverPort,
|
||||||
|
},
|
||||||
|
UUID: uuid.Nil.String(),
|
||||||
|
TLS: &option.OutboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
ECH: &option.OutboundECHOptions{
|
||||||
|
Enabled: true,
|
||||||
|
Config: []string{echConfig},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Inbound: []string{"mixed-in"},
|
||||||
|
Outbound: "tuic-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testSuitLargeUDP(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -10,6 +11,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/baderror"
|
"github.com/sagernet/sing/common/baderror"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
@ -25,7 +28,7 @@ type ClientOptions struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Dialer N.Dialer
|
Dialer N.Dialer
|
||||||
ServerAddress M.Socksaddr
|
ServerAddress M.Socksaddr
|
||||||
TLSConfig *tls.Config
|
TLSConfig tls.Config
|
||||||
UUID uuid.UUID
|
UUID uuid.UUID
|
||||||
Password string
|
Password string
|
||||||
CongestionControl string
|
CongestionControl string
|
||||||
|
@ -38,7 +41,7 @@ type Client struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig *tls.Config
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
password string
|
password string
|
||||||
|
@ -108,9 +111,9 @@ func (c *Client) offerNew(ctx context.Context) (*clientQUICConnection, error) {
|
||||||
}
|
}
|
||||||
var quicConn quic.Connection
|
var quicConn quic.Connection
|
||||||
if c.zeroRTTHandshake {
|
if c.zeroRTTHandshake {
|
||||||
quicConn, err = quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
quicConn, err = qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
||||||
} else {
|
} else {
|
||||||
quicConn, err = quic.Dial(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
quicConn, err = qtls.Dial(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
udpConn.Close()
|
udpConn.Close()
|
||||||
|
@ -141,13 +144,13 @@ func (c *Client) offerNew(ctx context.Context) (*clientQUICConnection, error) {
|
||||||
func (c *Client) clientHandshake(conn quic.Connection) error {
|
func (c *Client) clientHandshake(conn quic.Connection) error {
|
||||||
authStream, err := conn.OpenUniStream()
|
authStream, err := conn.OpenUniStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause(err, "open handshake stream")
|
||||||
}
|
}
|
||||||
defer authStream.Close()
|
defer authStream.Close()
|
||||||
handshakeState := conn.ConnectionState().TLS
|
handshakeState := conn.ConnectionState()
|
||||||
tuicAuthToken, err := handshakeState.ExportKeyingMaterial(string(c.uuid[:]), []byte(c.password), 32)
|
tuicAuthToken, err := handshakeState.ExportKeyingMaterial(string(c.uuid[:]), []byte(c.password), 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause(err, "export keying material")
|
||||||
}
|
}
|
||||||
authRequest := buf.NewSize(AuthenticateLen)
|
authRequest := buf.NewSize(AuthenticateLen)
|
||||||
authRequest.WriteByte(Version)
|
authRequest.WriteByte(Version)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -13,6 +14,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
"github.com/sagernet/sing/common/baderror"
|
"github.com/sagernet/sing/common/baderror"
|
||||||
|
@ -29,7 +32,7 @@ import (
|
||||||
type ServerOptions struct {
|
type ServerOptions struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Logger logger.Logger
|
Logger logger.Logger
|
||||||
TLSConfig *tls.Config
|
TLSConfig tls.ServerConfig
|
||||||
Users []User
|
Users []User
|
||||||
CongestionControl string
|
CongestionControl string
|
||||||
AuthTimeout time.Duration
|
AuthTimeout time.Duration
|
||||||
|
@ -52,7 +55,7 @@ type ServerHandler interface {
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
tlsConfig *tls.Config
|
tlsConfig tls.ServerConfig
|
||||||
heartbeat time.Duration
|
heartbeat time.Duration
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
userMap map[uuid.UUID]User
|
userMap map[uuid.UUID]User
|
||||||
|
@ -107,7 +110,7 @@ func NewServer(options ServerOptions) (*Server, error) {
|
||||||
|
|
||||||
func (s *Server) Start(conn net.PacketConn) error {
|
func (s *Server) Start(conn net.PacketConn) error {
|
||||||
if !s.quicConfig.Allow0RTT {
|
if !s.quicConfig.Allow0RTT {
|
||||||
listener, err := quic.Listen(conn, s.tlsConfig, s.quicConfig)
|
listener, err := qtls.Listen(conn, s.tlsConfig, s.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +130,7 @@ func (s *Server) Start(conn net.PacketConn) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
listener, err := quic.ListenEarly(conn, s.tlsConfig, s.quicConfig)
|
listener, err := qtls.ListenEarly(conn, s.tlsConfig, s.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -247,7 +250,7 @@ func (s *serverSession) handleUniStream(stream quic.ReceiveStream) error {
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("authentication: unknown user ", userUUID)
|
return E.New("authentication: unknown user ", userUUID)
|
||||||
}
|
}
|
||||||
handshakeState := s.quicConn.ConnectionState().TLS
|
handshakeState := s.quicConn.ConnectionState()
|
||||||
tuicToken, err := handshakeState.ExportKeyingMaterial(string(user.UUID[:]), []byte(user.Password), 32)
|
tuicToken, err := handshakeState.ExportKeyingMaterial(string(user.UUID[:]), []byte(user.Password), 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "authentication: export keying material")
|
return E.Cause(err, "authentication: export keying material")
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package tuic
|
package tuic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package v2rayquic
|
package v2rayquic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
@ -23,7 +26,7 @@ type Client struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig *tls.STDConfig
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn quic.Connection
|
conn quic.Connection
|
||||||
|
@ -34,18 +37,14 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
||||||
}
|
}
|
||||||
stdConfig, err := tlsConfig.Config()
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
if err != nil {
|
tlsConfig.SetNextProtos([]string{"h2", "http/1.1"})
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(stdConfig.NextProtos) == 0 {
|
|
||||||
stdConfig.NextProtos = []string{"h2", "http/1.1"}
|
|
||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
tlsConfig: stdConfig,
|
tlsConfig: tlsConfig,
|
||||||
quicConfig: quicConfig,
|
quicConfig: quicConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -75,7 +74,7 @@ func (c *Client) offerNew() (quic.Connection, error) {
|
||||||
}
|
}
|
||||||
var packetConn net.PacketConn
|
var packetConn net.PacketConn
|
||||||
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
||||||
quicConn, err := quic.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
quicConn, err := qtls.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package v2rayquic
|
package v2rayquic
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/transport/v2ray"
|
import "github.com/sagernet/sing-box/transport/v2ray"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
package v2rayquic
|
package v2rayquic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -7,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/qtls"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
@ -20,27 +23,23 @@ var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
tlsConfig *tls.STDConfig
|
tlsConfig tls.ServerConfig
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
handler adapter.V2RayServerTransportHandler
|
handler adapter.V2RayServerTransportHandler
|
||||||
udpListener net.PacketConn
|
udpListener net.PacketConn
|
||||||
quicListener *quic.Listener
|
quicListener qtls.QUICListener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
|
func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
||||||
}
|
}
|
||||||
stdConfig, err := tlsConfig.Config()
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
if err != nil {
|
tlsConfig.SetNextProtos([]string{"h2", "http/1.1"})
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(stdConfig.NextProtos) == 0 {
|
|
||||||
stdConfig.NextProtos = []string{"h2", "http/1.1"}
|
|
||||||
}
|
}
|
||||||
server := &Server{
|
server := &Server{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
tlsConfig: stdConfig,
|
tlsConfig: tlsConfig,
|
||||||
quicConfig: quicConfig,
|
quicConfig: quicConfig,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
}
|
}
|
||||||
|
@ -56,7 +55,7 @@ func (s *Server) Serve(listener net.Listener) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServePacket(listener net.PacketConn) error {
|
func (s *Server) ServePacket(listener net.PacketConn) error {
|
||||||
quicListener, err := quic.Listen(listener, s.tlsConfig, s.quicConfig)
|
quicListener, err := qtls.Listen(listener, s.tlsConfig, s.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -92,5 +91,5 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Close() error {
|
func (s *Server) Close() error {
|
||||||
return common.Close(s.udpListener, common.PtrOrNil(s.quicListener))
|
return common.Close(s.udpListener, s.quicListener)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue