Skip to content

Commit 9a9b36f

Browse files
committed
Add test to simulate handling of NAT'ed contact header and
check that the contact received in re-INVITE matches. This is to test add_contact_alias() and handle_ruri_alias() functions.
1 parent 8444ad6 commit 9a9b36f

File tree

6 files changed

+123
-13
lines changed

6 files changed

+123
-13
lines changed

functions

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ then
101101
BOB_TIMEOUT=90
102102
TEST_SET="${TEST_SET},11,12,13,14,reinv_brkn1,reinv_brkn2,reinv_bad_ack"
103103
fi
104+
TEST_SET="${TEST_SET},early_cancel_lost100"
104105

105106
MM_ROOT="${MM_ROOT:-"${BUILDDIR}/dist/${MM_TYPE}"}"
106107

@@ -109,10 +110,10 @@ then
109110
TEST_SET="${TEST_SET},inv_brkn1"
110111
fi
111112

112-
#if [ "${MM_TYPE}" != "opensips" -o "${MM_BRANCH}" = "1.11" ]
113-
#then
114-
TEST_SET="${TEST_SET},early_cancel_lost100"
115-
#fi
113+
if [ "${MM_TYPE}" = "opensips" -a "${MM_BRANCH}" = "master" ]
114+
then
115+
TEST_SET="${TEST_SET},nated_contact"
116+
fi
116117

117118
#if [ "${MM_TYPE}" = "kamailio" ]
118119
#then

lib/alice_testcore.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@
6161
from ..test_cases.reinv_frombob import a_test_reinv_frombob
6262
from ..test_cases.reinv_bad_ack import a_test_reinv_bad_ack
6363
from ..test_cases.inv_brkn1 import a_test_inv_brkn1
64+
from ..test_cases.nated_contact import a_test_nated_contact
6465

6566
ALL_TESTS = (a_test1, a_test2, a_test3, a_test4, a_test5, a_test6, a_test7, \
6667
a_test8, a_test9, a_test10, a_test11, a_test12, a_test13, a_test14, \
6768
a_test_early_cancel, a_test_early_cancel_lost100, a_test_reinvite, \
6869
a_test_reinv_fail, a_test_reinv_brkn1, a_test_reinv_brkn2, \
6970
a_test_reinv_onhold, a_test_reinv_adelay, a_test_reinv_frombob, \
70-
a_test_reinv_bad_ack, a_test_inv_brkn1)
71+
a_test_reinv_bad_ack, a_test_inv_brkn1, a_test_nated_contact)
7172

7273
class a_cfg(object):
7374
test_class = None

lib/bob_testcore.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@
6363
from ..test_cases.reinv_frombob import b_test_reinv_frombob
6464
from ..test_cases.reinv_bad_ack import b_test_reinv_bad_ack
6565
from ..test_cases.inv_brkn1 import b_test_inv_brkn1
66+
from ..test_cases.nated_contact import b_test_nated_contact
6667

6768
ALL_TESTS = (b_test1, b_test2, b_test3, b_test4, b_test5, b_test6, b_test7, \
6869
b_test8, b_test9, b_test10, b_test11, b_test12, b_test13, b_test14, \
6970
b_test_early_cancel, b_test_early_cancel_lost100, b_test_reinvite, \
7071
b_test_reinv_fail, b_test_reinv_brkn1, b_test_reinv_brkn2, \
7172
b_test_reinv_onhold, b_test_reinv_adelay, b_test_reinv_frombob, \
72-
b_test_reinv_bad_ack, b_test_inv_brkn1)
73+
b_test_reinv_bad_ack, b_test_inv_brkn1, b_test_nated_contact)
7374

7475
class STMHooks(object):
7576
lossemul = 0

scenarios/simple/opensips.cfg.in

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ loadmodule "sipmsgops/sipmsgops.so"
1515
loadmodule "sl/sl.so"
1616
loadmodule "tm/tm.so"
1717
loadmodule "rr/rr.so"
18+
loadmodule "nathelper/nathelper.so"
1819
loadmodule "maxfwd/maxfwd.so"
1920
#if RTPPC_TYPE == rtp.io
2021
loadmodule "rtp.io/rtp.io.so"
@@ -38,6 +39,12 @@ modparam("rtp.io", "rtpproxy_args", $$"-m 12000 -M 15000 RTPP_LISTEN"$$)
3839
modparam("rtpproxy", "rtpproxy_sock", RTPP_SOCK_TEST)
3940
#endif
4041

42+
#if OPENSIPS_VER_FULL == master || OPENSIPS_VER > 33
43+
#define NAT_PCONTACT "private-contact"
44+
#else
45+
#define NAT_PCONTACT 1
46+
#endif
47+
4148
listen=udp:127.0.0.1:5060
4249
listen=udp:[::1]:5060
4350

@@ -83,13 +90,27 @@ route {
8390
};
8491

8592
if (is_method("BYE")) {
86-
xlog(" calling rtpproxy_unforce()\n");
93+
xlog(" calling rtpproxy_unforce()\n");
8794
rtpproxy_unforce();
8895
};
8996

9097
record_route();
9198

