Skip to content

Commit 7d728ec

Browse files
committed
Add --advertise-addr flag to beacon for MIG public-DNAT deployments
When a beacon runs on a GCP MIG behind a public DNAT (e.g. the rendezvous reserved IP routing UDP :9001 to internal MIG instances), auto-detection from the TCP local addr to the registry yields the INTERNAL VPC address (10.128.0.x). External daemons cannot reach those, so beacon_list responses silently black-hole all relay traffic through them — the issue diagnosed in pkg/daemon/beacon_select.go's filterUnreachable. The proper fix is on the beacon side: with --advertise-addr set, the beacon registers the operator-supplied public DNAT entrypoint instead. All MIG instances share one public ingress; the registry's beacon_list returns one routable address; external daemons reach the beacons via the existing DNAT. beacon-template-v5 (created 2026-05-02) sets --advertise-addr 34.71.57.205:9001 in the systemd ExecStart. v4 is preserved for rollback.
1 parent 30e72f5 commit 7d728ec

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

cmd/beacon/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
func main() {
2020
configPath := flag.String("config", "", "path to config file (JSON)")
2121
addr := flag.String("addr", ":9001", "listen address (UDP)")
22+
advertiseAddr := flag.String("advertise-addr", "", "address to register with the registry (e.g. 34.71.57.205:9001). When unset, the beacon auto-detects its outbound IP from the TCP connection to the registry — which yields the INTERNAL VPC address on GCP MIG deployments and is unreachable from external daemons. MIG-deployed beacons MUST set this to the public rendezvous DNAT entrypoint.")
2223
beaconID := flag.Uint("beacon-id", 0, "unique beacon ID (0 = standalone)")
2324
peersFlag := flag.String("peers", "", "comma-separated peer beacon addresses for gossip")
2425
healthAddr := flag.String("health", "", "health check HTTP address (e.g. :8080)")
@@ -52,6 +53,9 @@ func main() {
5253
if *registryAddr != "" {
5354
s.SetRegistry(*registryAddr)
5455
}
56+
if *advertiseAddr != "" {
57+
s.SetAdvertiseAddr(*advertiseAddr)
58+
}
5559

5660
if *healthAddr != "" {
5761
go func() {

pkg/beacon/server.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ type Server struct {
5252
peerMu sync.RWMutex
5353
healthOk atomic.Bool
5454

55-
registryAddr string // registry address for dynamic peer discovery
55+
registryAddr string // registry address for dynamic peer discovery
56+
advertiseAddr string // address to register (overrides auto-detect from TCP local addr)
5657

5758
done chan struct{} // closed on shutdown
5859
}
@@ -615,6 +616,17 @@ func (s *Server) SetRegistry(addr string) {
615616
s.registryAddr = addr
616617
}
617618

619+
// SetAdvertiseAddr sets the address this beacon registers with the registry.
620+
// When empty (default), the beacon auto-detects from its TCP local addr to
621+
// the registry — which on a GCP MIG deployment yields the INTERNAL VPC
622+
// address (10.128.0.x), unreachable from external daemons. MIG-deployed
623+
// beacons must set this to the public DNAT entrypoint (e.g. the rendezvous
624+
// reserved IP on UDP 9001) so external clients receive a routable address
625+
// from beacon_list.
626+
func (s *Server) SetAdvertiseAddr(addr string) {
627+
s.advertiseAddr = addr
628+
}
629+
618630
// registryDiscoveryLoop registers this beacon with the registry and discovers
619631
// peers every 30 seconds. Requires the beacon to be listening (conn bound).
620632
func (s *Server) registryDiscoveryLoop() {
@@ -680,17 +692,24 @@ func (s *Server) registryDiscover() {
680692
return resp, json.Unmarshal(body, &resp)
681693
}
682694

683-
// Register this beacon with our listen address
684-
listenAddr := s.conn.LocalAddr().String()
685-
// Resolve wildcard to actual IP for peers to reach us
686-
host, port, _ := net.SplitHostPort(listenAddr)
687-
if host == "::" || host == "0.0.0.0" || host == "" {
688-
// Use the outbound IP (the IP used to reach the registry)
689-
if tcpAddr, ok := conn.LocalAddr().(*net.TCPAddr); ok {
690-
host = tcpAddr.IP.String()
695+
// Pick the address to register. Operator-supplied advertiseAddr wins
696+
// (MIG deployments need this — see SetAdvertiseAddr). Otherwise
697+
// auto-detect from the TCP local addr, which on a single-node deploy
698+
// is the right answer but on a VPC-internal beacon yields a private
699+
// 10.x address that external daemons cannot reach.
700+
var myAddr string
701+
if s.advertiseAddr != "" {
702+
myAddr = s.advertiseAddr
703+
} else {
704+
listenAddr := s.conn.LocalAddr().String()
705+
host, port, _ := net.SplitHostPort(listenAddr)
706+
if host == "::" || host == "0.0.0.0" || host == "" {
707+
if tcpAddr, ok := conn.LocalAddr().(*net.TCPAddr); ok {
708+
host = tcpAddr.IP.String()
709+
}
691710
}
711+
myAddr = net.JoinHostPort(host, port)
692712
}
693-
myAddr := net.JoinHostPort(host, port)
694713

695714
if err := sendMsg(map[string]interface{}{
696715
"type": "beacon_register",

0 commit comments

Comments
 (0)