1
0
Эх сурвалжийг харах

Add ECH support for NaiveProxy outbound and tls.ech.query_server_name option

- Enable ECH for NaiveProxy outbound with DNS resolver integration
- Add query_server_name option to override domain for ECH HTTPS record queries
- Update cronet-go dependency and remove windows_386 support
世界 1 долоо хоног өмнө
parent
commit
2e30ca2132

+ 1 - 1
.github/CRONET_GO_VERSION

@@ -1 +1 @@
-78951bf5e641fcb42bc82b73e70bd28d819e5e55
+4f12714a37cc546cbd171765e44988a348ba77fa

+ 17 - 6
common/tls/ech.go

@@ -51,6 +51,7 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
 		return &ECHClientConfig{
 			ECHCapableConfig: clientConfig,
 			dnsRouter:        service.FromContext[adapter.DNSRouter](ctx),
+			queryServerName:  options.ECH.QueryServerName,
 		}, nil
 	}
 }
@@ -108,10 +109,11 @@ func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
 
 type ECHClientConfig struct {
 	ECHCapableConfig
-	access     sync.Mutex
-	dnsRouter  adapter.DNSRouter
-	lastTTL    time.Duration
-	lastUpdate time.Time
+	access          sync.Mutex
+	dnsRouter       adapter.DNSRouter
+	queryServerName string
+	lastTTL         time.Duration
+	lastUpdate      time.Time
 }
 
 func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
