diff --git a/docs/configuration/inbound/hysteria2.md b/docs/configuration/inbound/hysteria2.md index 28caae87a..7b5b07a0f 100644 --- a/docs/configuration/inbound/hysteria2.md +++ b/docs/configuration/inbound/hysteria2.md @@ -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. diff --git a/docs/configuration/inbound/hysteria2.zh.md b/docs/configuration/inbound/hysteria2.zh.md index d59101b38..daf83b6f8 100644 --- a/docs/configuration/inbound/hysteria2.zh.md +++ b/docs/configuration/inbound/hysteria2.zh.md @@ -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 客户端。 diff --git a/docs/configuration/outbound/hysteria2.md b/docs/configuration/outbound/hysteria2.md index 1df36cfc2..a02749376 100644 --- a/docs/configuration/outbound/hysteria2.md +++ b/docs/configuration/outbound/hysteria2.md @@ -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. diff --git a/docs/configuration/outbound/hysteria2.zh.md b/docs/configuration/outbound/hysteria2.zh.md index 07ec96ce1..0c24e2d9b 100644 --- a/docs/configuration/outbound/hysteria2.zh.md +++ b/docs/configuration/outbound/hysteria2.zh.md @@ -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 客户端。 diff --git a/go.mod b/go.mod index bdec6394c..73d2b47c1 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/sagernet/sing v0.8.12-0.20260702081104-2ded2af32d3d github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 github.com/sagernet/sing-mux v0.3.5 - github.com/sagernet/sing-quic v0.6.4-0.20260709030527-cff0b763485d + github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowtls v0.2.1 @@ -104,14 +104,20 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/huin/goupnp v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/fs v0.1.0 // indirect + github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 // indirect + github.com/libp2p/go-netroute v0.2.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect diff --git a/go.sum b/go.sum index 6f3e45590..aad8fe262 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI= github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -109,10 +111,14 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8 github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= +github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY= github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I= github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E= @@ -122,6 +128,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= +github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -138,6 +146,10 @@ github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0v github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60= github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= +github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138 h1:YohuNPT/1k3VcThCQlBZ43PCPWPfMRS1zcxWBF2SLK8= +github.com/libp2p/go-nat v1.0.1-0.20250821073202-01afc089f138/go.mod h1:TXQg5tfSy+bUjnhT5728j5j/MBj7keIYqqZ1+8k/ui8= +github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= +github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco= @@ -264,8 +276,8 @@ github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3 h1:3y6 github.com/sagernet/sing-cloudflared v0.1.3-0.20260706062323-d9787e794aa3/go.mod h1:XEqEDYRCAYLaoPjZ1ifVWJg5iWAJHL2gOAXe/PM28Cg= github.com/sagernet/sing-mux v0.3.5 h1:RHnhVEc+SFqkrK4xMygYjDwwLhzp2Bj3lztSukONfhI= github.com/sagernet/sing-mux v0.3.5/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk= -github.com/sagernet/sing-quic v0.6.4-0.20260709030527-cff0b763485d h1:peXYrU5srsiMDUVOFdEoaQ6mRdywGXr6poWePWGmU0M= -github.com/sagernet/sing-quic v0.6.4-0.20260709030527-cff0b763485d/go.mod h1:+oqD54aHel4ALKkp1hVXWCgLU/EjLojvm6AUzDfvj0I= +github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc h1:zdc0fj4JdAdgAmQIoh7ZF+B/wPTEF2X75lYDqTmvlaw= +github.com/sagernet/sing-quic v0.6.4-0.20260709034545-e23afe1172dc/go.mod h1:9k+dzGsWMttUGldBzq3dU792YHXzW6NgfbOGltnXq+0= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= @@ -362,6 +374,8 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4 go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= @@ -369,17 +383,24 @@ golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1i golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -391,6 +412,7 @@ golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= @@ -398,8 +420,10 @@ golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/option/hysteria2.go b/option/hysteria2.go index d20c0bd37..fdf0d2cd8 100644 --- a/option/hysteria2.go +++ b/option/hysteria2.go @@ -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"` diff --git a/protocol/hysteria2/inbound.go b/protocol/hysteria2/inbound.go index b706b4920..cd7839bb3 100644 --- a/protocol/hysteria2/inbound.go +++ b/protocol/hysteria2/inbound.go @@ -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{ diff --git a/protocol/hysteria2/outbound.go b/protocol/hysteria2/outbound.go index 7651a0c65..742940ed4 100644 --- a/protocol/hysteria2/outbound.go +++ b/protocol/hysteria2/outbound.go @@ -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()