hotfix(panel): bind inbounds to stack wildcard instead of registered public address

Inbounds were pinned to the server's registered public address
(ipv4: PublicAddress, ipv6: PublicAddressV6), so addresses not
registered as 'the' public address - intranet NICs used for relay
landings, additional v6 addresses - had nothing listening and
intranet relays were unreachable (field-verified: ss showed the
landing port bound to the public IP only). 3x-ui/xray bind the
wildcard and serve these paths fine.

- listenAddress: explicit bindAddress still wins; ipv4 -> 0.0.0.0,
  ipv6 -> ::, dual -> :: (ipv6 wildcard per user decision
  2026-10-21: multi-v6 machines must serve all their v6 addresses).
- ResolveBindAddress: no longer resolves the registered address
  into a bind pin (that resolution was the production path of the
  pinning via resourceForNode/resourceForNodeExit); returns
  ("", nil) for all stacks, as it already did for dual/absent.
- serverAddress (client connection address / subscription host)
  is unchanged.
- Golden fixtures updated for the three listen fields that
  intentionally change; new listen_hotfix_test.go covers the
  listenAddress cases and the resourceForNode/exit deploy chains.
This commit is contained in:
aaaamoooo
2026-10-21 07:16:38 +00:00
parent 79639ac338
commit aa2ee62d32
3 changed files with 183 additions and 62 deletions
+23 -59
View File
@@ -1,7 +1,6 @@
package main
import (
"context"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
@@ -284,19 +283,19 @@ func listenAddress(networkMode string, server *Server, bindAddress string) strin
if bindAddress != "" {
return bindAddress
}
if networkMode == "ipv6" {
if server != nil && server.PublicAddressV6 != "" {
return server.PublicAddressV6
}
// Hotfix 2026-10-21: inbounds bind the wildcard address of their
// stack. The registered public address is a *connection* address
// (see serverAddress); pinning the listener to it left the
// machine's other addresses (intranet NICs, additional v6
// addresses) unserved and broke intranet relay landings, which
// 3x-ui/xray serve by binding the wildcard. The server parameter
// is kept for signature parity with the Node implementation.
switch networkMode {
case "ipv6", "dual":
return "::"
default: // ipv4
return "0.0.0.0"
}
if networkMode == "dual" {
return "::"
}
if server != nil && server.PublicAddress != "" {
return server.PublicAddress
}
return "0.0.0.0"
}
func serverAddress(server *Server, networkMode string) (string, error) {
@@ -309,54 +308,19 @@ func serverAddress(server *Server, networkMode string) (string, error) {
return server.PublicAddress, nil
}
// ResolveBindAddress mirrors resolveBindAddress: dual/absent -> ("", nil).
// DNS failures are HTTPError 400; the parenthesized code approximates
// err.code with the Go resolver's short error text (e.g. "no such host"),
// since Go exposes no getaddrinfo code.
// ResolveBindAddress mirrors resolveBindAddress. Since the 2026-10-21
// listen hotfix it always returns ("", nil): inbounds bind the wildcard
// address of their stack (see listenAddress), and the server's
// registered public address is a connection address (serverAddress),
// never a bind pin — resolving it into a bind address here is what
// pinned every inbound to the public NIC and broke intranet relays.
// It previously also returned ("", nil) for dual and for servers
// without a registered address, so wildcard binding was already the
// fallback semantics. The function is kept so the orchestrator's
// deploy path keeps its shape and an explicit per-deploy bind address
// can be reintroduced here if one is ever needed.
func ResolveBindAddress(networkMode string, server *Server) (string, error) {
if networkMode == "dual" {
return "", nil
}
var raw string
if server != nil {
if networkMode == "ipv6" {
raw = server.PublicAddressV6
} else {
raw = server.PublicAddress
}
}
if raw == "" {
return "", nil
}
text := strings.TrimSpace(raw)
family := 4
record := "A"
network := "ip4"
if networkMode == "ipv6" {
family = 6
record = "AAAA"
network = "ip6"
}
_ = family
if ip := net.ParseIP(text); ip != nil {
return text, nil
}
addrs, err := net.DefaultResolver.LookupIP(context.Background(), network, text)
if err != nil || len(addrs) == 0 {
code := ""
if err != nil {
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) && dnsErr.Err != "" {
code = dnsErr.Err
} else {
code = err.Error()
}
} else {
code = "no addresses"
}
return "", httpErr(400, "绑定地址 DNS 解析失败:%s 没有 %s 记录(%s),请检查该服务器登记的地址", text, record, code)
}
return addrs[0].String(), nil
return "", nil
}
// ---------- sing-box resource builders ----------