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:
+23
-59
@@ -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 ----------
|
||||
|
||||
Reference in New Issue
Block a user