/**
 *
 * Copyright (C) 2003-2023 Sippy Software, Inc., http://www.sippysoft.com
 *
 * 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
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#ifndef __USE_BSD
#define __USE_BSD
#endif
#include <netinet/ip.h>
#ifndef __FAVOR_BSD
#define __FAVOR_BSD
#endif
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../../core/flags.h"
#include "../../core/sr_module.h"
#include "../../core/dprint.h"
#include "../../core/data_lump.h"
#include "../../core/data_lump_rpl.h"
#include "../../core/error.h"
#include "../../core/forward.h"
#include "../../core/mem/mem.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_to.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/parser_f.h"
#include "../../core/parser/sdp/sdp.h"
#include "../../core/resolve.h"
#include "../../core/timer.h"
#include "../../core/trim.h"
#include "../../core/ut.h"
#include "../../core/pt.h"
#include "../../core/timer_proc.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"
#include "../../core/pvar.h"
#include "../../core/lvalue.h"
#include "../../core/msg_translator.h"
#include "../../core/usr_avp.h"
#include "../../core/socket_info.h"
#include "../../core/mod_fix.h"
#include "../../core/dset.h"
#include "../../core/route.h"
#include "../../core/kemi.h"
#include "../../modules/tm/tm_load.h"
#include "rtpproxy.h"
#include "rtpproxy_funcs.h"
#include "rtpproxy_stream.h"

MODULE_VERSION

#if !defined(AF_LOCAL)
#define AF_LOCAL AF_UNIX
#endif
#if !defined(PF_LOCAL)
#define PF_LOCAL PF_UNIX
#endif

/* NAT UAC test constants */
#define NAT_UAC_TEST_C_1918 0x01
#define NAT_UAC_TEST_RCVD 0x02
#define NAT_UAC_TEST_V_1918 0x04
#define NAT_UAC_TEST_S_1918 0x08
#define NAT_UAC_TEST_RPORT 0x10


#define DEFAULT_RTPP_SET_ID 0
static str DEFAULT_RTPP_SET_ID_STR = str_init("0");

#define RPC_DEFAULT_NATPING_STATE 1

#define RPC_MIN_RECHECK_TICKS 0
#define RPC_MAX_RECHECK_TICKS (unsigned int)-1


/* Supported version of the RTP proxy command protocol */
#define SUP_CPROTOVER 20040107
/* Required additional version of the RTP proxy command protocol */
#define REQ_CPROTOVER "20050322"
/* Additional version necessary for re-packetization support */
#define REP_CPROTOVER "20071116"
#define PTL_CPROTOVER "20081102"

#define CPORT "22222"
static int alter_mediaip(struct sip_msg *, str *, str *, int, str *, int, int);
static int alter_mediaport(struct sip_msg *, str *, str *, str *, int);
static int alter_rtcp(
		struct sip_msg *msg, str *body, str *oldport, str *newport);
static char *gencookie();
static int rtpp_test(struct rtpp_node *, int, int);
static int unforce_rtp_proxy1_f(struct sip_msg *, char *, char *);
static int unforce_rtp_proxy(struct sip_msg *, char *);
static int force_rtp_proxy(struct sip_msg *, char *, char *, int, int);
static int start_recording_f(struct sip_msg *, char *, char *);
static int rtpproxy_answer1_f(struct sip_msg *, char *, char *);
static int rtpproxy_answer2_f(struct sip_msg *, char *, char *);
static int rtpproxy_offer1_f(struct sip_msg *, char *, char *);
static int rtpproxy_offer2_f(struct sip_msg *, char *, char *);
static int rtpproxy_manage0(struct sip_msg *msg, char *flags, char *ip);
static int rtpproxy_manage1(struct sip_msg *msg, char *flags, char *ip);
static int rtpproxy_manage2(struct sip_msg *msg, char *flags, char *ip);

static int add_rtpproxy_socks(struct rtpp_set *rtpp_list, char *rtpproxy);
static int fixup_set_id(void **param, int param_no);
static int set_rtp_proxy_set_f(struct sip_msg *msg, char *str1, char *str2);
static struct rtpp_set *select_rtpp_set(int id_set);

static int rtpproxy_set_store(modparam_t type, void *val);
static int rtpproxy_add_rtpproxy_set(char *rtp_proxies);

static int mod_init(void);
static int child_init(int);
static void mod_destroy(void);

/* Pseudo-Variables */
static int pv_get_rtppstat_f(struct sip_msg *, pv_param_t *, pv_value_t *);

static int rtpproxy_disable_tout = 60;
static int rtpproxy_retr = 5;
static int rtpproxy_tout = 1;
static pid_t mypid;
static unsigned int myseqn = 0;
static str nortpproxy_str = str_init("a=nortpproxy:yes");
static str extra_id_pv_param = {NULL, 0};

static char **rtpp_strings = 0;
static int rtpp_sets = 0; /*used in rtpproxy_set_store()*/
static int rtpp_set_count = 0;
static unsigned int current_msg_id = (unsigned int)-1;
/* RTP proxy balancing list */
struct rtpp_set_head *rtpp_set_list = 0;
struct rtpp_set *selected_rtpp_set = 0;
struct rtpp_set *default_rtpp_set = 0;
static char *ice_candidate_priority_avp_param = NULL;
static int ice_candidate_priority_avp_type;
static int_str ice_candidate_priority_avp;
static str rtp_inst_pv_param = {NULL, 0};
static pv_spec_t *rtp_inst_pvar = NULL;

/* array with the sockets used by rtpproxy (per process)*/
static unsigned int rtpp_no = 0;
static int *rtpp_socks = 0;


typedef struct rtpp_set_link
{
	struct rtpp_set *rset;
	pv_spec_t *rpv;
} rtpp_set_link_t;

/* tm */
static struct tm_binds tmb;

/*0-> disabled, 1 ->enabled*/
unsigned int *natping_state = 0;

static str timeout_socket_str = {0, 0};
static str timeout_tag_pv_str = {0, 0};
static pv_elem_t *timeout_tag_pv = NULL;
static pv_elem_t *extra_id_pv = NULL;

static cmd_export_t cmds[] = {
		{"set_rtp_proxy_set", (cmd_function)set_rtp_proxy_set_f, 1,
				fixup_set_id, 0, ANY_ROUTE},
		{"unforce_rtp_proxy", (cmd_function)unforce_rtp_proxy1_f, 0, 0, 0,
				ANY_ROUTE},
		{"rtpproxy_destroy", (cmd_function)unforce_rtp_proxy1_f, 0, 0, 0,
				ANY_ROUTE},
		{"unforce_rtp_proxy", (cmd_function)unforce_rtp_proxy1_f, 1,
				fixup_spve_null, 0, ANY_ROUTE},
		{"rtpproxy_destroy", (cmd_function)unforce_rtp_proxy1_f, 1,
				fixup_spve_null, 0, ANY_ROUTE},
		{"start_recording", (cmd_function)start_recording_f, 0, 0, 0,
				ANY_ROUTE},
		{"rtpproxy_offer", (cmd_function)rtpproxy_offer1_f, 0, 0, 0, ANY_ROUTE},
		{"rtpproxy_offer", (cmd_function)rtpproxy_offer1_f, 1, fixup_spve_null,
				0, ANY_ROUTE},
		{"rtpproxy_offer", (cmd_function)rtpproxy_offer2_f, 2, fixup_spve_spve,
				0, ANY_ROUTE},
		{"rtpproxy_answer", (cmd_function)rtpproxy_answer1_f, 0, 0, 0,
				ANY_ROUTE},
		{"rtpproxy_answer", (cmd_function)rtpproxy_answer1_f, 1,
				fixup_spve_null, 0, ANY_ROUTE},
		{"rtpproxy_answer", (cmd_function)rtpproxy_answer2_f, 2,
				fixup_spve_spve, 0, ANY_ROUTE},
		{"rtpproxy_stream2uac", (cmd_function)rtpproxy_stream2uac2_f, 2,
				fixup_var_str_int, 0, ANY_ROUTE},
		{"rtpproxy_stream2uas", (cmd_function)rtpproxy_stream2uas2_f, 2,
				fixup_var_str_int, 0, ANY_ROUTE},
		{"rtpproxy_stop_stream2uac", (cmd_function)rtpproxy_stop_stream2uac2_f,
				0, NULL, 0, ANY_ROUTE},
		{"rtpproxy_stop_stream2uas", (cmd_function)rtpproxy_stop_stream2uas2_f,
				0, NULL, 0, ANY_ROUTE},
		{"rtpproxy_manage", (cmd_function)rtpproxy_manage0, 0, 0, 0, ANY_ROUTE},
		{"rtpproxy_manage", (cmd_function)rtpproxy_manage1, 1, fixup_spve_null,
				fixup_free_spve_null, ANY_ROUTE},
		{"rtpproxy_manage", (cmd_function)rtpproxy_manage2, 2, fixup_spve_spve,
				fixup_free_spve_spve, ANY_ROUTE},
		{0, 0, 0, 0, 0, 0}};

static pv_export_t mod_pvs[] = {
		{{"rtpstat", (sizeof("rtpstat") - 1)}, /* RTP-Statistics */
				PVT_OTHER, pv_get_rtppstat_f, 0, 0, 0, 0, 0},
		{{"rtppstat", (sizeof("rtppstat") - 1)}, /* RTP-Statistics */
				PVT_OTHER, pv_get_rtppstat_f, 0, 0, 0, 0, 0},
		{{0, 0}, 0, 0, 0, 0, 0, 0, 0}};

static param_export_t params[] = {
		{"nortpproxy_str", PARAM_STR, &nortpproxy_str},
		{"rtpproxy_sock", PARAM_STRING | USE_FUNC_PARAM,
				(void *)rtpproxy_set_store},
		{"rtpproxy_disable_tout", INT_PARAM, &rtpproxy_disable_tout},
		{"rtpproxy_retr", INT_PARAM, &rtpproxy_retr},
		{"rtpproxy_tout", INT_PARAM, &rtpproxy_tout},
		{"timeout_socket", PARAM_STR, &timeout_socket_str},
		{"timeout_tag_pv", PARAM_STR, &timeout_tag_pv_str},
		{"ice_candidate_priority_avp", PARAM_STRING,
				&ice_candidate_priority_avp_param},
		{"extra_id_pv", PARAM_STR, &extra_id_pv_param},
		{"db_url", PARAM_STR, &rtpp_db_url},
		{"table_name", PARAM_STR, &rtpp_table_name},
		{"rtp_inst_pvar", PARAM_STR, &rtp_inst_pv_param}, {0, 0, 0}};

struct module_exports exports = {
		"rtpproxy",		 /* module name */
		DEFAULT_DLFLAGS, /* dlopen flags */
		cmds,			 /* cmd exports */
		params,			 /* param exports */
		0,				 /* RPC method exports */
		mod_pvs,		 /* exported pseudo-variables */
		0,				 /* reply processing */
		mod_init,		 /* module init function */
		child_init,		 /* per-child init function */
		mod_destroy,	 /* destroy function */
};


static int rtpproxy_set_store(modparam_t type, void *val)
{

	char *p;
	int len;

	p = (char *)val;

	if(p == 0 || *p == '\0') {
		return 0;
	}

	if(rtpp_sets == 0) {
		rtpp_strings = (char **)pkg_malloc(sizeof(char *));
		if(!rtpp_strings) {
			LM_ERR("no pkg memory left\n");
			return -1;
		}
	} else { /*realloc to make room for the current set*/
		rtpp_strings = (char **)pkg_reallocxf(
				rtpp_strings, (rtpp_sets + 1) * sizeof(char *));
		if(!rtpp_strings) {
			LM_ERR("no pkg memory left\n");
			return -1;
		}
	}

	/*allocate for the current set of urls*/
	len = strlen(p);
	rtpp_strings[rtpp_sets] = (char *)pkg_malloc((len + 1) * sizeof(char));

	if(!rtpp_strings[rtpp_sets]) {
		LM_ERR("no pkg memory left\n");
		return -1;
	}

	memcpy(rtpp_strings[rtpp_sets], p, len);
	rtpp_strings[rtpp_sets][len] = '\0';
	rtpp_sets++;

	return 0;
}

