/*
 * Copyright (C) 2007-2009 Dan Pascu
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 <stdint.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <inttypes.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include "../../core/sr_module.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/mem/mem.h"
#include "../../core/lock_ops.h"
#include "../../core/dprint.h"
#include "../../core/str.h"
#include "../../core/ut.h"
#include "../../core/pvar.h"
#include "../../core/error.h"
#include "../../core/timer.h"
#include "../../core/resolve.h"
#include "../../core/data_lump.h"
#include "../../core/mod_fix.h"
#include "../../core/script_cb.h"
#include "../../core/strutils.h"
#include "../../core/timer_proc.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/parse_expires.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/counters.h"
#include "../../core/rand/kam_rand.h"
#include "../../core/kemi.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 (1u << 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 w_NAT_Keepalive(struct sip_msg *msg, char *p1, char *p2);
static int w_FixContact(struct sip_msg *msg, char *p1, char *p2);
static int w_ClientNatTest(struct sip_msg *msg, char *ptests, char *p2);

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 int mod_init(void);
static int child_init(int rank);
static void mod_destroy(void);

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 unsigned dialog_default_timeout = 12 * 3600; // 12 hours
static int natt_contact_match = 0;

stat_var *keepalive_endpoints = 0;
stat_var *registered_endpoints = 0;
stat_var *subscribed_endpoints = 0;
stat_var *dialog_endpoints = 0;

/** SL API structure */
sl_api_t slb;

/* clang-format off */
static NetInfo rfc1918nets[] = {
	{"10.0.0.0", 0x0a000000UL, 0xff000000UL},
	{"172.16.0.0", 0xac100000UL, 0xfff00000UL},
	{"192.168.0.0", 0xc0a80000UL, 0xffff0000UL},
	// include rfc6598 shared address space as technically the same for our purpose
	{"100.64.0.0", 0x64400000UL, 0xffc00000UL},
	// include rfc7335 IPv4 Service Continuity Prefix
	{"192.0.0.0", 0xc0000000UL, 0xfffffff8UL},
	{NULL, 0UL, 0UL}
};

static NatTest NAT_Tests[] = {
	{NTPrivateContact, test_private_contact},
	{NTSourceAddress, test_source_address},
	{NTPrivateVia, test_private_via},
	{NTNone, NULL}
};

static cmd_export_t commands[] = {
	{"nat_keepalive", (cmd_function)w_NAT_Keepalive, 0,
		0, 0, REQUEST_ROUTE},
	{"fix_contact", (cmd_function)w_FixContact, 0,
		0, 0, ANY_ROUTE},
	{"client_nat_test", (cmd_function)w_ClientNatTest, 1,
		fixup_igp_null, fixup_free_igp_null, ANY_ROUTE},

	{0, 0, 0, 0, 0, 0}
};

