@@ -107,7 +107,12 @@ func newEdgeTunnel(c *v1alpha1.EdgeTunnelConfig) (*EdgeTunnel, error) {
107107 return nil , fmt .Errorf ("failed to new conn manager: %w" , err )
108108 }
109109
110- listenAddr , err := generateListenAddr (c )
110+ ips , err := GetIPsFromInterfaces (c .ListenInterfaces , c .ExtraFilteredInterfaces )
111+ if err != nil {
112+ return nil , fmt .Errorf ("failed to get ips from listen interfaces: %w" , err )
113+ }
114+
115+ listenAddr , err := generateListenAddr (c , ips )
111116 if err != nil {
112117 return nil , fmt .Errorf ("failed to generate listenAddr: %w" , err )
113118 }
@@ -122,18 +127,19 @@ func newEdgeTunnel(c *v1alpha1.EdgeTunnelConfig) (*EdgeTunnel, error) {
122127 }))
123128 }
124129
125- // If the relayMap does not contain any public IP, NATService will not be able to assist this non-relay node to
126- // identify its own network(public, private or unknown) , so it needs to configure libp2p.ForceReachabilityPrivate()
127- if ! isRelay && ! relayMap .ContainsPublicIP () {
130+ // Nodes that only listen on non- public addresses cannot rely on AutoNAT to infer
131+ // public reachability in fronted-relay setups such as ACK+NLB , so force private reachability.
132+ if ! isRelay && ( ! relayMap .ContainsPublicIP () || ShouldForceReachabilityPrivate ( ips ) ) {
128133 klog .Infof ("Configure libp2p.ForceReachabilityPrivate()" )
129134 opts = append (opts , libp2p .ForceReachabilityPrivate ())
130135 }
131136
132137 relayNums := len (relayMap )
133- if c .MaxCandidates < relayNums {
134- klog .Infof ("MaxCandidates=%d is less than len(relayMap)=%d, set MaxCandidates to len(relayMap)" ,
135- c .MaxCandidates , relayNums )
136- c .MaxCandidates = relayNums
138+ minCandidates , maxCandidates , numRelays := normalizeAutoRelayConfig (c .MaxCandidates , relayNums )
139+ if maxCandidates != c .MaxCandidates {
140+ klog .Infof ("MaxCandidates adjusted from %d to %d (relayNums=%d)" ,
141+ c .MaxCandidates , maxCandidates , relayNums )
142+ c .MaxCandidates = maxCandidates
137143 }
138144
139145 // configures libp2p to use the given private network protector
@@ -165,9 +171,7 @@ func newEdgeTunnel(c *v1alpha1.EdgeTunnelConfig) (*EdgeTunnel, error) {
165171 func (ctx context.Context , numPeers int ) <- chan peer.AddrInfo {
166172 return peerSource
167173 },
168- autorelay .WithMinCandidates (0 ),
169- autorelay .WithMaxCandidates (c .MaxCandidates ),
170- autorelay .WithBackoff (30 * time .Second ),
174+ buildAutoRelayOpts (minCandidates , c .MaxCandidates , numRelays )... ,
171175 ),
172176 libp2p .EnableNATService (),
173177 libp2p .EnableHolePunching (),
@@ -270,12 +274,7 @@ func newEdgeTunnel(c *v1alpha1.EdgeTunnelConfig) (*EdgeTunnel, error) {
270274 return edgeTunnel , nil
271275}
272276
273- func generateListenAddr (c * v1alpha1.EdgeTunnelConfig ) (libp2p.Option , error ) {
274- ips , err := GetIPsFromInterfaces (c .ListenInterfaces , c .ExtraFilteredInterfaces )
275- if err != nil {
276- return nil , fmt .Errorf ("failed to get ips from listen interfaces: %w" , err )
277- }
278-
277+ func generateListenAddr (c * v1alpha1.EdgeTunnelConfig , ips []string ) (libp2p.Option , error ) {
279278 multiAddrStrings := make ([]string , 0 )
280279 if c .Mode == defaults .ServerClientMode {
281280 for _ , ip := range ips {
@@ -290,3 +289,45 @@ func generateListenAddr(c *v1alpha1.EdgeTunnelConfig) (libp2p.Option, error) {
290289 listenAddr := libp2p .ListenAddrStrings (multiAddrStrings ... )
291290 return listenAddr , nil
292291}
292+
293+ // normalizeAutoRelayConfig computes normalized autorelay parameters.
294+ //
295+ // Fixes the bug where WithMinCandidates(0) prevents peerSource from ever being
296+ // triggered (issue #583):
297+ // - minCandidates is set to at least 1 so relay_finder calls peerSource and
298+ // enters the Reserve() path.
299+ // - maxCandidates is at least relayNums so the candidate pool is large enough.
300+ // - For single-relay setups, numRelays=1 avoids waiting for a second relay
301+ // (the default desiredRelays=2 would stall indefinitely with only one relay).
302+ func normalizeAutoRelayConfig (cfgMaxCandidates , relayNums int ) (minCandidates , maxCandidates , numRelays int ) {
303+ maxCandidates = cfgMaxCandidates
304+ if maxCandidates < relayNums {
305+ maxCandidates = relayNums
306+ }
307+ // minCandidates must be >= 1; otherwise the condition
308+ // `numCandidates < minCandidates` in relay_finder.findNodes() is always
309+ // false and peerSource is never called.
310+ minCandidates = 1
311+ if maxCandidates > 0 && minCandidates > maxCandidates {
312+ minCandidates = maxCandidates
313+ }
314+ // For single-relay setups, explicitly set desiredRelays=1 to avoid
315+ // waiting forever for a second relay (default desiredRelays=2).
316+ if relayNums == 1 {
317+ numRelays = 1
318+ }
319+ return
320+ }
321+
322+ // buildAutoRelayOpts constructs the autorelay option list from normalized parameters.
323+ func buildAutoRelayOpts (minCandidates , maxCandidates , numRelays int ) []autorelay.Option {
324+ opts := []autorelay.Option {
325+ autorelay .WithMinCandidates (minCandidates ),
326+ autorelay .WithMaxCandidates (maxCandidates ),
327+ autorelay .WithBackoff (30 * time .Second ),
328+ }
329+ if numRelays > 0 {
330+ opts = append (opts , autorelay .WithNumRelays (numRelays ))
331+ }
332+ return opts
333+ }
0 commit comments