Skip to content

Conversation

@NormB
Copy link
Member

@NormB NormB commented Feb 12, 2026

Summary

fix_nated_contact() called after topology_hiding() produces a malformed Contact header containing two concatenated SIP URIs when the Contact lacks angle-bracket enclosure (<>).

Details

Both topology_hiding() and fix_nated_contact() rewrite the Contact header using the lump mechanism. Each creates a LUMP_DEL entry (marking a region for deletion) with an after chain LUMP_ADD entry (inserting replacement text).

topology_hiding() via delete_existing_contact() deletes msg->contact->body (the full Contact body). fix_nated_contact_f() deletes from c->uri.s through the host:port portion.

The Contact parser (parser/contact/contact.c:217-222) strips angle brackets from contact_t->uri:

  • With <>: body.s points to < (offset N), uri.s points to sip: (offset N+1) — offsets differ by 1
  • Without <>: body.s and uri.s point to the same address — offsets are identical

In msg_translator.c, process_lumps() iterates lumps sorted by offset. It has a last_del exception that allows processing of multiple lumps anchored at the same deletion point:

if (t->u.offset < s_offset && t->u.offset != last_del) {
    continue;
}

When offsets differ by 1 (with brackets), the second lump falls inside the first's deleted region and is correctly skipped. When offsets are identical (no brackets), the last_del exception prevents the skip, and both replacement values are emitted — producing:

Contact: <sip:10.0.0.40;did=282.a0601bb2>sip:alice@10.0.0.40:15060

The reverse call order (fix_nated_contact() then topology_hiding()) is already handled: delete_existing_contact() in topo_hiding_logic.c:319-336 scans the lump list for existing Contact DEL lumps and neutralizes them. No equivalent guard exists in fix_nated_contact_f().

Solution

Add a helper function contact_already_rewritten() that scans msg->add_rm for a prior LUMP_DEL of type HDR_CONTACT_T whose offset and length fully encompass the current Contact URI. This detects when another function (such as topology_hiding()) has already scheduled the Contact for rewriting.

A new module parameter nated_contact_override controls the behavior when a conflict is detected:

  • 0 (default): skip this Contact — the prior rewrite stands. fix_nated_contact() returns without creating lumps for this Contact.
  • 1: override — neutralize the prior lump by setting op = LUMP_NOP and inserting COND_FALSE guards on its before/after chains (the same technique used by delete_existing_contact() in topo_hiding_logic.c:331-334). fix_nated_contact() then proceeds to create its own replacement.

Design notes:

  • The check uses continue (not return) inside the get_contact_uri() loop, so only conflicting Contacts are affected; uncovered Contacts in sibling headers are processed normally.
  • The lump scan exploits the sort-by-offset invariant maintained by del_lump() (data_lump.c:369-377) for early exit: once we see a DEL lump with offset past the URI start, no later lump can fully cover the URI.
  • No locking is needed: msg->add_rm is per-process pkg memory owned by a single worker, processed sequentially in the routing script. This is the same concurrency model used by delete_existing_contact().
  • The coverage test (crt->u.offset <= uri_off && crt->u.offset + crt->len >= uri_end) intentionally requires full containment. Partial-overlap lumps (e.g., from a prior fix_nated_contact() call) are not matched — that case is already handled by the existing "URI outside buffer" check at nathelper.c:703.

Compatibility

  • Default nated_contact_override=0 preserves the current effective behavior: when topology_hiding() runs first, its Contact wins. The only change is that the concatenation bug is eliminated.
  • No changes to core lump processing (msg_translator.c, data_lump.c). All changes are localized to the nathelper module.
  • The detection is general: any HDR_CONTACT_T LUMP_DEL fully covering the URI will be detected, not only those from topology_hiding(). This is intentional and consistent with delete_existing_contact()'s general approach.
  • Module documentation for the new parameter is included in nathelper_admin.xml.

Closing issues

closes #2172

…ter topology_hiding() (GH OpenSIPS#2172)

When topology_hiding() is called before fix_nated_contact() and the
Contact header lacks angle-bracket enclosure (<>), both functions
create LUMP_DEL entries at the same buffer offset.  The message
builder's last_del exception prevents the second lump from being
skipped, causing both replacement values to be concatenated into
a single malformed Contact header.

With angle brackets the offsets differ by 1 byte (body.s vs uri.s)
so the message builder correctly suppresses the second lump.  Without
angle brackets body.s == uri.s, producing identical offsets and
triggering the bug.

Add a helper function contact_already_rewritten() that scans the
lump list for a prior LUMP_DEL covering the Contact URI.  The new
"nated_contact_override" module parameter controls behavior:

  0 (default) - skip: let the prior rewrite stand
  1           - override: neutralize the prior lump and let
                fix_nated_contact create its own replacement

Both modes prevent the concatenation bug.  The check is per-Contact
using continue, so multi-Contact messages still process uncovered
contacts normally.  The lump scan exits early since the list is
sorted by offset.

Closes: OpenSIPS#2172
@NormB NormB requested a review from liviuchircu February 12, 2026 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] fix_nated_contact() after topology_hiding() results in invalid Contact header

1 participant