Skip to content

Commit 6275582

Browse files
committed
Update hysteria2 realm
1 parent 0ce4cbd commit 6275582

11 files changed

Lines changed: 109 additions & 43 deletions

File tree

adapter/dns.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ type DNSQueryOptions struct {
4242
ClientSubnet netip.Prefix
4343
}
4444

45-
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
46-
if options == nil {
47-
return &DNSQueryOptions{}, nil
45+
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (DNSQueryOptions, error) {
46+
if options == nil || options.Server == "" {
47+
return DNSQueryOptions{}, nil
4848
}
4949
transportManager := service.FromContext[DNSTransportManager](ctx)
5050
transport, loaded := transportManager.Transport(options.Server)
5151
if !loaded {
52-
return nil, E.New("domain resolver not found: " + options.Server)
52+
return DNSQueryOptions{}, E.New("domain resolver not found: " + options.Server)
5353
}
54-
return &DNSQueryOptions{
54+
return DNSQueryOptions{
5555
Transport: transport,
5656
Strategy: C.DomainStrategy(options.Strategy),
5757
DisableCache: options.DisableCache,

docs/configuration/inbound/hysteria2.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ icon: material/alert-decagram
4646
"token": "",
4747
"realm_id": "",
4848
"stun_servers": [],
49+
"stun_domain_resolver": "", // or {}
4950
"http_client": {}
5051
}
5152
}
@@ -209,7 +210,15 @@ Outbounds must use the same `realm_id` to find this server.
209210

210211
List of STUN servers (`host` or `host:port`) used to discover public addresses.
211212

212-
Port defaults to `3478`.
213+
#### realm.stun_domain_resolver
214+
215+
Set domain resolver to use for resolving STUN server domain names.
216+
217+
This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.
218+
219+
Setting this option directly to a string is equivalent to setting `server` of this options.
220+
221+
If empty, the default domain resolver is used.
213222

214223
#### realm.http_client
215224

docs/configuration/inbound/hysteria2.zh.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ icon: material/alert-decagram
4646
"token": "",
4747
"realm_id": "",
4848
"stun_servers": [],
49+
"stun_domain_resolver": "", // 或 {}
4950
"http_client": {}
5051
}
5152
}
@@ -206,7 +207,15 @@ Realm 上的槽位标识符。
206207

207208
用于发现公网地址的 STUN 服务器列表(`host``host:port`)。
208209