struct rtpp_set *get_rtpp_set(str *const set_name)
{
	unsigned int this_set_id;
	struct rtpp_set *rtpp_list;
	if(rtpp_set_list == NULL) {
		LM_ERR("rtpp set list not configured\n");
		return NULL;
	}
	/* Only integer set_names are valid at the moment */
	if((set_name->s == NULL) || (set_name->len == 0)) {
		LM_ERR("Invalid set name '%.*s'\n", set_name->len, set_name->s);
		return NULL;
	}
	if(str2int(set_name, &this_set_id) < 0) {
		LM_ERR("Invalid set name '%.*s' - must be integer\n", set_name->len,
				set_name->s);
		return NULL;
	}

	rtpp_list = select_rtpp_set(this_set_id);

	if(rtpp_list == NULL) { /*if a new id_set : add a new set of rtpp*/
		rtpp_list = shm_malloc(sizeof(struct rtpp_set));
		if(!rtpp_list) {
			LM_ERR("no shm memory left\n");
			return NULL;
		}
		memset(rtpp_list, 0, sizeof(struct rtpp_set));
		rtpp_list->id_set = this_set_id;
		if(rtpp_set_list->rset_first == NULL) {
			rtpp_set_list->rset_first = rtpp_list;
		} else {
			rtpp_set_list->rset_last->rset_next = rtpp_list;
		}
		rtpp_set_list->rset_last = rtpp_list;
		rtpp_set_count++;

		if(this_set_id == DEFAULT_RTPP_SET_ID) {
			default_rtpp_set = rtpp_list;
		}
	}
	return rtpp_list;
}

int insert_rtpp_node(struct rtpp_set *const rtpp_list, const str *const url,
		const int weight, const int disabled)
{
	struct rtpp_node *pnode;

	if((pnode = shm_malloc(sizeof(struct rtpp_node) + url->len + 1)) == NULL) {
		LM_ERR("out of shm memory\n");
		return -1;
	}
	memset(pnode, 0, sizeof(struct rtpp_node) + url->len + 1);
	pnode->idx = rtpp_no++;
	pnode->rn_weight = weight;
	pnode->rn_umode = 0;
	pnode->rn_disabled = disabled;
	/* Permanently disable if marked as disabled */
	pnode->rn_recheck_ticks = disabled ? RPC_MAX_RECHECK_TICKS : 0;
	pnode->rn_url.s = (char *)(pnode + 1);
	memcpy(pnode->rn_url.s, url->s, url->len);
	pnode->rn_url.len = url->len;

	LM_DBG("url is '%.*s'\n", pnode->rn_url.len, pnode->rn_url.s);

	/* Find protocol and store address */
	pnode->rn_address = pnode->rn_url.s;
	if(strncasecmp(pnode->rn_address, "udp:", 4) == 0) {
		pnode->rn_umode = 1;
		pnode->rn_address += 4;
	} else if(strncasecmp(pnode->rn_address, "udp6:", 5) == 0) {
		pnode->rn_umode = 6;
		pnode->rn_address += 5;
	} else if(strncasecmp(pnode->rn_address, "unix:", 5) == 0) {
		pnode->rn_umode = 0;
		pnode->rn_address += 5;
	}

	if(rtpp_list->rn_first == NULL) {
		rtpp_list->rn_first = pnode;
	} else {
		rtpp_list->rn_last->rn_next = pnode;
	}
	rtpp_list->rn_last = pnode;
	rtpp_list->rtpp_node_count++;

	return 0;
}

static int add_rtpproxy_socks(struct rtpp_set *rtpp_list, char *rtpproxy)
{
	/* Make rtp proxies list. */
	char *p, *p1, *p2, *plim;
	int weight;
	str url;

	p = rtpproxy;
	plim = p + strlen(p);

	for(;;) {
		weight = 1;
		while(*p && isspace((int)*p))
			++p;
		if(p >= plim)
			break;
		p1 = p;
		while(*p && !isspace((int)*p))
			++p;
		if(p <= p1)
			break; /* may happen??? */
		/* Have weight specified? If yes, scan it */
		p2 = memchr(p1, '=', p - p1);
		if(p2 != NULL) {
			weight = strtoul(p2 + 1, NULL, 10);
		} else {
			p2 = p;
		}

		url.s = p1;
		url.len = (p2 - p1);
		insert_rtpp_node(rtpp_list, &url, weight, 0);
	}
	return 0;
}


/*	0-success
 *  -1 - error
 * */
static int rtpproxy_add_rtpproxy_set(char *rtp_proxies)
{
	char *p, *p2;
	struct rtpp_set *rtpp_list;
	str id_set;

	/* empty definition? */
	p = rtp_proxies;
	if(!p || *p == '\0') {
		return 0;
	}

	for(; *p && isspace(*p); p++)
		;
	if(*p == '\0') {
		return 0;
	}

	rtp_proxies = strstr(p, "==");
	if(rtp_proxies) {
		if(*(rtp_proxies + 2) == '\0') {
			LM_ERR("script error -invalid rtp proxy list!\n");
			return -1;
		}

		*rtp_proxies = '\0';
		p2 = rtp_proxies - 1;
		for(; isspace(*p2); *p2 = '\0', p2--)
			;
		id_set.s = p;
		id_set.len = p2 - p + 1;

		if(id_set.len <= 0) {
			LM_ERR("script error -invalid set_id value!\n");
			return -1;
		}

		rtp_proxies += 2;
	} else {
		rtp_proxies = p;
		id_set = DEFAULT_RTPP_SET_ID_STR;
	}

	for(; *rtp_proxies && isspace(*rtp_proxies); rtp_proxies++)
		;

	if(!(*rtp_proxies)) {
		LM_ERR("script error -empty rtp_proxy list\n");
		return -1;
		;
	}

	rtpp_list = get_rtpp_set(&id_set);
	if(rtpp_list == NULL) {
		LM_ERR("Failed to get or create rtpp_list for '%.*s'\n", id_set.len,
				id_set.s);
		return -1;
	}

	if(add_rtpproxy_socks(rtpp_list, rtp_proxies) != 0) {
		return -1;
	}

	return 0;
}


static int fixup_set_id(void **param, int param_no)
{
	int int_val, err;
	struct rtpp_set *rtpp_list;
	rtpp_set_link_t *rtpl = NULL;
	str s;

	rtpl = (rtpp_set_link_t *)pkg_malloc(sizeof(rtpp_set_link_t));
	if(rtpl == NULL) {
		LM_ERR("no more pkg memory\n");
		return -1;
	}
	memset(rtpl, 0, sizeof(rtpp_set_link_t));
	s.s = (char *)*param;
	s.len = strlen(s.s);

	if(s.s[0] == PV_MARKER) {
		int_val = pv_locate_name(&s);
		if(int_val < 0 || int_val != s.len) {
			LM_ERR("invalid parameter %s\n", s.s);
			pkg_free(rtpl);
			return -1;
		}
		rtpl->rpv = pv_cache_get(&s);
		if(rtpl->rpv == NULL) {
			LM_ERR("invalid pv parameter %s\n", s.s);
			pkg_free(rtpl);
			return -1;
		}
	} else {
		int_val = str2s(*param, strlen(*param), &err);
		if(err == 0) {
			pkg_free(*param);
			if((rtpp_list = select_rtpp_set(int_val)) == 0) {
				LM_ERR("rtpp_proxy set %i not configured\n", int_val);
				pkg_free(rtpl);
				return E_CFG;
			}
			rtpl->rset = rtpp_list;
		} else {
			LM_ERR("bad number <%s>\n", (char *)(*param));
			pkg_free(rtpl);
			return E_CFG;
		}
	}
	*param = (void *)rtpl;
	return 0;
}

static void rtpproxy_rpc_enable(rpc_t *rpc, void *ctx)
{
	str rtpp_url;
	int enable;
	struct rtpp_set *rtpp_list;
	struct rtpp_node *crt_rtpp;
	int found;

	found = 0;
	enable = 0;

	if(rtpp_set_list == NULL)
		goto end;

	if(rpc->scan(ctx, "Sd", &rtpp_url, &enable) < 2) {
		rpc->fault(ctx, 500, "Not enough parameters");
		return;
	}

	for(rtpp_list = rtpp_set_list->rset_first; rtpp_list != NULL;
			rtpp_list = rtpp_list->rset_next) {

		for(crt_rtpp = rtpp_list->rn_first; crt_rtpp != NULL;
				crt_rtpp = crt_rtpp->rn_next) {
			/*found a matching rtpp*/
			if(crt_rtpp->rn_url.len == rtpp_url.len) {

				if(strncmp(crt_rtpp->rn_url.s, rtpp_url.s, rtpp_url.len) == 0) {
					/*set the enabled/disabled status*/
					found = 1;
					crt_rtpp->rn_recheck_ticks = enable ? RPC_MIN_RECHECK_TICKS
														: RPC_MAX_RECHECK_TICKS;
					crt_rtpp->rn_disabled = enable ? 0 : 1;
				}
			}
		}
	}

end:
	if(!found) {
		rpc->fault(ctx, 404, "RTPProxy not found");
		return;
	}
}


static void rtpproxy_rpc_list(rpc_t *rpc, void *ctx)
{
	struct rtpp_set *rtpp_list;
	struct rtpp_node *crt_rtpp;
	void *vh;

	if(rtpp_set_list == NULL)
		return;

	for(rtpp_list = rtpp_set_list->rset_first; rtpp_list != NULL;
			rtpp_list = rtpp_list->rset_next) {

		for(crt_rtpp = rtpp_list->rn_first; crt_rtpp != NULL;
				crt_rtpp = crt_rtpp->rn_next) {

			if(rpc->add(ctx, "{", &vh) < 0) {
				rpc->fault(ctx, 500, "Server error");
				return;
			}
			rpc->struct_add(vh, "dSdddd", "setid", rtpp_list->id_set, "url",
					&crt_rtpp->rn_url, "index", crt_rtpp->idx, "disabled",
					crt_rtpp->rn_disabled, "weight", crt_rtpp->rn_weight,
					"recheck", crt_rtpp->rn_recheck_ticks);
		}
	}
}

static const char *rtpproxy_rpc_enable_doc[2] = {
		"Set state (enable/disable) for a rtp proxy.", 0};

static const char *rtpproxy_rpc_list_doc[2] = {"List rtp proxies.", 0};

rpc_export_t rtpproxy_rpc[] = {
		{"rtpproxy.list", rtpproxy_rpc_list, rtpproxy_rpc_list_doc, RET_ARRAY},
		{"rtpproxy.enable", rtpproxy_rpc_enable, rtpproxy_rpc_enable_doc, 0},
		{0, 0, 0, 0}};

