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

This commit is contained in:
世界
2026-04-14 22:59:46 +08:00
parent 94b4a4e718
commit 0f3d72cc9a
113 changed files with 13622 additions and 8053 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ type ACMECertificateProviderOptions struct {
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"`
KeyType ACMEKeyType `json:"key_type,omitempty"`
Detour string `json:"detour,omitempty"`
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
}
type _ACMEProviderDNS01ChallengeOptions struct {
+126
View File
@@ -0,0 +1,126 @@
package option
import (
"reflect"
"github.com/sagernet/sing/common/byteformats"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
)
type HTTP2Options struct {
IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
KeepAlivePeriod badoption.Duration `json:"keep_alive_period,omitempty"`
StreamReceiveWindow byteformats.MemoryBytes `json:"stream_receive_window,omitempty"`
ConnectionReceiveWindow byteformats.MemoryBytes `json:"connection_receive_window,omitempty"`
MaxConcurrentStreams int `json:"max_concurrent_streams,omitempty"`
}
type QUICOptions struct {
HTTP2Options
InitialPacketSize int `json:"initial_packet_size,omitempty"`
DisablePathMTUDiscovery bool `json:"disable_path_mtu_discovery,omitempty"`
}
type _HTTPClientOptions struct {
Tag string `json:"tag,omitempty"`
Engine string `json:"engine,omitempty"`
Version int `json:"version,omitempty"`
DisableVersionFallback bool `json:"disable_version_fallback,omitempty"`
Headers badoption.HTTPHeader `json:"headers,omitempty"`
HTTP2Options HTTP2Options `json:"-"`
HTTP3Options QUICOptions `json:"-"`
DefaultOutbound bool `json:"-"`
ResolveOnDetour bool `json:"-"`
DirectResolver bool `json:"-"`
OutboundTLSOptionsContainer
DialerOptions
}
type (
HTTPClient _HTTPClientOptions
HTTPClientOptions _HTTPClientOptions
)
func (h HTTPClient) Options() HTTPClientOptions {
options := HTTPClientOptions(h)
options.Tag = ""
return options
}
func (o HTTPClientOptions) IsEmpty() bool {
if o.Tag != "" {
return false
}
o.DefaultOutbound = false
o.ResolveOnDetour = false
o.DirectResolver = false
return reflect.ValueOf(_HTTPClientOptions(o)).IsZero()
}
func (o HTTPClientOptions) MarshalJSON() ([]byte, error) {
if o.Tag != "" {
return json.Marshal(o.Tag)
}
return badjson.MarshallObjects(_HTTPClientOptions(o), httpClientVariant(_HTTPClientOptions(o)))
}
func (o *HTTPClientOptions) UnmarshalJSON(content []byte) error {
if len(content) > 0 && content[0] == '"' {
*o = HTTPClientOptions{}
return json.Unmarshal(content, &o.Tag)
}
var options _HTTPClientOptions
err := json.Unmarshal(content, &options)
if err != nil {
return err
}
err = unmarshalHTTPClientVersionOptions(content, &options, &options)
if err != nil {
return err
}
options.Tag = ""
*o = HTTPClientOptions(options)
return nil
}
func (h HTTPClient) MarshalJSON() ([]byte, error) {
return badjson.MarshallObjects(_HTTPClientOptions(h), httpClientVariant(_HTTPClientOptions(h)))
}
func (h *HTTPClient) UnmarshalJSON(content []byte) error {
err := json.Unmarshal(content, (*_HTTPClientOptions)(h))
if err != nil {
return err
}
return unmarshalHTTPClientVersionOptions(content, (*_HTTPClientOptions)(h), (*_HTTPClientOptions)(h))
}
func unmarshalHTTPClientVersionOptions(content []byte, baseStruct any, options *_HTTPClientOptions) error {
switch options.Version {
case 1:
return json.UnmarshalDisallowUnknownFields(content, baseStruct)
case 0, 2:
options.Version = 2
return badjson.UnmarshallExcluded(content, baseStruct, &options.HTTP2Options)
case 3:
return badjson.UnmarshallExcluded(content, baseStruct, &options.HTTP3Options)
default:
return E.New("unknown HTTP version: ", options.Version)
}
}
func httpClientVariant(options _HTTPClientOptions) any {
switch options.Version {
case 1:
return nil
case 0, 2:
return options.HTTP2Options
case 3:
return options.HTTP3Options
default:
return nil
}
}
+32 -23
View File
@@ -7,17 +7,22 @@ import (
type HysteriaInboundOptions struct {
ListenOptions
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
MaxConnClient int `json:"max_conn_client,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Users []HysteriaUser `json:"users,omitempty"`
// Deprecated: use QUIC fields instead
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
// Deprecated: use QUIC fields instead
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
// Deprecated: use QUIC fields instead
MaxConnClient int `json:"max_conn_client,omitempty"`
// Deprecated: use QUIC fields instead
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
InboundTLSOptionsContainer
QUICOptions
}
type HysteriaUser struct {
@@ -29,18 +34,22 @@ type HysteriaUser struct {
type HysteriaOutboundOptions struct {
DialerOptions
ServerOptions
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
ReceiveWindow uint64 `json:"recv_window,omitempty"`
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs string `json:"obfs,omitempty"`
Auth []byte `json:"auth,omitempty"`
AuthString string `json:"auth_str,omitempty"`
// Deprecated: use QUIC fields instead
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
// Deprecated: use QUIC fields instead
ReceiveWindow uint64 `json:"recv_window,omitempty"`
// Deprecated: use QUIC fields instead
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
QUICOptions
}
+2
View File
@@ -18,6 +18,7 @@ type Hysteria2InboundOptions struct {
Users []Hysteria2User `json:"users,omitempty"`
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
InboundTLSOptionsContainer
QUICOptions
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
BBRProfile string `json:"bbr_profile,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
@@ -122,6 +123,7 @@ type Hysteria2OutboundOptions struct {
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
QUICOptions
BBRProfile string `json:"bbr_profile,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
}
+19
View File
@@ -17,6 +17,7 @@ type _Options struct {
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
CertificateProviders []CertificateProvider `json:"certificate_providers,omitempty"`
HTTPClients []HTTPClient `json:"http_clients,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
@@ -61,6 +62,10 @@ func checkOptions(options *Options) error {
if err != nil {
return err
}
err = checkHTTPClients(options.HTTPClients)
if err != nil {
return err
}
return nil
}
@@ -79,6 +84,20 @@ func checkCertificateProviders(providers []CertificateProvider) error {
return nil
}
func checkHTTPClients(clients []HTTPClient) error {
seen := make(map[string]bool)
for _, client := range clients {
if client.Tag == "" {
return E.New("missing http client tag")
}
if seen[client.Tag] {
return E.New("duplicate http client tag: ", client.Tag)
}
seen[client.Tag] = true
}
return nil
}
func checkInbounds(inbounds []Inbound) error {
seen := make(map[string]bool)
for i, inbound := range inbounds {
+1 -1
View File
@@ -15,7 +15,7 @@ type CloudflareOriginCACertificateProviderOptions struct {
OriginCAKey string `json:"origin_ca_key,omitempty"`
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"`
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
Detour string `json:"detour,omitempty"`
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
}
type CloudflareOriginCARequestType string
+1
View File
@@ -20,6 +20,7 @@ type RouteOptions struct {
DefaultNetworkType badoption.Listable[InterfaceType] `json:"default_network_type,omitempty"`
DefaultFallbackNetworkType badoption.Listable[InterfaceType] `json:"default_fallback_network_type,omitempty"`
DefaultFallbackDelay badoption.Duration `json:"default_fallback_delay,omitempty"`
DefaultHTTPClient string `json:"default_http_client,omitempty"`
}
type GeoIPOptions struct {
+3 -1
View File
@@ -122,8 +122,10 @@ type LocalRuleSet struct {
type RemoteRuleSet struct {
URL string `json:"url"`
DownloadDetour string `json:"download_detour,omitempty"`
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
// Deprecated: use http_client instead
DownloadDetour string `json:"download_detour,omitempty"`
}
type _HeadlessRule struct {
+25 -8
View File
@@ -3,18 +3,20 @@ package option
import (
"net/netip"
"net/url"
"reflect"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata"
)
type TailscaleEndpointOptions struct {
// Deprecated: use control_http_client instead
DialerOptions
StateDirectory string `json:"state_directory,omitempty"`
AuthKey string `json:"auth_key,omitempty"`
ControlURL string `json:"control_url,omitempty"`
ControlHTTPClient *HTTPClientOptions `json:"control_http_client,omitempty"`
Ephemeral bool `json:"ephemeral,omitempty"`
Hostname string `json:"hostname,omitempty"`
AcceptRoutes bool `json:"accept_routes,omitempty"`
@@ -53,9 +55,13 @@ type DERPServiceOptions struct {
STUN *DERPSTUNListenOptions `json:"stun,omitempty"`
}
type _DERPVerifyClientURLOptions struct {
type _DERPVerifyClientURLBase struct {
URL string `json:"url,omitempty"`
DialerOptions
}
type _DERPVerifyClientURLOptions struct {
_DERPVerifyClientURLBase
HTTPClientOptions
}
type DERPVerifyClientURLOptions _DERPVerifyClientURLOptions
@@ -69,21 +75,32 @@ func (d DERPVerifyClientURLOptions) ServerIsDomain() bool {
}
func (d DERPVerifyClientURLOptions) MarshalJSON() ([]byte, error) {
if reflect.DeepEqual(d, _DERPVerifyClientURLOptions{}) {
if d.URL != "" && d.HTTPClientOptions.IsEmpty() {
return json.Marshal(d.URL)
} else {
return json.Marshal(_DERPVerifyClientURLOptions(d))
}
return badjson.MarshallObjects(d._DERPVerifyClientURLBase, HTTPClient(d.HTTPClientOptions))
}
func (d *DERPVerifyClientURLOptions) UnmarshalJSON(bytes []byte) error {
var stringValue string
err := json.Unmarshal(bytes, &stringValue)
if err == nil {
d.URL = stringValue
*d = DERPVerifyClientURLOptions{
_DERPVerifyClientURLBase: _DERPVerifyClientURLBase{URL: stringValue},
}
return nil
}
return json.Unmarshal(bytes, (*_DERPVerifyClientURLOptions)(d))
err = json.Unmarshal(bytes, &d._DERPVerifyClientURLBase)
if err != nil {
return err
}
var client HTTPClient
err = badjson.UnmarshallExcluded(bytes, &d._DERPVerifyClientURLBase, &client)
if err != nil {
return err
}
d.HTTPClientOptions = HTTPClientOptions(client)
return nil
}
type DERPMeshOptions struct {
+3
View File
@@ -28,6 +28,7 @@ type InboundTLSOptions struct {
KeyPath string `json:"key_path,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"`
CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"`
// Deprecated: use certificate_provider
@@ -100,6 +101,7 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
type OutboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
Engine string `json:"engine,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
@@ -120,6 +122,7 @@ type OutboundTLSOptions struct {
RecordFragment bool `json:"record_fragment,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
HandshakeTimeout badoption.Duration `json:"handshake_timeout,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Reality *OutboundRealityOptions `json:"reality,omitempty"`
+2
View File
@@ -10,6 +10,7 @@ type TUICInboundOptions struct {
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
InboundTLSOptionsContainer
QUICOptions
}
type TUICUser struct {
@@ -30,4 +31,5 @@ type TUICOutboundOptions struct {
Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
QUICOptions
}