209-
端口默认为 `3478`
210+
#### realm.stun_domain_resolver
211+
212+
用于解析 STUN 服务器域名的域名解析器。
213+
214+
此选项的格式与 [路由 DNS 规则动作](/zh/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。
215+
216+
若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。
217+
218+
如果为空,则使用默认域名解析器。
210219

211220
#### realm.http_client
212221

docs/configuration/outbound/hysteria2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ The same slot identifier the target Hysteria2 server registered.
191191

192192
List of STUN servers (`host` or `host:port`) used to discover this client's public addresses.
193193

194-
Port defaults to `3478`.
194+
Domain names are resolved using [`domain_resolver`](/configuration/shared/dial/#domain_resolver) from Dial Fields.
195195

196196
#### realm.http_client
197197

docs/configuration/outbound/hysteria2.zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ Realm 的 Bearer 令牌,需与 realm 上配置的 `users[].token` 之一匹配
189189

190190
用于发现本客户端公网地址的 STUN 服务器列表(`host``host:port`)。
191191

192-
端口默认为 `3478`
192+
域名通过 [拨号字段](/zh/configuration/shared/dial/) 中的 [`domain_resolver`](/zh/configuration/shared/dial/#domain_resolver) 解析
193193

194194
#### realm.http_client
195195

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ require (
4040
github.com/sagernet/sing v0.8.11-0.20260514083559-7297f9541547
4141
github.com/sagernet/sing-cloudflared v0.1.0
4242
github.com/sagernet/sing-mux v0.3.4
43-
github.com/sagernet/sing-quic v0.6.2-0.20260510042401-4055a2bb2381
43+
github.com/sagernet/sing-quic v0.6.2-0.20260511084842-9deddf006024
4444
github.com/sagernet/sing-shadowsocks v0.2.8
4545
github.com/sagernet/sing-shadowsocks2 v0.2.1
4646
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ github.com/sagernet/sing-cloudflared v0.1.0 h1:to+2fcCx8zu4X/DirRw9Ihc+FrEZ7oEyI
248248
github.com/sagernet/sing-cloudflared v0.1.0/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
249249
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
250250
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
251-
github.com/sagernet/sing-quic v0.6.2-0.20260510042401-4055a2bb2381 h1:bwja5I7Sgr4Z1nyCLlN6T5eBhhSE/ilYZmkEFIoPAhQ=
252-
github.com/sagernet/sing-quic v0.6.2-0.20260510042401-4055a2bb2381/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
251+
github.com/sagernet/sing-quic v0.6.2-0.20260511084842-9deddf006024 h1:P1iab6udg2I2igIrn+mNKpPZNcuejSqno3jwJ/94upw=
252+
github.com/sagernet/sing-quic v0.6.2-0.20260511084842-9deddf006024/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I=
253253
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
254254
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
255255
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=

option/hysteria2.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ type Hysteria2InboundOptions struct {
1919
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
2020
InboundTLSOptionsContainer
2121
QUICOptions
22-
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
23-
BBRProfile string `json:"bbr_profile,omitempty"`
24-
BrutalDebug bool `json:"brutal_debug,omitempty"`
25-
Realm *Hysteria2Realm `json:"realm,omitempty"`
22+
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
23+
BBRProfile string `json:"bbr_profile,omitempty"`
24+
BrutalDebug bool `json:"brutal_debug,omitempty"`
25+
Realm *Hysteria2InboundRealm `json:"realm,omitempty"`
2626
}
2727

2828
type Hysteria2Realm struct {
@@ -33,6 +33,11 @@ type Hysteria2Realm struct {
3333
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
3434
}
3535

36+
type Hysteria2InboundRealm struct {
37+
Hysteria2Realm
38+
STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"`
39+
}
40+
3641
type Hysteria2Obfs struct {
3742
Type string `json:"type,omitempty"`
3843
Password string `json:"password,omitempty"`

protocol/hysteria2/inbound.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net"
66
"net/http"
77
"net/http/httputil"
8+
"net/netip"
89
"net/url"
910
"time"
1011

@@ -18,11 +19,13 @@ import (
1819
qtls "github.com/sagernet/sing-quic"
1920
"github.com/sagernet/sing-quic/hysteria"
2021
"github.com/sagernet/sing-quic/hysteria2"
22+
"github.com/sagernet/sing-quic/hysteria2/realm"
2123
"github.com/sagernet/sing/common"
2224
"github.com/sagernet/sing/common/auth"
2325
E "github.com/sagernet/sing/common/exceptions"
2426
M "github.com/sagernet/sing/common/metadata"
2527
N "github.com/sagernet/sing/common/network"
28+
"github.com/sagernet/sing/service"
2629
)
2730

2831
func RegisterInbound(registry *inbound.Registry) {
@@ -114,9 +117,35 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
114117
} else {
115118
udpTimeout = C.UDPTimeout
116119
}
117-
realmOptions, err := buildRealmOptions(ctx, logger, options.Realm)
118-
if err != nil {
119-
return nil, err
120+
var realmOptions *realm.Options
121+
if options.Realm != nil {
122+
queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.Realm.STUNDomainResolver)
123+
if err != nil {
124+
return nil, err
125+
}
126+
httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
127+
if err != nil {
128+
return nil, E.Cause(err, "create realm http client")
129+
}
130+
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
131+
realmOptions = &realm.Options{
132+
ServerURL: options.Realm.ServerURL,
133+
Token: options.Realm.Token,
134+
RealmID: options.Realm.RealmID,
135+
STUNServers: options.Realm.STUNServers,
136+
HTTPClient: &http.Client{Transport: httpClientTransport},
137+
Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) {
138+
dnsOptions := queryOptions
139+
switch {
140+
case ipv4 && !ipv6:
141+
dnsOptions.Strategy = C.DomainStrategyIPv4Only
142+
case !ipv4 && ipv6:
143+
dnsOptions.Strategy = C.DomainStrategyIPv6Only
144+
}
145+
return dnsRouter.Lookup(ctx, host, dnsOptions)
146+
},
147+
Logger: logger,
148+
}
120149
}
121150
hysteriaService, err := hysteria2.NewService[int](hysteria2.ServiceOptions{
122151
Context: ctx,

protocol/hysteria2/outbound.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package hysteria2
33
import (
44
"context"
55
"net"
6+
"net/http"
7+
"net/netip"
68
"net/url"
79
"os"
810
"time"
@@ -18,12 +20,14 @@ import (
1820
qtls "github.com/sagernet/sing-quic"
1921
"github.com/sagernet/sing-quic/hysteria"
2022
"github.com/sagernet/sing-quic/hysteria2"
23+
"github.com/sagernet/sing-quic/hysteria2/realm"
2124
"github.com/sagernet/sing/common"
2225
"github.com/sagernet/sing/common/bufio"
2326
E "github.com/sagernet/sing/common/exceptions"
2427
"github.com/sagernet/sing/common/logger"
2528
M "github.com/sagernet/sing/common/metadata"
2629
N "github.com/sagernet/sing/common/network"
30+
"github.com/sagernet/sing/service"
2731
)
2832

2933
func RegisterOutbound(registry *outbound.Registry) {
@@ -66,13 +70,43 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
6670
return nil, E.New("unknown obfs type: ", options.Obfs.Type)
6771
}
6872
}
69-
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
73+
outboundDialer, err := dialer.NewWithOptions(dialer.Options{
74+
Context: ctx,
75+
Options: options.DialerOptions,
76+
RemoteIsDomain: options.ServerIsDomain(),
77+
})
7078
if err != nil {
7179
return nil, err
7280
}
73-
realmOptions, err := buildRealmOptions(ctx, logger, options.Realm)
74-
if err != nil {
75-
return nil, err
81+
var realmOptions *realm.Options
82+
if options.Realm != nil {
83+
queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.DialerOptions.DomainResolver)
84+
if err != nil {
85+
return nil, err
86+
}
87+
httpClientTransport, err := service.FromContext[adapter.HTTPClientManager](ctx).ResolveTransport(ctx, logger, common.PtrValueOrDefault(options.Realm.HTTPClient))
88+
if err != nil {
89+
return nil, E.Cause(err, "create realm http client")
90+
}
91+
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
92+
realmOptions = &realm.Options{
93+
ServerURL: options.Realm.ServerURL,
94+
Token: options.Realm.Token,
95+
RealmID: options.Realm.RealmID,
96+
STUNServers: options.Realm.STUNServers,
97+
HTTPClient: &http.Client{Transport: httpClientTransport},
98+
Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) {
99+
dnsOptions := queryOptions
100+
switch {
101+
case ipv4 && !ipv6:
102+
dnsOptions.Strategy = C.DomainStrategyIPv4Only
103+
case !ipv4 && ipv6:
104+
dnsOptions.Strategy = C.DomainStrategyIPv6Only
105+
}
106+
return dnsRouter.Lookup(ctx, host, dnsOptions)
107+
},
108+
Logger: logger,
109+
}
76110
}
77111
networkList := options.Network.Build()
78112
client, err := hysteria2.NewClient(hysteria2.ClientOptions{

0 commit comments

Comments
 (0)