Skip to content

Commit 63bd734

Browse files
committed
nathelper: fix Contact concatenation when fix_nated_contact() runs after topology_hiding() (GH #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: #2172
1 parent aad6b85 commit 63bd734

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

modules/nathelper/doc/nathelper_admin.xml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,80 @@ modparam("nathelper", "cluster_id", 9)
584584
modparam("nathelper", "cluster_id", 9)
585585
modparam("nathelper", "cluster_sharing_tag", "vip")
586586
...
587+
</programlisting>
588+
</example>
589+
</section>
590+
591+
<section id="param_nated_contact_override" xreflabel="nated_contact_override">
592+
<title><varname>nated_contact_override</varname> (integer)</title>
593+
<para>
594+
Controls the behavior of
595+
<xref linkend="func_fix_nated_contact"/> when the Contact header
596+
has already been rewritten by a prior function in the same route,
597+
such as <function>topology_hiding()</function>.
598+
</para>
599+
<para>
600+
When both <function>topology_hiding()</function> and
601+
<function>fix_nated_contact()</function> are used in the same
602+
routing script, both attempt to rewrite the Contact header. If
603+
<function>topology_hiding()</function> runs first, the Contact
604+
is already scheduled for replacement by the time
605+
<function>fix_nated_contact()</function> executes. Without this
606+
parameter, calling them in this order can produce a malformed
607+
Contact header containing two concatenated SIP URIs (see
608+
<ulink url="https://github.com/OpenSIPS/opensips/issues/2172">
609+
GH #2172</ulink>).
610+
</para>
611+
<para>
612+
This parameter lets you choose which rewrite wins:
613+
</para>
614+
<itemizedlist>
615+
<listitem><para>
616+
<emphasis>0</emphasis> (default) - keep the existing
617+
Contact rewrite as-is. For example, if
618+
<function>topology_hiding()</function> already rewrote
619+
the Contact, its topology-hidden Contact will be
620+
preserved and <function>fix_nated_contact()</function>
621+
will be silently skipped for that Contact.
622+
</para></listitem>
623+
<listitem><para>
624+
<emphasis>1</emphasis> - override the existing rewrite
625+
with the NAT-fixed Contact from
626+
<function>fix_nated_contact()</function>. The Contact
627+
will contain the source IP and port of the request
628+
instead of the topology-hidden address.
629+
</para></listitem>
630+
</itemizedlist>
631+
<note><para>
632+
This parameter only takes effect when a Contact conflict is
633+
detected (i.e., another function has already scheduled the
634+
Contact for rewriting). When
635+
<function>fix_nated_contact()</function> is called on its own
636+
or before other Contact-rewriting functions, this parameter
637+
has no effect and <function>fix_nated_contact()</function>
638+
behaves normally.
639+
</para></note>
640+
<para>
641+
If the message contains multiple Contact URIs, the check is
642+
done per-Contact: only Contacts that conflict with a prior
643+
rewrite are affected by this setting. Other Contacts are
644+
processed by <function>fix_nated_contact()</function> normally.
645+
</para>
646+
<para>
647+
<emphasis>
648+
Default value is 0 (keep existing rewrite).
649+
</emphasis>
650+
</para>
651+
<example>
652+
<title>Set <varname>nated_contact_override</varname> parameter</title>
653+
<programlisting format="linespecific">
654+
...
655+
# Keep topology_hiding()'s Contact (default behavior)
656+
modparam("nathelper", "nated_contact_override", 0)
657+
...
658+
# Override with fix_nated_contact()'s NAT-fixed Contact
659+
modparam("nathelper", "nated_contact_override", 1)
660+
...
587661
</programlisting>
588662
</example>
589663
</section>

modules/nathelper/nathelper.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ static unsigned int raw_ip = 0;
217217
static unsigned short raw_port = 0;
218218
int skip_oldip=0;
219219

220+
/*
221+
* Controls fix_nated_contact() when the Contact has already been rewritten
222+
* by a prior function (e.g. topology_hiding()):
223+
* 0 (default) = keep the existing Contact rewrite as-is
224+
* 1 = override it with fix_nated_contact()'s NAT replacement
225+
*/
226+
static int nated_contact_override = 0;
227+
220228
/*0-> disabled, 1 ->enabled*/
221229
unsigned int *natping_state=0;
222230

@@ -261,6 +269,7 @@ static const param_export_t params[] = {
261269
{"max_pings_lost", INT_PARAM, &max_pings_lost },
262270
{"cluster_id", INT_PARAM, &nh_cluster_id },
263271
{"cluster_sharing_tag", STR_PARAM, &nh_cluster_shtag },
272+
{"nated_contact_override", INT_PARAM, &nated_contact_override},
264273
{0, 0, 0}
265274
};
266275

@@ -625,6 +634,49 @@ isnulladdr(str *sx, int pf)
625634
return (sx->len == 7 && memcmp("0.0.0.0", sx->s, 7) == 0);
626635
}
627636

637+
/*
638+
* Check if a prior lump already covers this Contact URI (GH #2172).
639+
* Returns 1 if fix_nated_contact should skip this contact, 0 to proceed.
640+
*/
641+
static int contact_already_rewritten(struct sip_msg *msg, contact_t *c)
642+
{
643+
struct lump *crt;
644+
unsigned int uri_off = c->uri.s - msg->buf;
645+
unsigned int uri_end = uri_off + c->uri.len;
646+
647+
for (crt = msg->add_rm; crt; crt = crt->next) {
648+
if (crt->type != HDR_CONTACT_T || crt->op != LUMP_DEL)
649+
continue;
650+
/* lumps are sorted by offset -- no further match possible */
651+
if (crt->u.offset > uri_off)
652+
break;
653+
if (crt->u.offset + crt->len < uri_end)
654+
continue;
655+
656+
/* prior lump fully covers this Contact URI */
657+
if (nated_contact_override == 0) {
658+
LM_DBG("Contact already rewritten at off=%u len=%u, "
659+
"keeping existing (nated_contact_override=0)\n",
660+
crt->u.offset, crt->len);
661+
return 1;
662+
}
663+
664+
/* override: neutralize the prior lump (same technique as
665+
* delete_existing_contact in topo_hiding_logic.c) */
666+
LM_DBG("Contact already rewritten at off=%u len=%u, "
667+
"overriding (nated_contact_override=1)\n",
668+
crt->u.offset, crt->len);
669+
crt->op = LUMP_NOP;
670+
if (crt->after)
671+
insert_cond_lump_after(crt, COND_FALSE, 0);
672+
if (crt->before)
673+
insert_cond_lump_before(crt, COND_FALSE, 0);
674+
return 0;
675+
}
676+
677+
return 0;
678+
}
679+
628680
/*
629681
* Replaces ip:port pair in the Contact: field with the source address
630682
* of the packet.
@@ -653,6 +705,11 @@ fix_nated_contact_f(struct sip_msg* msg, str *params)
653705
return -1;
654706
}
655707

708+
/* GH #2172: skip if Contact already rewritten by e.g.
709+
* topology_hiding(); override instead if configured */
710+
if (contact_already_rewritten(msg, c))
711+
continue;
712+
656713
hostport = uri.host;
657714
if (uri.port.len > 0)
658715
hostport.len = uri.port.s + uri.port.len - uri.host.s;

0 commit comments

Comments
 (0)