Skip to content

fix: autorelay reservation for private nodes#584

Open
fanny7d wants to merge 1 commit intokubeedge:mainfrom
fanny7d:fix/autorelay-reservation-issue-583
Open

fix: autorelay reservation for private nodes#584
fanny7d wants to merge 1 commit intokubeedge:mainfrom
fanny7d:fix/autorelay-reservation-issue-583

Conversation

@fanny7d
Copy link

@fanny7d fanny7d commented Mar 13, 2026

What this PR does / why we need it

Fix autorelay reservation failure for private nodes (closes #583).

Root cause: pkg/tunnel/module.go used WithMinCandidates(0) when
configuring libp2p AutoRelay. In go-libp2p autorelay, peerSource is
only invoked when numCandidates < minCandidates. With minCandidates=0
the condition is never true, so peerSource is never called and no relay
reservation is ever attempted → continuous NO_RESERVATION (204).

Changes

  • pkg/tunnel/module.go: Add normalizeAutoRelayConfig() to set
    minCandidates ≥ 1 so peerSource is always triggered. For single-relay
    setups, set numRelays=1 to avoid waiting for a second relay
    (default desiredRelays=2). Extract ShouldForceReachabilityPrivate()
    to correctly handle ACK+NLB fronted-relay scenarios.
  • pkg/tunnel/util.go: Add ShouldForceReachabilityPrivate() and
    buildAutoRelayOpts() helpers.
  • Tests: Add unit tests in module_test.go and util_test.go.

Which issue(s) this PR fixes

Fixes #583

Special notes for reviewers

Verified with EdgeMesh v1.17.0 + go-libp2p v0.28.2: relay side shows
reserving relay slot for ... and NO_RESERVATION no longer appears.

@kubeedge-bot kubeedge-bot added the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Mar 13, 2026
@kubeedge-bot
Copy link
Collaborator

Welcome @fanny7d! It looks like this is your first PR to kubeedge/edgemesh 🎉

@kubeedge-bot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: fanny7d
To complete the pull request process, please assign fisherxu after the PR has been reviewed.
You can assign the PR to them by writing /assign @fisherxu in a comment when ready.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubeedge-bot kubeedge-bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Mar 13, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical bug that prevented private nodes from successfully reserving autorelay slots within the libp2p network. The fix involves refining the AutoRelay configuration parameters, specifically ensuring that the minimum number of candidates for relay discovery is always met and optimizing relay selection for single-relay environments. Additionally, it enhances the logic for determining network reachability in complex setups, leading to more robust and reliable peer connectivity.

Highlights

  • Fixes Autorelay Reservation Failure: Resolved an issue where private nodes failed to reserve autorelay slots, addressing issue EdgeMesh v1.17.0;libp2p relay无法生效no reservation #583.
  • Corrected minCandidates Configuration: Modified pkg/tunnel/module.go to ensure autorelay.WithMinCandidates is always at least 1, which prevents the peerSource from being skipped and enables relay reservation attempts.
  • Optimized Single-Relay Setups: Introduced logic to set numRelays=1 for single-relay configurations, avoiding unnecessary waiting for a second relay when only one is desired.
  • Improved Reachability Detection: Added ShouldForceReachabilityPrivate() to pkg/tunnel/util.go and integrated it into pkg/tunnel/module.go to more accurately handle network reachability in fronted-relay scenarios like ACK+NLB.
  • New Helper Functions: Extracted normalizeAutoRelayConfig() and buildAutoRelayOpts() into pkg/tunnel/module.go for better modularity and clarity in configuring AutoRelay parameters.
  • Expanded Test Coverage: Added new unit tests in pkg/tunnel/module_test.go and pkg/tunnel/util_test.go to validate the new autorelay configuration logic and reachability checks.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • pkg/tunnel/module.go
    • The generateListenAddr function signature was updated to accept ips as an argument.
    • The condition for libp2p.ForceReachabilityPrivate was enhanced to include ShouldForceReachabilityPrivate.
    • The MaxCandidates configuration logic was refactored using the new normalizeAutoRelayConfig function.
    • AutoRelay options are now generated via the new buildAutoRelayOpts function.
    • New functions normalizeAutoRelayConfig and buildAutoRelayOpts were introduced to handle autorelay parameter normalization and option building.
  • pkg/tunnel/module_test.go
    • A new file module_test.go was added, containing unit tests for normalizeAutoRelayConfig and buildAutoRelayOpts.
  • pkg/tunnel/util.go
    • The manet package was imported for network address utilities.
    • The FilterPrivateMaddr logic was revised to correctly identify public and DNS multiaddrs using manet.IsPublicAddr and isDNSMaddr.
    • New helper functions isDNSMaddr, ShouldForceReachabilityPrivate, and isPublicIP were added to assist with network address analysis and reachability determination.
  • pkg/tunnel/util_test.go
    • Unit tests for FilterPrivateMaddr and ShouldForceReachabilityPrivate were added to validate the updated network address filtering and reachability logic.
Activity
  • The author verified the fix with EdgeMesh v1.17.0 + go-libp2p v0.28.2, confirming that relay slot reservations now occur and NO_RESERVATION messages are no longer present.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a critical bug in autorelay reservation for private nodes by ensuring minCandidates is at least 1. The changes are logical and well-supported by new helper functions and unit tests. My review includes a fix for a subtle bug in the new normalizeAutoRelayConfig function, suggestions to improve code clarity by simplifying variable handling, and a recommendation to enhance test coverage for an identified edge case. I also noted some comments that should be translated to English for consistency.

Comment on lines +293 to +329
// normalizeAutoRelayConfig 计算归一化后的 autorelay 参数。
//
// 修复 WithMinCandidates(0) 导致 peerSource 永远不被触发的 bug(issue #583):
// - minCandidates 至少为 1,确保 relay_finder 能启动 peerSource 并进入 Reserve() 路径
// - maxCandidates 不小于 relayNums,保证候选池容量够用
// - 单 relay 场景返回 numRelays=1,避免默认值 desiredRelays=2 导致一直等待第二个 relay
func normalizeAutoRelayConfig(cfgMaxCandidates, relayNums int) (minCandidates, maxCandidates, numRelays int) {
maxCandidates = cfgMaxCandidates
if maxCandidates < relayNums {
maxCandidates = relayNums
}
// minCandidates 必须 >= 1,否则 relay_finder.findNodes() 中的条件
// `numCandidates < minCandidates` 永远为假,peerSource 不会被调用。
minCandidates = 1
if maxCandidates > 0 && minCandidates > maxCandidates {
minCandidates = maxCandidates
}
// 单 relay 场景显式设置 desiredRelays=1,
// 避免默认值 desiredRelays=2 导致一直等待第二个 relay。
if relayNums == 1 {
numRelays = 1
}
return
}

// buildAutoRelayOpts 根据归一化参数构建 autorelay 选项列表。
func buildAutoRelayOpts(minCandidates, maxCandidates, numRelays int) []autorelay.Option {
opts := []autorelay.Option{
autorelay.WithMinCandidates(minCandidates),
autorelay.WithMaxCandidates(maxCandidates),
autorelay.WithBackoff(30 * time.Second),
}
if numRelays > 0 {
opts = append(opts, autorelay.WithNumRelays(numRelays))
}
return opts
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic in normalizeAutoRelayConfig for setting minCandidates has a potential bug. If cfgMaxCandidates and relayNums are both 0, maxCandidates will be 0, but minCandidates will be 1, resulting in an invalid configuration where minCandidates > maxCandidates. The logic should ensure minCandidates is 0 if maxCandidates is 0.

Additionally, the function comments for normalizeAutoRelayConfig and buildAutoRelayOpts are in Chinese and should be translated to English for consistency with the rest of the codebase.

// normalizeAutoRelayConfig calculates the normalized autorelay parameters.
//
// It fixes a bug (issue #583) where WithMinCandidates(0) prevents peerSource from ever being triggered:
//   - minCandidates is set to at least 1 to ensure the relay_finder starts peerSource and enters the Reserve() path.
//   - maxCandidates is ensured to be no less than relayNums to provide sufficient capacity for the candidate pool.
//   - For single-relay scenarios, numRelays is set to 1 to avoid the default desiredRelays=2, which would cause indefinite waiting for a second relay.
func normalizeAutoRelayConfig(cfgMaxCandidates, relayNums int) (minCandidates, maxCandidates, numRelays int) {
	maxCandidates = cfgMaxCandidates
	if maxCandidates < relayNums {
		maxCandidates = relayNums
	}

	// minCandidates must be >= 1 to trigger peerSource, unless there are no candidates possible.
	// The condition `numCandidates < minCandidates` in relay_finder.findNodes() will never be true
	// if minCandidates is 0, thus peerSource will not be called.
	if maxCandidates > 0 {
		minCandidates = 1
	} else {
		minCandidates = 0
	}

	// For single-relay scenarios, explicitly set desiredRelays=1.
	// This avoids the default (desiredRelays=2) which would cause waiting for a second relay that doesn't exist.
	if relayNums == 1 {
		numRelays = 1
	}
	return
}

// buildAutoRelayOpts builds the list of autorelay options from the normalized parameters.
func buildAutoRelayOpts(minCandidates, maxCandidates, numRelays int) []autorelay.Option {
	opts := []autorelay.Option{
		autorelay.WithMinCandidates(minCandidates),
		autorelay.WithMaxCandidates(maxCandidates),
		autorelay.WithBackoff(30 * time.Second),
	}
	if numRelays > 0 {
		opts = append(opts, autorelay.WithNumRelays(numRelays))
	}
	return opts
}

Comment on lines +138 to 143
minCandidates, maxCandidates, numRelays := normalizeAutoRelayConfig(c.MaxCandidates, relayNums)
if maxCandidates != c.MaxCandidates {
klog.Infof("MaxCandidates adjusted from %d to %d (relayNums=%d)",
c.MaxCandidates, maxCandidates, relayNums)
c.MaxCandidates = maxCandidates
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation for handling maxCandidates is a bit convoluted. It calculates a new maxCandidates, logs an update, modifies the input configuration c.MaxCandidates, and then uses this modified value in buildAutoRelayOpts.

For better clarity and to avoid side effects on the input configuration, it's preferable to use the local maxCandidates variable directly.

I suggest removing the update to c.MaxCandidates here, and then passing the local maxCandidates variable to buildAutoRelayOpts on line 174 like so: buildAutoRelayOpts(minCandidates, maxCandidates, numRelays)...,.

minCandidates, maxCandidates, numRelays := normalizeAutoRelayConfig(c.MaxCandidates, relayNums)
	if maxCandidates != c.MaxCandidates {
		klog.Infof("MaxCandidates adjusted from %d to %d (relayNums=%d)",
			c.MaxCandidates, maxCandidates, relayNums)
	}

wantMaxCandidates: 4,
wantNumRelays: 0,
},
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test suite for TestNormalizeAutoRelayConfig is missing a key edge case: when both cfgMaxCandidates and relayNums are 0. This case would have exposed the bug in the original implementation of normalizeAutoRelayConfig. Adding this test case will help prevent regressions.

Also, some comments in this file are in Chinese (e.g., lines 84 and 92). It would be great to translate them to English for consistency.

		},
		{
			// cfgMaxCandidates=0, no relay nodes
			// minCandidates should be 0 if maxCandidates is 0
			name:              "zero maxCandidates with no relays",
			cfgMaxCandidates:  0,
			relayNums:         0,
			wantMinCandidates: 0,
			wantMaxCandidates: 0,
			wantNumRelays:     0,
		},

@fanny7d fanny7d force-pushed the fix/autorelay-reservation-issue-583 branch from 0f506af to ec22ef5 Compare March 13, 2026 15:58
@fanny7d fanny7d changed the title fix: autorelay reservation failure for private nodes (closes #583) fix: autorelay reservation for private nodes Mar 13, 2026
@fanny7d fanny7d force-pushed the fix/autorelay-reservation-issue-583 branch from ec22ef5 to 823c850 Compare March 13, 2026 16:09
@kubeedge-bot kubeedge-bot removed the do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. label Mar 13, 2026
@fanny7d fanny7d force-pushed the fix/autorelay-reservation-issue-583 branch 3 times, most recently from 6316ef9 to b040d3e Compare March 13, 2026 16:43
In go-libp2p autorelay, peerSource is only invoked when
numCandidates < minCandidates. Using WithMinCandidates(0) means
the condition is never true, so peerSource is never called and
no relay reservation ever happens, resulting in NO_RESERVATION (204).

Fix by normalizing autorelay config: set minCandidates>=1,
adjust maxCandidates to cover all relay nodes, and set numRelays=1
for single-relay setups to avoid waiting for a second relay.

Signed-off-by: fanny7d <fanchao1510@outlook.com>
@fanny7d fanny7d force-pushed the fix/autorelay-reservation-issue-583 branch from b040d3e to 421e17d Compare March 13, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EdgeMesh v1.17.0;libp2p relay无法生效no reservation

2 participants