Summary
default_contact_uri() / transport-aware Contact helpers that pick an address from endpoint.get_addrs() can select an ephemeral local port belonging to an outbound client TLS connection (e.g. trunk REGISTER or outbound INVITE), not the configured TLS/UDP listener.
Observed Contact on outbound INVITE to a TLS UA:
Contact: <sips:…@192.168.10.252:43218;transport=TLS>
Expected (listener):
Contact: <sips:…@192.168.10.252:5061;transport=TLS>
Nothing listens on :43218, so the callee’s mid-dialog BYE never reaches the B2BUA. Hangup becomes one-way: hanging up the callee leaves the caller (e.g. UDP ATA) stuck; the reverse direction still works because the ATA BYEs to the real UDP listener :5060.
Affected version
restsend/rustpbx around commit 5cbcd39419c34a3ba8b1d08305a6504ec08549b9 (and any Contact path that uses endpoint.get_addrs() without filtering to listener sockets).
Environment
- LAN bind
192.168.x.x, TLS listener :5061, UDP :5060
- Outbound TLS activity (trunk REGISTER / external INVITE) creates additional transports
- Internal B2BUA call: UDP ATA → TLS softphone
Minimal reproduction
- Configure UDP + TLS listeners on fixed ports; set
proxy.addr to the bind IP.
- Enable an outbound TLS trunk REGISTER so the endpoint holds outbound TLS sockets.
- Place a call that originates a client INVITE to a TLS-registered UA.
- Inspect
Contact on that INVITE: host/port may be bind IP with a high ephemeral port.
- Answer, then hang up from the TLS UA → BYE targets Contact → no listener → caller leg stays up.
Expected
Contact host/port must be the configured listener for that transport (udp_port / tls_port on proxy.addr), optionally with external_ip for non-LAN destinations when NAT port-forwards exist. Ephemeral outbound sockets in get_addrs() must not be used for dialog Contact.
Suggested direction
Build Contact from ProxyConfig (addr + udp_port/tls_port/…) rather than endpoint.get_addrs(), or filter get_addrs() to sockets that match configured listener ports only. For internet-facing legs, prefer rtp_config.external_ip (or a dedicated SIP external host) + listener port when the destination is not RFC1918.
Summary
default_contact_uri()/ transport-aware Contact helpers that pick an address fromendpoint.get_addrs()can select an ephemeral local port belonging to an outbound client TLS connection (e.g. trunk REGISTER or outbound INVITE), not the configured TLS/UDP listener.Observed Contact on outbound INVITE to a TLS UA:
Expected (listener):
Nothing listens on
:43218, so the callee’s mid-dialogBYEnever reaches the B2BUA. Hangup becomes one-way: hanging up the callee leaves the caller (e.g. UDP ATA) stuck; the reverse direction still works because the ATA BYEs to the real UDP listener:5060.Affected version
restsend/rustpbxaround commit5cbcd39419c34a3ba8b1d08305a6504ec08549b9(and any Contact path that usesendpoint.get_addrs()without filtering to listener sockets).Environment
192.168.x.x, TLS listener:5061, UDP:5060Minimal reproduction
proxy.addrto the bind IP.Contacton that INVITE: host/port may be bind IP with a high ephemeral port.Expected
Contact host/port must be the configured listener for that transport (
udp_port/tls_portonproxy.addr), optionally withexternal_ipfor non-LAN destinations when NAT port-forwards exist. Ephemeral outbound sockets inget_addrs()must not be used for dialog Contact.Suggested direction
Build Contact from
ProxyConfig(addr+udp_port/tls_port/…) rather thanendpoint.get_addrs(), or filterget_addrs()to sockets that match configured listener ports only. For internet-facing legs, preferrtp_config.external_ip(or a dedicated SIP external host) + listener port when the destination is not RFC1918.