static int rtpproxy_rpc_init(void)
{
	if(rpc_register_array(rtpproxy_rpc) != 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}

static int mod_init(void)
{
	int i;
	pv_spec_t avp_spec;
	str s;
	unsigned short avp_flags;

	if(rtpproxy_rpc_init() < 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}

	/* Configure the head of the rtpp_set_list */
	rtpp_set_list = shm_malloc(sizeof(struct rtpp_set_head));
	if(rtpp_set_list == NULL) {
		LM_ERR("no shm memory for rtpp_set_list\n");
		return -1;
	}
	memset(rtpp_set_list, 0, sizeof(struct rtpp_set_head));

	if(nortpproxy_str.s == NULL || nortpproxy_str.len <= 0) {
		nortpproxy_str.len = 0;
	} else {
		while(nortpproxy_str.len > 0
				&& (nortpproxy_str.s[nortpproxy_str.len - 1] == '\r'
						|| nortpproxy_str.s[nortpproxy_str.len - 1] == '\n'))
			nortpproxy_str.len--;
	}

	if(rtpp_db_url.s != NULL) {
		init_rtpproxy_db();
		if(rtpp_sets > 0) {
			LM_WARN("rtpproxy db url configured - ignoring modparam sets\n");
		}
	}
	/* storing the list of rtp proxy sets in shared memory*/
	for(i = 0; i < rtpp_sets; i++) {
		LM_DBG("Adding RTP-Proxy set %d/%d: %s\n", i, rtpp_sets,
				rtpp_strings[i]);
		if((rtpp_db_url.s == NULL)
				&& (rtpproxy_add_rtpproxy_set(rtpp_strings[i]) != 0)) {
			for(; i < rtpp_sets; i++)
				if(rtpp_strings[i])
					pkg_free(rtpp_strings[i]);
			pkg_free(rtpp_strings);
			LM_ERR("Failed to add RTP-Proxy from Config!\n");
			return -1;
		}
		if(rtpp_strings[i])
			pkg_free(rtpp_strings[i]);
	}

	if(ice_candidate_priority_avp_param) {
		s.s = ice_candidate_priority_avp_param;
		s.len = strlen(s.s);
		if(pv_parse_spec(&s, &avp_spec) == 0 || avp_spec.type != PVT_AVP) {
			LM_ERR("malformed or non AVP definition <%s>\n",
					ice_candidate_priority_avp_param);
			return -1;
		}
		if(pv_get_avp_name(
				   0, &(avp_spec.pvp), &ice_candidate_priority_avp, &avp_flags)
				!= 0) {
			LM_ERR("invalid AVP definition <%s>\n",
					ice_candidate_priority_avp_param);
			return -1;
		}
		ice_candidate_priority_avp_type = avp_flags;
	}

	if(rtp_inst_pv_param.s) {
		rtp_inst_pvar = pv_cache_get(&rtp_inst_pv_param);
		if((rtp_inst_pvar == NULL)
				|| ((rtp_inst_pvar->type != PVT_AVP)
						&& (rtp_inst_pvar->type != PVT_XAVP)
						&& (rtp_inst_pvar->type != PVT_SCRIPTVAR))) {
			LM_ERR("Invalid pvar name <%.*s>\n", rtp_inst_pv_param.len,
					rtp_inst_pv_param.s);
			return -1;
		}
	}

	if(extra_id_pv_param.s && *extra_id_pv_param.s) {
		if(pv_parse_format(&extra_id_pv_param, &extra_id_pv) < 0) {
			LM_ERR("malformed PV string: %s\n", extra_id_pv_param.s);
			return -1;
		}
	} else {
		extra_id_pv = NULL;
	}
	if(timeout_socket_str.s != NULL && timeout_tag_pv_str.s == NULL) {
		LM_ERR("The timeout_tag_pv has to be set along with timeout_socket\n");
		return -1;
	}
	if(timeout_tag_pv_str.s != NULL) {
		if(timeout_tag_pv_str.len == 0) {
			LM_ERR("Empty timeout_tag_pv is not allowed\n");
			return -1;
		}
		if(pv_parse_format(&timeout_tag_pv_str, &timeout_tag_pv) < 0) {
			LM_ERR("malformed PV string: %s\n", timeout_tag_pv_str.s);
			return -1;
		}
	}

	if(rtpp_strings)
		pkg_free(rtpp_strings);

	if(load_tm_api(&tmb) < 0) {
		LM_DBG("could not load the TM-functions - answer-offer model"
			   " auto-detection is disabled\n");
		memset(&tmb, 0, sizeof(struct tm_binds));
	}

	return 0;
}


static int child_init(int rank)
{
	int n;
	char *cp;
	struct addrinfo hints, *res;
	struct rtpp_set *rtpp_list;
	struct rtpp_node *pnode;

	if(rtpp_set_list == NULL)
		return 0;

	/* do not init sockets for PROC_INIT and main process when fork=yes */
	if(rank == PROC_INIT || (rank == PROC_MAIN && dont_fork == 0)) {
		return 0;
	}

	/* Iterate known RTP proxies - create sockets */
	mypid = getpid();

	rtpp_socks = (int *)pkg_malloc(sizeof(int) * rtpp_no);
	if(rtpp_socks == NULL) {
		LM_ERR("no more pkg memory\n");
		return -1;
	}
	memset(rtpp_socks, -1, sizeof(int) * rtpp_no);

	for(rtpp_list = rtpp_set_list->rset_first; rtpp_list != 0;
			rtpp_list = rtpp_list->rset_next) {

		for(pnode = rtpp_list->rn_first; pnode != 0; pnode = pnode->rn_next) {
			char *hostname;

			if(pnode->rn_umode == 0) {
				rtpp_socks[pnode->idx] = -1;
				goto rptest;
			}

			/*
			 * This is UDP or UDP6. Detect host and port; lookup host;
			 * do connect() in order to specify peer address
			 */
			hostname = (char *)pkg_malloc(
					sizeof(char) * (strlen(pnode->rn_address) + 1));
			if(hostname == NULL) {
				LM_ERR("no more pkg memory\n");
				return -1;
			}
			strcpy(hostname, pnode->rn_address);

			cp = strrchr(hostname, ':');
			if(cp != NULL) {
				*cp = '\0';
				cp++;
			}
			if(cp == NULL || *cp == '\0')
				cp = CPORT;

			memset(&hints, 0, sizeof(hints));
			hints.ai_flags = 0;
			hints.ai_family = (pnode->rn_umode == 6) ? AF_INET6 : AF_INET;
			hints.ai_socktype = SOCK_DGRAM;
			if((n = getaddrinfo(hostname, cp, &hints, &res)) != 0) {
				LM_ERR("%s\n", gai_strerror(n));
				pkg_free(hostname);
				return -1;
			}
			pkg_free(hostname);

			rtpp_socks[pnode->idx] = socket(
					(pnode->rn_umode == 6) ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
			if(rtpp_socks[pnode->idx] == -1) {
				LM_ERR("can't create socket\n");
				freeaddrinfo(res);
				return -1;
			}

			if(connect(rtpp_socks[pnode->idx], res->ai_addr, res->ai_addrlen)
					== -1) {
				LM_ERR("can't connect to a RTP proxy\n");
				close(rtpp_socks[pnode->idx]);
				rtpp_socks[pnode->idx] = -1;
				freeaddrinfo(res);
				return -1;
			}
			freeaddrinfo(res);
		rptest:
			pnode->rn_disabled = rtpp_test(pnode, pnode->rn_disabled, 1);
		}
	}

	return 0;
}


static void mod_destroy(void)
{
	struct rtpp_set *crt_list, *last_list;
	struct rtpp_node *crt_rtpp, *last_rtpp;

	/*free the shared memory*/
	if(natping_state)
		shm_free(natping_state);

	if(rtpp_set_list == NULL)
		return;

	for(crt_list = rtpp_set_list->rset_first; crt_list != NULL;) {

		for(crt_rtpp = crt_list->rn_first; crt_rtpp != NULL;) {

			last_rtpp = crt_rtpp;
			crt_rtpp = last_rtpp->rn_next;
			shm_free(last_rtpp);
		}

		last_list = crt_list;
		crt_list = last_list->rset_next;
		shm_free(last_list);
	}

	shm_free(rtpp_set_list);
}


static int isnulladdr(str *sx, int pf)
{
	char *cp;

	if(pf == AF_INET6) {
		for(cp = sx->s; cp < sx->s + sx->len; cp++)
			if(*cp != '0' && *cp != ':')
				return 0;
		return 1;
	}
	return (sx->len == 7 && memcmp("0.0.0.0", sx->s, 7) == 0);
}

#define ADD_ADIRECTION 0x01
#define FIX_MEDIP 0x02
#define ADD_ANORTPPROXY 0x04
#define FIX_ORGIP 0x08

#define ADIRECTION "a=direction:active"
#define ADIRECTION_LEN (sizeof(ADIRECTION) - 1)

#define AOLDMEDIP "a=oldmediaip:"
#define AOLDMEDIP_LEN (sizeof(AOLDMEDIP) - 1)

#define AOLDMEDIP6 "a=oldmediaip6:"
#define AOLDMEDIP6_LEN (sizeof(AOLDMEDIP6) - 1)

#define AOLDMEDPRT "a=oldmediaport:"
#define AOLDMEDPRT_LEN (sizeof(AOLDMEDPRT) - 1)


static int alter_mediaip(struct sip_msg *msg, str *body, str *oldip, int oldpf,
		str *newip, int newpf, int preserve)
{
	char *buf;
	int offset;
	struct lump *anchor;
	str omip, nip, oip;

	/* check that updating mediaip is really necessary */
	if(oldpf == newpf && isnulladdr(oldip, oldpf))
		return 0;
	if(newip->len == oldip->len && memcmp(newip->s, oldip->s, newip->len) == 0)
		return 0;

	if(preserve != 0) {
		anchor = anchor_lump(msg, body->s + body->len - msg->buf, 0, 0);
		if(anchor == NULL) {
			LM_ERR("anchor_lump failed\n");
			return -1;
		}
		if(oldpf == AF_INET6) {
			omip.s = AOLDMEDIP6;
			omip.len = AOLDMEDIP6_LEN;
		} else {
			omip.s = AOLDMEDIP;
			omip.len = AOLDMEDIP_LEN;
		}
		buf = pkg_malloc(omip.len + oldip->len + CRLF_LEN);
		if(buf == NULL) {
			LM_ERR("out of pkg memory\n");
			return -1;
		}
		memcpy(buf, CRLF, CRLF_LEN);
		memcpy(buf + CRLF_LEN, omip.s, omip.len);
		memcpy(buf + CRLF_LEN + omip.len, oldip->s, oldip->len);
		if(insert_new_lump_after(
				   anchor, buf, omip.len + oldip->len + CRLF_LEN, 0)
				== NULL) {
			LM_ERR("insert_new_lump_after failed\n");
			pkg_free(buf);
			return -1;
		}
	}

	if(oldpf == newpf) {
		nip.len = newip->len;
		nip.s = pkg_malloc(nip.len);
		if(nip.s == NULL) {
			LM_ERR("out of pkg memory\n");
			return -1;
		}
		memcpy(nip.s, newip->s, newip->len);
	} else {
		nip.len = newip->len + 2;
		nip.s = pkg_malloc(nip.len);
		if(nip.s == NULL) {
			LM_ERR("out of pkg memory\n");
			return -1;
		}
		memcpy(nip.s + 2, newip->s, newip->len);
		nip.s[0] = (newpf == AF_INET6) ? '6' : '4';
		nip.s[1] = ' ';
	}

	oip = *oldip;
	if(oldpf != newpf) {
		do {
			oip.s--;
			oip.len++;
		} while(*oip.s != '6' && *oip.s != '4');
	}
	offset = oip.s - msg->buf;
	anchor = del_lump(msg, offset, oip.len, 0);
	if(anchor == NULL) {
		LM_ERR("del_lump failed\n");
		pkg_free(nip.s);
		return -1;
	}

	if(insert_new_lump_after(anchor, nip.s, nip.len, 0) == 0) {
		LM_ERR("insert_new_lump_after failed\n");
		pkg_free(nip.s);
		return -1;
	}
	return 0;
}

static int alter_mediaport(struct sip_msg *msg, str *body, str *oldport,
		str *newport, int preserve)
{
	char *buf;
	int offset;
	struct lump *anchor;

	/* check that updating mediaport is really necessary */
	if(newport->len == oldport->len
			&& memcmp(newport->s, oldport->s, newport->len) == 0)
		return 0;

		/*
	 * Since rewriting the same info twice will mess SDP up,
	 * apply simple anti foot shooting measure - put flag on
	 * messages that have been altered and check it when
	 * another request comes.
	 */
#if 0
	/* disabled: - it propagates to the reply and we don't want this
	 *  -- andrei */
	if (msg->msg_flags & FL_SDP_PORT_AFS) {
		LM_ERR("you can't rewrite the same SDP twice, check your config!\n");
		return -1;
	}
#endif

	if(preserve != 0) {
		anchor = anchor_lump(msg, body->s + body->len - msg->buf, 0, 0);
		if(anchor == NULL) {
			LM_ERR("anchor_lump failed\n");
			return -1;
		}
		buf = pkg_malloc(AOLDMEDPRT_LEN + oldport->len + CRLF_LEN);
		if(buf == NULL) {
			LM_ERR("out of pkg memory\n");
			return -1;
		}
		memcpy(buf, CRLF, CRLF_LEN);
		memcpy(buf + CRLF_LEN, AOLDMEDPRT, AOLDMEDPRT_LEN);
		memcpy(buf + CRLF_LEN + AOLDMEDPRT_LEN, oldport->s, oldport->len);
		if(insert_new_lump_after(
				   anchor, buf, AOLDMEDPRT_LEN + oldport->len + CRLF_LEN, 0)
				== NULL) {
			LM_ERR("insert_new_lump_after failed\n");
			pkg_free(buf);
			return -1;
		}
	}

	buf = pkg_malloc(newport->len);
	if(buf == NULL) {
		LM_ERR("out of pkg memory\n");
		return -1;
	}
	offset = oldport->s - msg->buf;
	anchor = del_lump(msg, offset, oldport->len, 0);
	if(anchor == NULL) {
		LM_ERR("del_lump failed\n");
		pkg_free(buf);
		return -1;
	}
	memcpy(buf, newport->s, newport->len);
	if(insert_new_lump_after(anchor, buf, newport->len, 0) == 0) {
		LM_ERR("insert_new_lump_after failed\n");
		pkg_free(buf);
		return -1;
	}

#if 0
	msg->msg_flags |= FL_SDP_PORT_AFS;
#endif
	return 0;
}

/**
 *
 */
static int alter_rtcp(
		struct sip_msg *msg, str *body, str *oldport, str *newport)
{
	char *buf;
	int offset;
	struct lump *anchor;

	/* check that updating rtcpport is really necessary */
	if(newport->len == oldport->len
			&& memcmp(newport->s, oldport->s, newport->len) == 0)
		return 0;

	buf = pkg_malloc(newport->len);
	if(buf == NULL) {
		LM_ERR("alter_rtcp: out of memory\n");
		return -1;
	}
	offset = oldport->s - msg->buf;
	anchor = del_lump(msg, offset, oldport->len, 0);
	if(anchor == NULL) {
		LM_ERR("alter_rtcp: del_lump failed\n");
		pkg_free(buf);
		return -1;
	}
	memcpy(buf, newport->s, newport->len);
	if(insert_new_lump_after(anchor, buf, newport->len, 0) == 0) {
		LM_ERR("alter_rtcp: insert_new_lump_after failed\n");
		pkg_free(buf);
		return -1;
	}

	return 0;
}


static char *append_filtered_ip(char *at, str *ip)
{
	int i;
	for(i = 0; i < ip->len; i++) {
		if(isdigit(ip->s[i])) {
			append_chr(at, ip->s[i]);
		}
	}
	return at;
}


static int insert_candidates(struct sip_msg *msg, char *where, str *ip,
		unsigned int port, int priority)
{
	char *buf, *at;
	struct lump *anchor;
	str rtp_port;
	str rtcp_port;

	rtcp_port.s = int2str(port + 1, &rtcp_port.len); /* beware static buffer */
	buf = pkg_malloc(
			24 + 78 + 14 + 24 + 2 * ip->len + 2 + 2 * rtcp_port.len + 24);
	if(buf == NULL) {
		LM_ERR("insert_candidates: out of memory\n");
		return -1;
	}

	at = buf;

	append_str(at, "a=candidate:", 12);
	at = append_filtered_ip(at, ip);
	append_str(at, " 2 UDP ", 7);
	if(priority == 2) {
		append_str(at, "16777214 ", 9);
	} else {
		append_str(at, "2197815294 ", 11);
	}
	append_str(at, ip->s, ip->len);
	append_chr(at, ' ');
	append_str(at, rtcp_port.s, rtcp_port.len);
	append_str(at, " typ relay\r\n", 12);

	rtp_port.s = int2str(port, &rtp_port.len); /* beware static buffer */
	append_str(at, "a=candidate:", 12);
	at = append_filtered_ip(at, ip);
	append_str(at, " 1 UDP ", 7);
	if(priority == 2) {
		append_str(at, "16777215 ", 9);
	} else {
		append_str(at, "2197815295 ", 11);
	}
	append_str(at, ip->s, ip->len);
	append_chr(at, ' ');
	append_str(at, rtp_port.s, rtp_port.len);
	append_str(at, " typ relay\r\n", 12);

	LM_DBG("inserting '%.*s'\n", (int)(at - buf), buf);

	anchor = anchor_lump(msg, where - msg->buf, 0, 0);
	if(anchor == 0) {
		LM_ERR("can't get anchor\n");
		pkg_free(buf);
		return -1;
	}
	if(insert_new_lump_before(anchor, buf, at - buf, 0) == 0) {
		LM_ERR("insert_new_lump_before failed\n");
		pkg_free(buf);
		return -1;
	}

	return 0;
}


static char *gencookie(void)
{
	static char cook[34];

	sprintf(cook, "%d_%u ", (int)mypid, myseqn);
	myseqn++;
	return cook;
}

static int rtpp_checkcap(struct rtpp_node *node, char *cap, int caplen)
{
	char *cp;
	struct iovec vf[4] = {{NULL, 0}, {"VF", 2}, {" ", 1}, {NULL, 0}};

	vf[3].iov_base = cap;
	vf[3].iov_len = caplen;

	cp = send_rtpp_command(node, vf, 4);
	if(cp == NULL)
		return -1;
	if(cp[0] == 'E' || atoi(cp) != 1)
		return 0;
	return 1;
}

static int rtpp_test(struct rtpp_node *node, int isdisabled, int force)
{
	int rtpp_ver, rval;
	char *cp;
	struct iovec v[2] = {{NULL, 0}, {"V", 1}};

	if(node->rn_recheck_ticks == RPC_MAX_RECHECK_TICKS) {
		LM_DBG("rtpp %s disabled for ever\n", node->rn_url.s);
		return 1;
	}

	if(force == 0) {
		if(isdisabled == 0)
			return 0;
		if(node->rn_recheck_ticks > get_ticks())
			return 1;
	}
	cp = send_rtpp_command(node, v, 2);
	if(cp == NULL) {
		LM_WARN("can't get version of the RTP proxy\n");
		goto error;
	}
	rtpp_ver = atoi(cp);
	if(rtpp_ver != SUP_CPROTOVER) {
		LM_WARN("unsupported version of RTP proxy <%s> found: %d supported,"
				"%d present\n",
				node->rn_url.s, SUP_CPROTOVER, rtpp_ver);
		goto error;
	}
	rval = rtpp_checkcap(node, REQ_CPROTOVER, sizeof(REQ_CPROTOVER) - 1);
	if(rval == -1) {
		LM_WARN("RTP proxy went down during version query\n");
		goto error;
	}
	if(rval == 0) {
		LM_WARN("of RTP proxy <%s> doesn't support required protocol version"
				"%s\n",
				node->rn_url.s, REQ_CPROTOVER);
		goto error;
	}
	LM_INFO("rtp proxy <%s> found, support for it %senabled\n", node->rn_url.s,
			force == 0 ? "re-" : "");
	/* Check for optional capabilities */
	rval = rtpp_checkcap(node, REP_CPROTOVER, sizeof(REP_CPROTOVER) - 1);
	if(rval != -1) {
		node->rn_rep_supported = rval;
	} else {
		node->rn_rep_supported = 0;
	}
	rval = rtpp_checkcap(node, PTL_CPROTOVER, sizeof(PTL_CPROTOVER) - 1);
	if(rval != -1) {
		node->rn_ptl_supported = rval;
	} else {
		node->rn_ptl_supported = 0;
	}
	return 0;
error:
	LM_WARN("support for RTP proxy <%s> has been disabled%s\n", node->rn_url.s,
			rtpproxy_disable_tout < 0 ? "" : " temporarily");
	if(rtpproxy_disable_tout >= 0)
		node->rn_recheck_ticks = get_ticks() + rtpproxy_disable_tout;

	return 1;
}

char *send_rtpp_command(struct rtpp_node *node, struct iovec *v, int vcnt)
{
	struct sockaddr_un addr;
	int fd, len, i;
	char *cp;
	static char buf[256];
	struct pollfd fds[1];

	len = 0;
	cp = buf;
	if(node->rn_umode == 0) {
		memset(&addr, 0, sizeof(addr));
		addr.sun_family = AF_LOCAL;
		strncpy(addr.sun_path, node->rn_address, sizeof(addr.sun_path) - 1);
#ifdef HAVE_SOCKADDR_SA_LEN
		addr.sun_len = strlen(addr.sun_path);
#endif

		fd = socket(AF_LOCAL, SOCK_STREAM, 0);
		if(fd < 0) {
			LM_ERR("can't create socket\n");
			goto badproxy;
		}
		if(connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
			close(fd);
			LM_ERR("can't connect to RTP proxy\n");
			goto badproxy;
		}

		do {
			len = writev(fd, v + 1, vcnt - 1);
		} while(len == -1 && errno == EINTR);
		if(len <= 0) {
			close(fd);
			LM_ERR("can't send command to a RTP proxy\n");
			goto badproxy;
		}
		do {
			len = read(fd, buf, sizeof(buf) - 1);
		} while(len == -1 && errno == EINTR);
		close(fd);
		if(len <= 0) {
			LM_ERR("can't read reply from a RTP proxy\n");
			goto badproxy;
		}
	} else {
		fds[0].fd = rtpp_socks[node->idx];
		fds[0].events = POLLIN;
		fds[0].revents = 0;
		/* Drain input buffer */
		while((poll(fds, 1, 0) == 1) && ((fds[0].revents & POLLIN) != 0)) {
			recv(rtpp_socks[node->idx], buf, sizeof(buf) - 1, 0);
			fds[0].revents = 0;
		}
		v[0].iov_base = gencookie();
		v[0].iov_len = strlen(v[0].iov_base);
		for(i = 0; i < rtpproxy_retr; i++) {
			do {
				len = writev(rtpp_socks[node->idx], v, vcnt);
			} while(len == -1 && (errno == EINTR || errno == ENOBUFS));
			if(len <= 0) {
				LM_ERR("can't send command to a RTP proxy\n");
				goto badproxy;
			}
			while((poll(fds, 1, rtpproxy_tout * 1000) == 1)
					&& (fds[0].revents & POLLIN) != 0) {
				do {
					len = recv(rtpp_socks[node->idx], buf, sizeof(buf) - 1, 0);
				} while(len == -1 && errno == EINTR);
				if(len <= 0) {
					LM_ERR("can't read reply from a RTP proxy\n");
					goto badproxy;
				}
				if(len >= (v[0].iov_len - 1)
						&& memcmp(buf, v[0].iov_base, (v[0].iov_len - 1))
								   == 0) {
					len -= (v[0].iov_len - 1);
					cp += (v[0].iov_len - 1);
					if(len != 0) {
						len--;
						cp++;
					}
					goto out;
				}
				fds[0].revents = 0;
			}
		}
		if(i == rtpproxy_retr) {
			LM_ERR("timeout waiting reply from a RTP proxy\n");
			goto badproxy;
		}
	}

out:
	cp[len] = '\0';
	return cp;
badproxy:
	LM_ERR("proxy <%s> does not respond, disable it\n", node->rn_url.s);
	node->rn_disabled = 1;
	node->rn_recheck_ticks = get_ticks() + rtpproxy_disable_tout;

	return NULL;
}

/*
 * select the set with the id_set id
 */

static struct rtpp_set *select_rtpp_set(int id_set)
{

	struct rtpp_set *rtpp_list;
	/*is it a valid set_id?*/

	if(!rtpp_set_list) {
		LM_ERR("rtpproxy set list not initialised\n");
		return NULL;
	}

	for(rtpp_list = rtpp_set_list->rset_first;
			rtpp_list != NULL && rtpp_list->id_set != id_set;
			rtpp_list = rtpp_list->rset_next)
		;

	return rtpp_list;
}
/*
 * Main balancing routine. This does not try to keep the same proxy for
 * the call if some proxies were disabled or enabled; proxy death considered
 * too rare. Otherwise we should implement "mature" HA clustering, which is
 * too expensive here.
 */
struct rtpp_node *select_rtpp_node(str callid, int do_test)
{
	unsigned sum, sumcut, weight_sum;
	struct rtpp_node *node;
	int was_forced;

	if(!selected_rtpp_set) {
		LM_ERR("script error -no valid set selected\n");
		return NULL;
	}
	/* Most popular case: 1 proxy, nothing to calculate */
	if(selected_rtpp_set->rtpp_node_count == 1) {
		node = selected_rtpp_set->rn_first;
		if(node->rn_disabled && node->rn_recheck_ticks <= get_ticks())
			node->rn_disabled = rtpp_test(node, 1, 0);
		return node->rn_disabled ? NULL : node;
	}

	/* XXX Use quick-and-dirty hashing algo */
	for(sum = 0; callid.len > 0; callid.len--)
		sum += callid.s[callid.len - 1];
	sum &= 0xff;

	was_forced = 0;
retry:
	weight_sum = 0;
	for(node = selected_rtpp_set->rn_first; node != NULL;
			node = node->rn_next) {

		if(node->rn_disabled && node->rn_recheck_ticks <= get_ticks()) {
			/* Try to enable if it's time to try. */
			node->rn_disabled = rtpp_test(node, 1, 0);
		}
		if(!node->rn_disabled)
			weight_sum += node->rn_weight;
	}
	if(weight_sum == 0) {
		/* No proxies? Force all to be redetected, if not yet */
		if(was_forced)
			return NULL;
		was_forced = 1;
		for(node = selected_rtpp_set->rn_first; node != NULL;
				node = node->rn_next) {
			node->rn_disabled = rtpp_test(node, 1, 1);
		}
		goto retry;
	}
	sumcut = sum % weight_sum;
	/*
	 * sumcut here lays from 0 to weight_sum-1.
	 * Scan proxy list and decrease until appropriate proxy is found.
	 */
	for(node = selected_rtpp_set->rn_first; node != NULL;
			node = node->rn_next) {
		if(node->rn_disabled)
			continue;
		if(sumcut < node->rn_weight)
			goto found;
		sumcut -= node->rn_weight;
	}
	/* No node list */
	return NULL;
found:
	if(do_test) {
		node->rn_disabled = rtpp_test(node, node->rn_disabled, 0);
		if(node->rn_disabled)
			goto retry;
	}
	return node;
}


static int get_extra_id(struct sip_msg *msg, str *id_str)
{
	if(msg == NULL || extra_id_pv == NULL || id_str == NULL) {
		LM_ERR("bad parameters\n");
		return 0;
	}
	if(pv_printf_s(msg, extra_id_pv, id_str) < 0) {
		LM_ERR("cannot print the additional id\n");
		return 0;
	}

	return 1;
}


static int get_timeout_tag(struct sip_msg *msg, str *ntag_str)
{
	if(msg == NULL || timeout_tag_pv == NULL || ntag_str == NULL) {
		LM_ERR("bad parameters\n");
		return 0;
	}
	if(pv_printf_s(msg, timeout_tag_pv, ntag_str) < 0) {
		LM_ERR("cannot print the notify tag\n");
		return 0;
	}

	return 1;
}


static int unforce_rtp_proxy1_f(struct sip_msg *msg, char *str1, char *str2)
{
	str flags;

	if(str1) {
		if(get_str_fparam(&flags, msg, (fparam_t *)str1) < 0) {
			LM_ERR("failed to get flags parameter\n");
			return -1;
		}
	} else {
		flags.s = NULL;
	}

	return unforce_rtp_proxy(msg, flags.s);
}


static int unforce_rtp_proxy(struct sip_msg *msg, char *flags)
{
	str callid, from_tag, to_tag, viabranch;
	char *cp;
	int via = 0;
	int to = 1;
	int extra = 0;
	str extra_id;
	int ret;
	struct rtpp_node *node;
	struct iovec v[1 + 4 + 3 + 2] = {{NULL, 0}, {"D", 1}, {" ", 1}, {NULL, 0},
			{NULL, 0}, {NULL, 0}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}};
	/* 1 */ /* 2 */ /* 3 */ /* 4 */ /* 5 */ /* 6 */ /* 7 */ /* 8 */ /* 9 */

	if(msg == NULL) {
		LM_ERR("invalid sip message structure\n");
		return -1;
	}

	for(cp = flags; cp && *cp; cp++) {
		switch(*cp) {
			case '1':
				via = 1;
				break;

			case '2':
				via = 2;
				break;

			case '3':
				if(msg->first_line.type == SIP_REPLY)
					via = 2;
				else
					via = 1;
				break;

			case 't':
			case 'T':
				to = 0;
				break;
			case 'b':
				extra = 1;
				break;
			case 'a':
			case 'A':
			case 'i':
			case 'I':
			case 'e':
			case 'E':
			case 'l':
			case 'L':
			case 'f':
			case 'F':
			case 'r':
			case 'R':
			case 'c':
			case 'C':
			case 'o':
			case 'O':
			case 'x':
			case 'X':
			case 'w':
			case 'W':
			case 'z':
			case 'Z':
				/* ignore them - they can be sent by rtpproxy_manage() */
				break;

			default:
				LM_ERR("unknown option `%c'\n", *cp);
				return -1;
		}
	}

	if(get_callid(msg, &callid) == -1 || callid.len == 0) {
		LM_ERR("can't get Call-Id field\n");
		return -1;
	}
	to_tag.s = 0;
	to_tag.len = 0;
	if((to == 1) && get_to_tag(msg, &to_tag) == -1) {
		LM_ERR("can't get To tag\n");
		return -1;
	}
	if(get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LM_ERR("can't get From tag\n");
		return -1;
	}
	if(via) {
		if(via == 1)
			ret = get_via_branch(msg, 1, &viabranch);
		else /* (via == 2) */
			ret = get_via_branch(msg, 2, &viabranch);
		if(ret == -1 || viabranch.len == 0) {
			LM_ERR("can't get Via branch\n");
			return -1;
		}
		v[4].iov_base = ";";
		v[4].iov_len = 1;
		STR2IOVEC(viabranch, v[5]);
	} else
		/* Append extra id to call-id */
		if(extra && extra_id_pv && get_extra_id(msg, &extra_id)) {
			v[4].iov_base = ";";
			v[4].iov_len = 1;
			STR2IOVEC(extra_id, v[5]);
		}
	STR2IOVEC(callid, v[3]);
	STR2IOVEC(from_tag, v[7]);
	STR2IOVEC(to_tag, v[9]);

	if(msg->id != current_msg_id) {
		selected_rtpp_set = default_rtpp_set;
	}

	node = select_rtpp_node(callid, 1);
	if(!node) {
		LM_ERR("no available proxies\n");
		return -1;
	}
	set_rtp_inst_pvar(msg, &node->rn_url);
	send_rtpp_command(node, v, (to_tag.len > 0) ? 10 : 8);

	return 1;
}

/* This function assumes p points to a line of requested type. */

static int set_rtp_proxy_set_f(struct sip_msg *msg, char *str1, char *str2)
{
	rtpp_set_link_t *rtpl;
	pv_value_t val;

	rtpl = (rtpp_set_link_t *)str1;

	current_msg_id = 0;
	selected_rtpp_set = 0;

	if(rtpl->rset != NULL) {
		current_msg_id = msg->id;
		selected_rtpp_set = rtpl->rset;
	} else {
		if(pv_get_spec_value(msg, rtpl->rpv, &val) < 0) {
			LM_ERR("cannot evaluate pv param\n");
			return -1;
		}
		if(!(val.flags & PV_VAL_INT)) {
			LM_ERR("pv param must hold an integer value\n");
			return -1;
		}
		selected_rtpp_set = select_rtpp_set(val.ri);
		if(selected_rtpp_set == NULL) {
			LM_ERR("could not locate rtpproxy set %ld\n", val.ri);
			return -1;
		}
		current_msg_id = msg->id;
	}
	return 1;
}

static int rtpproxy_manage(struct sip_msg *msg, char *flags, char *ip)
{
	str cp = STR_NULL;
	char newip[IP_ADDR_MAX_STR_SIZE];
	int method;
	int nosdp;

	if(route_type == BRANCH_FAILURE_ROUTE) {
		/* do nothing in branch failure event route
		 * - delete done on transaction failure route */
		return 1;
	}

	if(msg->cseq == NULL
			&& ((parse_headers(msg, HDR_CSEQ_F, 0) == -1)
					|| (msg->cseq == NULL))) {
		LM_ERR("no CSEQ header\n");
		return -1;
	}

	method = get_cseq(msg)->method_id;

	if(!(method == METHOD_INVITE || method == METHOD_ACK
			   || method == METHOD_CANCEL || method == METHOD_BYE
			   || method == METHOD_UPDATE || method == METHOD_PRACK))
		return -1;

	if(method == METHOD_CANCEL || method == METHOD_BYE)
		return unforce_rtp_proxy(msg, flags);

	if(ip == NULL) {
		cp.s = ip_addr2a(&msg->rcv.dst_ip);
		cp.len = strlen(cp.s);
		/* Copy, including teminating \0 */
		memcpy(newip, cp.s, cp.len + 1);
	}

	if(msg->msg_flags & FL_SDP_BODY)
		nosdp = 0;
	else
		nosdp = parse_sdp(msg);

	if(msg->first_line.type == SIP_REQUEST) {
		if(method == METHOD_ACK && nosdp == 0)
			return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip, 0,
					(ip != NULL) ? 1 : 0);
		if(method == METHOD_PRACK && nosdp == 0)
			return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip, 1,
					(ip != NULL) ? 1 : 0);
		if(method == METHOD_UPDATE && nosdp == 0)
			return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip, 1,
					(ip != NULL) ? 1 : 0);
		if(method == METHOD_INVITE && nosdp == 0) {
			msg->msg_flags |= FL_SDP_BODY;
			if(tmb.t_gett != NULL && tmb.t_gett() != NULL
					&& tmb.t_gett() != T_UNDEFINED)
				tmb.t_gett()->uas.request->msg_flags |= FL_SDP_BODY;
			if(route_type == FAILURE_ROUTE)
				return unforce_rtp_proxy(msg, flags);
			return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip, 1,
					(ip != NULL) ? 1 : 0);
		}
	} else if(msg->first_line.type == SIP_REPLY) {
		if(msg->first_line.u.reply.statuscode >= 300)
			return unforce_rtp_proxy(msg, flags);
		if(nosdp == 0) {
			if(method == METHOD_PRACK)
				return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip,
						0, (ip != NULL) ? 1 : 0);
			if(method == METHOD_UPDATE)
				return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip,
						0, (ip != NULL) ? 1 : 0);
			if(tmb.t_gett == NULL || tmb.t_gett() == NULL
					|| tmb.t_gett() == T_UNDEFINED)
				return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip,
						0, (ip != NULL) ? 1 : 0);
			if(tmb.t_gett()->uas.request->msg_flags & FL_SDP_BODY)
				return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip,
						0, (ip != NULL) ? 1 : 0);
			return force_rtp_proxy(msg, flags, (cp.s != NULL) ? newip : ip, 1,
					(ip != NULL) ? 1 : 0);
		}
	}
	return -1;
}

