Skip to content

Add port mapping to address rewrite rules - #831

Merged
Sean-Der merged 1 commit into
mainfrom
feat/port-map
Sep 14, 2026
Merged

Sean-Der merged 1 commit into
mainfrom
feat/port-map

Conversation

@xinze-zheng

@xinze-zheng xinze-zheng commented Nov 8, 2025 •

Copy link
Copy Markdown
Member

Summary

  • Extend AddressRewriteRule with OriginalPort and NewPort so address and port translation use one configuration API.
  • Select port mappings with the rule's existing candidate type, local address, interface, CIDR, network, and precedence filters.
  • Match one gathered port when OriginalPort is non-zero, or every gathered port when it is zero. NewPort must be non-zero unless both fields are zero, which disables port rewriting.
  • Apply port rewriting to gathered host, server-reflexive, and relay candidates across direct, UDP mux, universal UDP mux, STUN, TURN, and address-rewrite paths.
  • Change only the advertised candidate port; the underlying packet connection remains bound to its original port.

This replaces the earlier callback API, so the PR adds no handler or agent state.

Tests

  • Full Go test suite
  • golangci-lint
  • Pion shared commit-message linter
  • Public AddressRewriteRules validation plus wildcard and exact port gathering coverage

Reference

pion/webrtc#3155

@codecov

codecov Bot commented Nov 8, 2025 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.60%. Comparing base (27a9ac1) to head (7020974).

Files with missing lines Patch % Lines
external_ip_mapper.go 78.94% 2 Missing and 2 partials ⚠️
gather.go 84.00% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #831      +/-   ##
==========================================
- Coverage   88.66%   88.60%   -0.06%     
==========================================
  Files          46       46              
  Lines        6475     6513      +38     
==========================================
+ Hits         5741     5771      +30     
- Misses        500      502       +2     
- Partials      234      240       +6     
Flag Coverage Δ
go 88.60% <83.33%> (-0.06%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JoTurk JoTurk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you so much, I like this direction let me know when it's ready for test and review.

Comment thread agent_config.go Outdated
EnableUseCandidateCheckPriority bool

// MapPortHanlder is the handler used to compute mapped port for host candidate.
MapPortHanlder func(candidate Candidate) int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We've stopped adding new configurations to the AgentConfig struct since it's planned for deprecation in favor of a more Pion-aligned API. this feature should instead be exposed as an option, WithMapPortHandler, and used with the new NewAgentWithConfig API. Users who want to use this should migrate to the new API.

we did the same thing recently with the renomination API: #822 (comment)

@xinze-zheng
xinze-zheng marked this pull request as ready for review November 9, 2025 01:19
Comment thread candidate.go Outdated
Port() int

// Port mapping support for containers
MappedPort() int

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

question, do we have to make this public?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should we? Will webrtc care about this? I was thinking for design of separted ICE (maybe the future ion) this will be somehow used. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should keep it private for now, unless we need it, or someone requests that we expose it later.

@JoTurk

JoTurk commented Nov 9, 2025

Copy link
Copy Markdown
Member

@xinze-zheng I'm sorry about the internal API change, we had to fix the options so we can upgrade webrtc to use them.

@JoTurk

JoTurk commented Nov 9, 2025

Copy link
Copy Markdown
Member

I think it would be better if we can change the test to use the new Public api instead, NewAgentWithOptions, so it doesn't break if we change the internal API in the future, and so it matches the user contract.
But this is up to you.

@JoTurk

JoTurk commented Dec 20, 2025

Copy link
Copy Markdown
Member

@xinze-zheng hello, we merged the options API and added the new Address rewrite API if you want to continue working on this.

@xinze-zheng
xinze-zheng requested a review from JoTurk December 24, 2025 14:27
@JoTurk

JoTurk commented Jan 6, 2026

Copy link
Copy Markdown
Member

@xinze-zheng I'll try to test and review this today or by tomorrow, thank you a lot :)

@xinze-zheng

Copy link
Copy Markdown
Member Author

@JoTurk Hi I've been looking into integrating this feature with webrtc. Seems we need to replace webrtc.ICECandidate.Port with mappedPort in webrtc.newICECandidateFromICE (or the information of mappedPort is lost). I think we can implement via

	port := candidate.Port()
	if candidate.GetMappedPort() != 0 {
		port = candidate.GetMappedPort()
	}

we just need to make GetMappedPort public in ice.candidate.

@JoTurk

JoTurk commented Jan 8, 2026 •

Copy link
Copy Markdown
Member

@xinze-zheng I think the only clean path if we go this route is to make a custom extension for mapped ports but i think it's maybe too much. I changed webrtc's candidates so it keeps ICE extensions when converting. Not sure about exposing mapped ports as a helper (but we can do this if no other solution).

I'm maybe missing something, but why we can't overwrite candidates the same we do with addresses?

@xinze-zheng

Copy link
Copy Markdown
Member Author

@JoTurk Hello, I've changed the behavior of mapPort to overwrite the port field of a potential ice candidate.

Comment thread agent_options.go Outdated
Comment on lines +963 to +967

func WithMapPortHandler(handler func(cand Candidate) int, candTyp CandidateType) AgentOption {
return func(a *Agent) error {
a.mapPort = func(candidate Candidate) int {
if candidate.Type() == candTyp {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we'll either need to remove the candidate type filter or make it possible to add multiple filters for different candidates types.
I think removing is better because the user filter have access to the candidate type.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agree! Updated.

@JoTurk

JoTurk commented Feb 15, 2026

Copy link
Copy Markdown
Member

@xinze-zheng Sorry for losing track of this, I think we just need to keep track of the original port internally, but this lgtm

@Sean-Der just a quick review is this API what you meant in the original issue?

@Sean-Der Sean-Der changed the title MapPortHandler Callback for container support Add candidate port mapping callback Sep 14, 2026
@Sean-Der Sean-Der changed the title Add candidate port mapping callback Add port mapping to address rewrite rules Sep 14, 2026
Containers may bind ICE sockets on ports that differ from the ports
published outside the container. Extend AddressRewriteRule with
OriginalPort and NewPort so the same rules that publish external
addresses can also publish the corresponding external port.

Use the existing candidate type, local address, interface, CIDR,
network, and rule-precedence filters when selecting a port mapping.
OriginalPort selects one gathered port, or matches every port when zero.
NewPort must be a valid non-zero port unless both fields are zero, which
leaves ports unchanged.

Apply the mapping to gathered host, server-reflexive, and relay
candidates across direct, UDP mux, universal UDP mux, STUN, TURN, and
address-rewrite gathering paths. Only the advertised candidate port
changes; the underlying packet connection retains its bound port.

Cover validation, wildcard mapping, and exact mapping through the public
AddressRewriteRules option.
@Sean-Der
Sean-Der merged commit cd9e29d into main Sep 14, 2026
19 checks passed
@Sean-Der
Sean-Der deleted the feat/port-map branch September 14, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants