Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines

This commit is contained in:
世界
2026-04-14 22:59:46 +08:00
parent d725846081
commit c1da99eede
111 changed files with 14959 additions and 2588 deletions
+1 -7
View File
@@ -77,15 +77,9 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
TLSConfig: tlsConfig,
QUICOptions: buildInboundQUICOptions(options),
UDPTimeout: udpTimeout,
Handler: inbound,
// Legacy options
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindowClient,
MaxIncomingStreams: int64(options.MaxConnClient),
DisableMTUDiscovery: options.DisableMTUDiscovery,
})
if err != nil {
return nil, err
+13 -15
View File
@@ -70,21 +70,19 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}
client, err := hysteria.NewClient(hysteria.ClientOptions{
Context: ctx,
Dialer: outboundDialer,
Logger: logger,
ServerAddress: options.ServerOptions.Build(),
ServerPorts: options.ServerPorts,
HopInterval: time.Duration(options.HopInterval),
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
Password: password,
TLSConfig: tlsConfig,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindow,
DisableMTUDiscovery: options.DisableMTUDiscovery,
Context: ctx,
Dialer: outboundDialer,
Logger: logger,
ServerAddress: options.ServerOptions.Build(),
ServerPorts: options.ServerPorts,
HopInterval: time.Duration(options.HopInterval),
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
Password: password,
TLSConfig: tlsConfig,
QUICOptions: buildOutboundQUICOptions(options),
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
})
if err != nil {
return nil, err
+49
View File
@@ -0,0 +1,49 @@
package hysteria
import (
"github.com/sagernet/sing-box/option"
qtls "github.com/sagernet/sing-quic"
)
func buildBaseQUICOptions(options option.QUICOptions) qtls.QUICOptions {
return qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),
KeepAlivePeriod: options.KeepAlivePeriod.Build(),
StreamReceiveWindow: options.StreamReceiveWindow.Value(),
ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(),
MaxConcurrentStreams: options.MaxConcurrentStreams,
InitialPacketSize: options.InitialPacketSize,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
}
}
func buildInboundQUICOptions(options option.HysteriaInboundOptions) qtls.QUICOptions {
quicOptions := buildBaseQUICOptions(options.QUICOptions)
if quicOptions.ConnectionReceiveWindow == 0 {
quicOptions.ConnectionReceiveWindow = options.ReceiveWindowConn //nolint:staticcheck
}
if quicOptions.StreamReceiveWindow == 0 {
quicOptions.StreamReceiveWindow = options.ReceiveWindowClient //nolint:staticcheck
}
if quicOptions.MaxConcurrentStreams == 0 {
quicOptions.MaxConcurrentStreams = options.MaxConnClient //nolint:staticcheck
}
if !quicOptions.DisablePathMTUDiscovery {
quicOptions.DisablePathMTUDiscovery = options.DisableMTUDiscovery //nolint:staticcheck
}
return quicOptions
}
func buildOutboundQUICOptions(options option.HysteriaOutboundOptions) qtls.QUICOptions {
quicOptions := buildBaseQUICOptions(options.QUICOptions)
if quicOptions.ConnectionReceiveWindow == 0 {
quicOptions.ConnectionReceiveWindow = options.ReceiveWindowConn //nolint:staticcheck
}
if quicOptions.StreamReceiveWindow == 0 {
quicOptions.StreamReceiveWindow = options.ReceiveWindow //nolint:staticcheck
}
if !quicOptions.DisablePathMTUDiscovery {
quicOptions.DisablePathMTUDiscovery = options.DisableMTUDiscovery //nolint:staticcheck
}
return quicOptions
}
+17 -7
View File
@@ -15,6 +15,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
@@ -114,13 +115,22 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
udpTimeout = C.UDPTimeout
}
service, err := hysteria2.NewService[int](hysteria2.ServiceOptions{
Context: ctx,
Logger: logger,
BrutalDebug: options.BrutalDebug,
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
SalamanderPassword: salamanderPassword,
TLSConfig: tlsConfig,
Context: ctx,
Logger: logger,
BrutalDebug: options.BrutalDebug,
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
SalamanderPassword: salamanderPassword,
TLSConfig: tlsConfig,
QUICOptions: qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),
KeepAlivePeriod: options.KeepAlivePeriod.Build(),
StreamReceiveWindow: options.StreamReceiveWindow.Value(),
ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(),
MaxConcurrentStreams: options.MaxConcurrentStreams,
InitialPacketSize: options.InitialPacketSize,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
},
IgnoreClientBandwidth: options.IgnoreClientBandwidth,
UDPTimeout: udpTimeout,
Handler: inbound,
+12 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/tuic"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
@@ -79,8 +80,17 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
SalamanderPassword: salamanderPassword,
Password: options.Password,
TLSConfig: tlsConfig,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
BBRProfile: options.BBRProfile,
QUICOptions: qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),
KeepAlivePeriod: options.KeepAlivePeriod.Build(),
StreamReceiveWindow: options.StreamReceiveWindow.Value(),
ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(),
MaxConcurrentStreams: options.MaxConcurrentStreams,
InitialPacketSize: options.InitialPacketSize,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
},
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
BBRProfile: options.BBRProfile,
})
if err != nil {
return nil, err
@@ -39,9 +39,6 @@ func NewCertificateProvider(ctx context.Context, _ log.ContextLogger, tag string
return nil, E.New("missing tailscale endpoint tag")
}
endpointManager := service.FromContext[adapter.EndpointManager](ctx)
if endpointManager == nil {
return nil, E.New("missing endpoint manager in context")
}
rawEndpoint, loaded := endpointManager.Get(options.Endpoint)
if !loaded {
return nil, E.New("endpoint not found: ", options.Endpoint)
+22 -15
View File
@@ -4,7 +4,6 @@ package tailscale
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
@@ -28,6 +27,7 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule"
@@ -41,7 +41,6 @@ import (
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager"
tailscaleroot "github.com/sagernet/tailscale"
@@ -199,6 +198,19 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
// controlplane.tailscale.com
remoteIsDomain = true
}
hasLegacyDialer := !reflect.DeepEqual(options.DialerOptions, option.DialerOptions{})
hasControlHTTPClient := options.ControlHTTPClient != nil && !options.ControlHTTPClient.IsEmpty()
if hasLegacyDialer && hasControlHTTPClient {
return nil, E.New("control_http_client is conflict with deprecated dialer options")
}
controlHTTPClientOptions := common.PtrValueOrDefault(options.ControlHTTPClient)
if hasLegacyDialer {
deprecated.Report(ctx, deprecated.OptionLegacyTailscaleEndpointDialer)
controlHTTPClientOptions.DialerOptions = options.DialerOptions
}
if remoteIsDomain {
controlHTTPClientOptions.ResolveOnDetour = true
}
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
@@ -210,6 +222,12 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
httpClientManager := service.FromContext[adapter.HTTPClientManager](ctx)
controlTransport, err := httpClientManager.ResolveTransport(ctx, logger, controlHTTPClientOptions)
if err != nil {
return nil, E.Cause(err, "create control HTTP client")
}
controlHTTPClient := &http.Client{Transport: controlTransport}
server := &tsnet.Server{
Dir: stateDirectory,
Hostname: hostname,
@@ -227,19 +245,8 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) {
return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions())
},
DNS: &dnsConfigurtor{},
HTTPClient: &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address))
},
TLSClientConfig: &tls.Config{
RootCAs: adapter.RootPoolFromContext(ctx),
Time: ntp.TimeFuncFromContext(ctx),
},
},
},
DNS: &dnsConfigurtor{},
HTTPClient: controlHTTPClient,
}
return &Endpoint{
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
+7 -6
View File
@@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/proxybridge"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -34,7 +35,7 @@ type Outbound struct {
outbound.Adapter
ctx context.Context
logger logger.ContextLogger
proxy *ProxyListener
proxy *proxybridge.Bridge
startConf *tor.StartConf
options map[string]string
events chan control.Event
@@ -79,11 +80,15 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
proxy, err := proxybridge.New(ctx, logger, "proxy", outboundDialer)
if err != nil {
return nil, err
}
return &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, tag, []string{N.NetworkTCP}, options.DialerOptions),
ctx: ctx,
logger: logger,
proxy: NewProxyListener(ctx, logger, outboundDialer),
proxy: proxy,
startConf: &startConf,
options: options.Options,
}, nil
@@ -117,10 +122,6 @@ func (t *Outbound) start() error {
return err
}
go t.recvLoop()
err = t.proxy.Start()
if err != nil {
return err
}
proxyPort := "127.0.0.1:" + F.ToString(t.proxy.Port())
proxyUsername := t.proxy.Username()
proxyPassword := t.proxy.Password()
-121
View File
@@ -1,121 +0,0 @@
package tor
import (
std_bufio "bufio"
"context"
"crypto/rand"
"encoding/hex"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/service"
)
type ProxyListener struct {
ctx context.Context
logger log.ContextLogger
dialer N.Dialer
connection adapter.ConnectionManager
tcpListener *net.TCPListener
username string
password string
authenticator *auth.Authenticator
}
func NewProxyListener(ctx context.Context, logger log.ContextLogger, dialer N.Dialer) *ProxyListener {
var usernameB [64]byte
var passwordB [64]byte
rand.Read(usernameB[:])
rand.Read(passwordB[:])
username := hex.EncodeToString(usernameB[:])
password := hex.EncodeToString(passwordB[:])
return &ProxyListener{
ctx: ctx,
logger: logger,
dialer: dialer,
connection: service.FromContext[adapter.ConnectionManager](ctx),
authenticator: auth.NewAuthenticator([]auth.User{{Username: username, Password: password}}),
username: username,
password: password,
}
}
func (l *ProxyListener) Start() error {
tcpListener, err := net.ListenTCP("tcp", &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
})
if err != nil {
return err
}
l.tcpListener = tcpListener
go l.acceptLoop()
return nil
}
func (l *ProxyListener) Port() uint16 {
if l.tcpListener == nil {
panic("start listener first")
}
return M.SocksaddrFromNet(l.tcpListener.Addr()).Port
}
func (l *ProxyListener) Username() string {
return l.username
}
func (l *ProxyListener) Password() string {
return l.password
}
func (l *ProxyListener) Close() error {
return common.Close(l.tcpListener)
}
func (l *ProxyListener) acceptLoop() {
for {
tcpConn, err := l.tcpListener.AcceptTCP()
if err != nil {
return
}
ctx := log.ContextWithNewID(l.ctx)
go func() {
hErr := l.accept(ctx, tcpConn)
if hErr != nil {
if E.IsClosedOrCanceled(hErr) {
l.logger.DebugContext(ctx, E.Cause(hErr, "proxy connection closed"))
return
}
l.logger.ErrorContext(ctx, E.Cause(hErr, "proxy"))
}
}()
}
}
func (l *ProxyListener) accept(ctx context.Context, conn *net.TCPConn) error {
return socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), l.authenticator, l, nil, 0, M.SocksaddrFromNet(conn.RemoteAddr()), nil)
}
func (l *ProxyListener) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
metadata.Network = N.NetworkTCP
l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination)
l.connection.NewConnection(ctx, l.dialer, conn, metadata, onClose)
}
func (l *ProxyListener) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
var metadata adapter.InboundContext
metadata.Source = source
metadata.Destination = destination
metadata.Network = N.NetworkUDP
l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination)
l.connection.NewPacketConnection(ctx, l.dialer, conn, metadata, onClose)
}
+13 -3
View File
@@ -13,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing-quic/tuic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
@@ -64,9 +65,18 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
udpTimeout = C.UDPTimeout
}
service, err := tuic.NewService[int](tuic.ServiceOptions{
Context: ctx,
Logger: logger,
TLSConfig: tlsConfig,
Context: ctx,
Logger: logger,
TLSConfig: tlsConfig,
QUICOptions: qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),
KeepAlivePeriod: options.KeepAlivePeriod.Build(),
StreamReceiveWindow: options.StreamReceiveWindow.Value(),
ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(),
MaxConcurrentStreams: options.MaxConcurrentStreams,
InitialPacketSize: options.InitialPacketSize,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
},
CongestionControl: options.CongestionControl,
AuthTimeout: time.Duration(options.AuthTimeout),
ZeroRTTHandshake: options.ZeroRTTHandshake,
+14 -4
View File
@@ -13,6 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing-quic/tuic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
@@ -65,10 +66,19 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
return nil, err
}
client, err := tuic.NewClient(tuic.ClientOptions{
Context: ctx,
Dialer: outboundDialer,
ServerAddress: options.ServerOptions.Build(),
TLSConfig: tlsConfig,
Context: ctx,
Dialer: outboundDialer,
ServerAddress: options.ServerOptions.Build(),
TLSConfig: tlsConfig,
QUICOptions: qtls.QUICOptions{
IdleTimeout: options.IdleTimeout.Build(),
KeepAlivePeriod: options.KeepAlivePeriod.Build(),
StreamReceiveWindow: options.StreamReceiveWindow.Value(),
ConnectionReceiveWindow: options.ConnectionReceiveWindow.Value(),
MaxConcurrentStreams: options.MaxConcurrentStreams,
InitialPacketSize: options.InitialPacketSize,
DisablePathMTUDiscovery: options.DisablePathMTUDiscovery,
},
UUID: userUUID,
Password: options.Password,
CongestionControl: options.CongestionControl,