static int rtpproxy_manage0(struct sip_msg *msg, char *flags, char *ip)
{
	return rtpproxy_manage(msg, 0, 0);
}

static int rtpproxy_manage1(struct sip_msg *msg, char *flags, char *ip)
{
	str flag_str;
	if(fixup_get_svalue(msg, (gparam_p)flags, &flag_str) < 0) {
		LM_ERR("invalid flags parameter\n");
		return -1;
	}
	return rtpproxy_manage(msg, flag_str.s, 0);
}

static int rtpproxy_manage2(struct sip_msg *msg, char *flags, char *ip)
{
	str flag_str;
	str ip_str;
	if(fixup_get_svalue(msg, (gparam_p)flags, &flag_str) < 0) {
		LM_ERR("invalid flags parameter\n");
		return -1;
	}
	if(fixup_get_svalue(msg, (gparam_p)ip, &ip_str) < 0) {
		LM_ERR("invalid IP parameter\n");
		return -1;
	}
	return rtpproxy_manage(msg, flag_str.s, ip_str.s);
}

static int rtpproxy_offer1_helper_f(struct sip_msg *msg, char *flags)
{
	char newip[IP_ADDR_MAX_STR_SIZE];
	int len;

	len = ip_addr2sbuf(&msg->rcv.dst_ip, newip, IP_ADDR_MAX_STR_SIZE - 1);
	newip[len] = 0;

	return force_rtp_proxy(msg, flags, newip, 1, 0);
}

static int rtpproxy_offer1_f(struct sip_msg *msg, char *str1, char *str2)
{
	str flags;

	if(str1) {
		if(get_str_fparam(&flags, msg, (fparam_t *)str1) < 0) {
			LM_ERR("failed to get flags parameter\n");
			return -1;
		}
	} else {
		flags.s = NULL;
	}

	return rtpproxy_offer1_helper_f(msg, flags.s);
}

static int rtpproxy_offer2_f(struct sip_msg *msg, char *param1, char *param2)
{
	str flags, new_ip;

	if(get_str_fparam(&flags, msg, (fparam_t *)param1) < 0) {
		LM_ERR("failed to get flags parameter\n");
		return -1;
	}
	if(get_str_fparam(&new_ip, msg, (fparam_t *)param2) < 0) {
		LM_ERR("failed to get new ip parameter\n");
		return -1;
	}
	return force_rtp_proxy(msg, flags.s, new_ip.s, 1, 1);
}

static int rtpproxy_answer1_helper_f(struct sip_msg *msg, char *flags)
{
	str cp = STR_NULL;
	char newip[IP_ADDR_MAX_STR_SIZE];

	if(msg->first_line.type == SIP_REQUEST)
		if(msg->first_line.u.request.method_value != METHOD_ACK)
			return -1;

	cp.s = ip_addr2a(&msg->rcv.dst_ip);
	cp.len = strlen(cp.s);
	/* Copy, including teminating \0 */
	memcpy(newip, cp.s, cp.len + 1);

	return force_rtp_proxy(msg, flags, newip, 0, 0);
}

static int rtpproxy_answer1_f(struct sip_msg *msg, char *str1, char *str2)
{
	str flags;

	if(str1) {
		if(get_str_fparam(&flags, msg, (fparam_t *)str1) < 0) {
			LM_ERR("failed to get flags parameter\n");
			return -1;
		}
	} else {
		flags.s = NULL;
	}

	return rtpproxy_answer1_helper_f(msg, flags.s);
}

static int rtpproxy_answer2_f(struct sip_msg *msg, char *param1, char *param2)
{

	str flags, new_ip;

	if(msg->first_line.type == SIP_REQUEST)
		if(msg->first_line.u.request.method_value != METHOD_ACK)
			return -1;

	if(get_str_fparam(&flags, msg, (fparam_t *)param1) < 0) {
		LM_ERR("failed to get flags parameter\n");
		return -1;
	}
	if(get_str_fparam(&new_ip, msg, (fparam_t *)param2) < 0) {
		LM_ERR("failed to get new ip parameter\n");
		return -1;
	}
	return force_rtp_proxy(msg, flags.s, new_ip.s, 0, 1);
}


struct options
{
	str s;
	int oidx;
};

static int append_opts(struct options *op, char ch)
{
	void *p;

	if(op->s.len <= op->oidx) {
		p = pkg_realloc(op->s.s, op->oidx + 32);
		if(p == NULL) {
			return (-1);
		}
		op->s.s = p;
		op->s.len = op->oidx + 32;
	}
	op->s.s[op->oidx++] = ch;
	return (0);
}

static void free_opts(
		struct options *op1, struct options *op2, struct options *op3)
{

	if(op1->s.len > 0 && op1->s.s != NULL) {
		pkg_free(op1->s.s);
		op1->s.len = 0;
	}
	if(op2->s.len > 0 && op2->s.s != NULL) {
		pkg_free(op2->s.s);
		op2->s.len = 0;
	}
	if(op3->s.len > 0 && op3->s.s != NULL) {
		pkg_free(op3->s.s);
		op3->s.len = 0;
	}
}

#define FORCE_RTP_PROXY_RET(e)                 \
	do {                                       \
		free_opts(&opts, &rep_opts, &pt_opts); \
		return (e);                            \
	} while(0);

struct new_mediaip
{
	str strip;
	int pf;
};

static int force_rtp_proxy(
		struct sip_msg *msg, char *str1, char *str2, int offer, int forcedIP)
{
	str body, body1, oldport, oldip, newport;
	struct new_mediaip newip;
	str callid, from_tag, to_tag, tmp, payload_types;
	str newrtcp = {0, 0};
	str viabranch;
	int create, port, len, flookup, argc, proxied, real, via, ret;
	int orgip, commip;
	int pf, force;
	struct options opts, rep_opts, pt_opts;
	char *cp, *cp1;
	char *cpend, *next;
	char **ap, *argv[10];
	struct lump *anchor;
	struct rtpp_node *node;
	struct iovec v[] = {
			{NULL, 0}, /* reserved (cookie) */
			{NULL, 0}, /* command & common options */
			{NULL, 0}, /* per-media/per-node options 1 */
			{NULL, 0}, /* per-media/per-node options 2 */
			{" ", 1},  /* separator */
			{NULL, 0}, /* callid */
			{NULL, 0}, /* via-branch separator ";" */
			{NULL, 0}, /* via-branch */
			{" ", 1},  /* separator */
			{NULL, 7}, /* newip */
			{" ", 1},  /* separator */
			{NULL, 1}, /* oldport */
			{" ", 1},  /* separator */
			{NULL, 0}, /* from_tag */
			{";", 1},  /* separator */
			{NULL, 0}, /* medianum */
			{" ", 1},  /* separator */
			{NULL, 0}, /* to_tag */
			{";", 1},  /* separator */
			{NULL, 0}, /* medianum */
			{" ", 1},  /* separator */
			{NULL, 0}, /* Timeout-Socket */
			{" ", 1},  /* separator */
			{NULL, 0}, /* Timeout-Tag */
	};
	int iovec_param_count;
	int autobridge_ipv4v6;
	int extra;
	str extra_id;

	char *c1p, *c2p, *bodylimit, *o1p;
	char itoabuf_buf[20];
	int medianum, media_multi;
	str itoabuf_str;
	int c1p_altered;

	int sdp_session_num, sdp_stream_num;
	sdp_session_cell_t *sdp_session;
	sdp_stream_cell_t *sdp_stream;

	if(msg == NULL) {
		LM_ERR("invalid sip message structure\n");
		return -1;
	}

	int_str ice_candidate_priority_val;