99+
if (is_method("INVITE") && !has_totag() && nat_uac_test(NAT_PCONTACT)) {
100+
xlog(" NAT contact detected\n");
101+
#if OPENSIPS_VER_FULL == master
102+
if (add_contact_alias()) {
103+
xlog(" adding alias\n");
104+
};
105+
#endif
106+
};
107+
92108
if (loose_route()) {
109+
#if OPENSIPS_VER_FULL == master
110+
if (handle_ruri_alias()) {
111+
xlog(" alias removed from the R-URI\n");
112+
};
113+
#endif
93114
t_relay();
94115
exit;
95116
};
@@ -109,6 +130,14 @@ onreply_route[1]
109130
{
110131
xlog("OpenSIPS received a reply $rs/$rm from $si\n");
111132
if (STATUS =~ "(183)|2[0-9][0-9]") {
133+
if (nat_uac_test(NAT_PCONTACT)) {
134+
xlog(" NAT contact detected\n");
135+
#if OPENSIPS_VER_FULL == master
136+
if (add_contact_alias()) {
137+
xlog(" adding alias\n");
138+
};
139+
#endif
140+
};
112141
xlog(" calling search()\n");
113142
if(!search("^Content-Length:[ ]*0")) {
114143
xlog(" calling rtpproxy_answer()\n");
@@ -121,6 +150,6 @@ onreply_route[1]
121150
failure_route[1]
122151
{
123152
xlog("OpenSIPS handling $rm failure in from $si in failure_route[1]\n");
124-
xlog(" calling rtpproxy_unforce()\n");
153+
xlog(" calling rtpproxy_unforce()\n");
125154
rtpproxy_unforce();
126155
}

test_cases/nated_contact.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright (c) 2025 Sippy Software, Inc. All rights reserved.
2+
#
3+
# All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without modification,
6+
# are permitted provided that the following conditions are met:
7+
#
8+
# 1. Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# 2. Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation and/or
13+
# other materials provided with the distribution.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
from .reinv_frombob import a_test_reinv_frombob, b_test_reinv_frombob
27+
from .TExceptions import ScenarioFailure
28+
29+
from sippy.SipAddress import SipAddress
30+
from sippy.SipContact import SipContact
31+
from sippy.SipURL import SipURL
32+
from sippy.UA import UA
33+
34+
35+
class _ReinviteTrackingUA(UA):
36+
"""UA variant that keeps the R-URI of inbound in-dialog INVITEs."""
37+
38+
def recvRequest(self, req, *args, **kwargs):
39+
if req.getMethod() == 'INVITE':
40+
self.last_in_dialog_invite_ruri = str(req.getRURI())
41+
return super(_ReinviteTrackingUA, self).recvRequest(req, *args, **kwargs)
42+
43+
class a_test_nated_contact(a_test_reinv_frombob):
44+
cld = 'bob_nated_contact'
45+
cli = 'alice_nated_contact'
46+
name = f'{a_test_reinv_frombob.name}: NATed Contact preserved on re-INVITE'
47+
compact_sip = False
48+
nat_contact_ip = '192.168.255.10'
49+
nat_contact_port = 5090
50+
51+
def build_ua(self, tccfg):
52+
uaO = super(a_test_nated_contact, self).build_ua(tccfg, UA_class=_ReinviteTrackingUA)
53+
nat_contact = self._build_nat_contact()
54+
uaO.lContact = nat_contact
55+
self.expected_contact_uri = str(nat_contact.getUrl())
56+
return uaO
57+
58+
def _build_nat_contact(self):
59+
nat_url = SipURL(username = self.cli, host = self.nat_contact_ip,
60+
port = self.nat_contact_port)
61+
nat_address = SipAddress(url = nat_url)
62+
return SipContact(address = nat_address)
63+
64+
def on_reinvite_connected(self, ua):
65+
super(a_test_nated_contact, self).on_reinvite_connected(ua)
66+
ruri = getattr(ua, 'last_in_dialog_invite_ruri', None)
67+
if ruri != self.expected_contact_uri:
68+
self.nerrs += 1
69+
raise ScenarioFailure('%s: expected re-INVITE R-URI %s, got %s' %
70+
(self.failed_msg(), self.expected_contact_uri, ruri))
71+
72+
class b_test_nated_contact(b_test_reinv_frombob):
73+
cli = a_test_nated_contact.cld
74+
name = a_test_nated_contact.name

test_cases/t1.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,14 @@ def __init__(self, tccfg):
127127
self.tccfg = tccfg
128128
if tccfg.cli != None:
129129
self.cli = tccfg.cli
130-
uaO = UA(tccfg.global_config, event_cb = self.recvEvent, nh_address = tccfg.nh_address, \
130+
uaO = self.build_ua(tccfg)
131+
self.call_id = SipCallId(body = gen_test_cid())
132+
event = CCEventTry((self.call_id, self.cli, self.cld, tccfg.body, \
133+
None, 'Alice Smith'))
134+
self.run(uaO, event)
135+
136+
def build_ua(self, tccfg, UA_class = UA):
137+
uaO = UA_class(tccfg.global_config, event_cb = self.recvEvent, nh_address = tccfg.nh_address, \
131138
conn_cbs = (self.connected,), disc_cbs = (self.disconnected,), fail_cbs = (self.disconnected,), \
132139
dead_cbs = (self.alldone,), ltag = gen_test_tag())
133140
if tccfg.uac_creds != None:
@@ -137,10 +144,7 @@ def __init__(self, tccfg):
137144

138145
uaO.godead_timeout = self.godead_timeout
139146
uaO.compact_sip = self.compact_sip
140-
self.call_id = SipCallId(body = gen_test_cid())
141-
event = CCEventTry((self.call_id, self.cli, self.cld, tccfg.body, \
142-
None, 'Alice Smith'))
143-
self.run(uaO, event)
147+
return uaO
144148

145149
def run(self, ua, event):
146150
ua.recvEvent(event)

0 commit comments

Comments
 (0)