Add hysteria2 realm ip_version and port_mapping options

This commit is contained in:
世界
2026-08-30 17:41:42 +08:00
parent b35a747dab
commit 970d93d428
7 changed files with 181 additions and 2 deletions
+38
View File
@@ -48,6 +48,12 @@ icon: material/alert-decagram
"realm_id": "",
"stun_servers": [],
"stun_domain_resolver": "", // or {}
"ip_version": 0,
"port_mapping": {
"enabled": false,
"timeout": "",
"lifetime": ""
},
"http_client": {}
}
}
@@ -237,6 +243,38 @@ Setting this option directly to a string is equivalent to setting `server` of th
If empty, the default domain resolver is used.
#### realm.ip_version
Restrict realm connections (STUN, hole punching, and the resulting QUIC path) to a single IP version.
`4` or `6`. Both are used if empty.
The `listen` address must be compatible with the selected version.
#### realm.port_mapping
Maintain a UDP port mapping on the local gateway via UPnP or NAT-PMP.
The mapping is established before STUN discovery and improves hole-punching reliability behind gateways that support it; failures are non-fatal.
Requires IPv4: conflicts with `"ip_version": 6`.
#### realm.port_mapping.enabled
Enable port mapping.
#### realm.port_mapping.timeout
Timeout for gateway discovery and mapping operations.
`10s` is used by default.
#### realm.port_mapping.lifetime
Lease lifetime of the mapping; it is renewed at half the lifetime.
`10m` is used by default.
#### realm.http_client
HTTP client used to talk to the realm.
@@ -48,6 +48,12 @@ icon: material/alert-decagram
"realm_id": "",
"stun_servers": [],
"stun_domain_resolver": "", // 或 {}
"ip_version": 0,
"port_mapping": {
"enabled": false,
"timeout": "",
"lifetime": ""
},
"http_client": {}
}
}
@@ -234,6 +240,38 @@ Realm 上的槽位标识符。
如果为空,则使用默认域名解析器。
#### realm.ip_version
将 realm 连接(STUN、打洞与最终的 QUIC 路径)限制为单一 IP 版本。
`4` 或 `6`。默认使用两者。
`listen` 地址必须与所选版本兼容。
#### realm.port_mapping
通过 UPnP 或 NAT-PMP 在本地网关上维护 UDP 端口映射。
映射在 STUN 发现之前建立,可在支持的网关后提升打洞成功率;失败不影响正常流程。
需要 IPv4:与 `"ip_version": 6` 冲突。
#### realm.port_mapping.enabled
启用端口映射。
#### realm.port_mapping.timeout
网关发现与映射操作的超时。
默认使用 `10s`。
#### realm.port_mapping.lifetime
映射的租约时长;每过一半时长续期一次。
默认使用 `10m`。
#### realm.http_client
与 realm 通信使用的 HTTP 客户端。
+36
View File
@@ -43,6 +43,12 @@
"token": "",
"realm_id": "",
"stun_servers": [],
"ip_version": 0,
"port_mapping": {
"enabled": false,
"timeout": "",
"lifetime": ""
},
"http_client": {}
},
@@ -210,6 +216,36 @@ List of STUN servers (`host` or `host:port`) used to discover this client's publ
Domain names are resolved using [`domain_resolver`](/configuration/shared/dial/#domain_resolver) from Dial Fields.
#### realm.ip_version
Restrict realm connections (STUN, hole punching, and the resulting QUIC path) to a single IP version.
`4` or `6`. Both are used if empty.
#### realm.port_mapping
Maintain a UDP port mapping on the local gateway via UPnP or NAT-PMP.
The mapping is established before STUN discovery and improves hole-punching reliability behind gateways that support it; failures are non-fatal.
Requires IPv4: conflicts with `"ip_version": 6`.
#### realm.port_mapping.enabled
Enable port mapping.
#### realm.port_mapping.timeout
Timeout for gateway discovery and mapping operations.
`10s` is used by default.
#### realm.port_mapping.lifetime
Lease lifetime of the mapping; it is renewed at half the lifetime.
`10m` is used by default.
#### realm.http_client
HTTP client used to talk to the realm.
@@ -43,6 +43,12 @@
"token": "",
"realm_id": "",
"stun_servers": [],
"ip_version": 0,
"port_mapping": {
"enabled": false,
"timeout": "",
"lifetime": ""
},
"http_client": {}
},
@@ -208,6 +214,36 @@ Realm 的 Bearer 令牌,需与 realm 上配置的 `users[].token` 之一匹配
域名通过 [拨号字段](/zh/configuration/shared/dial/) 中的 [`domain_resolver`](/zh/configuration/shared/dial/#domain_resolver) 解析。
#### realm.ip_version
将 realm 连接(STUN、打洞与最终的 QUIC 路径)限制为单一 IP 版本。
`4` 或 `6`。默认使用两者。
#### realm.port_mapping
通过 UPnP 或 NAT-PMP 在本地网关上维护 UDP 端口映射。
映射在 STUN 发现之前建立,可在支持的网关后提升打洞成功率;失败不影响正常流程。
需要 IPv4:与 `"ip_version": 6` 冲突。
#### realm.port_mapping.enabled
启用端口映射。
#### realm.port_mapping.timeout
网关发现与映射操作的超时。
默认使用 `10s`。
#### realm.port_mapping.lifetime
映射的租约时长;每过一半时长续期一次。
默认使用 `10m`。
#### realm.http_client
与 realm 通信使用的 HTTP 客户端。
+8
View File
@@ -30,9 +30,17 @@ type Hysteria2Realm struct {
Token string `json:"token,omitempty"`
RealmID string `json:"realm_id"`
STUNServers badoption.Listable[string] `json:"stun_servers"`
IPVersion int `json:"ip_version,omitempty"`
PortMapping *Hysteria2RealmPortMapping `json:"port_mapping,omitempty"`
HTTPClient *HTTPClientOptions `json:"http_client,omitempty"`
}
type Hysteria2RealmPortMapping struct {
Enabled bool `json:"enabled,omitempty"`
Timeout badoption.Duration `json:"timeout,omitempty"`
Lifetime badoption.Duration `json:"lifetime,omitempty"`
}
type Hysteria2InboundRealm struct {
Hysteria2Realm
STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"`
+17 -1
View File
@@ -125,6 +125,15 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}
var realmOptions *realm.Options
if options.Realm != nil {
if options.Realm.IPVersion != 0 && options.ListenOptions.Listen != nil {
listenAddr := netip.Addr(*options.ListenOptions.Listen).Unmap()
if options.Realm.IPVersion == 6 && listenAddr.Is4() {
return nil, E.New("realm.ip_version 6 conflicts with listen address ", listenAddr)
}
if options.Realm.IPVersion == 4 && listenAddr.Is6() && !listenAddr.IsUnspecified() {
return nil, E.New("realm.ip_version 4 conflicts with listen address ", listenAddr)
}
}
queryOptions, err := adapter.DNSQueryOptionsFrom(ctx, options.Realm.STUNDomainResolver)
if err != nil {
return nil, err
@@ -150,7 +159,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}
return dnsRouter.Lookup(ctx, host, dnsOptions)
},
Logger: logger,
Logger: logger,
IPVersion: options.Realm.IPVersion,
}
if options.Realm.PortMapping != nil && options.Realm.PortMapping.Enabled {
realmOptions.PortMapping = &realm.PortMappingOptions{
Timeout: time.Duration(options.Realm.PortMapping.Timeout),
Lifetime: time.Duration(options.Realm.PortMapping.Lifetime),
}
}
}
hysteriaService, err := hysteria2.NewService[int](hysteria2.ServiceOptions{
+8 -1
View File
@@ -111,7 +111,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
}
return dnsRouter.Lookup(ctx, host, dnsOptions)
},
Logger: logger,
Logger: logger,
IPVersion: options.Realm.IPVersion,
}
if options.Realm.PortMapping != nil && options.Realm.PortMapping.Enabled {
realmOptions.PortMapping = &realm.PortMappingOptions{
Timeout: time.Duration(options.Realm.PortMapping.Timeout),
Lifetime: time.Duration(options.Realm.PortMapping.Lifetime),
}
}
}
networkList := options.Network.Build()