feat(sip): M6 CP2 — external signaling address + port on all Via/Contact/From (New-3) - #150
Merged
Merged
Conversation
added 2 commits
July 17, 2026 03:19
…t/From (M6 CP2, New-3) external_signaling_address was parsed but never applied to any Via/Contact/From builder. Apply it — plus a new external_signaling_port override (New-3) — to every signaling builder (build_invite/build_200_ok/build_ack/build_bye/ build_reinvite/build_reinvite_ack, and the event_handler UPDATE/re-INVITE 200 OK Contacts), transport-scoped by local_net exactly like advertised_media_ip does for SDP: a peer outside local_net sees the external address AND external port; a peer inside local_net sees the internal bind. This lets a NAT/forward map an external port to a different internal bind port without breaking the first in-dialog request (which targets the advertised Contact). New advertised_signaling_hostport(local, remote) in sdp/mod.rs mirrors advertised_media_ip's transport-coverage + local_net logic and brackets IPv6 literals. SipSession::signaling_hostport() is the per-session entry point. Receiver-side proof (e2e_signaling_address.rs): a peer establishes a call and CAPTURES rustisk's 200 OK Contact and rtptimeout BYE Via+From datagrams, asserting external addr:port for a non-local peer and internal bind for a local_net peer. RED: force signaling_hostport to the internal bind -> the external peer sees 127.0.0.1 not 203.0.113.99. Plus 7 unit tests on the scoping core (port override, local_net bypass, IPv6 bracketing, FQDN, foreign-transport no-donate).
…onger e2e - F1 (High): select the transport that COVERS local_addr first, then read its NAT config, instead of filtering for transports that HAVE an external address. Stops a same-ip/wildcard transport from donating its external addr/port to a covering transport that deliberately set none. Unit test added. - F6 (Low): reject external_signaling_port=0 and malformed values (warn, fall back to the bind port) rather than silently advertising an unusable port 0. - Strengthen e2e: assert the internal-peer BYE From host:port (was logged but not asserted) and that the external port override never reaches it.
Owner
Author
Codex review disposition (gpt-5.5, xhigh)Codex verified the happy path, the port override / bind-port fallback,
CI green; workspace test + clippy green locally. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M6 CP2 — external_signaling_address on all Via/Contact/From sites + external signaling PORT (New-3)
FINDING.
external_signaling_addresswas parsed intoTransportConfigbut applied to NOVia/Contact/From builder — every signaling header interpolated the raw bind
local_addr. A NATdeployment leaked the internal address in Via/Contact/From, and any topology where the advertised
port differs from the bind port (external port forward) died at the first in-dialog request (New-3).
FIX.
advertised_signaling_hostport(local, remote)insdp/mod.rs, mirroringadvertised_media_ip's transport-coverage lookup +local_netscoping: a peer OUTSIDElocal_netgetsexternal_signaling_address(with the newexternal_signaling_portoverride,else the bind port); a peer INSIDE gets the internal bind
host:port. IPv6 literals arebracketed; FQDNs pass through (legal in a SIP sent-by / URI host).
external_signaling_port: Option<u16>onTransportConfig(New-3) + config parse.SipSession::signaling_hostport()is the per-session entry point (scoped by the session'sremote peer). Applied to every signaling builder:
build_invite_with_uri(Via/From/Contact),build_200_ok(Contact),build_ack(Via),build_bye(Via/From),build_reinvite(Via/From/Contact),
build_reinvite_ack(Via/From), and theevent_handlerUPDATE / re-INVITE200 OK Contacts. No behavior change when no external is configured or the peer is local — the
bind
host:portis returned unchanged, so non-NAT deployments are unaffected.RECEIVER-SIDE PROOF (
e2e_signaling_address.rs). A SIP peer establishes a call and CAPTURESrustisk's real datagrams: the INVITE 200 OK Contact and the rtptimeout BYE Via sent-by +
From URI. It asserts EXTERNAL
203.0.113.99:6666for a peer outsidelocal_net, and the INTERNALbind
127.0.0.1:<bind-port>(explicitly!= 6666) for a peer insidelocal_net. The BYE is stillphysically delivered to the peer's real transport address, so the advertised external address is
proven at the receiver independent of the send target. Plus 7 unit tests on the scoping core (port
override applies; local_net bypass; IPv6 bracketing; FQDN; foreign-transport no-donate).
CAPTURED RED. Force
signaling_hostportto always return the internal bind (defeat scoping):GATES.
cargo test --workspace --exclude pjsip-shimgreen;cargo clippy --workspace --exclude pjsip-shim -- -D warningsclean. Rust 1.97.0.