/* $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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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=
, 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= [/] , 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; imedia_count; i++) { if (sdp_sess->media[i].active) { for (j=0; jmedia[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; istream_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 (; jstream_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 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; iin_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 #include 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