mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1429 lines
42 KiB
1429 lines
42 KiB
/* $Id$
|
|
*
|
|
* Copyright (C) 2007 Tomas Mandys
|
|
*
|
|
* This file is part of ser, a free SIP server.
|
|
*
|
|
* ser 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
|
|
*
|
|
* For a license to use the ser software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* ser 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include "../../sr_module.h"
|
|
#include "../../dprint.h"
|
|
#include "../../data_lump.h"
|
|
#include "../../data_lump_rpl.h"
|
|
#include "../../error.h"
|
|
#include "../../forward.h"
|
|
#include "../../mem/mem.h"
|
|
#include "../../parser/parse_content.h"
|
|
#include "../../parser/parse_uri.h"
|
|
#include "../../parser/parser_f.h"
|
|
#include "../../parser/parse_body.h"
|
|
#include "../../resolve.h"
|
|
#include "../../trim.h"
|
|
#include "../../ut.h"
|
|
#include "../../msg_translator.h"
|
|
#include "../../socket_info.h"
|
|
#include "../../select.h"
|
|
#include "../../select_buf.h"
|
|
#include "../../script_cb.h"
|
|
#include "../../cfg_parser.h"
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
#include <netinet/in.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 <linux/netfilter/xt_RTPPROXY.h>
|
|
#include <arpa/inet.h>
|
|
|
|
MODULE_VERSION
|
|
|
|
#define MODULE_NAME "iptrtpproxy"
|
|
|
|
/* max.number of RTP streams per session */
|
|
#define MAX_MEDIA_NUMBER 20
|
|
#define MAX_SWITCHBOARD_NAME_LEN 20
|
|
|
|
struct switchboard_item {
|
|
str name;
|
|
int ringing_timeout;
|
|
struct xt_rtpproxy_sockopt_in_switchboard in_switchboard;
|
|
struct xt_rtpproxy_sockopt_in_alloc_session in_session;
|
|
unsigned int param_ids;
|
|
|
|
struct switchboard_item* next;
|
|
};
|
|
|
|
static char* global_session_ids;
|
|
static str sdp_ip;
|
|
static struct xt_rtpproxy_handle handle = {.sockfd = 0};
|
|
static struct switchboard_item* switchboards = NULL;
|
|
static struct switchboard_item* found_switchboard;
|
|
static int found_direction;
|
|
static int switchboard_count = 0;
|
|
static str iptrtpproxy_cfg_filename = STR_STATIC_INIT("/etc/iptrtpproxy.cfg");
|
|
static int iptrtpproxy_cfg_flag = 0;
|
|
|
|
|
|
static struct switchboard_item* find_switchboard(str *name) {
|
|
struct switchboard_item* p;
|
|
for (p = switchboards; p; p=p->next) {
|
|
if (name->len == p->name.len && strncasecmp(p->name.s, name->s, name->len)==0) break;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/** if succesfull allocated sessions available @rtpproxy.session_ids
|
|
*/
|
|
|
|
static int rtpproxy_alloc_fixup(void** param, int param_no) {
|
|
switch (param_no) {
|
|
case 1:
|
|
return fixup_var_int_12(param, param_no);
|
|
case 2:
|
|
return fixup_var_str_12(param, param_no);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int rtpproxy_update_fixup(void** param, int param_no) {
|
|
switch (param_no) {
|
|
case 1:
|
|
return rtpproxy_alloc_fixup(param, param_no);
|
|
case 2:
|
|
return fixup_var_str_12(param, param_no);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int rtpproxy_delete_fixup(void** param, int param_no) {
|
|
return rtpproxy_update_fixup(param, 2);
|
|
}
|
|
|
|
static int rtpproxy_find_fixup(void** param, int param_no) {
|
|
return fixup_var_str_12(param, param_no);
|
|
}
|
|
|
|
struct sdp_session {
|
|
unsigned int media_count;
|
|
struct {
|
|
int active;
|
|
unsigned short port;
|
|
unsigned int ip;
|
|
str ip_s;
|
|
str port_s;
|
|
} media[MAX_MEDIA_NUMBER];
|
|
};
|
|
|
|
struct ipt_session {
|
|
struct switchboard_item *switchboard;
|
|
unsigned int stream_count;
|
|
struct {
|
|
int sess_id;
|
|
int created;
|
|
unsigned short proxy_port;
|
|
} streams[MAX_MEDIA_NUMBER];
|
|
};
|
|
|
|
static unsigned int s2ip4(str *s) {
|
|
struct in_addr res;
|
|
char c2;
|
|
c2 = s->s[s->len];
|
|
s->s[s->len] = '\0';
|
|
if (!inet_aton(s->s, &res)) {
|
|
s->s[s->len] = c2;
|
|
return 0;
|
|
}
|
|
s->s[s->len] = c2;
|
|
return res.s_addr;
|
|
}
|
|
|
|
static void ip42s(unsigned int ip, str *s) {
|
|
struct in_addr ip2 = { ip };
|
|
s->s = inet_ntoa(ip2);
|
|
s->len = strlen(s->s);
|
|
}
|
|
|
|
#define is_alpha(_c) (((_c) >= 'a' && (_c) <= 'z') || ((_c) >= 'A' && (_c) <= 'Z') || ((_c) >= '0' && (_c) <= '9') || ((_c) == '_') || ((_c) == '-'))
|
|
|
|
inline static int next_sdp_line(char** p, char* pend, char *ltype, str* line) {
|
|
char *cp;
|
|
while (*p < pend) {
|
|
while (*p < pend && (**p == '\n' || **p == '\r')) (*p)++;
|
|
for (cp = *p; cp < pend && *cp != '\n' && *cp != '\r'; cp++);
|
|
|
|
if (cp-*p > 2 && (*p)[1] == '=') {
|
|
*ltype = **p;
|
|
line->s = (*p)+2;
|
|
line->len = cp-line->s;
|
|
*p = cp;
|
|
return 0;
|
|
}
|
|
*p = cp;
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
/* SDP RFC2327 */
|
|
static int parse_sdp_content(struct sip_msg* msg, struct sdp_session *sess) {
|
|
char *p, *pend, *cp, *cp2, *lend;
|
|
str line, cline_ip_s, body;
|
|
int sess_fl, i, cline_count;
|
|
char ltype, savec;
|
|
unsigned int cline_ip;
|
|
|
|
static str supported_media_types[] = {
|
|
STR_STATIC_INIT("rtp/avp"),
|
|
STR_STATIC_INIT("rtp/savp"),
|
|
STR_STATIC_INIT("rtp/avpf"),
|
|
STR_STATIC_INIT("rtp/savpf"),
|
|
STR_STATIC_INIT("udp"),
|
|
STR_STATIC_INIT("udptl"),
|
|
STR_NULL
|
|
};
|
|
memset(sess, 0, sizeof(*sess));
|
|
|
|
|
|
/* try to get the body part with application/sdp */
|
|
body.s = get_body_part(msg, TYPE_APPLICATION, SUBTYPE_SDP, &body.len);
|
|
if (!body.s) {
|
|
ERR(MODULE_NAME": parse_sdp_content: failed to get the application/sdp body\n");
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
body.s = get_body(msg);
|
|
if (body.s==0) {
|
|
ERR(MODULE_NAME": parse_sdp_content: failed to get the message body\n");
|
|
return -1;
|
|
}
|
|
body.len = msg->len -(int)(body.s - msg->buf);
|
|
if (body.len==0) {
|
|
ERR(MODULE_NAME": parse_sdp_content: message body has length zero\n");
|
|
return -1;
|
|
}
|
|
|
|
/* no need for parse_headers(msg, EOH), get_body will parse everything */
|
|
if (!msg->content_type)
|
|
{
|
|
WARN(MODULE_NAME": parse_sdp_content: Content-TYPE header absent!"
|
|
"let's assume the content is text/plain\n");
|
|
}
|
|
else {
|
|
trim_len(line.len, line.s, msg->content_type->body);
|
|
if (line.len != sizeof("application/sdp")-1 || strncasecmp(line.s, "application/sdp", line.len) != 0) {
|
|
ERR(MODULE_NAME": parse_sdp_content: bad content type '%.*s'\n", line.len, line.s);
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
* 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.
|
|
* There are sendonly,recvonly modifiers which signalize one-way
|
|
* streaming, it probably won't work but it's handled the same way,
|
|
* RTCP commands are still bi-directional. "Inactive" modifier
|
|
* is not handled anyway. See RFC3264
|
|
*/
|
|
|
|
p = body.s;
|
|
pend = body.s + body.len;
|
|
sess_fl = 0;
|
|
sess->media_count = 0;
|
|
cline_ip_s.s = NULL; /* make gcc happy */
|
|
cline_ip_s.len = 0;
|
|
cline_ip = 0;
|
|
cline_count = 0;
|
|
while (p < pend) {
|
|
if (next_sdp_line(&p, pend, <ype, &line) < 0) break;
|
|
switch (ltype) {
|
|
case 'v':
|
|
/* Protocol Version: v=0 */
|
|
if (sess_fl != 0) {
|
|
ERR(MODULE_NAME": parse_sdp_content: only one session allowed\n"); /* RFC3264 */
|
|
return -1;
|
|
}
|
|
sess_fl = 1;
|
|
break;
|
|
case 'c':
|
|
/* Connection Data: c=<network type> <address type> <connection address>, ex. c=IN IP4 224.2.17.12/127 */
|
|
switch (sess_fl) {
|
|
case 0:
|
|
ERR(MODULE_NAME": parse_sdp_content: c= line is not in session section\n");
|
|
return -1;
|
|
case 1:
|
|
case 2:
|
|
cline_count++;
|
|
if (cline_count > 1) {
|
|
/* multicast not supported */
|
|
if (sess_fl == 2) {
|
|
goto invalidate;
|
|
}
|
|
else {
|
|
cline_ip_s.len = 0;
|
|
}
|
|
break;
|
|
}
|
|
lend = line.s + line.len;
|
|
cp = eat_token_end(line.s, lend);
|
|
if (cp-line.s != 2 || memcmp(line.s, "IN", 2) != 0) {
|
|
goto invalidate;
|
|
}
|
|
cp = eat_space_end(cp, lend);
|
|
line.s = cp;
|
|
cp = eat_token_end(cp, lend);
|
|
if (cp-line.s != 3 || memcmp(line.s, "IP4", 3) != 0) {
|
|
goto invalidate;
|
|
}
|
|
cp = eat_space_end(cp, lend);
|
|
line.s = cp;
|
|
cp = eat_token_end(cp, lend);
|
|
line.len = cp-line.s;
|
|
if (line.len == 0 || q_memchr(line.s, '/', line.len)) {
|
|
/* multicast address not supported */
|
|
goto invalidate;
|
|
}
|
|
if (sess_fl == 1) {
|
|
cline_ip_s = line;
|
|
cline_ip = s2ip4(&line);
|
|
}
|
|
else {
|
|
sess->media[sess->media_count-1].ip = s2ip4(&line);
|
|
sess->media[sess->media_count-1].active = 1; /* IP may by specified by hostname */
|
|
sess->media[sess->media_count-1].ip_s = line;
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
break;
|
|
invalidate:
|
|
if (sess_fl == 2) {
|
|
sess->media[sess->media_count-1].active = 0;
|
|
}
|
|
break;
|
|
case 'm':
|
|
/* Media Announcements: m=<media> <port>[/<number of ports>] <transport> <fmt list>, eg. m=audio 49170 RTP/AVP 0 */
|
|
switch (sess_fl) {
|
|
case 0:
|
|
ERR(MODULE_NAME": parse_sdp_content: m= line is not in session section\n");
|
|
return -1;
|
|
case 1:
|
|
case 2:
|
|
if (sess->media_count >= MAX_MEDIA_NUMBER) {
|
|
ERR(MODULE_NAME": parse_sdp_content: max.number of medias (%d) exceeded\n", MAX_MEDIA_NUMBER);
|
|
return -1;
|
|
}
|
|
cline_count = 0;
|
|
sess_fl = 2;
|
|
sess->media_count++;
|
|
sess->media[sess->media_count-1].active = 0;
|
|
lend = line.s + line.len;
|
|
cp = eat_token_end(line.s, lend);
|
|
if (cp-line.s == 0) {
|
|
break;
|
|
}
|
|
cp = eat_space_end(cp, lend);
|
|
line.s = cp;
|
|
cp = eat_token_end(cp, lend);
|
|
line.len = cp-line.s;
|
|
|
|
cp2 = q_memchr(line.s, '/', line.len);
|
|
if (cp2) {
|
|
/* strip optional number of ports, if present should be 2 */
|
|
line.len = cp2-line.s;
|
|
}
|
|
sess->media[sess->media_count-1].port_s = line;
|
|
if (line.len == 0) { /* invalid port? */
|
|
break;
|
|
}
|
|
savec = line.s[line.len];
|
|
line.s[line.len] = '\0';
|
|
sess->media[sess->media_count-1].port = atol(line.s);
|
|
line.s[line.len] = savec;
|
|
if (sess->media[sess->media_count-1].port == 0) {
|
|
break;
|
|
}
|
|
cp = eat_space_end(cp, lend);
|
|
|
|
line.s = cp;
|
|
cp = eat_token_end(cp, lend);
|
|
line.len = cp-line.s;
|
|
for (i = 0; supported_media_types[i].s != NULL; i++) {
|
|
if (line.len == supported_media_types[i].len &&
|
|
strncasecmp(line.s, supported_media_types[i].s, line.len) == 0) {
|
|
sess->media[sess->media_count-1].active = cline_ip_s.len != 0; /* IP may by specified by hostname */
|
|
sess->media[sess->media_count-1].ip_s = cline_ip_s;
|
|
sess->media[sess->media_count-1].ip = cline_ip;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int prepare_lumps(struct sip_msg* msg, str* position, str* s) {
|
|
struct lump* anchor;
|
|
char *buf;
|
|
|
|
//ERR("'%.*s' --> '%.*s'\n", position->len, position->s, s->len, s->s);
|
|
anchor = del_lump(msg, position->s - msg->buf, position->len, 0);
|
|
if (anchor == NULL) {
|
|
ERR(MODULE_NAME": prepare_lumps: del_lump failed\n");
|
|
return -1;
|
|
}
|
|
buf = pkg_malloc(s->len);
|
|
if (buf == NULL) {
|
|
ERR(MODULE_NAME": prepare_lumps: out of memory\n");
|
|
return -1;
|
|
}
|
|
memcpy(buf, s->s, s->len);
|
|
if (insert_new_lump_after(anchor, buf, s->len, 0) == 0) {
|
|
ERR(MODULE_NAME": prepare_lumps: insert_new_lump_after failed\n");
|
|
pkg_free(buf);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int update_sdp_content(struct sip_msg* msg, int gate_a_to_b, struct sdp_session *sdp_sess, struct ipt_session *ipt_sess) {
|
|
int i, j;
|
|
str s;
|
|
/* we must apply lumps for relevant c= and m= lines */
|
|
sdp_ip.len = 0;
|
|
for (i=0; i<sdp_sess->media_count; i++) {
|
|
if (sdp_sess->media[i].active) {
|
|
for (j=0; j<i; j++) {
|
|
if (sdp_sess->media[j].active && sdp_sess->media[i].ip_s.s == sdp_sess->media[j].ip_s.s) {
|
|
goto cline_fixed;
|
|
}
|
|
}
|
|
if (sdp_ip.len == 0) {
|
|
/* takes 1st ip to be rewritten, for aux purposes only */
|
|
sdp_ip = sdp_sess->media[i].ip_s;
|
|
}
|
|
/* apply lump for ip address in c= line */
|
|
ip42s(ipt_sess->switchboard->in_switchboard.gate[!gate_a_to_b].ip, &s);
|
|
if (prepare_lumps(msg, &sdp_sess->media[i].ip_s, &s) < 0)
|
|
return -1;
|
|
cline_fixed:
|
|
/* apply lump for port in m= line */
|
|
s.s = int2str(ipt_sess->streams[i].proxy_port, &s.len);
|
|
if (prepare_lumps(msg, &sdp_sess->media[i].port_s, &s) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* null terminated result is allocated at static buffer */
|
|
static void serialize_ipt_session(struct ipt_session* sess, str* session_ids) {
|
|
static char buf[MAX_SWITCHBOARD_NAME_LEN+1+(5+1+1+10)*MAX_MEDIA_NUMBER+1];
|
|
char *p;
|
|
int i;
|
|
buf[0] = '\0';
|
|
p = buf;
|
|
memcpy(p, sess->switchboard->name.s, sess->switchboard->name.len);
|
|
p += sess->switchboard->name.len;
|
|
*p = ':';
|
|
p++;
|
|
for (i=0; i<sess->stream_count; i++) {
|
|
if (sess->streams[i].sess_id >= 0) {
|
|
p += sprintf(p, "%u/%u", sess->streams[i].sess_id, sess->streams[i].created);
|
|
}
|
|
*p = ',';
|
|
p++;
|
|
}
|
|
p--;
|
|
*p = '\0';
|
|
session_ids->s = buf;
|
|
session_ids->len = p - buf;
|
|
}
|
|
|
|
/* switchboardname [":" [sess_id "/" created] [ * ( "," [sess_id "/" created] )] ] */
|
|
static int unserialize_ipt_session(str* session_ids, struct ipt_session* sess) {
|
|
char *p, *pend, savec;
|
|
str s;
|
|
memset(sess, 0, sizeof(*sess));
|
|
p = session_ids->s;
|
|
pend = session_ids->s+session_ids->len;
|
|
s.s = p;
|
|
while (p < pend && is_alpha(*p)) p++;
|
|
s.len = p-s.s;
|
|
sess->switchboard = find_switchboard(&s);
|
|
if (!sess->switchboard) {
|
|
ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', switchboard '%.*s' not found\n", session_ids->len, session_ids->s, s.len, s.s);
|
|
return -1;
|
|
}
|
|
if (p == pend) return 0;
|
|
if (*p != ':') {
|
|
ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', colon expected near '%.*s'\n", session_ids->len, session_ids->s, pend-p, p);
|
|
return -1;
|
|
}
|
|
do {
|
|
if (sess->stream_count >= MAX_MEDIA_NUMBER) {
|
|
ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', max.media number (%d) exceeded\n", session_ids->len, session_ids->s, MAX_MEDIA_NUMBER);
|
|
return -1;
|
|
}
|
|
p++;
|
|
sess->stream_count++;
|
|
sess->streams[sess->stream_count-1].sess_id = -1;
|
|
sess->streams[sess->stream_count-1].created = 0;
|
|
s.s = p;
|
|
while (p < pend && (*p >= '0' && *p <= '9')) p++;
|
|
if (p != pend && *p != '/') {
|
|
ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', '/' expected near '%.*s'\n", session_ids->len, session_ids->s, pend-p, p);
|
|
return -1;
|
|
}
|
|
s.len = p-s.s;
|
|
if (s.len > 0) {
|
|
savec = s.s[s.len];
|
|
s.s[s.len] = '\0';
|
|
sess->streams[sess->stream_count-1].sess_id = atol(s.s);
|
|
s.s[s.len] = savec;
|
|
}
|
|
p++;
|
|
s.s = p;
|
|
while (p < pend && (*p >= '0' && *p <= '9')) p++;
|
|
if (p != pend && *p != ',') {
|
|
sess->streams[sess->stream_count-1].sess_id = -1;
|
|
ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', comma expected near '%.*s'\n", session_ids->len, session_ids->s, pend-p, p);
|
|
return -1;
|
|
}
|
|
s.len = p-s.s;
|
|
if (s.len > 0) {
|
|
savec = s.s[s.len];
|
|
s.s[s.len] = '\0';
|
|
sess->streams[sess->stream_count-1].created = atol(s.s);
|
|
s.s[s.len] = savec;
|
|
}
|
|
} while (p < pend);
|
|
return 0;
|
|
}
|
|
|
|
static void delete_ipt_sessions(struct ipt_session* ipt_sess) {
|
|
struct xt_rtpproxy_sockopt_in_sess_id in_sess_id;
|
|
int i, j;
|
|
for (i=0; i < ipt_sess->stream_count; i++) {
|
|
if (ipt_sess->streams[i].sess_id >= 0) {
|
|
j = i;
|
|
in_sess_id.sess_id_min = ipt_sess->streams[i].sess_id;
|
|
in_sess_id.sess_id_max = in_sess_id.sess_id_min;
|
|
in_sess_id.created = ipt_sess->streams[i].created;
|
|
/* group more sessions if possible */
|
|
for (; i < ipt_sess->stream_count-1; i++) {
|
|
if (ipt_sess->streams[i+1].sess_id >= 0) {
|
|
if (ipt_sess->streams[i+1].sess_id == in_sess_id.sess_id_max+1) {
|
|
in_sess_id.sess_id_max = ipt_sess->streams[i+1].sess_id;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (xt_RTPPROXY_delete_session(&handle, &ipt_sess->switchboard->in_switchboard, &in_sess_id) < 0) {
|
|
ERR(MODULE_NAME": rtpproxy_delete: xt_RTPPROXY_delete_session error: %s (%d)\n", handle.err_str, handle.err_no);
|
|
/* what to do ? */
|
|
}
|
|
/* invalidate sessions including duplicates */
|
|
for (; j<ipt_sess->stream_count; j++) {
|
|
if (ipt_sess->streams[j].sess_id >= in_sess_id.sess_id_min && ipt_sess->streams[j].sess_id <= in_sess_id.sess_id_max)
|
|
ipt_sess->streams[j].sess_id = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define GATE_FLAG 0x01
|
|
#define RINGING_TIMEOUT_FLAG 0x02
|
|
|
|
/* gate_a_to_b has index 0, gate_b_to_a 1 */
|
|
#define GATE_A_TO_B(flags) (((flags) & GATE_FLAG) == 0)
|
|
|
|
inline static void fill_in_session(int flags, int media_idx, struct sdp_session *sdp_sess, struct ipt_session *ipt_sess, struct xt_rtpproxy_sockopt_in_alloc_session *in_session) {
|
|
int j;
|
|
for (j=0; j<2; j++) {
|
|
in_session->source[GATE_A_TO_B(flags)].stream[j].flags =
|
|
XT_RTPPROXY_SOCKOPT_FLAG_SESSION_ADDR |
|
|
ipt_sess->switchboard->in_session.source[GATE_A_TO_B(flags)].stream[j].flags |
|
|
((flags & RINGING_TIMEOUT_FLAG) ? XT_RTPPROXY_SOCKOPT_FLAG_SESSION_LEARNING_TIMEOUT : 0);
|
|
in_session->source[GATE_A_TO_B(flags)].stream[j].learning_timeout = (flags & RINGING_TIMEOUT_FLAG) ?
|
|
ipt_sess->switchboard->ringing_timeout :
|
|
ipt_sess->switchboard->in_session.source[GATE_A_TO_B(flags)].stream[j].learning_timeout;
|
|
in_session->source[GATE_A_TO_B(flags)].stream[j].addr.ip = sdp_sess->media[media_idx].ip;
|
|
in_session->source[GATE_A_TO_B(flags)].stream[j].addr.port = sdp_sess->media[media_idx].port+j;
|
|
}
|
|
in_session->source[GATE_A_TO_B(flags)].always_learn = ipt_sess->switchboard->in_session.source[GATE_A_TO_B(flags)].always_learn;
|
|
}
|
|
|
|
static int rtpproxy_alloc(struct sip_msg* msg, char* _flags, char* _switchboard_id) {
|
|
int flags;
|
|
struct switchboard_item* si = 0;
|
|
struct sdp_session sdp_sess;
|
|
struct ipt_session ipt_sess;
|
|
struct xt_rtpproxy_sockopt_in_alloc_session in_session;
|
|
struct xt_rtpproxy_session out_session;
|
|
str s;
|
|
int i;
|
|
|
|
if (get_int_fparam(&flags, msg, (fparam_t*) _flags) < 0) {
|
|
return -1;
|
|
}
|
|
if (get_str_fparam(&s, msg, (fparam_t*) _switchboard_id) < 0) {
|
|
return -1;
|
|
}
|
|
if (s.len) {
|
|
/* switchboard must be fully qualified, it simplifies helper because it's not necessary to store full identification to session_ids - name is sufficient */
|
|
si = find_switchboard(&s);
|
|
if (!si) {
|
|
ERR(MODULE_NAME": rtpproxy_alloc: switchboard '%.*s' not found\n", s.len, s.s);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
if (!found_switchboard) {
|
|
ERR(MODULE_NAME": rtpproxy_alloc: no implicit switchboard\n");
|
|
return -1;
|
|
}
|
|
si = found_switchboard;
|
|
}
|
|
if (parse_sdp_content(msg, &sdp_sess) < 0)
|
|
return -1;
|
|
memset(&ipt_sess, 0, sizeof(ipt_sess));
|
|
ipt_sess.switchboard = si;
|
|
memset(&in_session, 0, sizeof(in_session));
|
|
for (i = 0; i < sdp_sess.media_count; i++) {
|
|
ipt_sess.streams[i].sess_id = -1;
|
|
ipt_sess.stream_count = i+1;
|
|
if (sdp_sess.media[i].active) {
|
|
int j;
|
|
for (j = 0; j < i; j++) {
|
|
/* if two media streams have equal source address than we will allocate only one ipt session */
|
|
if (sdp_sess.media[j].active && sdp_sess.media[i].ip == sdp_sess.media[j].ip && sdp_sess.media[i].port == sdp_sess.media[j].port) {
|
|
ipt_sess.streams[i].sess_id = ipt_sess.streams[j].sess_id;
|
|
ipt_sess.streams[i].proxy_port = ipt_sess.streams[j].proxy_port;
|
|
ipt_sess.streams[i].created = ipt_sess.streams[j].created;
|
|
goto cont;
|
|
}
|
|
}
|
|
fill_in_session(flags, i, &sdp_sess, &ipt_sess, &in_session);
|
|
if (xt_RTPPROXY_alloc_session(&handle, &ipt_sess.switchboard->in_switchboard, &in_session, NULL, &out_session) < 0) {
|
|
ERR(MODULE_NAME": rtpproxy_alloc: xt_RTPPROXY_alloc_session error: %s (%d)\n", handle.err_str, handle.err_no);
|
|
delete_ipt_sessions(&ipt_sess);
|
|
return -1;
|
|
}
|
|
ipt_sess.streams[i].sess_id = out_session.sess_id;
|
|
ipt_sess.streams[i].created = out_session.created;
|
|
ipt_sess.streams[i].proxy_port = out_session.gate[!GATE_A_TO_B(flags)].stream[0].port;
|
|
cont: ;
|
|
}
|
|
}
|
|
if (update_sdp_content(msg, GATE_A_TO_B(flags), &sdp_sess, &ipt_sess) < 0) {
|
|
delete_ipt_sessions(&ipt_sess);
|
|
return -1;
|
|
}
|
|
serialize_ipt_session(&ipt_sess, &s);
|
|
global_session_ids = s.s; /* it's static and null terminated */
|
|
return 1;
|
|
}
|
|
|
|
static int rtpproxy_update(struct sip_msg* msg, char* _flags, char* _session_ids) {
|
|
str session_ids;
|
|
int flags, i;
|
|
struct sdp_session sdp_sess;
|
|
struct ipt_session ipt_sess;
|
|
struct xt_rtpproxy_sockopt_in_sess_id in_sess_id;
|
|
struct xt_rtpproxy_sockopt_in_alloc_session in_session;
|
|
|
|
if (get_int_fparam(&flags, msg, (fparam_t*) _flags) < 0) {
|
|
return -1;
|
|
}
|
|
if (get_str_fparam(&session_ids, msg, (fparam_t*) _session_ids) < 0) {
|
|
return -1;
|
|
}
|
|
if (unserialize_ipt_session(&session_ids, &ipt_sess) < 0) {
|
|
return -1;
|
|
}
|
|
if (parse_sdp_content(msg, &sdp_sess) < 0)
|
|
return -1;
|
|
|
|
if (ipt_sess.stream_count != sdp_sess.media_count) {
|
|
ERR(MODULE_NAME": rtpproxy_update: number of m= item in offer (%d) and answer (%d) do not correspond\n", ipt_sess.stream_count, sdp_sess.media_count);
|
|
return -1;
|
|
}
|
|
/* first we check for unexpected duplicate source ports */
|
|
for (i = 0; i < sdp_sess.media_count; i++) {
|
|
if (ipt_sess.streams[i].sess_id >= 0 && sdp_sess.media[i].active) {
|
|
int j;
|
|
for (j = i+1; j < sdp_sess.media_count; j++) {
|
|
if (ipt_sess.streams[j].sess_id >= 0 && sdp_sess.media[j].active) {
|
|
/* if two media streams have equal source address XOR have equal session */
|
|
if ( (sdp_sess.media[i].ip == sdp_sess.media[j].ip && sdp_sess.media[i].port == sdp_sess.media[j].port) ^
|
|
(ipt_sess.streams[i].sess_id == ipt_sess.streams[j].sess_id) ) {
|
|
ERR(MODULE_NAME": rtpproxy_update: media (%d,%d) violation number\n", i, j);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
memset(&in_session, 0, sizeof(in_session));
|
|
for (i = 0; i < sdp_sess.media_count; i++) {
|
|
if (ipt_sess.streams[i].sess_id >= 0) {
|
|
in_sess_id.sess_id_min = ipt_sess.streams[i].sess_id;
|
|
in_sess_id.created = ipt_sess.streams[i].created;
|
|
in_sess_id.sess_id_max = in_sess_id.sess_id_min;
|
|
if (sdp_sess.media[i].active) {
|
|
fill_in_session(flags, i, &sdp_sess, &ipt_sess, &in_session);
|
|
if (xt_RTPPROXY_update_session(&handle, &ipt_sess.switchboard->in_switchboard, &in_sess_id, &in_session) < 0) {
|
|
ERR(MODULE_NAME": rtpproxy_alloc: xt_RTPPROXY_update_session error: %s (%d)\n", handle.err_str, handle.err_no);
|
|
/* delete all sessions ? */
|
|
return -1;
|
|
}
|
|
/* we don't know proxy port - it was known when being allocated so we got from switchboard - it's not too clear solution because it requires knowledge how ports are allocated */
|
|
ipt_sess.streams[i].proxy_port = ipt_sess.switchboard->in_switchboard.gate[!GATE_A_TO_B(flags)].port + 2*ipt_sess.streams[i].sess_id;
|
|
}
|
|
else {
|
|
/* can we delete any session allocated during offer? */
|
|
if (xt_RTPPROXY_delete_session(&handle, &ipt_sess.switchboard->in_switchboard, &in_sess_id) < 0) {
|
|
ERR(MODULE_NAME": rtpproxy_update: xt_RTPPROXY_delete_session error: %s (%d)\n", handle.err_str, handle.err_no);
|
|
}
|
|
ipt_sess.streams[i].sess_id = -1;
|
|
}
|
|
}
|
|
}
|
|
if (update_sdp_content(msg, GATE_A_TO_B(flags), &sdp_sess, &ipt_sess) < 0) {
|
|
/* delete all sessions ? */
|
|
return -1;
|
|
}
|
|
serialize_ipt_session(&ipt_sess, &session_ids);
|
|
global_session_ids = session_ids.s; /* it's static and null terminated */
|
|
return 1;
|
|
}
|
|
|
|
static int rtpproxy_adjust_timeout(struct sip_msg* msg, char* _flags, char* _session_ids) {
|
|
str session_ids;
|
|
int flags, i;
|
|
struct ipt_session ipt_sess;
|
|
struct xt_rtpproxy_sockopt_in_sess_id in_sess_id;
|
|
struct xt_rtpproxy_sockopt_in_alloc_session in_session;
|
|
|
|
if (get_int_fparam(&flags, msg, (fparam_t*) _flags) < 0) {
|
|
return -1;
|
|
}
|
|
if (get_str_fparam(&session_ids, msg, (fparam_t*) _session_ids) < 0) {
|
|
return -1;
|
|
}
|
|
if (unserialize_ipt_session(&session_ids, &ipt_sess) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
memset(&in_session, 0, sizeof(in_session));
|
|
for (i = 0; i < ipt_sess.stream_count; i++) {
|
|
if (ipt_sess.streams[i].sess_id >= 0) {
|
|
int j;
|
|
in_sess_id.sess_id_min = ipt_sess.streams[i].sess_id;
|
|
in_sess_id.created = ipt_sess.streams[i].created;
|
|
in_sess_id.sess_id_max = in_sess_id.sess_id_min;
|
|
|
|
|
|
for (j=0; j<2; j++) {
|
|
|
|
in_session.source[GATE_A_TO_B(flags)].stream[j].flags =
|
|
(flags & RINGING_TIMEOUT_FLAG) ?
|
|
XT_RTPPROXY_SOCKOPT_FLAG_SESSION_LEARNING_TIMEOUT :
|
|
(ipt_sess.switchboard->in_session.source[GATE_A_TO_B(flags)].stream[j].flags & XT_RTPPROXY_SOCKOPT_FLAG_SESSION_LEARNING_TIMEOUT)
|
|
;
|
|
in_session.source[GATE_A_TO_B(flags)].stream[j].learning_timeout = (flags & RINGING_TIMEOUT_FLAG) ?
|
|
ipt_sess.switchboard->ringing_timeout :
|
|
ipt_sess.switchboard->in_session.source[GATE_A_TO_B(flags)].stream[j].learning_timeout;
|
|
|
|
}
|
|
|
|
if (xt_RTPPROXY_update_session(&handle, &ipt_sess.switchboard->in_switchboard, &in_sess_id, &in_session) < 0) {
|
|
ERR(MODULE_NAME": rtpproxy_alloc: xt_RTPPROXY_adjust_timeout error: %s (%d)\n", handle.err_str, handle.err_no);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
/* do not serialize sessions because it affect static buffer and more valuable values disappears */
|
|
return 1;
|
|
}
|
|
|
|
static int rtpproxy_delete(struct sip_msg* msg, char* _session_ids, char* dummy) {
|
|
str session_ids;
|
|
struct ipt_session ipt_sess;
|
|
if (get_str_fparam(&session_ids, msg, (fparam_t*) _session_ids) < 0) {
|
|
return -1;
|
|
}
|
|
if (unserialize_ipt_session(&session_ids, &ipt_sess) < 0) {
|
|
return -1;
|
|
}
|
|
delete_ipt_sessions(&ipt_sess);
|
|
/* do not serialize sessions because it affect static buffer and more valuable values disappears */
|
|
return 1;
|
|
}
|
|
|
|
static int rtpproxy_find(struct sip_msg* msg, char* _gate_a, char* _gate_b) {
|
|
unsigned int ip_a, ip_b;
|
|
str gate_a, gate_b;
|
|
|
|
if (get_str_fparam(&gate_a, msg, (fparam_t*) _gate_a) < 0) {
|
|
return -1;
|
|
}
|
|
ip_a = s2ip4(&gate_a);
|
|
if (get_str_fparam(&gate_b, msg, (fparam_t*) _gate_b) < 0) {
|
|
return -1;
|
|
}
|
|
ip_b = s2ip4(&gate_b);
|
|
|
|
found_direction = -1;
|
|
for (found_switchboard = switchboards; found_switchboard; found_switchboard=found_switchboard->next) {
|
|
if (ip_a == found_switchboard->in_switchboard.gate[0].ip) {
|
|
if (ip_b == found_switchboard->in_switchboard.gate[1].ip) {
|
|
found_direction = 1;
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
else if (ip_a == found_switchboard->in_switchboard.gate[1].ip) {
|
|
if (ip_b == found_switchboard->in_switchboard.gate[0].ip) {
|
|
found_direction = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* @select implementation */
|
|
static int sel_rtpproxy(str* res, select_t* s, struct sip_msg* msg) { /* dummy */
|
|
return 0;
|
|
}
|
|
|
|
static int sel_sdp_ip(str* res, select_t* s, struct sip_msg* msg) {
|
|
*res = sdp_ip;
|
|
return 0;
|
|
}
|
|
|
|
static int sel_session_ids(str* res, select_t* s, struct sip_msg* msg) {
|
|
if (!global_session_ids)
|
|
return 1;
|
|
res->s = global_session_ids;
|
|
res->len = strlen(res->s);
|
|
return 0;
|
|
}
|
|
|
|
static int sel_switchboard(str* res, select_t* s, struct sip_msg* msg) {
|
|
if (!found_switchboard)
|
|
return 1;
|
|
*res = found_switchboard->name;
|
|
return 0;
|
|
}
|
|
|
|
static int sel_direction(str* res, select_t* s, struct sip_msg* msg) {
|
|
static char buf[2] = {'0', '1'};
|
|
if (!found_direction < 0)
|
|
return 1;
|
|
res->s = buf+found_direction;
|
|
res->len = 1;
|
|
return 0;
|
|
}
|
|
|
|
select_row_t sel_declaration[] = {
|
|
{ NULL, SEL_PARAM_STR, STR_STATIC_INIT(MODULE_NAME), sel_rtpproxy, SEL_PARAM_EXPECTED},
|
|
{ sel_rtpproxy, SEL_PARAM_STR, STR_STATIC_INIT("sdp_ip"), sel_sdp_ip, 0 },
|
|
{ sel_rtpproxy, SEL_PARAM_STR, STR_STATIC_INIT("session_ids"), sel_session_ids, 0 },
|
|
{ sel_rtpproxy, SEL_PARAM_STR, STR_STATIC_INIT("switchboard"), sel_switchboard, 0 },
|
|
{ sel_rtpproxy, SEL_PARAM_STR, STR_STATIC_INIT("direction"), sel_direction, 0 },
|
|
|
|
{ NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
|
|
};
|
|
|
|
static int mod_pre_script_cb(struct sip_msg *msg, unsigned int flags, void *param) {
|
|
sdp_ip.s = "";
|
|
sdp_ip.len = 0;
|
|
found_switchboard = NULL;
|
|
found_direction = -1;
|
|
global_session_ids = NULL;
|
|
return 1;
|
|
}
|
|
|
|
static struct {
|
|
int flag;
|
|
struct switchboard_item *si;
|
|
struct xt_rtpproxy_sockopt_in_switchboard in_switchboard;
|
|
} parse_config_vals;
|
|
|
|
|
|
int cfg_parse_addr_port(void* param, cfg_parser_t* st, unsigned int flags) {
|
|
struct xt_rtpproxy_sockopt_in_switchboard *sw;
|
|
int i, ret;
|
|
cfg_token_t t;
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret < 0) return ret;
|
|
if (ret > 0) return 0;
|
|
if (t.type != '-') return 0;
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret < 0) return ret;
|
|
if (ret > 0) return 0;
|
|
if (parse_config_vals.flag == 1) {
|
|
sw = &parse_config_vals.in_switchboard;
|
|
}
|
|
else {
|
|
sw = &parse_config_vals.si->in_switchboard;
|
|
}
|
|
if (t.type == CFG_TOKEN_ALPHA && t.val.len == 1) {
|
|
switch (t.val.s[0]) {
|
|
case 'a':
|
|
case 'b':
|
|
i = t.val.s[0]-'a';
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret < 0) return ret;
|
|
if (ret > 0) return 0;
|
|
if (t.type != '=') break;
|
|
|
|
if (param == NULL) {
|
|
str val;
|
|
char buff[50];
|
|
val.s = buff;
|
|
val.len = sizeof(buff)-1;
|
|
if (cfg_parse_str(&val, st, CFG_STR_STATIC|CFG_EXTENDED_ALPHA) < 0) return -1;
|
|
sw->gate[i].ip = s2ip4(&val);
|
|
if (sw->gate[i].ip == 0) {
|
|
ERR(MODULE_NAME": parse_switchboard_section: bad ip address '%.*s'\n", val.len, val.s);
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
int val;
|
|
if (cfg_parse_int(&val, st, 0) < 0)
|
|
return -1;
|
|
sw->gate[i].port = val;
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cfg_parse_dummy(void* param, cfg_parser_t* st, unsigned int flags) {
|
|
int ret;
|
|
cfg_token_t t;
|
|
str val;
|
|
do {
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret < 0) return ret;
|
|
if (ret > 0) return 0;
|
|
}
|
|
while (t.type != '=');
|
|
if (cfg_parse_str(&val, st, CFG_EXTENDED_ALPHA) < 0) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static cfg_option_t section_options[] = {
|
|
{"addr", .f = cfg_parse_addr_port, .flags = CFG_PREFIX|CFG_CASE_SENSITIVE, .param = NULL},
|
|
{"port", .f = cfg_parse_addr_port, .flags = CFG_PREFIX|CFG_CASE_SENSITIVE, .param = (void*) 1},
|
|
{NULL, .flags = CFG_DEFAULT, .f = cfg_parse_dummy},
|
|
};
|
|
|
|
#define DEFAULT_SECTION "default"
|
|
#define SWITCHBOARD_PREFIX "switchboard"
|
|
|
|
static int parse_switchboard_section(void* param, cfg_parser_t* st, unsigned int flags) {
|
|
str name;
|
|
cfg_token_t t;
|
|
int ret, fl;
|
|
parse_config_vals.flag = 0;
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret != 0) return ret;
|
|
if (t.type != CFG_TOKEN_ALPHA)
|
|
goto skip;
|
|
if (t.val.len == (sizeof(DEFAULT_SECTION)-1) && strncmp(t.val.s, DEFAULT_SECTION, t.val.len) == 0)
|
|
fl = 1;
|
|
else if (t.val.len == (sizeof(SWITCHBOARD_PREFIX)-1) && strncmp(t.val.s, SWITCHBOARD_PREFIX, t.val.len) == 0)
|
|
fl = 2;
|
|
else
|
|
goto skip;
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret != 0) return ret;
|
|
if (t.type != ':')
|
|
goto skip;
|
|
name.s = NULL; name.len = 0;
|
|
ret = cfg_parse_section(&name, st, CFG_STR_PKGMEM);
|
|
if (ret != 0) return ret;
|
|
|
|
if (fl==1 && name.len == (sizeof(SWITCHBOARD_PREFIX)-1) && strncmp(name.s, SWITCHBOARD_PREFIX, name.len) == 0) {
|
|
parse_config_vals.flag = 1;
|
|
if (name.s)
|
|
pkg_free(name.s);
|
|
|
|
}
|
|
else if (fl == 2) {
|
|
int i;
|
|
if (find_switchboard(&name)) {
|
|
ERR(MODULE_NAME": parse_switchboard_section: name '%.*s' already declared\n", name.len, name.s);
|
|
return -1;
|
|
}
|
|
for (i=0; i<name.len; i++) {
|
|
if (!is_alpha(name.s[i])) {
|
|
ERR(MODULE_NAME": parse_switchboard_section: bad section name '%.*s'\n", name.len, name.s);
|
|
return -1;
|
|
}
|
|
}
|
|
if (name.len > MAX_SWITCHBOARD_NAME_LEN) {
|
|
ERR(MODULE_NAME": parse_switchboard_section: name '%.*s' is too long (%d>%d)\n", name.len, name.s, name.len, MAX_SWITCHBOARD_NAME_LEN);
|
|
return -1;
|
|
}
|
|
|
|
parse_config_vals.si = pkg_malloc(sizeof(*parse_config_vals.si));
|
|
if (!parse_config_vals.si) {
|
|
ERR(MODULE_NAME": parse_switchboard_section: not enough pkg memory\n");
|
|
return -1;
|
|
}
|
|
memset(parse_config_vals.si, 0, sizeof(*parse_config_vals.si));
|
|
parse_config_vals.si->name = name;
|
|
parse_config_vals.si->ringing_timeout = 60;
|
|
parse_config_vals.si->next = switchboards;
|
|
switchboards = parse_config_vals.si;
|
|
|
|
parse_config_vals.flag = 2;
|
|
}
|
|
return 0;
|
|
skip:
|
|
while (t.type != ']') {
|
|
ret = cfg_get_token(&t, st, 0);
|
|
if (ret != 0) return ret;
|
|
}
|
|
return cfg_eat_eol(st, 0);
|
|
}
|
|
|
|
static int parse_iptrtpproxy_cfg() {
|
|
cfg_parser_t* parser = NULL;
|
|
struct switchboard_item *si;
|
|
if ((parser = cfg_parser_init(0, &iptrtpproxy_cfg_filename)) == NULL) {
|
|
ERR(MODULE_NAME"parse_iptrtpproxy_cfg: Error while initializing configuration file parser.\n");
|
|
return -1;
|
|
}
|
|
cfg_section_parser(parser, parse_switchboard_section, NULL);
|
|
cfg_set_options(parser, section_options);
|
|
memset(&parse_config_vals, 0, sizeof(parse_config_vals));
|
|
if (sr_cfg_parse(parser)) {
|
|
return -1;
|
|
}
|
|
cfg_parser_close(parser);
|
|
|
|
for (si = switchboards; si; si = si->next) {
|
|
int i;
|
|
for (i=0; i<2; i++) {
|
|
if (!si->in_switchboard.gate[i].ip)
|
|
si->in_switchboard.gate[i].ip = parse_config_vals.in_switchboard.gate[i].ip;
|
|
if (!si->in_switchboard.gate[i].port)
|
|
si->in_switchboard.gate[i].port = parse_config_vals.in_switchboard.gate[i].port;
|
|
}
|
|
for (i=0; i<2; i++) {
|
|
if (!si->in_switchboard.gate[i^1].ip)
|
|
si->in_switchboard.gate[i^1].ip = si->in_switchboard.gate[i].ip;
|
|
if (!si->in_switchboard.gate[i^1].port)
|
|
si->in_switchboard.gate[i^1].port = si->in_switchboard.gate[i].port;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* module initialization */
|
|
static int mod_init(void) {
|
|
struct switchboard_item *si;
|
|
int i;
|
|
if (iptrtpproxy_cfg_flag == 0) {
|
|
if (parse_iptrtpproxy_cfg() < 0)
|
|
return E_CFG;
|
|
}
|
|
|
|
for (si = switchboards; si; si=si->next) {
|
|
str ips[2];
|
|
char buf[17];
|
|
ip42s(si->in_switchboard.gate[0].ip, ips+0);
|
|
strncpy(buf, ips[0].s, sizeof(buf)-1);
|
|
ips[0].s = buf;
|
|
ip42s(si->in_switchboard.gate[1].ip, ips+1);
|
|
|
|
DBG(MODULE_NAME": mod_init: name=%.*s;addr-a=%.*s;port-a=%d;addr-b=%.*s;port-b=%d;learning-timeout-a=%d;learning-timeout-b=%d;always-learn-a=%d;always-learn-b=%d;ringing-timeout=%d\n",
|
|
STR_FMT(&si->name),
|
|
STR_FMT(ips+0),
|
|
si->in_switchboard.gate[0].port,
|
|
STR_FMT(ips+1),
|
|
si->in_switchboard.gate[1].port,
|
|
si->in_session.source[0].stream[0].learning_timeout,
|
|
si->in_session.source[1].stream[0].learning_timeout,
|
|
si->in_session.source[0].always_learn,
|
|
si->in_session.source[1].always_learn,
|
|
si->ringing_timeout
|
|
);
|
|
}
|
|
if (xt_RTPPROXY_open(&handle) < 0) goto err;
|
|
for (si = switchboards; si; si=si->next) {
|
|
struct xt_rtpproxy_switchboard *out_switchboard;
|
|
if (xt_RTPPROXY_get_switchboards(&handle, &si->in_switchboard, NULL, XT_RTPPROXY_SOCKOPT_FLAG_OUT_SWITCHBOARD, &out_switchboard) < 0) {
|
|
goto err;
|
|
}
|
|
/* update switchboard info, we need real ports for rtpproxy_update, it may sometimes differ from in_switchboard when addr-a=addr-b. We'll take first switchboard returned, should be always only one */
|
|
if (!out_switchboard) {
|
|
ERR(MODULE_NAME": switchboard '%.*s' not found in iptables\n", si->name.len, si->name.s);
|
|
goto err2;
|
|
}
|
|
if (si->in_switchboard.gate[0].ip == si->in_switchboard.gate[1].ip) {
|
|
for (i=0; i<XT_RTPPROXY_MAX_GATE; i++) {
|
|
si->in_switchboard.gate[i].port = out_switchboard->so.gate[i].addr.port;
|
|
}
|
|
}
|
|
xt_RTPPROXY_release_switchboards(&handle, out_switchboard);
|
|
}
|
|
|
|
register_script_cb(mod_pre_script_cb, REQUEST_CB | ONREPLY_CB | PRE_SCRIPT_CB, 0);
|
|
register_select_table(sel_declaration);
|
|
return 0;
|
|
err:
|
|
ERR(MODULE_NAME": %s (%d)\n", handle.err_str, handle.err_no);
|
|
err2:
|
|
if (handle.sockfd >= 0) {
|
|
xt_RTPPROXY_close(&handle);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void mod_cleanup(void) {
|
|
if (handle.sockfd >= 0) {
|
|
xt_RTPPROXY_close(&handle);
|
|
}
|
|
}
|
|
|
|
static int child_init(int rank) {
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#define eat_spaces(_p) \
|
|
while( *(_p)==' ' || *(_p)=='\t' ){\
|
|
(_p)++;}
|
|
|
|
|
|
static int declare_config(modparam_t type, void* val) {
|
|
if (!val) return 0;
|
|
if (iptrtpproxy_cfg_flag == 0) {
|
|
|
|
iptrtpproxy_cfg_flag = 1;
|
|
iptrtpproxy_cfg_filename = * (str*) val;
|
|
if (parse_iptrtpproxy_cfg() == 0)
|
|
return 0;
|
|
}
|
|
else {
|
|
switch (iptrtpproxy_cfg_flag) {
|
|
case 1:
|
|
ERR(MODULE_NAME": declare_config: config param may be used only once\n");
|
|
break;
|
|
case 2:
|
|
ERR(MODULE_NAME": declare_config: config param may not be used after 'switchboard'\n");
|
|
break;
|
|
default:
|
|
BUG(MODULE_NAME": declare_config: unexpected 'iptrtpproxy_cfg_filename' value %d\n", iptrtpproxy_cfg_flag);
|
|
}
|
|
}
|
|
return E_CFG;
|
|
}
|
|
|
|
static int declare_switchboard_param(modparam_t type, void* val) {
|
|
|
|
char *s, *c;
|
|
int i, all_flag;
|
|
struct switchboard_item *si = NULL;
|
|
enum param_id {
|
|
par_GateB = 8,
|
|
par_Name = 0x000001,
|
|
par_RingingTimeout = 0x000002,
|
|
par_AlwaysLearn = 0x000400,
|
|
par_LearningTimeout = 0x000800
|
|
};
|
|
#define IS_GATE_B(id) ((id & 0xFF0000)!=0)
|
|
static struct {
|
|
char *name;
|
|
unsigned int id;
|
|
} params[] = {
|
|
{.name = "name", .id = par_Name},
|
|
{.name = "always-learn-a", .id = par_AlwaysLearn},
|
|
{.name = "always-learn-b", .id = par_AlwaysLearn << par_GateB},
|
|
{.name = "learning-timeout-a", .id = par_LearningTimeout},
|
|
{.name = "learning-timeout-b", .id = par_LearningTimeout << par_GateB},
|
|
{.name = "ringing-timeout", .id = par_RingingTimeout},
|
|
|
|
{.name = 0, .id = 0}
|
|
};
|
|
|
|
if (!val) return 0;
|
|
if (iptrtpproxy_cfg_flag == 0) {
|
|
iptrtpproxy_cfg_flag = 2;
|
|
if (parse_iptrtpproxy_cfg() < 0)
|
|
return E_CFG;
|
|
}
|
|
|
|
s = val;
|
|
all_flag = -1;
|
|
|
|
eat_spaces(s);
|
|
if (!*s) return 0;
|
|
/* parse param: name=;addr-a=;addr-b=;port-a=;port-b=; */
|
|
while (*s) {
|
|
str p, val;
|
|
unsigned int id;
|
|
|
|
c = s;
|
|
while ( is_alpha(*c) ) {
|
|
c++;
|
|
}
|
|
if (c == s) {
|
|
ERR(MODULE_NAME": declare_switchboard_param: param name expected near '%s'\n", s);
|
|
goto err_E_CFG;
|
|
}
|
|
p.s = s;
|
|
p.len = c-s;
|
|
eat_spaces(c);
|
|
s = c;
|
|
if (*c != '=') {
|
|
ERR(MODULE_NAME": declare_switchboard_param: equal char expected near '%s'\n", s);
|
|
goto err_E_CFG;
|
|
}
|
|
c++;
|
|
eat_spaces(c);
|
|
s = c;
|
|
while (*c && *c != ';') c++;
|
|
val.s = s;
|
|
val.len = c-s;
|
|
while (val.len > 0 && val.s[val.len-1]<=' ') val.len--;
|
|
if (*c) c++;
|
|
eat_spaces(c);
|
|
|
|
id = 0;
|
|
for (i=0; params[i].id; i++) {
|
|
if (strlen(params[i].name)==p.len && strncasecmp(params[i].name, p.s, p.len) == 0) {
|
|
id = params[i].id;
|
|
break;
|
|
}
|
|
}
|
|
if (!id) {
|
|
ERR(MODULE_NAME": declare_switchboard_param: unknown param name '%.*s'\n", p.len, p.s);
|
|
goto err_E_CFG;
|
|
}
|
|
if (all_flag >= 0 && id == par_Name) {
|
|
ERR(MODULE_NAME": declare_switchboard_param: name must be the first param\n");
|
|
goto err_E_CFG;
|
|
}
|
|
if (id == par_Name) {
|
|
all_flag = 0;
|
|
si = find_switchboard(&val);
|
|
if (!si) {
|
|
if (val.len == 1 && val.s[0] == '*')
|
|
all_flag = 1;
|
|
else {
|
|
ERR(MODULE_NAME": declare_switchboard_param: switchboard '%.*s' not found\n", val.len, val.s);
|
|
goto err_E_CFG;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (all_flag)
|
|
si = switchboards;
|
|
while (si) {
|
|
|
|
switch (id) {
|
|
case par_Name:
|
|
break;
|
|
case par_AlwaysLearn:
|
|
case par_AlwaysLearn << par_GateB: {
|
|
unsigned int u;
|
|
if (str2int(&val, &u) < 0) {
|
|
goto err_E_CFG;
|
|
}
|
|
si->in_session.source[IS_GATE_B(id)].always_learn = u != 0;
|
|
break;
|
|
}
|
|
case par_LearningTimeout:
|
|
case par_LearningTimeout << par_GateB: {
|
|
unsigned int u;
|
|
if (str2int(&val, &u) < 0) {
|
|
goto err_E_CFG;
|
|
}
|
|
if (u) {
|
|
for (i=0; i<2; i++) {
|
|
si->in_session.source[IS_GATE_B(id)].stream[i].learning_timeout = u;
|
|
si->in_session.source[IS_GATE_B(id)].stream[i].flags = XT_RTPPROXY_SOCKOPT_FLAG_SESSION_LEARNING_TIMEOUT;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case par_RingingTimeout: {
|
|
unsigned int u;
|
|
if (str2int(&val, &u) < 0) {
|
|
goto err_E_CFG;
|
|
}
|
|
if (u) {
|
|
si->ringing_timeout = u;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
BUG(MODULE_NAME": declare_switchboard_param: unknown id '%x\n", id);
|
|
goto err_E_CFG;
|
|
}
|
|
si->param_ids |= id;
|
|
if (!all_flag) break;
|
|
si = si->next;
|
|
}
|
|
}
|
|
s = c;
|
|
}
|
|
if (all_flag) {
|
|
return 0;
|
|
}
|
|
|
|
#define DEF_PARAMS(_id,_s,_fld) \
|
|
if ( (si->param_ids & (_id)) && !(si->param_ids & ((_id) << par_GateB)) ) \
|
|
si->_s[1]._fld = si->_s[0]._fld; \
|
|
if ( !(si->param_ids & (_id)) && (si->param_ids & ((_id) << par_GateB)) ) \
|
|
si->_s[0]._fld = si->_s[1]._fld;
|
|
|
|
DEF_PARAMS(par_AlwaysLearn,in_session.source,always_learn);
|
|
for (i=0; i<2; i++) {
|
|
DEF_PARAMS(par_LearningTimeout,in_session.source,stream[i].learning_timeout);
|
|
DEF_PARAMS(par_LearningTimeout,in_session.source,stream[i].flags);
|
|
}
|
|
switchboard_count++;
|
|
|
|
return 0;
|
|
|
|
err_E_CFG:
|
|
ERR(MODULE_NAME": declare_switchboard_param(#%d): parse error near \"%s\"\n", switchboard_count, s);
|
|
|
|
return E_CFG;
|
|
}
|
|
|
|
static cmd_export_t cmds[] = {
|
|
{MODULE_NAME "_alloc", rtpproxy_alloc, 2, rtpproxy_alloc_fixup, REQUEST_ROUTE | ONREPLY_ROUTE },
|
|
{MODULE_NAME "_update", rtpproxy_update, 2, rtpproxy_update_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
|
|
{MODULE_NAME "_adjust_timeout", rtpproxy_adjust_timeout, 2, rtpproxy_update_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
|
|
{MODULE_NAME "_delete", rtpproxy_delete, 1, rtpproxy_delete_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
|
|
{MODULE_NAME "_find", rtpproxy_find, 2, rtpproxy_find_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
|
|
|
|
{0, 0, 0, 0, 0}
|
|
};
|
|
|
|
static param_export_t params[] = {
|
|
{"config", PARAM_STR | PARAM_USE_FUNC, &declare_config},
|
|
{"switchboard", PARAM_STRING | PARAM_USE_FUNC, &declare_switchboard_param},
|
|
{0, 0, 0}
|
|
};
|
|
|
|
struct module_exports exports = {
|
|
MODULE_NAME,
|
|
cmds,
|
|
0, /* RPC methods */
|
|
params,
|
|
mod_init,
|
|
0, /* reply processing */
|
|
mod_cleanup, /* destroy function */
|
|
0, /* on_break */
|
|
child_init
|
|
};
|
|
|
|
|
|
#if !defined(NO_SHARED_LIBS) || NO_SHARED_LIBS==0
|
|
/* make compiler happy and give it missing symbols */
|
|
#include <iptables.h>
|
|
#include <stdarg.h>
|
|
void xtables_register_target(struct xtables_target *me) {
|
|
}
|
|
|
|
void exit_error(enum exittype status, const char *msg, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, msg);
|
|
// ERR(msg/*, args*/); /* TODO: how to pass ... to macro? */
|
|
ERR(MODULE_NAME": %s", msg);
|
|
va_end(args);
|
|
}
|
|
|
|
#ifndef TRUE
|
|
#define TRUE 1
|
|
#endif
|
|
#ifndef FALSE
|
|
#define FALSE 0
|
|
#endif
|
|
|
|
int check_inverse(const char option[], int *invert, int *optind, int argc)
|
|
{
|
|
if (option && strcmp(option, "!") == 0) {
|
|
if (*invert)
|
|
exit_error(PARAMETER_PROBLEM, "Multiple `!' flags not allowed");
|
|
*invert = TRUE;
|
|
if (optind) {
|
|
*optind = *optind+1;
|
|
if (argc && *optind > argc)
|
|
exit_error(PARAMETER_PROBLEM, "no argument following `!'");
|
|
}
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
#endif
|
|
|
|
|