@@ -130,13 +132,17 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
 	s.access.Lock()
 	defer s.access.Unlock()
 	if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Since(s.lastUpdate) > s.lastTTL {
+		queryServerName := s.queryServerName
+		if queryServerName == "" {
+			queryServerName = s.ServerName()
+		}
 		message := &mDNS.Msg{
 			MsgHdr: mDNS.MsgHdr{
 				RecursionDesired: true,
 			},
 			Question: []mDNS.Question{
 				{
-					Name:   mDNS.Fqdn(s.ServerName()),
+					Name:   mDNS.Fqdn(queryServerName),
 					Qtype:  mDNS.TypeHTTPS,
 					Qclass: mDNS.ClassINET,
 				},
@@ -175,7 +181,12 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
 }
 
 func (s *ECHClientConfig) Clone() Config {
-	return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
+	return &ECHClientConfig{
+		ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig),
+		dnsRouter:        s.dnsRouter,
+		queryServerName:  s.queryServerName,
+		lastUpdate:       s.lastUpdate,
+	}
 }
 
 func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

+ 1 - 1
docs/configuration/outbound/naive.md

@@ -86,7 +86,7 @@ See [UDP Over TCP](/configuration/shared/udp-over-tcp/) for details.
 
 TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
 
-Only `server_name`, `certificate`, `certificate_path` and `certificate_public_key_sha256` are supported.
+Only `server_name`, `certificate`, `certificate_path`, `certificate_public_key_sha256` and `ech` are supported.
 
 ### Dial Fields
 

+ 1 - 1
docs/configuration/outbound/naive.zh.md

@@ -86,7 +86,7 @@ UDP over TCP 配置。
 
 TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
 
-只有 `server_name`、`certificate`、`certificate_path` 和 `certificate_public_key_sha256` 是被支持的。
+只有 `server_name`、`certificate`、`certificate_path`、`certificate_public_key_sha256` 和 `ech` 是被支持的。
 
 ### 拨号字段
 

+ 12 - 0
docs/configuration/shared/tls.md

@@ -14,6 +14,7 @@ icon: material/new-box
     :material-plus: [client_key_path](#client_key_path)
     :material-plus: [client_authentication](#client_authentication)
     :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
+    :material-plus: [ech.query_server_name](#query_server_name)
 
 !!! quote "Changes in sing-box 1.12.0"
 
@@ -118,6 +119,7 @@ icon: material/new-box
     "enabled": false,
     "config": [],
     "config_path": "",
+    "query_server_name": "",
 
     // Deprecated
     "pq_signature_schemes_enabled": false,
@@ -505,6 +507,16 @@ The path to ECH configuration, in PEM format.
 
 If empty, load from DNS will be attempted.
 
+#### query_server_name
+
+!!! question "Since sing-box 1.13.0"
+
+==Client only==
+
+Overrides the domain name used for ECH HTTPS record queries.
+
+If empty, `server_name` is used for queries.
+
 #### fragment
 
 !!! question "Since sing-box 1.12.0"

+ 12 - 0
docs/configuration/shared/tls.zh.md

@@ -14,6 +14,7 @@ icon: material/new-box
     :material-plus: [client_key_path](#client_key_path)
     :material-plus: [client_authentication](#client_authentication)
     :material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
+    :material-plus: [ech.query_server_name](#query_server_name)
 
 !!! quote "sing-box 1.12.0 中的更改"
 
@@ -118,6 +119,7 @@ icon: material/new-box
     "enabled": false,
     "config": [],
     "config_path": "",
+    "query_server_name": "",
 
     // 废弃的
     "pq_signature_schemes_enabled": false,
@@ -503,6 +505,16 @@ ECH 配置路径,PEM 格式。
 
 如果为空,将尝试从 DNS 加载。
 
+#### query_server_name
+
+!!! question "自 sing-box 1.13.0 起"
+
+==仅客户端==
+
+覆盖用于 ECH HTTPS 记录查询的域名。
+
+如果为空,使用 `server_name` 查询。
+
 #### fragment
 
 !!! question "自 sing-box 1.12.0 起"

+ 24 - 25
go.mod

@@ -26,8 +26,8 @@ require (
 	github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
 	github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
 	github.com/sagernet/cors v1.2.1
-	github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f
-	github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f
+	github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585
+	github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585
 	github.com/sagernet/fswatch v0.1.1
 	github.com/sagernet/gomobile v0.1.10
 	github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
@@ -108,29 +108,28 @@ require (
 	github.com/prometheus-community/pro-bing v0.4.0 // indirect
 	github.com/quic-go/qpack v0.6.0 // indirect
 	github.com/safchain/ethtool v0.3.0 // indirect
-	github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 // indirect
-	github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 // indirect
+	github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f // indirect
+	github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f // indirect
 	github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
 	github.com/sagernet/nftables v0.3.0-beta.4 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect

+ 48 - 50
go.sum

@@ -154,56 +154,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
 github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
 github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
-github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f h1:VMlH3aVnoN0bDUUCXHRJ6o8pb3ZJe/XpRLZsUGljrJ0=
-github.com/sagernet/cronet-go v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo=
-github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f h1:eZcIuGEbGT9Ldh4QP14UxVzWOeMpwltU8lWCthefaGw=
-github.com/sagernet/cronet-go/all v0.0.0-20251217073804-0aadbdd7485f/go.mod h1:qt0I3+OORnNmYDKKg3sN2/JguaNNl3ckgdoYEMeMGSA=
-github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1 h1:JPrpsOAMZL7WpRfRymdazS/LTguAJVTqTPgIM1hRGSk=
-github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
-github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1 h1:yKOa/aPDWyB8+Vw2bze3lczwA0T+Jp014F82bb04RZg=
-github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
-github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1 h1:bFWOKRv1gadjBtcCj5eSMVITdM/nFAM9WRvHGrFkW2M=
-github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
-github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1 h1:rwk5kw6tggldlnYkufttjrLgNZ4m2oDj8VPVbUkMIj8=
-github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
-github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Kz7898MkdKck/F3KhpQ3iSbDvuMcBKgcIT9kblbEAag=
-github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
-github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1 h1:Csm66Pf4aFnrKW9SQ7/QAD+ODxrXM1y/1tOsMeWJt2Q=
-github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
-github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:xYt0vGrcBe5E/4S1AhZrXZKqzfyMVv2EJzms+hkyMwE=
-github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
-github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1 h1:HUIwo+G+gp5xJO7oFLXE8wUBuAMSlZCff7x8BkGwrV8=
-github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
-github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:aM0ZlZyaMsj+ckqffWuwQ/LvlEocp4+8jf0CS3Y7C08=
-github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
-github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1 h1:vAFYo+d3JZauxyW2fL8Kqqzjx2CoMSNYwS3mj2O3vig=
-github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
-github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1 h1:vitvkHM3isRtAt6J/hJbi1YEkkmoJQYxCWFyWBzetK0=
-github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
-github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1 h1:Y5OW7wtkGkAICo1c4TjsSVsRe0YY0rGXBZS3W4kWEMM=
-github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
-github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1 h1:U4fj5bK6Bp/Fc3OaQT2+pXioRTC4NkWXD8M+kcJIXx0=
-github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
-github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1 h1:6aMDLdA+GF0J6CPBAXh6yn8wCJHhmREIU8I/uCVl16w=
-github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217073321-98bf559282b1/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
-github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1 h1:OvZQkFRfs5fhN1ZNEjBRj1foZfwP9EdYhLFzrF23TO4=
-github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
-github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1 h1:HJYoVGlafnfo7qwsNgocIwqg/ScCWH3MwC7oqD4Hj3I=
-github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
-github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1 h1:U/eE0HxhS6iJxBhvt2mmkxvAuwjGQ6SfGz0IZc0NWQE=
-github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217073321-98bf559282b1/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
-github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1 h1:V8mKJHu2iMoHjXH2deNQAY+Itcn9mlTeLDqhrFXkwcY=
-github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
-github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1 h1:uHy+Un5hEvONSH5+nWHLWisvfhPIAE0EWq8cLjJpAuU=
-github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
-github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1 h1:IXNOyK596srcY+OtnrCrUdclKVL5lakKcO30H85Kryc=
-github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217073321-98bf559282b1/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
-github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1 h1:aPMCg3h46G8Mx5WIiYyPTr/1EQ8j4wgCI/HUPGm/occ=
-github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251217073321-98bf559282b1/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk=
-github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1 h1:snfESeRYtwkOlfQyoxfHKVNj4N0cpIpxMfsM+y52rw4=
-github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
-github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1 h1:weCViGxMO6iajrI1EjHH4hMT/cXwGc+HhXH24xUrVGE=
-github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217073321-98bf559282b1/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
+github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585 h1:fu/J7Z+umFy5JwIxo2/7ixvuMRouJx93VU0RUXQx8aI=
+github.com/sagernet/cronet-go v0.0.0-20251217133746-1955399f1585/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
+github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585 h1:QN3WvGKiBg9N5vrSVKJzNTmK/BfEVMJNbRZDOlOfUpo=
+github.com/sagernet/cronet-go/all v0.0.0-20251217133746-1955399f1585/go.mod h1:JTqmHTkUqWDX5Hz+jnx8kdDKJ07U80QS1hN0Wfe7CTI=
+github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f h1:hjyZ7QZWWxYEW13lBT3+yAk7yENgKBuqhiw5rl/oxng=
+github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
+github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:RHPD2Opa4kGqlTtHyhtMWhzvr3F1PZsT6GW+IMBaMYs=
+github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
+github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f h1:wPrD+ADgFZC7AFFqUDWaKdcElqGODX10WrNpmUywX00=
+github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
+github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:MC0T2f2SXwai6hdmTkkulE7PlHA11lHp1re7alZCQLo=
+github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
+github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:X3RxBK5D5P3fcb+j8HqiEwhXEGMZkCg9ecmGYHo2qpg=
+github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
+github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:K/CJaXL1djRbHyoVHGNx9nacncZPKE0QkEoO12nWL4s=
+github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
+github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:uN/ay0BtmWWPdaoAsK5yu+bbQfr6iujEUTo7zZhF3a4=
+github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
+github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:vdDIjy2y8DQEu1tVkpS/WOx0fEbK4yjMQv7X1e7MbFU=
+github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
+github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:BElLblhAfW8Yd5ehlI9xk1FhlsGnKIX6oVak3C8cCnc=
+github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
+github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f h1:vALFH1gx8nLLiAqkTy7tgrT9uLWCPC4e+Ir1UvEQkRw=
+github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
+github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f h1:vQ99pp3CGklVoUGO2x//xne3BkfzSmNKDOzB5V9BoaU=
+github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
+github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:MqINoyziFNrN8t5d/XCu1z+BZs2CkRLLKKOdx0g6bJ4=
+github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
+github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f h1:L7HQmTsLFI7nAzyi2RIkmkuraSY7LRivBYoflIjQcxk=
+github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
+github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f h1:DAswotlvnP6zYlP2KleGfMUy4dIBNNYGg3sh1reS53I=
+github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
+github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:b2OhFosX8oUEdOpYkDS4fkbotbEe9WRMCpYGAWWq4VI=
+github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
+github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f h1:H+GUhGCYe/4wHi+XRw96AgFxETSE3yCDhyAH7G6GbQc=
+github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
+github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f h1:ziXSJVeboo4ZiCulyMbbEgG9ArfP98hqN1pALA15XeM=
+github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
+github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:X9FaZ28M35Hj23agNDRDCklSrR0zb8PZz4K4EBalILE=
+github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
+github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:qKlncyWLNJ3YtJrF3hK5YPd2Tb66ZF1iQXjq1mEADhE=
+github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
+github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f h1:qpBg3PtrVx4Tgv+AwwoRgPqYsfEZeZ3Jn+zHfqr7Z1A=
+github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
+github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f h1:PvjxP4e6qosQrhkPrpW2ZnRm2OKZsvEXIADV29fKWeY=
+github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
+github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f h1:l3+QN68ENC0KHCxHa0iUO0vMGiG00TJlHgH8+jVrDNU=
+github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251217133247-ea405ec61e5f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
 github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
 github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
 github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c=

+ 4 - 3
option/tls.go

@@ -215,9 +215,10 @@ type InboundECHOptions struct {
 }
 
 type OutboundECHOptions struct {
-	Enabled    bool                       `json:"enabled,omitempty"`
-	Config     badoption.Listable[string] `json:"config,omitempty"`
-	ConfigPath string                     `json:"config_path,omitempty"`
+	Enabled         bool                       `json:"enabled,omitempty"`
+	Config          badoption.Listable[string] `json:"config,omitempty"`
+	ConfigPath      string                     `json:"config_path,omitempty"`
+	QueryServerName string                     `json:"query_server_name,omitempty"`
 
 	// Deprecated: not supported by stdlib
 	PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`

+ 47 - 3
protocol/naive/outbound.go

@@ -4,6 +4,7 @@ package naive
 
 import (
 	"context"
+	"encoding/pem"
 	"net"
 	"os"
 	"strings"
@@ -14,6 +15,7 @@ import (
 	"github.com/sagernet/sing-box/adapter/outbound"
 	"github.com/sagernet/sing-box/common/dialer"
 	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/dns"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
@@ -22,6 +24,9 @@ import (
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 	"github.com/sagernet/sing/common/uot"
+	"github.com/sagernet/sing/service"
+
+	mDNS "github.com/miekg/dns"
 )
 
 func RegisterOutbound(registry *outbound.Registry) {
@@ -73,9 +78,6 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
 	if options.TLS.KernelTx || options.TLS.KernelRx {
 		return nil, E.New("kernel TLS is not supported on naive outbound")
 	}
-	if options.TLS.ECH != nil && options.TLS.ECH.Enabled {
-		return nil, E.New("ECH is not currently supported on naive outbound")
-	}
 	if options.TLS.UTLS != nil && options.TLS.UTLS.Enabled {
 		return nil, E.New("uTLS is not supported on naive outbound")
 	}
@@ -121,6 +123,44 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
 		}
 	}
 
+	dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
+	var dnsResolver cronet.DNSResolverFunc
+	if dnsRouter != nil {
+		dnsResolver = func(dnsContext context.Context, request *mDNS.Msg) *mDNS.Msg {
+			response, err := dnsRouter.Exchange(dnsContext, request, adapter.DNSQueryOptions{})
+			if err != nil {
+				logger.Error("DNS exchange failed: ", err)
+				return dns.FixedResponseStatus(request, mDNS.RcodeServerFailure)
+			}
+			return response
+		}
+	}
+
+	var echEnabled bool
+	var echConfigList []byte
+	var echQueryServerName string
+	if options.TLS.ECH != nil && options.TLS.ECH.Enabled {
+		echEnabled = true
+		echQueryServerName = options.TLS.ECH.QueryServerName
+		var echConfig []byte
+		if len(options.TLS.ECH.Config) > 0 {
+			echConfig = []byte(strings.Join(options.TLS.ECH.Config, "\n"))
+		} else if options.TLS.ECH.ConfigPath != "" {
+			content, err := os.ReadFile(options.TLS.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 {
+				return nil, E.New("invalid ECH configs pem")
+			}
+			echConfigList = block.Bytes
+		}
+	}
+
 	client, err := cronet.NewNaiveClient(cronet.NaiveClientConfig{
 		Context:                           ctx,
 		ServerAddress:                     serverAddress,
@@ -132,6 +172,10 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
 		TrustedRootCertificates:           trustedRootCertificates,
 		TrustedCertificatePublicKeySHA256: options.TLS.CertificatePublicKeySHA256,
 		Dialer:                            outboundDialer,
+		DNSResolver:                       dnsResolver,
+		ECHEnabled:                        echEnabled,
+		ECHConfigList:                     echConfigList,
+		ECHQueryServerName:                echQueryServerName,
 	})
 	if err != nil {
 		return nil, err