Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/re_sipreg.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ void sipreg_incfailc(struct sipreg *reg);
int sipreg_set_fbregint(struct sipreg *reg, uint32_t fbregint);
void sipreg_set_srcport(struct sipreg *reg, uint16_t srcport);
int sipreg_set_contact_params(struct sipreg *reg, const char *cparams);
int sipreg_set_contact_rewrite(struct sipreg *reg, bool enable);
const struct sa *sipreg_contact_addr(const struct sipreg *reg);
118 changes: 116 additions & 2 deletions src/sipreg/reg.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@

enum {
DEFAULT_EXPIRES = 3600,
MAX_REWRITES = 2,
};


/** Defines a SIP Registration client */
struct sipreg {
struct sip_loopstate ls;
struct sa laddr;
struct sa caddr; /**< Contact address learned from Via */
struct sa oldaddr; /**< Stale binding to remove, if any */
struct tmr tmr;
struct sip *sip;
struct sip_keepalive *ka;
Expand All @@ -51,12 +54,66 @@ struct sipreg {
char *params;
int regid;
uint16_t srcport;
bool rewrite; /**< Rewrite Contact from Via */
bool rewritten; /**< caddr is in use */
uint8_t rewrites; /**< Consecutive rewrites */
};


static int request(struct sipreg *reg, bool reset_ls);


static const struct sa *contact_addr(const struct sipreg *reg)
{
return reg->rewritten ? &reg->caddr : &reg->laddr;
}


static int via_public_addr(const struct sip_msg *msg, struct sa *addr)
{
struct pl received, rport;
uint16_t port = sa_port(&msg->via.addr);

if (msg_param_decode(&msg->via.params, "received", &received))
return ENOENT;

if (!msg_param_decode(&msg->via.params, "rport", &rport) &&
pl_isset(&rport))
port = pl_u32(&rport);

return sa_set(addr, &received, port);
}


static bool rewrite_contact(struct sipreg *reg, const struct sip_msg *msg)
{
struct sa pub;

if (!reg->rewrite || !reg->expires || reg->terminated)
return false;

if (via_public_addr(msg, &pub) ||
sa_cmp(&pub, contact_addr(reg), SA_ALL)) {
reg->rewrites = 0;
sa_init(&reg->oldaddr, AF_UNSPEC);
return false;
}

/* Two addresses taking turns: keep the current one */
if (reg->rewrites >= MAX_REWRITES) {
sa_init(&reg->oldaddr, AF_UNSPEC);
return false;
}

reg->oldaddr = *contact_addr(reg);
reg->caddr = pub;
reg->rewritten = true;
++reg->rewrites;

return 0 == request(reg, true);
}


static void dummy_handler(int err, const struct sip_msg *msg, void *arg)
{
(void)err;
Expand Down Expand Up @@ -166,7 +223,7 @@ static bool contact_handler(const struct sip_hdr *hdr,
if (err)
return false;

if (!sa_cmp(&host, &reg->laddr, SA_ADDR))
if (!sa_cmp(&host, contact_addr(reg), SA_ADDR))
return false;

err = uri_param_get(&c.auri, &transp, &pval);
Expand Down Expand Up @@ -205,6 +262,11 @@ static void response_handler(int err, const struct sip_msg *msg, void *arg)
return;
}
else if (msg->scode < 300) {
if (rewrite_contact(reg, msg)) {
reg->registered = true;
return;
}

reg->wait = reg->expires;
sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT, contact_handler,
reg);
Expand Down Expand Up @@ -308,8 +370,21 @@ static int send_handler(enum sip_transp tp, struct sa *src,
sa_set_port(src, reg->srcport);

reg->laddr = *src;

if (sa_isset(&reg->oldaddr, SA_ALL)) {
err = mbuf_printf(mb,
"Contact: <sip:%s@%J%s%s%s>;expires=0\r\n",
reg->cuser, &reg->oldaddr,
sip_transp_param(reg->tp),
reg->cparams ? ";" : "",
reg->cparams ? reg->cparams : "");
if (err)
return err;
}

err = mbuf_printf(mb, "Contact: <sip:%s@%J%s%s%s>;expires=%u%s%s",
reg->cuser, &reg->laddr, sip_transp_param(reg->tp),
reg->cuser, contact_addr(reg),
sip_transp_param(reg->tp),
reg->cparams ? ";" : "",
reg->cparams ? reg->cparams : "",
reg->expires,
Expand Down Expand Up @@ -518,6 +593,21 @@ const struct sa *sipreg_laddr(const struct sipreg *reg)
}


/**
* Get the address in the Contact header of a SIP Registration client: the
* address learned from the registrar when the Contact was rewritten (see
* sipreg_set_contact_rewrite()), otherwise the local socket address
*
* @param reg SIP Registration client
*
* @return Contact address
*/
const struct sa *sipreg_contact_addr(const struct sipreg *reg)
{
return reg ? contact_addr(reg) : NULL;
}


/**
* Get the proxy expires value of a SIP registration client
*
Expand Down Expand Up @@ -594,3 +684,27 @@ int sipreg_set_contact_params(struct sipreg *reg, const char *cparams)

return str_dup(&reg->cparams, cparams);
}


/**
* Enable or disable Contact rewrite for the SIP registration client
*
* Behind a NAT the local address in the Contact header is not reachable
* from the registrar. With Contact rewrite enabled, the address the
* registrar reports in the received and rport parameters of the response's
* top Via (RFC 3261 18.2.1, RFC 3581) is registered instead, and the stale
* binding is removed in the same request.
*
* @param reg SIP registration client
* @param enable True to enable, false to disable
*
* @return 0 if success, otherwise errorcode
*/
int sipreg_set_contact_rewrite(struct sipreg *reg, bool enable)
{
if (!reg)
return EINVAL;

reg->rewrite = enable;
return 0;
}
89 changes: 89 additions & 0 deletions test/sipreg.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,95 @@ static int reg_test(enum sip_transp tp, uint16_t srcport)
}


/**
* Contact rewrite behind a NAT: the mock NAT makes the registrar see us at
* a public address, so the client must register that address and remove
* the binding to its local one in the same, second, request.
*/
int test_sipreg_contact_rewrite(void)
{
struct test test;
struct sip_server *srv = NULL;
struct sipreg *reg = NULL;
struct sip *sip = NULL;
struct nat *nat = NULL;
struct sa public_addr;
const struct sip_msg *req;
const struct sip_hdr *contact;
char reg_uri[256];
int err;

memset(&test, 0, sizeof(test));
test.tp = SIP_TRANSP_UDP;

err = sip_server_alloc(&srv);
TEST_ERR(err);

err = sa_set_str(&public_addr, "192.0.2.10", 0);
TEST_ERR(err);

err = nat_alloc(&nat, NAT_INBOUND_SNAT,
sip_transp_udp_sock(srv->sip), &public_addr);
TEST_ERR(err);

err = sipstack_fixture(&sip);
TEST_ERR(err);

err = sip_server_uri(srv, reg_uri, sizeof(reg_uri), SIP_TRANSP_UDP);
TEST_ERR(err);

err = sipreg_alloc(&reg, sip, reg_uri, "sip:x@test", NULL,
"sip:x@test", 3600, "x", NULL, 0, 0, NULL, NULL,
false, sip_resp_handler, &test, NULL, NULL);
TEST_ERR(err);

err = sipreg_set_contact_rewrite(reg, true);
TEST_ERR(err);

err = sipreg_send(reg);
TEST_ERR(err);

err = re_main_timeout(1000);
TEST_ERR(err);
TEST_ERR(test.err);

/* One REGISTER to learn the address, one to register it */
ASSERT_EQ(2, srv->n_register_req);
ASSERT_EQ(1, test.n_resp);
ASSERT_TRUE(sipreg_registered(reg));

ASSERT_TRUE(sa_cmp(sipreg_contact_addr(reg), &public_addr, SA_ADDR));
ASSERT_TRUE(!sa_cmp(sipreg_contact_addr(reg), sipreg_laddr(reg),
SA_ADDR));

/* The second request: stale local binding first, then the new one */
req = srv->sip_msgs[1];
ASSERT_EQ(2, sip_msg_hdr_count(req, SIP_HDR_CONTACT));

contact = sip_msg_hdr(req, SIP_HDR_CONTACT);
err = re_regex(contact->val.p, contact->val.l,
"<sip:x@127.0.0.1:[0-9]+>;expires=0", NULL);
TEST_ERR(err);

contact = sip_msg_hdr_apply(req, false, SIP_HDR_CONTACT, NULL, NULL);
ASSERT_TRUE(contact != NULL);
err = re_regex(contact->val.p, contact->val.l,
"<sip:x@192.0.2.10:[0-9]+>;expires=3600", NULL);
TEST_ERR(err);

out:
tmr_cancel(&test.tmr);

mem_deref(reg);
sip_close(sip, true);
mem_deref(sip);
mem_deref(nat);
mem_deref(srv);

return err;
}


int test_sipreg_udp(void)
{
return reg_test(SIP_TRANSP_UDP, 0);
Expand Down
1 change: 1 addition & 0 deletions test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ static const struct test tests[] = {
#endif
TEST(test_fmt_trim),
TEST(test_sipevent),
TEST(test_sipreg_contact_rewrite),
TEST(test_sipreg_tcp),
#ifdef USE_TLS
TEST(test_sipreg_tls),
Expand Down
1 change: 1 addition & 0 deletions test/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ int test_sip_transp_wss_verify_host(void);
#endif
int test_sipevent(void);
int test_sipreg_udp(void);
int test_sipreg_contact_rewrite(void);
int test_sipreg_tcp(void);
#ifdef USE_TLS
int test_sipreg_tls(void);
Expand Down
Loading