blob: 39efa890f39bc9fe9c713897ddc95fccee832863 [file] [log] [blame]
/**
* @file keepalive_udp.c SIP UDP Keepalive
*
* Copyright (C) 2010 Creytiv.com
*/
#include <string.h>
#include <re_types.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_sa.h>
#include <re_list.h>
#include <re_hash.h>
#include <re_fmt.h>
#include <re_uri.h>
#include <re_sys.h>
#include <re_tmr.h>
#include <re_udp.h>
#include <re_stun.h>
#include <re_msg.h>
#include <re_sip.h>
#include "sip.h"
enum {
UDP_KEEPALIVE_INTVAL = 29,
};
struct sip_udpconn {
struct le he;
struct list kal;
struct tmr tmr_ka;
struct sa maddr;
struct sa paddr;
struct udp_sock *us;
struct stun_ctrans *ct;
struct stun *stun;
uint32_t ka_interval;
};
static void udpconn_keepalive_handler(void *arg);
static void destructor(void *arg)
{
struct sip_udpconn *uc = arg;
list_flush(&uc->kal);
hash_unlink(&uc->he);
tmr_cancel(&uc->tmr_ka);
mem_deref(uc->ct);
mem_deref(uc->us);
mem_deref(uc->stun);
}
static void udpconn_close(struct sip_udpconn *uc, int err)
{
sip_keepalive_signal(&uc->kal, err);
hash_unlink(&uc->he);
tmr_cancel(&uc->tmr_ka);
uc->ct = mem_deref(uc->ct);
uc->us = mem_deref(uc->us);
uc->stun = mem_deref(uc->stun);
}
static void stun_response_handler(int err, uint16_t scode, const char *reason,
const struct stun_msg *msg, void *arg)
{
struct sip_udpconn *uc = arg;
struct stun_attr *attr;
(void)reason;
if (err || scode) {
err = err ? err : EPROTO;
goto out;
}
attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
if (!attr) {
attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
if (!attr) {
err = EPROTO;
goto out;
}
}
if (!sa_isset(&uc->maddr, SA_ALL)) {
uc->maddr = attr->v.sa;
}
else if (!sa_cmp(&uc->maddr, &attr->v.sa, SA_ALL)) {
err = ENOTCONN;
goto out;
}
out:
if (err) {
udpconn_close(uc, err);
mem_deref(uc);
}
else {
tmr_start(&uc->tmr_ka, sip_keepalive_wait(uc->ka_interval),
udpconn_keepalive_handler, uc);
}
}
static void udpconn_keepalive_handler(void *arg)
{
struct sip_udpconn *uc = arg;
int err;
if (!uc->kal.head) {
/* no need for us anymore */
udpconn_close(uc, 0);
mem_deref(uc);
return;
}
err = stun_request(&uc->ct, uc->stun, IPPROTO_UDP, uc->us,
&uc->paddr, 0, STUN_METHOD_BINDING, NULL, 0,
false, stun_response_handler, uc, 1,
STUN_ATTR_SOFTWARE, stun_software);
if (err) {
udpconn_close(uc, err);
mem_deref(uc);
}
}
static struct sip_udpconn *udpconn_find(struct sip *sip, struct udp_sock *us,
const struct sa *paddr)
{
struct le *le;
le = list_head(hash_list(sip->ht_udpconn, sa_hash(paddr, SA_ALL)));
for (; le; le = le->next) {
struct sip_udpconn *uc = le->data;
if (!sa_cmp(&uc->paddr, paddr, SA_ALL))
continue;
if (uc->us != us)
continue;
return uc;
}
return NULL;
}
int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
struct udp_sock *us, const struct sa *paddr,
uint32_t interval)
{
struct sip_udpconn *uc;
if (!ka || !sip || !us || !paddr)
return EINVAL;
uc = udpconn_find(sip, us, paddr);
if (!uc) {
uc = mem_zalloc(sizeof(*uc), destructor);
if (!uc)
return ENOMEM;
hash_append(sip->ht_udpconn, sa_hash(paddr, SA_ALL),
&uc->he, uc);
uc->paddr = *paddr;
uc->stun = mem_ref(sip->stun);
uc->us = mem_ref(us);
uc->ka_interval = interval ? interval : UDP_KEEPALIVE_INTVAL;
/* learn mapped address immediately */
tmr_start(&uc->tmr_ka, 0, udpconn_keepalive_handler, uc);
}
list_append(&uc->kal, &ka->le, ka);
return 0;
}