mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2052 lines
53 KiB
2052 lines
53 KiB
/*
|
|
* Copyright (C) 2007-2009 Dan Pascu
|
|
*
|
|
* This file is part of Kamailio, a free SIP server.
|
|
*
|
|
* Kamailio is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version
|
|
*
|
|
* Kamailio is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
/*!
|
|
* \file
|
|
* \brief Module interface and functions
|
|
* \ingroup nat_traversal
|
|
* Module: \ref nat_traversal
|
|
*/
|
|
|
|
/**
|
|
* @defgroup nat_traversal Nat
|
|
* @brief Kamailio nat_traversal module
|
|
|
|
The nat_traversal module provides support for handling far-end NAT
|
|
traversal for SIP signaling.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include "../../sr_module.h"
|
|
#include "../../mem/shm_mem.h"
|
|
#include "../../mem/mem.h"
|
|
#include "../../lock_ops.h"
|
|
#include "../../dprint.h"
|
|
#include "../../str.h"
|
|
#include "../../pvar.h"
|
|
#include "../../error.h"
|
|
#include "../../timer.h"
|
|
#include "../../resolve.h"
|
|
#include "../../data_lump.h"
|
|
#include "../../mod_fix.h"
|
|
#include "../../script_cb.h"
|
|
#include "../../timer_proc.h"
|
|
#include "../../parser/msg_parser.h"
|
|
#include "../../parser/parse_from.h"
|
|
#include "../../parser/parse_uri.h"
|
|
#include "../../parser/parse_expires.h"
|
|
#include "../../parser/contact/parse_contact.h"
|
|
#include "../../lib/kcore/statistics.h"
|
|
#include "../dialog/dlg_load.h"
|
|
#include "../../modules/tm/tm_load.h"
|
|
#include "../../modules/sl/sl.h"
|
|
|
|
|
|
MODULE_VERSION
|
|
|
|
|
|
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
|
|
# define INLINE inline
|
|
#else
|
|
# define INLINE
|
|
#endif
|
|
|
|
|
|
/* WARNING: Keep this aligned with parser/msg_parser.h! */
|
|
#define FL_DO_KEEPALIVE (1<<31)
|
|
|
|
#define HASH_SIZE 512
|
|
|
|
|
|
#define max(a, b) ((a)>(b) ? (a) : (b))
|
|
#define min(a, b) ((a)<(b) ? (a) : (b))
|
|
|
|
#define STR_MATCH(str, buf) ((str).len==strlen(buf) && memcmp(buf, (str).s, (str).len)==0)
|
|
#define STR_IMATCH(str, buf) ((str).len==strlen(buf) && strncasecmp(buf, (str).s, (str).len)==0)
|
|
|
|
#define STR_MATCH_STR(str, str2) ((str).len==(str2).len && memcmp((str).s, (str2).s, (str).len)==0)
|
|
#define STR_IMATCH_STR(str, str2) ((str).len==(str2).len && strncasecmp((str).s, (str2).s, (str).len)==0)
|
|
|
|
#define STR_HAS_PREFIX(str, prefix) ((str).len>(prefix).len && memcmp((prefix).s, (str).s, (prefix).len)==0)
|
|
#define STR_HAS_IPREFIX(str, prefix) ((str).len>(prefix).len && strncasecmp((prefix).s, (str).s, (prefix).len)==0)
|
|
|
|
|
|
typedef bool (*NatTestFunction)(struct sip_msg *msg);
|
|
|
|
typedef enum {
|
|
NTNone=0,
|
|
NTPrivateContact=1,
|
|
NTSourceAddress=2,
|
|
NTPrivateVia=4
|
|
} NatTestType;
|
|
|
|
typedef struct {
|
|
NatTestType test;
|
|
NatTestFunction proc;
|
|
} NatTest;
|
|
|
|
typedef struct {
|
|
const char *name;
|
|
uint32_t address;
|
|
uint32_t mask;
|
|
} NetInfo;
|
|
|
|
|
|
typedef struct SIP_Dialog {
|
|
struct dlg_cell *dlg;
|
|
time_t expire;
|
|
struct SIP_Dialog *next;
|
|
} SIP_Dialog;
|
|
|
|
|
|
typedef struct NAT_Contact {
|
|
char *uri;
|
|
struct socket_info *socket;
|
|
|
|
time_t registration_expire;
|
|
time_t subscription_expire;
|
|
SIP_Dialog *dialogs;
|
|
|
|
struct NAT_Contact *next;
|
|
} NAT_Contact;
|
|
|
|
|
|
typedef struct HashSlot {
|
|
NAT_Contact *head; // pointer to the head of the linked list stored in this slot
|
|
gen_lock_t lock;
|
|
} HashSlot;
|
|
|
|
|
|
typedef struct HashTable {
|
|
HashSlot *slots;
|
|
unsigned size; // table size (number of slots)
|
|
} HashTable;
|
|
|
|
|
|
#define URI_LIST_INITIAL_SIZE 8
|
|
#define URI_LIST_RESIZE_INCREMENT 8
|
|
|
|
typedef struct Dialog_Param {
|
|
char *caller_uri;
|
|
char *callee_uri;
|
|
time_t expire;
|
|
bool confirmed;
|
|
gen_lock_t lock;
|
|
struct {
|
|
char **uri;
|
|
int count;
|
|
int size;
|
|
} callee_candidates;
|
|
} Dialog_Param;
|
|
|
|
|
|
// Module parameters
|
|
//
|
|
typedef struct Keepalive_Params {
|
|
// user specified
|
|
char *method;
|
|
char *from;
|
|
char *extra_headers;
|
|
|
|
// internally generated
|
|
char callid_prefix[20];
|
|
unsigned callid_counter;
|
|
unsigned from_tag;
|
|
char *event_header; // this will be set if method is NOTIFY
|
|
} Keepalive_Params;
|
|
|
|
|
|
// Function prototypes
|
|
//
|
|
static int NAT_Keepalive(struct sip_msg *msg);
|
|
static int FixContact(struct sip_msg *msg);
|
|
static int ClientNatTest(struct sip_msg *msg, unsigned int tests);
|
|
|
|
static bool test_private_contact(struct sip_msg *msg);
|
|
static bool test_source_address(struct sip_msg *msg);
|
|
static bool test_private_via(struct sip_msg *msg);
|
|
|
|
static INLINE char* shm_strdup(char *source);
|
|
|
|
static int mod_init(void);
|
|
static int child_init(int rank);
|
|
static void mod_destroy(void);
|
|
static int preprocess_request(struct sip_msg *msg, unsigned int flags, void *param);
|
|
static int reply_filter(struct sip_msg *reply);
|
|
|
|
static int pv_parse_nat_contact_name(pv_spec_p sp, str *in);
|
|
static int pv_get_keepalive_socket(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
|
|
static int pv_get_source_uri(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
|
|
|
|
|
|
// Module global variables and state
|
|
//
|
|
static HashTable *nat_table = NULL;
|
|
|
|
static bool keepalive_disabled = false;
|
|
|
|
static unsigned int keepalive_interval = 60;
|
|
|
|
static char *keepalive_state_file = "keepalive_state";
|
|
|
|
static Keepalive_Params keepalive_params = {"NOTIFY", NULL, "", "", 0, 0, ""};
|
|
|
|
struct tm_binds tm_api;
|
|
struct dlg_binds dlg_api;
|
|
bool have_dlg_api = false;
|
|
|
|
static int dialog_flag = -1;
|
|
static unsigned dialog_default_timeout = 12*3600; // 12 hours
|
|
|
|
stat_var *keepalive_endpoints = 0;
|
|
stat_var *registered_endpoints = 0;
|
|
stat_var *subscribed_endpoints = 0;
|
|
stat_var *dialog_endpoints = 0;
|
|
|
|
static NetInfo rfc1918nets[] = {
|
|
{"10.0.0.0", 0x0a000000UL, 0xff000000UL},
|
|
{"172.16.0.0", 0xac100000UL, 0xfff00000UL},
|
|
{"192.168.0.0", 0xc0a80000UL, 0xffff0000UL},
|
|
{"100.64.0.0", 0x64400000UL, 0xffc00000UL}, // include rfc6598 shared address space as technically the same for our purpose
|
|
{NULL, 0UL, 0UL}
|
|
};
|
|
|
|
static NatTest NAT_Tests[] = {
|
|
{NTPrivateContact, test_private_contact},
|
|
{NTSourceAddress, test_source_address},
|
|
{NTPrivateVia, test_private_via},
|
|
{NTNone, NULL}
|
|
};
|
|
|
|
/** SL API structure */
|
|
sl_api_t slb;
|
|
|
|
static cmd_export_t commands[] = {
|
|
{"nat_keepalive", (cmd_function)NAT_Keepalive, 0, NULL, 0, REQUEST_ROUTE},
|
|
{"fix_contact", (cmd_function)FixContact, 0, NULL, 0, REQUEST_ROUTE | ONREPLY_ROUTE | BRANCH_ROUTE |LOCAL_ROUTE},
|
|
{"client_nat_test", (cmd_function)ClientNatTest, 1, fixup_uint_null, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | BRANCH_ROUTE|LOCAL_ROUTE},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
static param_export_t parameters[] = {
|
|
{"keepalive_interval", INT_PARAM, &keepalive_interval},
|
|
{"keepalive_method", PARAM_STRING, &keepalive_params.method},
|
|
{"keepalive_from", PARAM_STRING, &keepalive_params.from},
|
|
{"keepalive_extra_headers", PARAM_STRING, &keepalive_params.extra_headers},
|
|
{"keepalive_state_file", PARAM_STRING, &keepalive_state_file},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
static pv_export_t pvars[] = {
|
|
{str_init("keepalive.socket"), PVT_OTHER, pv_get_keepalive_socket, NULL, pv_parse_nat_contact_name, NULL, NULL, 0},
|
|
{str_init("source_uri"), PVT_OTHER, pv_get_source_uri, NULL, NULL, NULL, NULL, 0},
|
|
{{0, 0}, 0, 0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
#ifdef STATISTICS
|
|
static stat_export_t statistics[] = {
|
|
{"keepalive_endpoints", STAT_NO_RESET, &keepalive_endpoints},
|
|
{"registered_endpoints", STAT_NO_RESET, ®istered_endpoints},
|
|
{"subscribed_endpoints", STAT_NO_RESET, &subscribed_endpoints},
|
|
{"dialog_endpoints", STAT_NO_RESET, &dialog_endpoints},
|
|
{0, 0, 0}
|
|
};
|
|
#endif
|
|
|
|
struct module_exports exports = {
|
|
"nat_traversal", // module name
|
|
DEFAULT_DLFLAGS, // dlopen flags
|
|
commands, // exported functions
|
|
parameters, // exported parameters
|
|
NULL, // exported statistics (initialized early in mod_init)
|
|
NULL, // exported MI functions
|
|
pvars, // exported pseudo-variables
|
|
NULL, // extra processes
|
|
mod_init, // module init function (before fork. kids will inherit)
|
|
reply_filter, // reply processing function
|
|
mod_destroy, // destroy function
|
|
child_init // child init function
|
|
};
|
|
|
|
|
|
|
|
// SIP_Dialog structure handling functions
|
|
//
|
|
|
|
static SIP_Dialog*
|
|
SIP_Dialog_new(struct dlg_cell *dlg, time_t expire)
|
|
{
|
|
SIP_Dialog *dialog;
|
|
|
|
dialog = (SIP_Dialog*)shm_malloc(sizeof(SIP_Dialog));
|
|
if (!dialog) {
|
|
LM_ERR("out of memory while creating new SIP_Dialog structure\n");
|
|
return NULL;
|
|
}
|
|
dialog->dlg = dlg;
|
|
dialog->expire = expire;
|
|
dialog->next = NULL;
|
|
|
|
// we assume expire is always strictly positive on new dialogs
|
|
update_stat(dialog_endpoints, 1);
|
|
|
|
return dialog;
|
|
}
|
|
|
|
|
|
static void
|
|
SIP_Dialog_del(SIP_Dialog *dialog)
|
|
{
|
|
if (!dialog)
|
|
return;
|
|
|
|
if (dialog->expire > 0)
|
|
update_stat(dialog_endpoints, -1);
|
|
shm_free(dialog);
|
|
}
|
|
|
|
|
|
// Purge expired dialogs from the linked list pointed by dialog
|
|
//
|
|
static SIP_Dialog*
|
|
SIP_Dialog_purge_expired(SIP_Dialog *dialog, time_t now)
|
|
{
|
|
SIP_Dialog *next;
|
|
|
|
if (dialog==NULL)
|
|
return NULL;
|
|
|
|
dialog->next = SIP_Dialog_purge_expired(dialog->next, now);
|
|
|
|
if (now > dialog->expire) {
|
|
next = dialog->next;
|
|
SIP_Dialog_del(dialog);
|
|
return next;
|
|
}
|
|
|
|
return dialog;
|
|
}
|
|
|
|
|
|
static INLINE void
|
|
SIP_Dialog_end(SIP_Dialog *dialog)
|
|
{
|
|
if (dialog->expire > 0) {
|
|
dialog->expire = 0;
|
|
update_stat(dialog_endpoints, -1);
|
|
}
|
|
}
|
|
|
|
|
|
// Helpers to handle registration and subscription timeouts for NAT_Contacts
|
|
//
|
|
static INLINE void
|
|
SIP_Registration_update(NAT_Contact *contact, time_t expire)
|
|
{
|
|
if (expire > contact->registration_expire) {
|
|
if (contact->registration_expire == 0)
|
|
update_stat(registered_endpoints, 1);
|
|
contact->registration_expire = expire;
|
|
}
|
|
}
|
|
|
|
static INLINE void
|
|
SIP_Registration_expire(NAT_Contact *contact, time_t now)
|
|
{
|
|
if (contact->registration_expire && now > contact->registration_expire) {
|
|
update_stat(registered_endpoints, -1);
|
|
contact->registration_expire = 0;
|
|
}
|
|
}
|
|
|
|
static INLINE void
|
|
SIP_Subscription_update(NAT_Contact *contact, time_t expire)
|
|
{
|
|
if (expire > contact->subscription_expire) {
|
|
if (contact->subscription_expire == 0)
|
|
update_stat(subscribed_endpoints, 1);
|
|
contact->subscription_expire = expire;
|
|
}
|
|
}
|
|
|
|
static INLINE void
|
|
SIP_Subscription_expire(NAT_Contact *contact, time_t now)
|
|
{
|
|
if (contact->subscription_expire && now > contact->subscription_expire) {
|
|
update_stat(subscribed_endpoints, -1);
|
|
contact->subscription_expire = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// NAT_Contact structure handling functions
|
|
//
|
|
|
|
static NAT_Contact*
|
|
NAT_Contact_new(char *uri, struct socket_info *socket)
|
|
{
|
|
NAT_Contact *contact;
|
|
|
|
contact = (NAT_Contact*)shm_malloc(sizeof(NAT_Contact));
|
|
if (!contact) {
|
|
LM_ERR("out of memory while creating new NAT_Contact structure\n");
|
|
return NULL;
|
|
}
|
|
memset(contact, 0, sizeof(NAT_Contact));
|
|
|
|
contact->uri = shm_strdup(uri);
|
|
if (!contact->uri) {
|
|
LM_ERR("out of memory while creating new NAT_Contact structure\n");
|
|
shm_free(contact);
|
|
return NULL;
|
|
}
|
|
contact->socket = socket;
|
|
|
|
update_stat(keepalive_endpoints, 1);
|
|
|
|
return contact;
|
|
}
|
|
|
|
|
|
static void
|
|
NAT_Contact_del(NAT_Contact *contact)
|
|
{
|
|
SIP_Dialog *dialog, *next;
|
|
|
|
if (!contact)
|
|
return;
|
|
|
|
dialog = contact->dialogs;
|
|
while (dialog) {
|
|
next = dialog->next;
|
|
SIP_Dialog_del(dialog);
|
|
dialog = next;
|
|
}
|
|
|
|
if (contact->registration_expire > 0)
|
|
update_stat(registered_endpoints, -1);
|
|
if (contact->subscription_expire > 0)
|
|
update_stat(subscribed_endpoints, -1);
|
|
update_stat(keepalive_endpoints, -1);
|
|
|
|
shm_free(contact->uri);
|
|
shm_free(contact);
|
|
}
|
|
|
|
|
|
static bool
|
|
NAT_Contact_match(NAT_Contact *contact, const char *uri)
|
|
{
|
|
return strcmp(contact->uri, uri)==0;
|
|
}
|
|
|
|
|
|
static SIP_Dialog*
|
|
NAT_Contact_get_dialog(NAT_Contact *contact, struct dlg_cell *dlg)
|
|
{
|
|
SIP_Dialog *dialog;
|
|
|
|
dialog = contact->dialogs;
|
|
|
|
while (dialog) {
|
|
if (dialog->dlg == dlg)
|
|
break;
|
|
dialog = dialog->next;
|
|
}
|
|
|
|
return dialog;
|
|
}
|
|
|
|
|
|
static NAT_Contact*
|
|
NAT_Contact_purge_expired(NAT_Contact *contact, time_t now)
|
|
{
|
|
NAT_Contact *next;
|
|
|
|
if (contact==NULL)
|
|
return NULL;
|
|
|
|
contact->next = NAT_Contact_purge_expired(contact->next, now);
|
|
|
|
SIP_Registration_expire(contact, now);
|
|
SIP_Subscription_expire(contact, now);
|
|
contact->dialogs = SIP_Dialog_purge_expired(contact->dialogs, now);
|
|
|
|
if (!contact->registration_expire && !contact->subscription_expire && !contact->dialogs) {
|
|
next = contact->next;
|
|
NAT_Contact_del(contact);
|
|
return next;
|
|
}
|
|
|
|
return contact;
|
|
}
|
|
|
|
|
|
// HashTable structure manipulation
|
|
//
|
|
|
|
#define HASH(table, key) (hash_string(key) % (table)->size)
|
|
|
|
static INLINE unsigned
|
|
hash_string(const char *key)
|
|
{
|
|
register unsigned ret = 0;
|
|
register unsigned ctr = 0;
|
|
|
|
while (*key) {
|
|
ret ^= *(char*)key++ << ctr;
|
|
ctr = (ctr + 1) % sizeof (char *);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static HashTable*
|
|
HashTable_new(void)
|
|
{
|
|
HashTable *table;
|
|
int i, j;
|
|
|
|
table = shm_malloc(sizeof(HashTable));
|
|
if (!table) {
|
|
LM_ERR("cannot allocate shared memory for hash table\n");
|
|
return NULL;
|
|
}
|
|
memset(table, 0, sizeof(HashTable));
|
|
|
|
table->size = HASH_SIZE;
|
|
|
|
table->slots = shm_malloc(sizeof(HashSlot)*table->size);
|
|
if (!table->slots) {
|
|
LM_ERR("cannot allocate shared memory for hash table\n");
|
|
shm_free(table);
|
|
return NULL;
|
|
}
|
|
memset(table->slots, 0, sizeof(HashSlot)*table->size);
|
|
|
|
for (i=0; i<table->size; i++) {
|
|
if (!lock_init(&table->slots[i].lock)) {
|
|
LM_ERR("cannot initialize hash table locks\n");
|
|
for (j=0; j<i; j++)
|
|
lock_destroy(&table->slots[j].lock);
|
|
shm_free(table->slots);
|
|
shm_free(table);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
|
|
static void
|
|
HashTable_del(HashTable *table)
|
|
{
|
|
NAT_Contact *contact, *next;
|
|
int i;
|
|
|
|
for (i=0; i < table->size; i++) {
|
|
lock_get(&table->slots[i].lock);
|
|
contact = table->slots[i].head;
|
|
while (contact) {
|
|
next = contact->next;
|
|
NAT_Contact_del(contact);
|
|
contact = next;
|
|
}
|
|
table->slots[i].head = NULL;
|
|
lock_release(&table->slots[i].lock);
|
|
}
|
|
|
|
shm_free(table->slots);
|
|
shm_free(table);
|
|
}
|
|
|
|
|
|
// This function assumes that the caller has locked the slot already
|
|
//
|
|
static NAT_Contact*
|
|
HashTable_search(HashTable *table, char *uri, unsigned slot)
|
|
{
|
|
NAT_Contact *contact;
|
|
|
|
contact = table->slots[slot].head;
|
|
|
|
while (contact) {
|
|
if (NAT_Contact_match(contact, uri))
|
|
break;
|
|
contact = contact->next;
|
|
}
|
|
|
|
return contact;
|
|
}
|
|
|
|
|
|
// Dialog_Param structure handling functions
|
|
//
|
|
|
|
static Dialog_Param*
|
|
Dialog_Param_new(void)
|
|
{
|
|
Dialog_Param *param;
|
|
|
|
param = shm_malloc(sizeof(Dialog_Param));
|
|
if (!param) {
|
|
LM_ERR("cannot allocate shared memory for dialog callback param\n");
|
|
return NULL;
|
|
}
|
|
memset(param, 0, sizeof(Dialog_Param));
|
|
|
|
param->callee_candidates.uri = shm_malloc(sizeof(char*) * URI_LIST_INITIAL_SIZE);
|
|
if (!param->callee_candidates.uri) {
|
|
LM_ERR("cannot allocate shared memory for callee_candidates uri list\n");
|
|
shm_free(param);
|
|
return NULL;
|
|
}
|
|
memset(param->callee_candidates.uri, 0, sizeof(char*) * URI_LIST_INITIAL_SIZE);
|
|
param->callee_candidates.size = URI_LIST_INITIAL_SIZE;
|
|
|
|
param->expire = time(NULL) + dialog_default_timeout;
|
|
|
|
if (!lock_init(¶m->lock)) {
|
|
LM_ERR("cannot initialize dialog param structure lock\n");
|
|
shm_free(param->callee_candidates.uri);
|
|
shm_free(param);
|
|
return NULL;
|
|
}
|
|
|
|
return param;
|
|
}
|
|
|
|
|
|
static void
|
|
Dialog_Param_del(Dialog_Param *param)
|
|
{
|
|
int i;
|
|
|
|
if (!param)
|
|
return;
|
|
|
|
lock_destroy(¶m->lock);
|
|
|
|
if (param->caller_uri)
|
|
shm_free(param->caller_uri);
|
|
if (param->callee_uri)
|
|
shm_free(param->callee_uri);
|
|
for (i=0; i<param->callee_candidates.count; i++)
|
|
shm_free(param->callee_candidates.uri[i]);
|
|
shm_free(param->callee_candidates.uri);
|
|
shm_free(param);
|
|
}
|
|
|
|
|
|
// This function assumes the caller has locked the Dialog_Param while operating on it
|
|
//
|
|
static bool
|
|
Dialog_Param_has_candidate(Dialog_Param *param, char *candidate)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<param->callee_candidates.count; i++) {
|
|
if (strcmp(candidate, param->callee_candidates.uri[i])==0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// This function assumes the caller has locked the Dialog_Param while operating on it
|
|
//
|
|
static bool
|
|
Dialog_Param_add_candidate(Dialog_Param *param, char *candidate)
|
|
{
|
|
char **new_uri, *new_candidate;
|
|
int new_size;
|
|
|
|
if (param->callee_candidates.count == param->callee_candidates.size) {
|
|
new_size = param->callee_candidates.size + URI_LIST_RESIZE_INCREMENT;
|
|
LM_DBG("growing callee_candidates list size from %d to %d entries\n", param->callee_candidates.size, new_size);
|
|
new_uri = shm_realloc(param->callee_candidates.uri, new_size * sizeof(char*));
|
|
if (!new_uri) {
|
|
LM_ERR("failed to grow callee_candidates uri list\n");
|
|
return false;
|
|
}
|
|
param->callee_candidates.uri = new_uri;
|
|
param->callee_candidates.size = new_size;
|
|
}
|
|
|
|
new_candidate = shm_strdup(candidate);
|
|
if (!new_candidate) {
|
|
LM_ERR("cannot allocate shared memory for new candidate uri\n");
|
|
return false;
|
|
}
|
|
|
|
param->callee_candidates.uri[param->callee_candidates.count] = new_candidate;
|
|
param->callee_candidates.count++;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Miscellaneous helper functions
|
|
//
|
|
|
|
// returns str with leading whitespace removed
|
|
static INLINE void
|
|
ltrim(str *string)
|
|
{
|
|
while (string->len>0 && isspace((int)*(string->s))) {
|
|
string->len--;
|
|
string->s++;
|
|
}
|
|
}
|
|
|
|
// returns str with trailing whitespace removed
|
|
static INLINE void
|
|
rtrim(str *string)
|
|
{
|
|
char *ptr;
|
|
|
|
ptr = string->s + string->len - 1;
|
|
while (string->len>0 && (*ptr==0 || isspace((int)*ptr))) {
|
|
string->len--;
|
|
ptr--;
|
|
}
|
|
}
|
|
|
|
// returns str with leading and trailing whitespace removed
|
|
static INLINE void
|
|
trim(str *string)
|
|
{
|
|
ltrim(string);
|
|
rtrim(string);
|
|
}
|
|
|
|
|
|
static INLINE char*
|
|
shm_strdup(char *source)
|
|
{
|
|
char *copy;
|
|
|
|
if (!source)
|
|
return NULL;
|
|
|
|
copy = (char*)shm_malloc(strlen(source) + 1);
|
|
if (!copy)
|
|
return NULL;
|
|
strcpy(copy, source);
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
static bool
|
|
get_contact_uri(struct sip_msg* msg, struct sip_uri *uri, contact_t **_c)
|
|
{
|
|
|
|
if ((parse_headers(msg, HDR_CONTACT_F, 0) == -1) || !msg->contact)
|
|
return false;
|
|
|
|
if (!msg->contact->parsed && parse_contact(msg->contact) < 0) {
|
|
LM_ERR("cannot parse the Contact header\n");
|
|
return false;
|
|
}
|
|
|
|
*_c = ((contact_body_t*)msg->contact->parsed)->contacts;
|
|
|
|
if (*_c == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (parse_uri((*_c)->uri.s, (*_c)->uri.len, uri) < 0 || uri->host.len <= 0) {
|
|
LM_ERR("cannot parse the Contact URI\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
#define is_private_address(x) (rfc1918address(x)==1 ? 1 : 0)
|
|
|
|
// Test if IP in `address' belongs to a RFC1918 network
|
|
static INLINE int
|
|
rfc1918address(str *address)
|
|
{
|
|
struct ip_addr *ip;
|
|
uint32_t netaddr;
|
|
int i;
|
|
|
|
ip = str2ip(address);
|
|
if (ip == NULL)
|
|
return -1; // invalid address to test
|
|
|
|
netaddr = ntohl(ip->u.addr32[0]);
|
|
|
|
for (i=0; rfc1918nets[i].name!=NULL; i++) {
|
|
if ((netaddr & rfc1918nets[i].mask)==rfc1918nets[i].address) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Test if address of signaling is different from address in 1st Via field
|
|
static bool
|
|
test_source_address(struct sip_msg *msg)
|
|
{
|
|
bool different_ip, different_port;
|
|
int via1_port;
|
|
|
|
different_ip = received_via_test(msg);
|
|
via1_port = (msg->via1->port ? msg->via1->port : SIP_PORT);
|
|
different_port = (msg->rcv.src_port != via1_port);
|
|
|
|
return (different_ip || different_port);
|
|
}
|
|
|
|
|
|
// Test if Contact field contains a private IP address as defined in RFC1918
|
|
static bool
|
|
test_private_contact(struct sip_msg *msg)
|
|
{
|
|
struct sip_uri uri;
|
|
contact_t* contact;
|
|
|
|
if (!get_contact_uri(msg, &uri, &contact))
|
|
return false;
|
|
|
|
return is_private_address(&(uri.host));
|
|
}
|
|
|
|
|
|
// Test if top Via field contains a private IP address as defined in RFC1918
|
|
static bool
|
|
test_private_via(struct sip_msg *msg)
|
|
{
|
|
return is_private_address(&(msg->via1->host));
|
|
}
|
|
|
|
|
|
// return the Expires header value (converted to an UNIX timestamp if > 0)
|
|
static int
|
|
get_expires(struct sip_msg *msg)
|
|
{
|
|
exp_body_t *expires;
|
|
|
|
if (parse_headers(msg, HDR_EXPIRES_F, 0) < 0) {
|
|
LM_ERR("failed to parse the Expires header\n");
|
|
return 0;
|
|
}
|
|
if (!msg->expires)
|
|
return 0;
|
|
|
|
if (parse_expires(msg->expires) < 0) {
|
|
LM_ERR("failed to parse the Expires header body\n");
|
|
return 0;
|
|
}
|
|
|
|
expires = (exp_body_t*)msg->expires->parsed;
|
|
|
|
return ((expires->valid && expires->val) ? expires->val + time(NULL) : 0);
|
|
}
|
|
|
|
|
|
// return the highest expire value from all registered contacts in the request
|
|
static time_t
|
|
get_register_expire(struct sip_msg *request, struct sip_msg *reply)
|
|
{
|
|
struct hdr_field contact_hdr, *hdr, *r_hdr;
|
|
contact_body_t *contact_body, *r_contact_body;
|
|
contact_t *contact, *r_contact;
|
|
param_t *expires_param;
|
|
time_t now, expire=0;
|
|
unsigned exp;
|
|
bool matched;
|
|
|
|
if (!request->contact)
|
|
return 0;
|
|
|
|
if (parse_headers(reply, HDR_EOH_F, 0) < 0) {
|
|
LM_ERR("failed to parse headers for REGISTER reply\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!reply->contact)
|
|
return 0;
|
|
|
|
now = time(NULL);
|
|
|
|
// request may be R/O (if we are called from the TM callback),
|
|
// thus we copy the hdr_field structures before parsing them
|
|
|
|
for (hdr=request->contact; hdr; hdr = next_sibling_hdr(hdr)) {
|
|
if (!hdr->parsed) {
|
|
memcpy(&contact_hdr, hdr, sizeof(struct hdr_field));
|
|
if (parse_contact(&contact_hdr) < 0) {
|
|
LM_ERR("failed to parse the Contact header body\n");
|
|
continue;
|
|
}
|
|
contact_body = (contact_body_t*)contact_hdr.parsed;
|
|
} else {
|
|
contact_body = (contact_body_t*)hdr->parsed;
|
|
}
|
|
|
|
if (contact_body->star) {
|
|
if (!hdr->parsed)
|
|
clean_hdr_field(&contact_hdr);
|
|
return 0;
|
|
}
|
|
|
|
for (contact=contact_body->contacts; contact; contact=contact->next) {
|
|
for (r_hdr=reply->contact, matched=false; r_hdr && !matched; r_hdr=next_sibling_hdr(r_hdr)) {
|
|
if (!r_hdr->parsed && parse_contact(r_hdr) < 0) {
|
|
LM_ERR("failed to parse the Contact header body in reply\n");
|
|
continue;
|
|
}
|
|
r_contact_body = (contact_body_t*)r_hdr->parsed;
|
|
for (r_contact=r_contact_body->contacts; r_contact; r_contact=r_contact->next) {
|
|
if (STR_MATCH_STR(contact->uri, r_contact->uri)) {
|
|
expires_param = r_contact->expires;
|
|
if (expires_param && expires_param->body.len && str2int(&expires_param->body, &exp) == 0)
|
|
expire = max(expire, exp);
|
|
matched = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!hdr->parsed) {
|
|
clean_hdr_field(&contact_hdr);
|
|
}
|
|
}
|
|
|
|
LM_DBG("maximum expire for all contacts: %u\n", (unsigned)expire);
|
|
|
|
return (expire ? expire + now : 0);
|
|
}
|
|
|
|
|
|
static char*
|
|
get_source_uri(struct sip_msg *msg)
|
|
{
|
|
static char uri[64];
|
|
snprintf(uri, 64, "sip:%s:%d", ip_addr2a(&msg->rcv.src_ip), msg->rcv.src_port);
|
|
return uri;
|
|
}
|
|
|
|
|
|
static void
|
|
keepalive_registration(struct sip_msg *request, time_t expire)
|
|
{
|
|
NAT_Contact *contact;
|
|
unsigned h;
|
|
char *uri;
|
|
|
|
uri = get_source_uri(request);
|
|
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (contact) {
|
|
SIP_Registration_update(contact, expire);
|
|
} else {
|
|
contact = NAT_Contact_new(uri, request->rcv.bind_address);
|
|
if (contact) {
|
|
SIP_Registration_update(contact, expire);
|
|
contact->next = nat_table->slots[h].head;
|
|
nat_table->slots[h].head = contact;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new NAT contact\n");
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
|
|
static void
|
|
keepalive_subscription(struct sip_msg *request, time_t expire)
|
|
{
|
|
NAT_Contact *contact;
|
|
unsigned h;
|
|
char *uri;
|
|
|
|
uri = get_source_uri(request);
|
|
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (contact) {
|
|
SIP_Subscription_update(contact, expire);
|
|
} else {
|
|
contact = NAT_Contact_new(uri, request->rcv.bind_address);
|
|
if (contact) {
|
|
SIP_Subscription_update(contact, expire);
|
|
contact->next = nat_table->slots[h].head;
|
|
nat_table->slots[h].head = contact;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new NAT contact\n");
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
|
|
static void
|
|
__dialog_early(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
|
|
{
|
|
Dialog_Param *param = (Dialog_Param*)*_params->param;
|
|
NAT_Contact *contact;
|
|
SIP_Dialog *dialog;
|
|
unsigned h;
|
|
char *uri;
|
|
|
|
lock_get(¶m->lock);
|
|
|
|
if (param->confirmed) {
|
|
// this 1xx is late; dialog already confirmed by 200 OK; ignore it
|
|
lock_release(¶m->lock);
|
|
return;
|
|
}
|
|
|
|
uri = get_source_uri(_params->rpl);
|
|
if (!Dialog_Param_has_candidate(param, uri)) {
|
|
if (!Dialog_Param_add_candidate(param, uri)) {
|
|
LM_ERR("cannot add callee candidate uri to the list\n");
|
|
} else {
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (!dialog) {
|
|
dialog = SIP_Dialog_new(dlg, param->expire);
|
|
if (dialog) {
|
|
dialog->next = contact->dialogs;
|
|
contact->dialogs = dialog;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new SIP dialog\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
}
|
|
|
|
lock_release(¶m->lock);
|
|
}
|
|
|
|
|
|
static void
|
|
__dialog_confirmed(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
|
|
{
|
|
Dialog_Param *param = (Dialog_Param*)*_params->param;
|
|
NAT_Contact *contact;
|
|
SIP_Dialog *dialog;
|
|
char *callee_uri, *uri;
|
|
unsigned h;
|
|
int i;
|
|
|
|
lock_get(¶m->lock);
|
|
|
|
param->confirmed = true;
|
|
|
|
callee_uri = get_source_uri(_params->rpl);
|
|
|
|
// remove all keepalives on unanswered branches
|
|
for (i=0; i<param->callee_candidates.count; i++) {
|
|
uri = param->callee_candidates.uri[i];
|
|
|
|
if (strcmp(uri, callee_uri) != 0) {
|
|
// this is an unanswered branch
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (dialog) {
|
|
SIP_Dialog_end(dialog);
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
shm_free(param->callee_candidates.uri[i]);
|
|
param->callee_candidates.uri[i] = NULL;
|
|
}
|
|
|
|
param->callee_candidates.count = 0;
|
|
|
|
// add dialog keepalive for answered branch, if needed and not already there
|
|
h = HASH(nat_table, callee_uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, callee_uri, h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (!dialog) {
|
|
dialog = SIP_Dialog_new(dlg, param->expire);
|
|
if (dialog) {
|
|
dialog->next = contact->dialogs;
|
|
contact->dialogs = dialog;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new SIP dialog\n");
|
|
}
|
|
}
|
|
// free old uri in case this callback is called more than once (shouldn't normally happen)
|
|
if (param->callee_uri)
|
|
shm_free(param->callee_uri);
|
|
param->callee_uri = shm_strdup(callee_uri);
|
|
if (!param->callee_uri) {
|
|
LM_ERR("cannot allocate shared memory for callee_uri in dialog param\n");
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
|
|
lock_release(¶m->lock);
|
|
}
|
|
|
|
|
|
static void
|
|
__dialog_destroy(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
|
|
{
|
|
Dialog_Param *param = (Dialog_Param*)*_params->param;
|
|
NAT_Contact *contact;
|
|
SIP_Dialog *dialog;
|
|
unsigned h;
|
|
int i;
|
|
|
|
if (!param)
|
|
return;
|
|
|
|
// If nat_table is NULL, it's because it was already removed during
|
|
// shutdown by mod_destroy. However we can still receive dialog destroy
|
|
// notifications when the dialog module removes dialogs on shutdown.
|
|
if (!nat_table) {
|
|
Dialog_Param_del(param);
|
|
*_params->param = NULL;
|
|
return;
|
|
}
|
|
|
|
if (param->caller_uri) {
|
|
h = HASH(nat_table, param->caller_uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, param->caller_uri, h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (dialog) {
|
|
SIP_Dialog_end(dialog);
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
if (param->callee_uri) {
|
|
h = HASH(nat_table, param->callee_uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, param->callee_uri, h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (dialog) {
|
|
SIP_Dialog_end(dialog);
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
lock_get(¶m->lock);
|
|
|
|
// remove all keepalives on unanswered branches. this is neded because
|
|
// we may transit from early to ended without going through confirmed
|
|
for (i=0; i<param->callee_candidates.count; i++) {
|
|
h = HASH(nat_table, param->callee_candidates.uri[i]);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, param->callee_candidates.uri[i], h);
|
|
if (contact) {
|
|
dialog = NAT_Contact_get_dialog(contact, dlg);
|
|
if (dialog) {
|
|
SIP_Dialog_end(dialog);
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
|
|
shm_free(param->callee_candidates.uri[i]);
|
|
param->callee_candidates.uri[i] = NULL;
|
|
}
|
|
|
|
param->callee_candidates.count = 0;
|
|
|
|
lock_release(¶m->lock);
|
|
|
|
Dialog_Param_del(param);
|
|
|
|
*_params->param = NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
__dialog_created(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
|
|
{
|
|
struct sip_msg *request = _params->req;
|
|
NAT_Contact *contact;
|
|
SIP_Dialog *dialog;
|
|
Dialog_Param *param;
|
|
unsigned h;
|
|
char *uri;
|
|
|
|
if (request->REQ_METHOD != METHOD_INVITE)
|
|
return;
|
|
|
|
param = Dialog_Param_new();
|
|
if (!param) {
|
|
LM_ERR("cannot create dialog callback param\n");
|
|
return;
|
|
}
|
|
|
|
if (dlg_api.register_dlgcb(dlg, DLGCB_DESTROY, __dialog_destroy, param, NULL) != 0) {
|
|
LM_ERR("cannot register callback for dialog destruction\n");
|
|
Dialog_Param_del(param);
|
|
return;
|
|
}
|
|
|
|
if (dlg_api.register_dlgcb(dlg, DLGCB_EARLY, __dialog_early, param, NULL) != 0)
|
|
LM_ERR("cannot register callback for dialog early replies\n");
|
|
if (dlg_api.register_dlgcb(dlg, DLGCB_CONFIRMED_NA, __dialog_confirmed, param, NULL) != 0)
|
|
LM_ERR("cannot register callback for dialog confirmation\n");
|
|
|
|
if ((request->msg_flags & FL_DO_KEEPALIVE) == 0)
|
|
return;
|
|
|
|
uri = get_source_uri(request);
|
|
param->caller_uri = shm_strdup(uri);
|
|
if (!param->caller_uri) {
|
|
LM_ERR("cannot allocate shared memory for caller_uri in dialog param\n");
|
|
return;
|
|
}
|
|
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (contact) {
|
|
dialog = SIP_Dialog_new(dlg, param->expire);
|
|
if (dialog) {
|
|
dialog->next = contact->dialogs;
|
|
contact->dialogs = dialog;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new SIP dialog\n");
|
|
}
|
|
} else {
|
|
contact = NAT_Contact_new(uri, request->rcv.bind_address);
|
|
if (contact) {
|
|
contact->dialogs = SIP_Dialog_new(dlg, param->expire);
|
|
if (contact->dialogs) {
|
|
contact->next = nat_table->slots[h].head;
|
|
nat_table->slots[h].head = contact;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new SIP dialog\n");
|
|
NAT_Contact_del(contact);
|
|
}
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new NAT contact\n");
|
|
}
|
|
}
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
}
|
|
|
|
|
|
// callback to handle all SL generated replies
|
|
//
|
|
static void
|
|
__sl_reply_out(sl_cbp_t *slcbp)
|
|
{
|
|
struct sip_msg reply;
|
|
struct sip_msg *request;
|
|
time_t expire;
|
|
|
|
request = slcbp->req;
|
|
if (request->REQ_METHOD == METHOD_INVITE)
|
|
return;
|
|
|
|
if ((request->msg_flags & FL_DO_KEEPALIVE) == 0)
|
|
return;
|
|
|
|
if (slcbp->code >= 200 && slcbp->code < 300) {
|
|
memset(&reply, 0, sizeof(struct sip_msg));
|
|
reply.buf = slcbp->reply->s;
|
|
reply.len = slcbp->reply->len;
|
|
|
|
if (parse_msg(reply.buf, reply.len, &reply) != 0) {
|
|
LM_ERR("cannot parse outgoing SL reply for keepalive"
|
|
" information\n");
|
|
return;
|
|
}
|
|
|
|
switch (request->REQ_METHOD) {
|
|
case METHOD_SUBSCRIBE:
|
|
expire = get_expires(&reply);
|
|
if (expire > 0)
|
|
keepalive_subscription(request, expire);
|
|
break;
|
|
case METHOD_REGISTER:
|
|
expire = get_register_expire(request, &reply);
|
|
if (expire > 0)
|
|
keepalive_registration(request, expire);
|
|
break;
|
|
default:
|
|
LM_ERR("called with keepalive flag set for unsupported method\n");
|
|
break;
|
|
}
|
|
|
|
free_sip_msg(&reply);
|
|
}
|
|
}
|
|
|
|
|
|
// callback to handle incoming replies for the request's transactions
|
|
//
|
|
static void
|
|
__tm_reply_in(struct cell *trans, int type, struct tmcb_params *param)
|
|
{
|
|
time_t expire;
|
|
|
|
if (param->req==NULL || param->rpl==NULL)
|
|
return;
|
|
|
|
if (param->code >= 200 && param->code < 300) {
|
|
switch (param->req->REQ_METHOD) {
|
|
case METHOD_SUBSCRIBE:
|
|
expire = get_expires(param->rpl);
|
|
if (expire > 0)
|
|
keepalive_subscription(param->req, expire);
|
|
break;
|
|
case METHOD_REGISTER:
|
|
expire = get_register_expire(param->req, param->rpl);
|
|
if (expire > 0)
|
|
keepalive_registration(param->req, expire);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Keepalive NAT for an UA while it has registered contacts or active dialogs
|
|
//
|
|
static int
|
|
NAT_Keepalive(struct sip_msg *msg)
|
|
{
|
|
|
|
if (keepalive_disabled)
|
|
return -1;
|
|
|
|
// keepalive is only supported for UDP dialogs
|
|
if (msg->rcv.proto!=PROTO_UDP)
|
|
return -1;
|
|
|
|
switch (msg->REQ_METHOD) {
|
|
|
|
case METHOD_REGISTER:
|
|
// make the expires & contact headers available later in the TM cloned msg
|
|
if (parse_headers(msg, HDR_EOH_F, 0) < 0) {
|
|
LM_ERR("failed to parse headers in REGISTER request\n");
|
|
return -1;
|
|
}
|
|
// fallthrough
|
|
case METHOD_SUBSCRIBE:
|
|
msg->msg_flags |= FL_DO_KEEPALIVE;
|
|
if (tm_api.register_tmcb(msg, 0, TMCB_RESPONSE_IN, __tm_reply_in, 0, 0) <= 0) {
|
|
LM_ERR("cannot register TM callback for incoming replies\n");
|
|
return -1;
|
|
}
|
|
return 1;
|
|
|
|
case METHOD_INVITE:
|
|
if (!have_dlg_api) {
|
|
LM_ERR("cannot keep alive dialog without the dialog module being loaded\n");
|
|
return -1;
|
|
}
|
|
msg->msg_flags |= FL_DO_KEEPALIVE;
|
|
setflag(msg, dialog_flag); // have the dialog module trace this dialog
|
|
return 1;
|
|
|
|
default:
|
|
LM_ERR("unsupported method for keepalive\n");
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Replace IP:Port in Contact field with the source address of the packet.
|
|
static int
|
|
FixContact(struct sip_msg *msg)
|
|
{
|
|
str before_host, after, newip;
|
|
unsigned short port, newport;
|
|
contact_t* contact;
|
|
struct lump* anchor;
|
|
struct sip_uri uri;
|
|
int len, offset;
|
|
char *buf;
|
|
|
|
if (!get_contact_uri(msg, &uri, &contact))
|
|
return -1;
|
|
|
|
newip.s = ip_addr2a(&msg->rcv.src_ip);
|
|
newip.len = strlen(newip.s);
|
|
newport = msg->rcv.src_port;
|
|
|
|
port = uri.port_no ? uri.port_no : 5060;
|
|
|
|
// Don't do anything if the address is the same, just return success.
|
|
if (STR_MATCH_STR(uri.host, newip) && port==newport)
|
|
return 1;
|
|
|
|
if (uri.port.len == 0)
|
|
uri.port.s = uri.host.s + uri.host.len;
|
|
|
|
before_host.s = contact->uri.s;
|
|
before_host.len = uri.host.s - contact->uri.s;
|
|
after.s = uri.port.s + uri.port.len;
|
|
after.len = contact->uri.s + contact->uri.len - after.s;
|
|
|
|
len = before_host.len + newip.len + after.len + 20;
|
|
|
|
// first try to alloc mem. if we fail we don't want to have the lump
|
|
// deleted and not replaced. at least this way we keep the original.
|
|
buf = pkg_malloc(len);
|
|
if (buf == NULL) {
|
|
LM_ERR("out of memory\n");
|
|
return -1;
|
|
}
|
|
|
|
offset = contact->uri.s - msg->buf;
|
|
anchor = del_lump(msg, offset, contact->uri.len, (enum _hdr_types_t)HDR_CONTACT_F);
|
|
|
|
if (!anchor) {
|
|
pkg_free(buf);
|
|
return -1;
|
|
}
|
|
|
|
len = sprintf(buf, "%.*s%s:%d%.*s", before_host.len, before_host.s,
|
|
newip.s, newport, after.len, after.s);
|
|
|
|
if (insert_new_lump_after(anchor, buf, len, (enum _hdr_types_t)HDR_CONTACT_F) == 0) {
|
|
pkg_free(buf);
|
|
return -1;
|
|
}
|
|
|
|
contact->uri.s = buf;
|
|
contact->uri.len = len;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
ClientNatTest(struct sip_msg *msg, unsigned int tests)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; NAT_Tests[i].test!=NTNone; i++) {
|
|
if ((tests & NAT_Tests[i].test)!=0 && NAT_Tests[i].proc(msg)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return -1; // all failed
|
|
}
|
|
|
|
|
|
#define FROM_PREFIX "sip:keepalive@"
|
|
#define MAX_BRANCHID 9999999
|
|
#define MIN_BRANCHID 1000000
|
|
|
|
static void
|
|
send_keepalive(NAT_Contact *contact)
|
|
{
|
|
char buffer[8192], *from_uri, *ptr;
|
|
static char from[64] = FROM_PREFIX;
|
|
static char *from_ip = from + sizeof(FROM_PREFIX) - 1;
|
|
static struct socket_info *last_socket = NULL;
|
|
struct hostent* hostent;
|
|
struct dest_info dst;
|
|
int nat_port, len;
|
|
str nat_ip;
|
|
unsigned short lport;
|
|
char lproto;
|
|
|
|
if (keepalive_params.from == NULL) {
|
|
if (contact->socket != last_socket) {
|
|
memcpy(from_ip, contact->socket->address_str.s, contact->socket->address_str.len);
|
|
from_ip[contact->socket->address_str.len] = 0;
|
|
last_socket = contact->socket;
|
|
}
|
|
from_uri = from;
|
|
} else {
|
|
from_uri = keepalive_params.from;
|
|
}
|
|
|
|
len = snprintf(buffer, sizeof(buffer),
|
|
"%s %s SIP/2.0\r\n"
|
|
"Via: SIP/2.0/UDP %.*s:%d;branch=z9hG4bK%ld\r\n"
|
|
"From: %s;tag=%x\r\n"
|
|
"To: %s\r\n"
|
|
"Call-ID: %s-%x-%x@%.*s\r\n"
|
|
"CSeq: 1 %s\r\n"
|
|
"%s%s"
|
|
"Content-Length: 0\r\n\r\n",
|
|
keepalive_params.method, contact->uri,
|
|
contact->socket->address_str.len,
|
|
contact->socket->address_str.s, contact->socket->port_no,
|
|
(long)(rand()/(float)RAND_MAX * (MAX_BRANCHID-MIN_BRANCHID) + MIN_BRANCHID),
|
|
from_uri, keepalive_params.from_tag++,
|
|
contact->uri, keepalive_params.callid_prefix,
|
|
keepalive_params.callid_counter++, get_ticks(),
|
|
contact->socket->address_str.len,
|
|
contact->socket->address_str.s,
|
|
keepalive_params.method,
|
|
keepalive_params.event_header,
|
|
keepalive_params.extra_headers);
|
|
|
|
if (len >= sizeof(buffer)) {
|
|
LM_ERR("keepalive message is longer than %lu bytes\n", (unsigned long)sizeof(buffer));
|
|
return;
|
|
}
|
|
|
|
init_dest_info(&dst);
|
|
//nat_ip.s = strchr(contact->uri, ':') + 1;
|
|
nat_ip.s = &contact->uri[4]; // skip over "sip:"
|
|
ptr = strchr(nat_ip.s, ':');
|
|
nat_ip.len = ptr - nat_ip.s;
|
|
nat_port = strtol(ptr+1, NULL, 10);
|
|
lport = 0;
|
|
lproto = PROTO_NONE;
|
|
hostent = sip_resolvehost(&nat_ip, &lport, &lproto);
|
|
hostent2su(&dst.to, hostent, 0, nat_port);
|
|
dst.proto=PROTO_UDP;
|
|
dst.send_sock=contact->socket;
|
|
udp_send(&dst, buffer, len);
|
|
}
|
|
|
|
|
|
static void
|
|
keepalive_timer(unsigned int ticks, void *data)
|
|
{
|
|
static unsigned iteration = 0;
|
|
NAT_Contact *contact;
|
|
HashSlot *slot;
|
|
time_t now;
|
|
int i;
|
|
|
|
now = time(NULL);
|
|
|
|
for (i=0; i<nat_table->size; i++) {
|
|
|
|
if ((i % keepalive_interval) != iteration)
|
|
continue;
|
|
|
|
slot = &nat_table->slots[i];
|
|
|
|
lock_get(&slot->lock);
|
|
|
|
slot->head = NAT_Contact_purge_expired(slot->head, now);
|
|
contact = slot->head;
|
|
|
|
lock_release(&slot->lock);
|
|
|
|
while (contact) {
|
|
send_keepalive(contact);
|
|
contact = contact->next;
|
|
}
|
|
}
|
|
|
|
iteration = (iteration+1) % keepalive_interval;
|
|
}
|
|
|
|
|
|
// Functions to save and restore the keepalive NAT table. They should only be
|
|
// called from mod_init/mod_destroy as they access shm memory without locking
|
|
//
|
|
|
|
#define STATE_FILE_HEADER "# Automatically generated file from internal keepalive state. Do NOT modify!\n"
|
|
|
|
static void
|
|
save_keepalive_state(void)
|
|
{
|
|
NAT_Contact *contact;
|
|
FILE *f;
|
|
int i;
|
|
|
|
if (!keepalive_state_file)
|
|
return;
|
|
|
|
f = fopen(keepalive_state_file, "w");
|
|
if (!f) {
|
|
LM_ERR("failed to open keepalive state file for writing: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
fprintf(f, STATE_FILE_HEADER);
|
|
|
|
for (i=0; i<nat_table->size; i++) {
|
|
contact = nat_table->slots[i].head;
|
|
while (contact) {
|
|
fprintf(f, "%s %.*s %ld %ld\n",
|
|
contact->uri,
|
|
contact->socket->sock_str.len, contact->socket->sock_str.s,
|
|
(long int)contact->registration_expire,
|
|
(long int)contact->subscription_expire);
|
|
contact = contact->next;
|
|
}
|
|
}
|
|
|
|
if (ferror(f))
|
|
LM_ERR("couldn't write keepalive state file: %s\n", strerror(errno));
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
|
|
static void
|
|
restore_keepalive_state(void)
|
|
{
|
|
char uri[64], socket[64];
|
|
time_t rtime, stime, now;
|
|
NAT_Contact *contact;
|
|
struct socket_info *sock;
|
|
int port, proto, res;
|
|
unsigned h;
|
|
str host;
|
|
FILE *f;
|
|
|
|
if (!keepalive_state_file)
|
|
return;
|
|
|
|
f = fopen(keepalive_state_file, "r");
|
|
if (!f) {
|
|
if (errno != ENOENT)
|
|
LM_ERR("failed to open keepalive state file for reading: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
now = time(NULL);
|
|
|
|
res = fscanf(f, STATE_FILE_HEADER); // skip header
|
|
|
|
while (true) {
|
|
res = fscanf(f, "%63s %63s %ld %ld", uri, socket, &rtime, &stime);
|
|
if (res == EOF) {
|
|
if (ferror(f))
|
|
LM_ERR("error while reading keepalive state file: %s\n", strerror(errno));
|
|
break;
|
|
} else if (res != 4) {
|
|
LM_ERR("invalid/corrupted keepalive state file. ignoring remaining entries.\n");
|
|
break;
|
|
} else {
|
|
if (now > rtime && now > stime)
|
|
continue; // expired entry
|
|
|
|
if (parse_phostport(socket, &host.s, &host.len, &port, &proto) < 0)
|
|
continue;
|
|
|
|
sock = grep_sock_info(&host, (unsigned short)port, (unsigned short)proto);
|
|
if (!sock)
|
|
continue; // socket no longer available since last time. ignore.
|
|
|
|
h = HASH(nat_table, uri);
|
|
contact = NAT_Contact_new(uri, sock);
|
|
if (contact) {
|
|
SIP_Registration_update(contact, rtime);
|
|
SIP_Subscription_update(contact, stime);
|
|
contact->next = nat_table->slots[h].head;
|
|
nat_table->slots[h].head = contact;
|
|
} else {
|
|
LM_ERR("cannot allocate shared memory for new NAT contact\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
|
|
// Module management: initialization/destroy/function-parameter-fixing/...
|
|
//
|
|
|
|
static int
|
|
mod_init(void)
|
|
{
|
|
sl_cbelem_t slcb;
|
|
int *param;
|
|
modparam_t type;
|
|
|
|
if (keepalive_interval <= 0) {
|
|
LM_NOTICE("keepalive functionality is disabled from the configuration\n");
|
|
keepalive_disabled = true;
|
|
return 0;
|
|
}
|
|
|
|
/* bind the SL API */
|
|
if (sl_load_api(&slb)!=0) {
|
|
LM_ERR("cannot bind to SL API\n");
|
|
return -1;
|
|
}
|
|
// set SL module callback function
|
|
memset(&slcb, 0, sizeof(sl_cbelem_t));
|
|
slcb.type = SLCB_REPLY_READY;
|
|
slcb.cbf = __sl_reply_out;
|
|
if (slb.register_cb(&slcb) != 0) {
|
|
LM_ERR("cannot register callback for stateless outgoing replies\n");
|
|
return -1;
|
|
}
|
|
|
|
// bind to the TM API
|
|
if (load_tm_api(&tm_api)!=0) {
|
|
LM_ERR("cannot load the tm module API\n");
|
|
return -1;
|
|
}
|
|
|
|
// bind to the dialog API
|
|
if (load_dlg_api(&dlg_api)==0) {
|
|
// load dlg_flag and default_timeout parameters from the dialog module
|
|
param = find_param_export(find_module_by_name("dialog"),
|
|
"dlg_flag", INT_PARAM, &type);
|
|
if (param) {
|
|
have_dlg_api = true;
|
|
|
|
dialog_flag = *param;
|
|
|
|
param = find_param_export(find_module_by_name("dialog"),
|
|
"default_timeout", INT_PARAM, &type);
|
|
if (!param) {
|
|
LM_ERR("cannot find default_timeout parameter in the dialog module\n");
|
|
return -1;
|
|
}
|
|
dialog_default_timeout = *param;
|
|
|
|
// register dialog creation callback
|
|
if (dlg_api.register_dlgcb(NULL, DLGCB_CREATED, __dialog_created, NULL, NULL) != 0) {
|
|
LM_ERR("cannot register callback for dialog creation\n");
|
|
return -1;
|
|
}
|
|
|
|
// register a pre-script callback to automatically enable dialog tracing
|
|
if (register_script_cb(preprocess_request, PRE_SCRIPT_CB|REQUEST_CB, 0)!=0) {
|
|
LM_ERR("could not register request preprocessing callback\n");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
if (!have_dlg_api) {
|
|
LM_NOTICE("keeping alive dialogs is disabled because the dialog module is not loaded\n");
|
|
}
|
|
|
|
// initialize the keepalive message parameters
|
|
if (keepalive_params.from!=NULL && *(keepalive_params.from)==0) {
|
|
LM_WARN("ignoring empty keepalive_from parameter\n");
|
|
keepalive_params.from = NULL;
|
|
}
|
|
if (strcasecmp(keepalive_params.method, "NOTIFY")==0)
|
|
keepalive_params.event_header = "Event: keep-alive\r\n";
|
|
snprintf(keepalive_params.callid_prefix, 20, "%x", rand());
|
|
keepalive_params.callid_counter = rand();
|
|
keepalive_params.from_tag = rand();
|
|
|
|
#ifdef STATISTICS
|
|
// we need the statistics initialized before restoring the keepalive state
|
|
if (register_module_stats(exports.name, statistics) < 0) {
|
|
LM_ERR("failed to initialize module statistics\n");
|
|
return -1;
|
|
}
|
|
#endif /*STATISTICS*/
|
|
|
|
// create hash table to hold NAT contacts
|
|
nat_table = HashTable_new();
|
|
if (!nat_table) {
|
|
LM_ERR("cannot create hash table to store NAT endpoints\n");
|
|
return -1;
|
|
}
|
|
restore_keepalive_state();
|
|
|
|
// check keepalive interval and add keepalive timer process
|
|
if (keepalive_interval < 10) {
|
|
LM_WARN("keepalive_interval should be at least 10 seconds\n");
|
|
LM_NOTICE("using 10 seconds for keepalive_interval\n");
|
|
keepalive_interval = 10;
|
|
}
|
|
register_dummy_timers(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
child_init(int rank)
|
|
{
|
|
if (rank==PROC_MAIN) {
|
|
if(fork_dummy_timer(PROC_TIMER, "TIMER NT", 1 /*socks flag*/,
|
|
keepalive_timer, NULL, 1 /*sec*/)<0) {
|
|
LM_ERR("failed to register keepalive timer process\n");
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
mod_destroy(void)
|
|
{
|
|
if (nat_table) {
|
|
save_keepalive_state();
|
|
HashTable_del(nat_table);
|
|
nat_table = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// Preprocess a request before it is processed in the main script route
|
|
//
|
|
// Here we enable dialog tracing to be able to automatically extend an
|
|
// existing registration keepalive to a destination, for the duration of
|
|
// the dialog, even if the dialog source is not kept alive by explicitly
|
|
// calling nat_keepalive(). This is needed to still be able to forward
|
|
// messages to the callee, even if the registration keepalive expires
|
|
// during the dialog and it is not renewed.
|
|
//
|
|
static int
|
|
preprocess_request(struct sip_msg *msg, unsigned int flags, void *_param)
|
|
{
|
|
str totag;
|
|
|
|
if (msg->first_line.u.request.method_value!=METHOD_INVITE)
|
|
return 1;
|
|
|
|
if (parse_headers(msg, HDR_TO_F, 0) == -1) {
|
|
LM_ERR("failed to parse To header\n");
|
|
return -1;
|
|
}
|
|
if (!msg->to) {
|
|
LM_ERR("missing To header\n");
|
|
return -1;
|
|
}
|
|
totag = get_to(msg)->tag_value;
|
|
if (totag.s==0 || totag.len==0) {
|
|
setflag(msg, dialog_flag);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Filter out replies to keepalive messages
|
|
//
|
|
static int
|
|
reply_filter(struct sip_msg *reply)
|
|
{
|
|
struct cseq_body *cseq;
|
|
static str prefix = {NULL, 0};
|
|
str call_id;
|
|
|
|
parse_headers(reply, HDR_VIA2_F, 0);
|
|
if (reply->via2)
|
|
return 1;
|
|
|
|
// check if the method from CSeq header matches our method
|
|
if (!reply->cseq && parse_headers(reply, HDR_CSEQ_F, 0) < 0) {
|
|
LM_ERR("failed to parse the CSeq header\n");
|
|
return -1;
|
|
}
|
|
if (!reply->cseq) {
|
|
LM_ERR("missing CSeq header\n");
|
|
return -1;
|
|
}
|
|
cseq = reply->cseq->parsed;
|
|
if (!STR_MATCH(cseq->method, keepalive_params.method))
|
|
return 1;
|
|
|
|
// check if callid_prefix matches
|
|
if (!reply->callid && parse_headers(reply, HDR_CALLID_F, 0) < 0) {
|
|
LM_ERR("failed to parse the Call-ID header\n");
|
|
return -1;
|
|
}
|
|
if (!reply->callid) {
|
|
LM_ERR("missing Call-ID header\n");
|
|
return -1;
|
|
}
|
|
call_id = reply->callid->body;
|
|
if (prefix.s == NULL) {
|
|
prefix.s = keepalive_params.callid_prefix;
|
|
prefix.len = strlen(prefix.s);
|
|
}
|
|
if (!STR_HAS_PREFIX(call_id, prefix) || call_id.s[prefix.len]!='-')
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Pseudo variable management
|
|
//
|
|
|
|
static int
|
|
pv_parse_nat_contact_name(pv_spec_p sp, str *in)
|
|
{
|
|
char *p;
|
|
char *s;
|
|
pv_spec_p nsp = 0;
|
|
|
|
if(in==NULL || in->s==NULL || sp==NULL)
|
|
return -1;
|
|
p = in->s;
|
|
if (*p==PV_MARKER) {
|
|
nsp = (pv_spec_p)pkg_malloc(sizeof(pv_spec_t));
|
|
if (nsp==NULL) {
|
|
LM_ERR("cannot allocate private memory\n");
|
|
return -1;
|
|
}
|
|
s = pv_parse_spec(in, nsp);
|
|
if (s==NULL) {
|
|
LM_ERR("invalid name [%.*s]\n", in->len, in->s);
|
|
pv_spec_free(nsp);
|
|
return -1;
|
|
}
|
|
sp->pvp.pvn.type = PV_NAME_PVAR;
|
|
sp->pvp.pvn.u.dname = (void*)nsp;
|
|
return 0;
|
|
}
|
|
|
|
sp->pvp.pvn.type = PV_NAME_INTSTR;
|
|
sp->pvp.pvn.u.isname.type = AVP_NAME_STR;
|
|
sp->pvp.pvn.u.isname.name.s = *in;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
pv_get_keepalive_socket(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
|
|
{
|
|
static char uri[128];
|
|
NAT_Contact *contact;
|
|
pv_value_t tv;
|
|
unsigned h;
|
|
|
|
if (msg==NULL || param==NULL || res==NULL)
|
|
return -1;
|
|
|
|
if (pv_get_spec_name(msg, param, &tv)!=0 || (!(tv.flags&PV_VAL_STR))) {
|
|
LM_ERR("invalid NAT contact uri\n");
|
|
return -1;
|
|
}
|
|
|
|
if (tv.rs.len > sizeof(uri)-1) {
|
|
LM_ERR("NAT contact uri too long\n");
|
|
return -1;
|
|
}
|
|
|
|
strncpy(uri, tv.rs.s, tv.rs.len);
|
|
uri[tv.rs.len] = 0;
|
|
|
|
h = HASH(nat_table, uri);
|
|
lock_get(&nat_table->slots[h].lock);
|
|
|
|
contact = HashTable_search(nat_table, uri, h);
|
|
if (!contact) {
|
|
lock_release(&nat_table->slots[h].lock);
|
|
return pv_get_null(msg, param, res);
|
|
}
|
|
|
|
res->rs = contact->socket->sock_str;
|
|
res->flags = PV_VAL_STR;
|
|
|
|
lock_release(&nat_table->slots[h].lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
pv_get_source_uri(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
|
|
{
|
|
static char uri[128];
|
|
|
|
if (msg==NULL || res==NULL)
|
|
return -1;
|
|
|
|
snprintf(uri, 64, "sip:%s:%d", ip_addr2a(&msg->rcv.src_ip), msg->rcv.src_port);
|
|
|
|
switch (msg->rcv.proto) {
|
|
case PROTO_TCP:
|
|
strcat(uri, ";transport=tcp");
|
|
break;
|
|
case PROTO_TLS:
|
|
strcat(uri, ";transport=tls");
|
|
break;
|
|
case PROTO_SCTP:
|
|
strcat(uri, ";transport=sctp");
|
|
break;
|
|
case PROTO_WS:
|
|
case PROTO_WSS:
|
|
strcat(uri, ";transport=ws");
|
|
break;
|
|
}
|
|
|
|
res->rs.s = uri;
|
|
res->rs.len = strlen(uri);
|
|
res->flags = PV_VAL_STR;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|