static param_export_t parameters[] = {
	{"keepalive_interval", PARAM_INT, &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},
	{"contact_match", PARAM_INT, &natt_contact_match},

	{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, &registered_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
	0,				 // exported RPC methods
	pvars,			 // exported pseudo-variables
	reply_filter,	 // reply processing function
	mod_init,	// module init function (before fork. kids will inherit)
	child_init, // child init function
	mod_destroy // destroy function
};
/* clang-format on */

// 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_char_dup(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(&param->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(&param->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_char_dup(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
//

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 a 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((natt_contact_match == 0
							   && STR_MATCH_STR(contact->uri, r_contact->uri))
							|| (natt_contact_match == 1
									&& cmp_uri_light_str(
											   &contact->uri, &r_contact->uri)
											   == 0)) {
						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: %" PRIu64 "\n", (uint64_t)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(&param->lock);

	if(param->confirmed) {
		// this 1xx is late; dialog already confirmed by 200 OK; ignore it
		lock_release(&param->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(&param->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(&param->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_char_dup(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(&param->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(&param->lock);

	// remove all keepalives on unanswered branches. This is needed 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(&param->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_char_dup(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 = 0;

	if(param->req == NULL || param->rpl == NULL)
		return;

	if(type == TMCB_RESPONSE_SENT && param->rpl != FAKED_REPLY)
		return;

	if(param->code >= 200 && param->code < 300) {
		switch(param->req->REQ_METHOD) {
			case METHOD_SUBSCRIBE:
				if(type == TMCB_RESPONSE_SENT) {
					char *tmp = pkg_malloc(param->send_buf.len + 1);
					if(tmp) {
						sip_msg_t msg;
						strncpy(tmp, param->send_buf.s, param->send_buf.len);
						tmp[param->send_buf.len] = '\0';
						memset(&msg, 0, sizeof(sip_msg_t));
						msg.buf = tmp;
						msg.len = param->send_buf.len;
						if(parse_msg(tmp, param->send_buf.len, &msg) != 0) {
							LM_ERR("ERROR PARSING REPLY\n");
						} else {
							expire = get_expires(&msg);
						}
						free_sip_msg(&msg);
						pkg_free(tmp);
					}
				} else {
					expire = get_expires(param->rpl);
				}
				if(expire > 0) {
					keepalive_subscription(param->req, expire);
				} else {
					LM_DBG("expires == 0\n");
				}
				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 | TMCB_RESPONSE_SENT, __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;
			return 1;

		default:
			LM_ERR("unsupported method for keepalive\n");
			return -1;
	}
}

static int w_NAT_Keepalive(struct sip_msg *msg, char *p1, char *p2)
{
	return NAT_Keepalive(msg);
}

// 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;
	str 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.s = pkg_malloc(len);
	if(buf.s == 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.s);
		return -1;
	}

	if(msg->rcv.src_ip.af == AF_INET6) {
		buf.len = snprintf(buf.s, len, "%.*s[%s]:%d%.*s", before_host.len,
				before_host.s, newip.s, newport, after.len, after.s);
	} else {
		buf.len = snprintf(buf.s, len, "%.*s%s:%d%.*s", before_host.len,
				before_host.s, newip.s, newport, after.len, after.s);
	}
	if(buf.len < 0 || buf.len >= len) {
		pkg_free(buf.s);
		return -1;
	}

	if(insert_new_lump_after(
			   anchor, buf.s, buf.len, (enum _hdr_types_t)HDR_CONTACT_F)
			== 0) {
		pkg_free(buf.s);
		return -1;
	}

	contact->uri.s = buf.s;
	contact->uri.len = buf.len;

	return 1;
}

static int w_FixContact(struct sip_msg *msg, char *p1, char *p2)
{
	return FixContact(msg);
}

static int ClientNatTest(struct sip_msg *msg, 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
}

static int w_ClientNatTest(struct sip_msg *msg, char *ptests, char *p2)
{
	int tests;

	if(fixup_get_ivalue(msg, (gparam_t *)ptests, &tests) < 0) {
		LM_ERR("failed to get tests parameter\n");
		return -1;
	}

	return ClientNatTest(msg, tests);
}

#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(contact == NULL || contact->socket == NULL) {
		LM_ERR("invalid parameters\n");
		return;
	}

	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)(kam_rand() / (float)KAM_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);
	if(hostent == NULL) {
		LM_ERR("sip resolve host failed\n");
		return;
	}
	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 (%s) for writing: %s\n",
				keepalive_state_file, 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): %s\n",
				keepalive_state_file, 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;
	long long ll_1, ll_2;

	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) {
		ll_1 = 0;
		ll_2 = 0;
		uri[0] = '\0';
		socket[0] = '\0';
		res = fscanf(f, "%63s %63s %" TIME_T_FMT " %" TIME_T_FMT, uri, socket,
				&ll_1, &ll_2);
		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 {
			rtime = ll_1;
			stime = ll_2;
			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(natt_contact_match != 0) {
		natt_contact_match = 1;
	}

	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 default_timeout parameters from the dialog module
		param = find_param_export(find_module_by_name("dialog"),
				"default_timeout", PARAM_INT, &type);
		if(!param) {
			LM_ERR("cannot find default_timeout parameter in the dialog "
				   "module\n");
			return -1;
		} else {
			have_dlg_api = true;

			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;
			}
		}
	}
	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", kam_rand());
	keepalive_params.callid_counter = kam_rand();
	keepalive_params.from_tag = kam_rand();

#ifdef STATISTICS
	// we need the statistics initialized before restoring the keepalive state
	if(register_module_stats("nat_traversal", 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_basic_timers(1);

	return 0;
}

static int child_init(int rank)
{
	if(rank == PROC_MAIN) {
		if(fork_basic_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;
	}
}

// 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;

	if(parse_headers(reply, HDR_VIA2_F, 0) < 0) {
		LM_DBG("second via not parsed\n");
	}
	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(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
{
	static char uri[128];

	if(msg == NULL || res == NULL)
		return -1;

	uri[0] = '\0';
	snprintf(uri, 64, "sip:%s:%d", ip_addr2strz(&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;
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_nat_traversal_exports[] = {
	{ str_init("nat_traversal"), str_init("nat_keepalive"),
		SR_KEMIP_INT, NAT_Keepalive,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("nat_traversal"), str_init("fix_contact"),
		SR_KEMIP_INT, FixContact,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("nat_traversal"), str_init("client_nat_test"),
		SR_KEMIP_INT, ClientNatTest,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},

	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */

int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_nat_traversal_exports);
	return 0;
}