	memset(&opts, '\0', sizeof(opts));
	memset(&rep_opts, '\0', sizeof(rep_opts));
	memset(&pt_opts, '\0', sizeof(pt_opts));
	/* Leave space for U/L prefix TBD later */
	if(append_opts(&opts, '?') == -1) {
		LM_ERR("out of pkg memory\n");
		FORCE_RTP_PROXY_RET(-1);
	}
	flookup = force = real = orgip = commip = via = autobridge_ipv4v6 = extra =
			0;
	for(cp = str1; cp != NULL && *cp != '\0'; cp++) {
		switch(*cp) {
			case '1':
				via = 1;
				break;

			case '2':
				via = 2;
				break;

			case '3':
				if(msg->first_line.type == SIP_REPLY)
					via = 2;
				else
					via = 1;
				break;

			case 'a':
			case 'A':
				if(append_opts(&opts, 'A') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				real = 1;
				break;

			case 'b':
				extra = 1;
				break;

			case 'i':
			case 'I':
				if(append_opts(&opts, 'I') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				break;

			case 'e':
			case 'E':
				if(append_opts(&opts, 'E') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				break;

			case 'l':
			case 'L':
				if(offer == 0) {
					FORCE_RTP_PROXY_RET(-1);
				}
				flookup = 1;
				break;

			case 'f':
			case 'F':
				force = 1;
				break;

			case 'r':
			case 'R':
				real = 1;
				break;

			case 'c':
			case 'C':
				commip = 1;
				break;

			case 'o':
			case 'O':
				orgip = 1;
				break;

			case 'x':
			case 'X':
				autobridge_ipv4v6 = 1;
				break;

			case 'w':
			case 'W':
				if(append_opts(&opts, 'S') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				break;

			case 'z':
			case 'Z':
				if(append_opts(&rep_opts, 'Z') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				/* If there are any digits following Z copy them into the command */
				for(; cp[1] != '\0' && isdigit(cp[1]); cp++) {
					if(append_opts(&rep_opts, cp[1]) == -1) {
						LM_ERR("out of pkg memory\n");
						FORCE_RTP_PROXY_RET(-1);
					}
				}
				break;

			case 't':
			case 'T':
				/* Only used in rtpproxy_destroy */
				break;

			default:
				LM_ERR("unknown option `%c'\n", *cp);
				FORCE_RTP_PROXY_RET(-1);
		}
	}

	if(offer != 0) {
		create = 1;
	} else {
		create = 0;
	}
	/* extract_body will also parse all the headers in the message as
	 * a side effect => don't move get_callid/get_to_tag in front of it
	 * -- andrei */
	if(extract_body(msg, &body) == -1) {
		LM_ERR("can't extract body from the message\n");
		FORCE_RTP_PROXY_RET(-1);
	}
	if(get_callid(msg, &callid) == -1 || callid.len == 0) {
		LM_ERR("can't get Call-Id field\n");
		FORCE_RTP_PROXY_RET(-1);
	}
	to_tag.s = 0;
	if(get_to_tag(msg, &to_tag) == -1) {
		LM_ERR("can't get To tag\n");
		FORCE_RTP_PROXY_RET(-1);
	}
	if(get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LM_ERR("can't get From tag\n");
		FORCE_RTP_PROXY_RET(-1);
	}
	if(via) {
		if(via == 1)
			ret = get_via_branch(msg, 1, &viabranch);
		else /* (via == 2) */
			ret = get_via_branch(msg, 2, &viabranch);
		if(ret == -1 || viabranch.len == 0) {
			LM_ERR("can't get Via branch\n");
			FORCE_RTP_PROXY_RET(-1);
		}
		v[6].iov_base = ";";
		v[6].iov_len = 1;
		STR2IOVEC(viabranch, v[7]);
	} else
		/* Append extra id to call-id */
		if(extra && extra_id_pv && get_extra_id(msg, &extra_id)) {
			v[6].iov_base = ";";
			v[6].iov_len = 1;
			STR2IOVEC(extra_id, v[7]);
		}
	if(flookup != 0) {
		if(to_tag.len == 0) {
			FORCE_RTP_PROXY_RET(-1);
		}
		if(msg->first_line.type == SIP_REQUEST) {
			tmp = from_tag;
			from_tag = to_tag;
			to_tag = tmp;
		}
		create = 0;
	} else if((msg->first_line.type == SIP_REPLY && offer != 0)
			  || (msg->first_line.type == SIP_REQUEST && offer == 0)) {
		if(to_tag.len == 0) {
			FORCE_RTP_PROXY_RET(-1);
		}
		tmp = from_tag;
		from_tag = to_tag;
		to_tag = tmp;
	}
	proxied = 0;
	if(nortpproxy_str.len) {
		for(cp = body.s;
				(len = body.s + body.len - cp) >= nortpproxy_str.len;) {
			cp1 = ser_memmem(cp, nortpproxy_str.s, len, nortpproxy_str.len);
			if(cp1 == NULL)
				break;
			if(cp1[-1] == '\n' || cp1[-1] == '\r') {
				proxied = 1;
				break;
			}
			cp = cp1 + nortpproxy_str.len;
		}
	}
	if(proxied != 0 && force == 0) {
		FORCE_RTP_PROXY_RET(-2);
	}
	/*
	 * Parsing of SDP body.
	 * It can contain a few session descriptions (each starts with
	 * v-line), and each session may contain a few media descriptions
	 * (each starts with m-line).
	 * We have to change ports in m-lines, and also change IP addresses in
	 * c-lines which can be placed either in session header (fallback for
	 * all medias) or media description.
	 * Ports should be allocated for any media. IPs all should be changed
	 * to the same value (RTP proxy IP), so we can change all c-lines
	 * unconditionally.
	 */
	if(0 != parse_sdp(msg)) {
		LM_ERR("Unable to parse sdp\n");
		FORCE_RTP_PROXY_RET(-1);
	}
#ifdef EXTRA_DEBUG
	print_sdp((sdp_info_t *)msg->body, L_DBG);
#endif

	bodylimit = body.s + body.len;

	if(msg->id != current_msg_id) {
		selected_rtpp_set = default_rtpp_set;
	}

	opts.s.s[0] = (create == 0) ? 'L' : 'U';
	v[1].iov_base = opts.s.s;
	v[1].iov_len = opts.oidx;
	STR2IOVEC(callid, v[5]);
	STR2IOVEC(from_tag, v[13]);
	STR2IOVEC(to_tag, v[17]);

	if(ice_candidate_priority_avp_param) {
		if(search_first_avp(ice_candidate_priority_avp_type,
				   ice_candidate_priority_avp, &ice_candidate_priority_val, 0)
				== NULL) {
			ice_candidate_priority_val.n = 2;
		} else if((ice_candidate_priority_val.n < 0)
				  || (ice_candidate_priority_val.n > 2)) {
			LM_ERR("invalid ice candidate priority value %ld\n",
					ice_candidate_priority_val.n);
			FORCE_RTP_PROXY_RET(-1);
		}
	} else {
		ice_candidate_priority_val.n = 0;
	}

	/* check if this is a single or a multi stream SDP offer/answer */
	sdp_stream_num = get_sdp_stream_num(msg);
	switch(sdp_stream_num) {
		case 0:
			LM_ERR("sdp w/o streams\n");
			FORCE_RTP_PROXY_RET(-1);
			break;
		case 1:
			media_multi = 0;
			break;
		default:
			media_multi = 1;
	}
#ifdef EXTRA_DEBUG
	LM_DBG("my new media_multi=%d\n", media_multi);
#endif
	medianum = 0;
	sdp_session_num = 0;
	for(;;) {
		sdp_session = get_sdp_session(msg, sdp_session_num);
		if(!sdp_session)
			break;
		sdp_stream_num = 0;
		c1p_altered = 0;
		o1p = sdp_session->o_ip_addr.s;
		for(;;) {
			sdp_stream = get_sdp_stream(msg, sdp_session_num, sdp_stream_num);
			if(!sdp_stream
					|| (ice_candidate_priority_val.n
							&& sdp_stream->remote_candidates.len))
				break;

			if(sdp_stream->ip_addr.s && sdp_stream->ip_addr.len > 0) {
				oldip = sdp_stream->ip_addr;
				pf = sdp_stream->pf;
			} else {
				oldip = sdp_session->ip_addr;
				pf = sdp_session->pf;
			}
			oldport = sdp_stream->port;
			payload_types = sdp_stream->payloads;
			medianum++;

			if(real != 0) {
				newip.strip = oldip;
				newip.pf = pf;
			} else {
				newip.strip.s = ip_addr2a(&msg->rcv.src_ip);
				newip.strip.len = strlen(newip.strip.s);
				newip.pf = msg->rcv.src_ip.af;
			}
			/* XXX must compare address families in all addresses */
			if(pf == AF_INET6) {
				if(autobridge_ipv4v6 != 0) {
					if((append_opts(&opts, 'E') == -1)
							|| (append_opts(&opts, 'I') == -1)) {
						LM_ERR("out of pkg memory\n");
						FORCE_RTP_PROXY_RET(-1);
					}
					/* Only execute once */
					autobridge_ipv4v6 = 0;
				}
				if(append_opts(&opts, '6') == -1) {
					LM_ERR("out of pkg memory\n");
					FORCE_RTP_PROXY_RET(-1);
				}
				/* We need to update the pointers and the length here, it has changed. */
				v[1].iov_base = opts.s.s;
				v[1].iov_len = opts.oidx;
			} else {
				if(autobridge_ipv4v6 != 0) {
					if((append_opts(&opts, 'I') == -1)
							|| (append_opts(&opts, 'E') == -1)) {
						LM_ERR("out of pkg memory\n");
						FORCE_RTP_PROXY_RET(-1);
					}
					/* We need to update the pointers and the length here, it has changed. */
					v[1].iov_base = opts.s.s;
					v[1].iov_len = opts.oidx;
					/* Only execute once */
					autobridge_ipv4v6 = 0;
				}
			}

			STR2IOVEC(newip.strip, v[9]);
			STR2IOVEC(oldport, v[11]);
#ifdef EXTRA_DEBUG
			LM_DBG("STR2IOVEC(newip[%.*s], v[9])", newip.strip.len,
					newip.strip.s);
			LM_DBG("STR2IOVEC(oldport[%.*s], v[11])", oldport.len, oldport.s);
#endif
			if(1 || media_multi) /* XXX netch: can't choose now*/
			{
				snprintf(itoabuf_buf, sizeof itoabuf_buf, "%d", medianum);
				itoabuf_str.s = itoabuf_buf;
				itoabuf_str.len = strlen(itoabuf_buf);
				STR2IOVEC(itoabuf_str, v[15]);
				STR2IOVEC(itoabuf_str, v[19]);
#ifdef EXTRA_DEBUG
				LM_DBG("STR2IOVEC(itoabuf_str, v[15])\n");
				LM_DBG("STR2IOVEC(itoabuf_str, v[19])\n");
#endif
			} else {
				v[14].iov_len = v[15].iov_len = 0;
				v[18].iov_len = v[19].iov_len = 0;
			}
			do {
				node = select_rtpp_node(callid, 1);
				if(!node) {
					LM_ERR("no available proxies\n");
					FORCE_RTP_PROXY_RET(-3);
				}
				set_rtp_inst_pvar(msg, &node->rn_url);
				if(rep_opts.oidx > 0) {
					if(node->rn_rep_supported == 0) {
						LM_WARN("re-packetization is requested but is not "
								"supported by the selected RTP proxy node\n");
						v[2].iov_len = 0;
					} else {
						v[2].iov_base = rep_opts.s.s;
						v[2].iov_len += rep_opts.oidx;
					}
				}
#ifdef EXTRA_DEBUG
				LM_DBG("payload_types='%.*s'\n", payload_types.len,
						payload_types.s);
#endif
				if(sdp_stream->is_rtp && payload_types.len > 0
						&& node->rn_ptl_supported != 0) {
					pt_opts.oidx = 0;
					if(append_opts(&pt_opts, 'c') == -1) {
						LM_ERR("out of pkg memory\n");
						FORCE_RTP_PROXY_RET(-1);
					}
					/*
					 * Convert space-separated payload types list into
					 * a comma-separated list.
					 */
					for(cp = payload_types.s;
							cp < payload_types.s + payload_types.len; cp++) {
						if(isdigit(*cp)) {
							if(append_opts(&pt_opts, *cp) == -1) {
								LM_ERR("out of pkg memory\n");
								FORCE_RTP_PROXY_RET(-1);
							}
							continue;
						}
						do {
							cp++;
						} while(!isdigit(*cp)
								&& cp < payload_types.s + payload_types.len);
						/* Check EOL */
						if(cp >= payload_types.s + payload_types.len)
							break;
						if(append_opts(&pt_opts, ',') == -1) {
							LM_ERR("out of pkg memory\n");
							FORCE_RTP_PROXY_RET(-1);
						}
						cp--;
					}
					v[3].iov_base = pt_opts.s.s;
					v[3].iov_len = pt_opts.oidx;
				} else {
					v[3].iov_len = 0;
				}
				if(to_tag.len > 0) {
					iovec_param_count = 20;
				} else {
					iovec_param_count = 16;
				}
				if(opts.s.s[0] == 'U' && timeout_socket_str.len > 0) {
					str ntag = {0, 0};
					if(get_timeout_tag(msg, &ntag) == 0 || ntag.s == NULL
							|| ntag.len == 0) {
						LM_ERR("can't get timeout notification tag\n");
						FORCE_RTP_PROXY_RET(-1);
					}
					STR2IOVEC(timeout_socket_str, v[iovec_param_count + 1]);
					iovec_param_count += 2;
					STR2IOVEC(ntag, v[iovec_param_count + 1]);
					iovec_param_count += 2;
				}
				cp = send_rtpp_command(node, v, iovec_param_count);
			} while(cp == NULL);
			LM_DBG("proxy reply: %s\n", cp);
			/* Parse proxy reply to <argc,argv> */
			argc = 0;
			memset(argv, 0, sizeof(argv));
			cpend = cp + strlen(cp);
			next = eat_token_end(cp, cpend);
			for(ap = argv; cp < cpend;
					cp = next + 1, next = eat_token_end(cp, cpend)) {
				*next = 0;
				if(*cp != '\0') {
					*ap = cp;
					argc++;
					if((char *)++ap >= ((char *)argv + sizeof(argv)))
						break;
				}
			}
			if(argc < 1) {
				LM_ERR("no reply from rtp proxy\n");
				FORCE_RTP_PROXY_RET(-1);
			}
			port = atoi(argv[0]);
			if(port <= 0 || port > 65535) {
				if(port != 0 || flookup == 0)
					LM_ERR("incorrect port %i in reply "
						   "from rtp proxy\n",
							port);
				FORCE_RTP_PROXY_RET(-1);
			}

			/*
			 * if (argc == 1) {
			 *      Assume AF in reply stays the same as one in
			 *      the original request.
			 * }
			 */
			if(argc == 2) {
				/*
				 * For historical reasons, if rtpproxy returns
				 * bare address without AF flag, this means
				 * IPv4.
				 */
				newip.pf = AF_INET;
			} else if(argc >= 3) {
				/*
				 * When rtpproxy returns explicit address +
				 * "AF" flag, use that.
				 */
				newip.pf = (argv[2][0] == '6') ? AF_INET6 : AF_INET;
			}

			if(isnulladdr(&oldip, pf)) {
				if(newip.pf == AF_INET6) {
					newip.strip.s = "::";
					newip.strip.len = 2;
				} else {
					newip.strip.s = "0.0.0.0";
					newip.strip.len = 7;
				}
			} else {
				if(forcedIP) {
					newip.strip.s = str2;
					newip.strip.len = strlen(newip.strip.s);
#ifdef EXTRA_DEBUG
					LM_DBG("forcing IP='%.*s'\n", newip.strip.len,
							newip.strip.s);
#endif
				} else {
					newip.strip.s = (argc < 2) ? str2 : argv[1];
					newip.strip.len = strlen(newip.strip.s);
				}
			}
			/* marker to double check : newport goes: str -> int -> str ?!?! */
			newport.s = int2str(port, &newport.len); /* beware static buffer */
			/* Alter port. */
			body1.s = sdp_stream->media.s;
			body1.len = bodylimit - body1.s;
#ifdef EXTRA_DEBUG
			LM_DBG("alter port body1='%.*s'\n", body1.len, body1.s);
#endif
			/* do not do it if old port was 0 (means media disable)
			 * - check if actually should be better done in rtpproxy,
			 *   by returning also 0
			 * - or by not sending to rtpproxy the old port if 0
			 */
			if(oldport.len != 1 || oldport.s[0] != '0') {
				if(alter_mediaport(msg, &body1, &oldport, &newport, 0) == -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
			}

			/*
			 * Alter RTCP attribute if present. Inserting RTP port + 1 (as allocated
			 * by RTP proxy). No IP-address is needed in the new RTCP attribute as the
			 * 'c' attribute (altered below) will contain the RTP proxy IP address.
			 * See RFC 3605 for definition of RTCP attribute.
			 * ported from ser
			 */

			if(sdp_stream->rtcp_port.s && sdp_stream->rtcp_port.len) {
				newrtcp.s = int2str(
						port + 1, &newrtcp.len); /* beware static buffer */
				/* Alter port. */
				body1.s = sdp_stream->rtcp_port.s;
				body1.len = bodylimit - body1.s;
#ifdef EXTRA_DEBUG
				LM_DBG("alter rtcp body1='%.*s'\n", body1.len, body1.s);
#endif
				if(alter_rtcp(msg, &body1, &sdp_stream->rtcp_port, &newrtcp)
						== -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
			}

			/* Add ice relay candidates */
			if(ice_candidate_priority_val.n && sdp_stream->ice_attrs_num > 0) {
				body1.s = sdp_stream->ice_attr->foundation.s - 12;
				body1.len = bodylimit - body1.s;
				if(insert_candidates(msg,
						   sdp_stream->ice_attr->foundation.s - 12,
						   &newip.strip, port, ice_candidate_priority_val.n)
						== -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
			}

			c1p = sdp_session->ip_addr.s;
			c2p = sdp_stream->ip_addr.s;
			/*
			 * Alter IP. Don't alter IP common for the session
			 * more than once.
			 */
			if(c2p != NULL || !c1p_altered) {
				body1.s = c2p ? c2p : c1p;
				body1.len = bodylimit - body1.s;
#ifdef EXTRA_DEBUG
				LM_DBG("alter ip body1='%.*s'\n", body1.len, body1.s);
#endif
				if(alter_mediaip(
						   msg, &body1, &oldip, pf, &newip.strip, newip.pf, 0)
						== -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
				if(!c2p)
					c1p_altered = 1;
			}
			/*
			 * Alter common IP if required, but don't do it more than once.
			 */
			if(commip && c1p && !c1p_altered) {
				body1.s = c1p;
				body1.len = bodylimit - body1.s;
#ifdef EXTRA_DEBUG
				LM_DBG("alter common ip body1='%.*s'\n", body1.len, body1.s);
#endif
				if(alter_mediaip(msg, &body1, &sdp_session->ip_addr,
						   sdp_session->pf, &newip.strip, newip.pf, 0)
						== -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
				c1p_altered = 1;
			}
			/*
			 * Alter the IP in "o=", but only once per session
			 */
			if(o1p) {
				body1.s = o1p;
				body1.len = bodylimit - body1.s;
#ifdef EXTRA_DEBUG
				LM_DBG("alter media ip body1='%.*s'\n", body1.len, body1.s);
#endif
				if(alter_mediaip(msg, &body1, &sdp_session->o_ip_addr,
						   sdp_session->o_pf, &newip.strip, newip.pf, 0)
						== -1) {
					FORCE_RTP_PROXY_RET(-1);
				}
				o1p = 0;
			}
			sdp_stream_num++;
		}
		sdp_session_num++;
	}


	free_opts(&opts, &rep_opts, &pt_opts);

	if(proxied == 0 && nortpproxy_str.len) {
		cp = pkg_malloc((nortpproxy_str.len + CRLF_LEN) * sizeof(char));
		if(cp == NULL) {
			LM_ERR("out of pkg memory\n");
			return -1;
		}
		anchor = anchor_lump(msg, body.s + body.len - msg->buf, 0, 0);
		if(anchor == NULL) {
			LM_ERR("anchor_lump failed\n");
			pkg_free(cp);
			return -1;
		}
		memcpy(cp, CRLF, CRLF_LEN);
		memcpy(cp + CRLF_LEN, nortpproxy_str.s, nortpproxy_str.len);
		if(insert_new_lump_after(anchor, cp, nortpproxy_str.len + CRLF_LEN, 0)
				== NULL) {
			LM_ERR("insert_new_lump_after failed\n");
			pkg_free(cp);
			return -1;
		}
	}

	return 1;
}


static int ki_start_recording(sip_msg_t *msg)
{
	int nitems;
	str callid = {0, 0};
	str from_tag = {0, 0};
	str to_tag = {0, 0};
	struct rtpp_node *node;
	struct iovec v[1 + 4 + 3] = {{NULL, 0}, {"R", 1}, {" ", 1}, {NULL, 0},
			{" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}};
	/* 1 */ /* 2 */ /* 3 */ /* 4 */ /* 5 */ /* 6 */ /* 1 */

	if(get_callid(msg, &callid) == -1 || callid.len == 0) {
		LM_ERR("can't get Call-Id field\n");
		return -1;
	}

	if(get_to_tag(msg, &to_tag) == -1) {
		LM_ERR("can't get To tag\n");
		return -1;
	}

	if(get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LM_ERR("can't get From tag\n");
		return -1;
	}

	if(msg->id != current_msg_id) {
		selected_rtpp_set = default_rtpp_set;
	}

	STR2IOVEC(callid, v[3]);
	STR2IOVEC(from_tag, v[5]);
	STR2IOVEC(to_tag, v[7]);
	node = select_rtpp_node(callid, 1);
	if(!node) {
		LM_ERR("no available proxies\n");
		return -1;
	}
	set_rtp_inst_pvar(msg, &node->rn_url);

	nitems = 8;
	if(msg->first_line.type == SIP_REPLY) {
		if(to_tag.len == 0)
			return -1;
		STR2IOVEC(to_tag, v[5]);
		STR2IOVEC(from_tag, v[7]);
	} else {
		STR2IOVEC(from_tag, v[5]);
		STR2IOVEC(to_tag, v[7]);
		if(to_tag.len <= 0)
			nitems = 6;
	}
	send_rtpp_command(node, v, nitems);

	return 1;
}

static int start_recording_f(struct sip_msg *msg, char *foo, char *bar)
{
	return ki_start_recording(msg);
}

/*
 * Returns the current RTP-Statistics from the RTP-Proxy
 */
static int pv_get_rtppstat_f(
		struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
{
	str ret_val = {0, 0};
	int nitems;
	str callid = {0, 0};
	str from_tag = {0, 0};
	str to_tag = {0, 0};
	struct rtpp_node *node;
	struct iovec v[1 + 4 + 3 + 1] = {{NULL, 0}, {"Q", 1}, {" ", 1}, {NULL, 0},
			{" ", 1}, {NULL, 0}, {";1 ", 3},
			{
					";1",
			},
			{NULL, 0}};

	if(get_callid(msg, &callid) == -1 || callid.len == 0) {
		LM_ERR("can't get Call-Id field\n");
		return pv_get_null(msg, param, res);
	}
	if(get_to_tag(msg, &to_tag) == -1) {
		LM_ERR("can't get To tag\n");
		return pv_get_null(msg, param, res);
	}
	if(get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LM_ERR("can't get From tag\n");
		return pv_get_null(msg, param, res);
	}
	if(msg->id != current_msg_id) {
		selected_rtpp_set = default_rtpp_set;
	}

	STR2IOVEC(callid, v[3]);
	STR2IOVEC(from_tag, v[5]);
	STR2IOVEC(to_tag, v[7]);
	node = select_rtpp_node(callid, 1);
	if(!node) {
		LM_ERR("no available proxies\n");
		return -1;
	}
	set_rtp_inst_pvar(msg, &node->rn_url);
	nitems = 8;
	if(msg->first_line.type == SIP_REPLY) {
		if(to_tag.len == 0)
			return -1;
		STR2IOVEC(to_tag, v[5]);
		STR2IOVEC(from_tag, v[7]);
	} else {
		STR2IOVEC(from_tag, v[5]);
		STR2IOVEC(to_tag, v[7]);
		if(to_tag.len <= 0)
			nitems = 6;
	}
	ret_val.s = send_rtpp_command(node, v, nitems);
	if(ret_val.s == NULL)
		return pv_get_null(msg, param, res);
	ret_val.len = strlen(ret_val.s);
	return pv_get_strval(msg, param, res, &ret_val);
}

int set_rtp_inst_pvar(struct sip_msg *msg, const str *const uri)
{
	pv_value_t val;

	if(rtp_inst_pvar == NULL)
		return 0;

	memset(&val, 0, sizeof(pv_value_t));
	val.flags = PV_VAL_STR;
	val.rs = *uri;

	if(rtp_inst_pvar->setf(msg, &rtp_inst_pvar->pvp, (int)EQ_T, &val) < 0) {
		LM_ERR("Failed to add RTPProxy URI to pvar\n");
		return -1;
	}
	return 0;
}

/**
 *
 */
static int ki_rtpproxy_manage0(sip_msg_t *msg)
{
	return rtpproxy_manage(msg, NULL, NULL);
}

/**
 *
 */
static int ki_rtpproxy_manage(sip_msg_t *msg, str *flags)
{
	return rtpproxy_manage(
			msg, ((flags && flags->len > 0) ? flags->s : NULL), NULL);
}

/**
 *
 */
static int ki_rtpproxy_manage_ip(sip_msg_t *msg, str *flags, str *mip)
{
	/* str->s from kemi params is zero-terminated */
	return rtpproxy_manage(msg, ((flags && flags->len > 0) ? flags->s : NULL),
			((mip && mip->len > 0) ? mip->s : NULL));
}

static int ki_set_rtp_proxy_set(sip_msg_t *msg, int rset)
{
	selected_rtpp_set = select_rtpp_set(rset);
	if(selected_rtpp_set == NULL) {
		LM_ERR("could not locate rtpproxy set %d\n", rset);
		return -1;
	}
	current_msg_id = msg->id;
	return 1;
}

static int ki_rtpproxy_destroy0(sip_msg_t *msg)
{
	return unforce_rtp_proxy(msg, NULL);
}

static int ki_rtpproxy_destroy(sip_msg_t *msg, str *flags)
{
	if(flags == NULL || flags->len <= 0)
		return unforce_rtp_proxy(msg, NULL);
	return unforce_rtp_proxy(msg, flags->s);
}

static int ki_rtpproxy_offer0(sip_msg_t *msg)
{
	return rtpproxy_offer1_helper_f(msg, NULL);
}

static int ki_rtpproxy_offer(sip_msg_t *msg, str *flags)
{
	if(flags == NULL || flags->len <= 0)
		return rtpproxy_offer1_helper_f(msg, NULL);
	return rtpproxy_offer1_helper_f(msg, flags->s);
}

static int ki_rtpproxy_offer_ip(sip_msg_t *msg, str *flags, str *mip)
{
	return force_rtp_proxy(msg, flags->s, mip->s, 1, 1);
}

static int ki_rtpproxy_answer0(sip_msg_t *msg)
{
	return rtpproxy_answer1_helper_f(msg, NULL);
}

static int ki_rtpproxy_answer(sip_msg_t *msg, str *flags)
{
	if(flags == NULL || flags->len <= 0)
		return rtpproxy_answer1_helper_f(msg, NULL);
	return rtpproxy_answer1_helper_f(msg, flags->s);
}

static int ki_rtpproxy_answer_ip(sip_msg_t *msg, str *flags, str *mip)
{
	return force_rtp_proxy(msg, flags->s, mip->s, 0, 1);
}

static int ki_rtpproxy_stream2uac(sip_msg_t *msg, str *pname, int count)
{
	return rtpproxy_stream(msg, pname, count, 1);
}

static int ki_rtpproxy_stream2uas(sip_msg_t *msg, str *pname, int count)
{
	return rtpproxy_stream(msg, pname, count, 0);
}

static int ki_rtpproxy_stop_stream2uac(sip_msg_t *msg)
{
	return rtpproxy_stop_stream(msg, 1);
}

static int ki_rtpproxy_stop_stream2uas(sip_msg_t *msg)
{
	return rtpproxy_stop_stream(msg, 0);
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_rtpproxy_exports[] = {
	{ str_init("rtpproxy"), str_init("rtpproxy_manage0"),
		SR_KEMIP_INT, ki_rtpproxy_manage0,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_manage"),
		SR_KEMIP_INT, ki_rtpproxy_manage,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_manage_ip"),
		SR_KEMIP_INT, ki_rtpproxy_manage_ip,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("set_rtpproxy_set"),
		SR_KEMIP_INT, ki_set_rtp_proxy_set,
		{ SR_KEMIP_INT, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_destroy0"),
		SR_KEMIP_INT, ki_rtpproxy_destroy0,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_destroy"),
		SR_KEMIP_INT, ki_rtpproxy_destroy,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("start_recording"),
		SR_KEMIP_INT, ki_start_recording,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_offer0"),
		SR_KEMIP_INT, ki_rtpproxy_offer0,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_offer"),
		SR_KEMIP_INT, ki_rtpproxy_offer,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_offer_ip"),
		SR_KEMIP_INT, ki_rtpproxy_offer_ip,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_answer0"),
		SR_KEMIP_INT, ki_rtpproxy_answer0,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_answer"),
		SR_KEMIP_INT, ki_rtpproxy_answer,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_answer_ip"),
		SR_KEMIP_INT, ki_rtpproxy_answer_ip,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_stream2uac"),
		SR_KEMIP_INT, ki_rtpproxy_stream2uac,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_stream2uas"),
		SR_KEMIP_INT, ki_rtpproxy_stream2uas,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_stop_stream2uac"),
		SR_KEMIP_INT, ki_rtpproxy_stop_stream2uac,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rtpproxy"), str_init("rtpproxy_stop_stream2uas"),
		SR_KEMIP_INT, ki_rtpproxy_stop_stream2uas,
		{ SR_KEMIP_NONE, 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_rtpproxy_exports);
	return 0;
}
