11// mesh-conn — userspace port-forwarding agent over pion/ice + QUIC.
22//
3- // Addressing model: peer VIPs + static infra- port allowlist.
3+ // Addressing model: peer VIPs + platform-supplied port allowlist.
44//
55// Every peer is identified by a /8 loopback IP `127.50.0.<vip>`. The
66// `vip` is a small integer (typically the peer's ordinal in the
1010// - the entrypoint provisions `127.50.0.<vip>/32 dev lo` for ALL
1111// peers, including self — so dialing self's VIP loops back through
1212// the kernel without crossing mesh-conn.
13- // - mesh-conn binds the static infra-port allowlist on every OTHER
14- // peer's VIP — and forwards each accepted connection to the right
15- // remote peer over QUIC.
13+ // - mesh-conn binds the allowlist on every OTHER peer's VIP — and
14+ // forwards each accepted connection to the right remote peer over
15+ // QUIC.
1616//
17- // Allowlist (every port in this list, every other peer):
17+ // Allowlist source: MESH_CONN_ALLOWLIST env, JSON shape `[{port, udp}, …]`.
18+ // Populated by the platform sidecar entrypoint at startup from
19+ // SERVICES_JSON (per-service Connect-sidecar ports) plus the two
20+ // static Consul-infra ports {8300, 8301}. The substance — which ports
21+ // cross peer boundaries — is platform state declared one level up in
22+ // `cluster.tf`'s `local.services`; mesh-conn just reads what the
23+ // platform tells it.
24+ //
25+ // Today's defaults (3-service example: webdemo + postgres-master/-replica):
1826//
1927// 21000 Envoy Connect public mTLS — webdemo sidecar (TCP)
2028// 21001 Envoy Connect public mTLS — postgres sidecar (TCP)
2129// 8300 Consul server RPC (TCP)
2230// 8301 Consul serf-LAN gossip (UDP + TCP)
2331//
32+ // Sidecar ports come in one-per-backend because stock Consul Connect
33+ // requires one sidecar Envoy process per Connect-mesh-accessible
34+ // service. Services sharing a canonical port (postgres-master and
35+ // postgres-replica both on :5432, same Patroni backend) collapse onto
36+ // one sidecar; distinct canonical ports get distinct sidecars and
37+ // distinct allowlist entries.
38+ //
2439// The allowlist is intentionally minimal: only platform infrastructure
2540// that needs unmediated peer-to-peer reachability. App-level traffic
2641// (including Patroni's Postgres replication) goes through the Connect
2742// mesh — mesh-conn knows peers, never services.
2843//
29- // Sidecar ports come in pairs because stock Consul Connect requires
30- // one sidecar Envoy process per Connect-mesh-accessible service:
31- // 21000 terminates inbound mTLS for `webdemo`, 21001 terminates inbound
32- // mTLS for `postgres`. Both are reserved cluster-wide; any new
33- // mesh-accessible service either reuses one of these (by SNI-routing
34- // custom Envoy config — out of scope here) or is added explicitly to
35- // this list.
36- //
3744// Wire format on each QUIC stream is unchanged from the old shape:
3845// the first 3 bytes are (tag, port-big-endian-uint16), where port is
3946// the receiver's local port to dial / write into. The receiver
@@ -100,43 +107,34 @@ func (p *Peer) vipAddr() net.IP {
100107 return net .IPv4 (127 , 50 , 0 , byte (p .Vip ))
101108}
102109
103- // infraPort is one entry in the static peer-VIP port allowlist. mesh-conn
110+ // InfraPort is one entry in the peer-VIP port allowlist. mesh-conn
104111// binds (peer.vipAddr(), Port) on every OTHER peer for each entry.
105- type infraPort struct {
106- Port int
107- UDP bool // some ports carry UDP (e.g. serf gossip); most are TCP-only
112+ // JSON-tagged because the value comes in over MESH_CONN_ALLOWLIST env.
113+ type InfraPort struct {
114+ Port int `json:"port"`
115+ UDP bool `json:"udp"` // some ports carry UDP (e.g. serf gossip); most are TCP-only
108116}
109117
110- // peerVIPAllowlist is the entire cluster-wide set of ports any host may
111- // dial at a peer VIP. Adding a port here is a deliberate, reviewable
112- // change — see consul-postgres-ha/ARCHITECTURE.md for the layering
113- // invariant ("mesh-conn knows peers, not services").
114- var peerVIPAllowlist = []infraPort {
115- {Port : 21000 , UDP : false }, // Envoy Connect sidecar — webdemo
116- {Port : 21001 , UDP : false }, // Envoy Connect sidecar — postgres
117- {Port : 8300 , UDP : false }, // Consul server RPC (server-to-server + client-to-server)
118- {Port : 8301 , UDP : true }, // Consul serf-LAN gossip (UDP datagrams + TCP fallback)
118+ type Config struct {
119+ SelfID string
120+ Peers []Peer
121+ Allowlist []InfraPort
122+ SignalingURL string
123+ TurnHost string
124+ TurnSecret string
119125}
120126
121127// allowedPort reports whether the given port is in the allowlist.
122128// Used by the receiver to reject streams targeting unknown ports.
123- func allowedPort (port int ) bool {
124- for _ , ip := range peerVIPAllowlist {
129+ func ( c * Config ) allowedPort (port int ) bool {
130+ for _ , ip := range c . Allowlist {
125131 if ip .Port == port {
126132 return true
127133 }
128134 }
129135 return false
130136}
131137
132- type Config struct {
133- SelfID string
134- Peers []Peer
135- SignalingURL string
136- TurnHost string
137- TurnSecret string
138- }
139-
140138func loadConfig () * Config {
141139 // Primary sources of truth (with env fallback so this binary
142140 // stays runnable in a bare-metal smoke test outside the dstack
@@ -162,8 +160,16 @@ func loadConfig() *Config {
162160 if err := json .Unmarshal ([]byte (mustEnv ("PEERS_JSON" )), & cfg .Peers ); err != nil {
163161 log .Fatalf ("PEERS_JSON: %v" , err )
164162 }
165- if err := validatePeers (cfg ); err != nil {
166- log .Fatalf ("PEERS_JSON: %v" , err )
163+ // MESH_CONN_ALLOWLIST: JSON list `[{port, udp}, …]` generated by
164+ // the platform sidecar entrypoint from SERVICES_JSON (per-service
165+ // Connect-sidecar ports) plus the static Consul-infra ports
166+ // (8300 RPC + 8301 gossip). This is platform plumbing; developers
167+ // edit `local.services` in cluster.tf, not this env.
168+ if err := json .Unmarshal ([]byte (mustEnv ("MESH_CONN_ALLOWLIST" )), & cfg .Allowlist ); err != nil {
169+ log .Fatalf ("MESH_CONN_ALLOWLIST: %v" , err )
170+ }
171+ if err := validateConfig (cfg ); err != nil {
172+ log .Fatalf ("config: %v" , err )
167173 }
168174 return cfg
169175}
@@ -215,11 +221,27 @@ func readTurnSecret() string {
215221 return ""
216222}
217223
218- // validatePeers fails fast on any silent mis-configuration that would
219- // otherwise manifest as confusing runtime failures: collided VIPs,
220- // missing self, etc. Bound at startup because a peer's PEERS_JSON is
221- // shared with every other peer's configuration and must round-trip
222- // identically across the cluster.
224+ // validateConfig fails fast on any silent mis-configuration that
225+ // would otherwise manifest as confusing runtime failures: collided
226+ // peer VIPs, missing self, malformed allowlist, etc. Bound at startup
227+ // because PEERS_JSON and MESH_CONN_ALLOWLIST are shared cluster-wide
228+ // state and must round-trip identically across peers.
229+ func validateConfig (cfg * Config ) error {
230+ if err := validatePeers (cfg ); err != nil {
231+ return err
232+ }
233+ if err := validateAllowlist (cfg ); err != nil {
234+ return err
235+ }
236+ // Log a digest of the validated config so operators can check that
237+ // every peer in the cluster sees the same PEERS_JSON. Differences
238+ // across peers would indicate a deploy-script discrepancy.
239+ digest := peersDigest (cfg .Peers )
240+ log .Printf ("config validated: %d peers, allowlist=%d ports/peer (%v), digest=%s" ,
241+ len (cfg .Peers ), len (cfg .Allowlist ), allowlistPorts (cfg .Allowlist ), digest )
242+ return nil
243+ }
244+
223245func validatePeers (cfg * Config ) error {
224246 if len (cfg .Peers ) < 2 {
225247 return fmt .Errorf ("need at least 2 peers in PEERS_JSON, got %d" , len (cfg .Peers ))
@@ -257,13 +279,27 @@ func validatePeers(cfg *Config) error {
257279 if ! selfFound {
258280 return fmt .Errorf ("PEER_ID %q not in PEERS_JSON (peers: %v)" , cfg .SelfID , knownIDs (cfg .Peers ))
259281 }
282+ return nil
283+ }
260284
261- // Log a digest of the validated config so operators can check that
262- // every peer in the cluster sees the same PEERS_JSON. Differences
263- // across peers would indicate a deploy-script discrepancy.
264- digest := peersDigest (cfg .Peers )
265- log .Printf ("PEERS_JSON validated: %d peers, allowlist=%d ports/peer, digest=%s" ,
266- len (cfg .Peers ), len (peerVIPAllowlist ), digest )
285+ // validateAllowlist fails fast on MESH_CONN_ALLOWLIST malformations:
286+ // empty list (mesh-conn with nothing to forward is always a config
287+ // bug), out-of-range ports, or duplicate port entries (would race for
288+ // the same listener bind at runtime).
289+ func validateAllowlist (cfg * Config ) error {
290+ if len (cfg .Allowlist ) == 0 {
291+ return fmt .Errorf ("MESH_CONN_ALLOWLIST is empty — platform sidecar should always emit at least the Consul-infra ports" )
292+ }
293+ seen := map [int ]bool {}
294+ for i , ip := range cfg .Allowlist {
295+ if ip .Port < 1 || ip .Port > 65535 {
296+ return fmt .Errorf ("allowlist[%d] port=%d out of range (1..65535)" , i , ip .Port )
297+ }
298+ if seen [ip .Port ] {
299+ return fmt .Errorf ("allowlist port %d appears twice — each port may only be entered once" , ip .Port )
300+ }
301+ seen [ip .Port ] = true
302+ }
267303 return nil
268304}
269305
@@ -276,9 +312,9 @@ func knownIDs(peers []Peer) []string {
276312}
277313
278314// allowlistPorts returns the bare port numbers for log output.
279- func allowlistPorts () []int {
280- out := make ([]int , len (peerVIPAllowlist ))
281- for i , ip := range peerVIPAllowlist {
315+ func allowlistPorts (allowlist [] InfraPort ) []int {
316+ out := make ([]int , len (allowlist ))
317+ for i , ip := range allowlist {
282318 out [i ] = ip .Port
283319 }
284320 return out
@@ -348,7 +384,7 @@ func main() {
348384 }
349385 }
350386 log .Printf ("mesh-conn: self=%s vip=%d allowlist=%v other=%d" ,
351- cfg .SelfID , self .Vip , allowlistPorts (), len (others ))
387+ cfg .SelfID , self .Vip , allowlistPorts (cfg . Allowlist ), len (others ))
352388
353389 go pollLoop (cfg )
354390
@@ -501,8 +537,8 @@ func dialAndPump(cfg *Config, self, peer Peer, sess *peerSession) error {
501537 // here means the alias didn't get set up.
502538 peerVip := peer .vipAddr ()
503539 udpSocks := map [int ]* net.UDPConn {} // port -> listener (only UDP-bearing ports)
504- tcpListeners := make ([]* net.TCPListener , 0 , len (peerVIPAllowlist ))
505- for _ , ip := range peerVIPAllowlist {
540+ tcpListeners := make ([]* net.TCPListener , 0 , len (cfg . Allowlist ))
541+ for _ , ip := range cfg . Allowlist {
506542 tl , err := net .ListenTCP ("tcp" , & net.TCPAddr {IP : peerVip , Port : ip .Port })
507543 if err != nil {
508544 return fmt .Errorf ("tcp listen %s:%d: %w" , peerVip , ip .Port , err )
@@ -525,15 +561,15 @@ func dialAndPump(cfg *Config, self, peer Peer, sess *peerSession) error {
525561 // loop to handle ad-hoc incoming TCP streams.
526562 udpStreams := map [int ]* quic.Stream {}
527563 allUDPReady := make (chan struct {})
528- errCh := make (chan error , 4 * len (peerVIPAllowlist ))
564+ errCh := make (chan error , 4 * len (cfg . Allowlist ))
529565
530566 udpPortCount := len (udpSocks )
531567 go func () {
532- errCh <- runAcceptLoop (connCtx , qconn , & self , udpStreams , udpPortCount , allUDPReady )
568+ errCh <- runAcceptLoop (connCtx , qconn , cfg , & self , udpStreams , udpPortCount , allUDPReady )
533569 }()
534570
535571 if isClient {
536- for _ , ip := range peerVIPAllowlist {
572+ for _ , ip := range cfg . Allowlist {
537573 if ! ip .UDP {
538574 continue
539575 }
@@ -559,12 +595,12 @@ func dialAndPump(cfg *Config, self, peer Peer, sess *peerSession) error {
559595 }
560596
561597 log .Printf ("[%s] link up — peer vip=127.50.0.%d, allowlist=%v (udp=%d, tcp=%d)" ,
562- peer .ID , peer .Vip , allowlistPorts (), udpPortCount , len (tcpListeners ))
598+ peer .ID , peer .Vip , allowlistPorts (cfg . Allowlist ), udpPortCount , len (tcpListeners ))
563599
564600 // 5. Start pumps. UDP: one bidirectional pump pair per UDP port.
565601 // TCP: one accept-loop per port (each accepted conn opens an
566602 // ephemeral streamTCP).
567- for _ , ip := range peerVIPAllowlist {
603+ for _ , ip := range cfg . Allowlist {
568604 port := ip .Port
569605 if ip .UDP {
570606 us := udpSocks [port ]
@@ -583,7 +619,7 @@ func dialAndPump(cfg *Config, self, peer Peer, sess *peerSession) error {
583619 }()
584620 }
585621 }
586- for i , ip := range peerVIPAllowlist {
622+ for i , ip := range cfg . Allowlist {
587623 tl := tcpListeners [i ]
588624 port := ip .Port
589625 go func () { errCh <- acceptLocalTCP (connCtx , tl , qconn , port ) }()
@@ -597,7 +633,7 @@ func dialAndPump(cfg *Config, self, peer Peer, sess *peerSession) error {
597633// local Consul / Envoy bound on the peer's own loopback alias). The
598634// receiver validates port against the static allowlist; anything
599635// else is rejected — that's the only port-knowledge the receiver needs.
600- func runAcceptLoop (ctx context.Context , qconn * quic.Conn , self * Peer , udpStreams map [int ]* quic.Stream , expectedUDP int , allUDPReady chan struct {}) error {
636+ func runAcceptLoop (ctx context.Context , qconn * quic.Conn , cfg * Config , self * Peer , udpStreams map [int ]* quic.Stream , expectedUDP int , allUDPReady chan struct {}) error {
601637 udpRegistered := 0
602638 for {
603639 s , err := qconn .AcceptStream (ctx )
@@ -612,7 +648,7 @@ func runAcceptLoop(ctx context.Context, qconn *quic.Conn, self *Peer, udpStreams
612648 }
613649 tag := hdr [0 ]
614650 port := int (hdr [1 ])<< 8 | int (hdr [2 ])
615- if ! allowedPort (port ) {
651+ if ! cfg . allowedPort (port ) {
616652 log .Printf ("stream for non-allowlist port %d (tag=0x%x)" , port , tag )
617653 s .CancelRead (0 )
618654 s .Close ()
0 commit comments