/* $Id: iptrtpproxy.c 30494 2010-07-20 15:05:24Z tma $ * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ // #include will be needed to define __user macro #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 "../../mem/shm_mem.h" #include "../../atomic_ops.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 "../../rand/kam_rand.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 XT_RTPPROXY_MAX_ALLOC_SESSION #define MAX_CODEC_NUMBER MAX_MEDIA_NUMBER*5 #define MAX_SWITCHBOARD_NAME_LEN 20 #define MAX_AGGREGATED_NUMBER 30 /* warningless cast at 64-bit */ #define PTR2INT(v) (int)(long) (v) #define INT2PTR(v) (void*)(long) (v) struct host_item_stat { int last_error_stamp; int last_ok_stamp; }; struct host_item { str name; struct xt_rtpproxy_connection_rpc_params rpc_params; int local; struct xt_rtpproxy_handle handle; int handle_is_opened; struct host_item_stat *stat; struct host_item *next; }; struct switchboard_item_stat { atomic_t free; atomic_t alloc; }; struct switchboard_item { str name; struct xt_rtpproxy_switchboard_id switchboard_addr; unsigned int sip_ip; str hostname; struct host_item *host; unsigned int weight; struct switchboard_item_stat *stat; struct switchboard_item *next; }; enum sdp_media_type { sdpmtUnknown = 0, sdpmtAudio, sdpmtVideo, sdpmtApplication, sdpmtText, sdpmtMessage, sdpmtData, /* not recommended in RFC4566 */ sdpmtControl /* dtto */ }; #define NUM_MEDIA_TYPES (sdpmtControl+1) static str sdp_media_types_str[NUM_MEDIA_TYPES] = { STR_STATIC_INIT("unknown"), STR_STATIC_INIT("audio"), STR_STATIC_INIT("video"), STR_STATIC_INIT("application"), STR_STATIC_INIT("text"), STR_STATIC_INIT("message"), STR_STATIC_INIT("data"), STR_STATIC_INIT("control") }; struct aggregation_item { str name; unsigned int switchboard_count; unsigned int sip_ip; struct switchboard_item *(*switchboards)[]; struct aggregation_item *next; }; struct codec_set_item_throttle { int max_streams; struct xt_rtpproxy_throttle_stat bandwidth[2]; /* RTP, RTCP */ }; struct codec_set_item { str name; struct { struct codec_set_item_throttle throttle; unsigned int (*codec_rights)[]; } media_types[NUM_MEDIA_TYPES]; struct codec_set_item *next; }; struct ipt_session { unsigned int session_count; struct xt_rtpproxy_sockopt_session sessions[MAX_MEDIA_NUMBER]; unsigned int sdp_media_count; int sdp_media[MAX_MEDIA_NUMBER]; struct switchboard_item *switchboard; }; static struct { str session_ids; str sdp_ip; str oline_user; str oline_addr; struct switchboard_item *switchboard[2]; struct aggregation_item *aggregation[2]; int learning_timeout; int expiration_timeout; int ttl; int always_learn; struct { u_int32_t mark; struct xt_rtpproxy_throttle_stat bandwidth[2]; } throttle; struct codec_set_item *codec_set; int remove_codec_mask; unsigned int auth_rights; struct ipt_session protected_sess; } global_params; static struct switchboard_item *switchboards = NULL; static struct host_item *hosts = NULL; static struct aggregation_item *aggregations = NULL; static struct codec_set_item *codec_sets = NULL; static int switchboard_count = 0; static str iptrtpproxy_cfg_filename = STR_STATIC_INIT("/etc/iptrtpproxy.cfg"); static int iptrtpproxy_cfg_flag = 0; static char* iptrtpproxy_cfg_hostname = NULL; static int rpc_heartbeat_timeout = 30; static int sdp_parsed = 999; /* we need share parsed SDP between authorize_media & alloc/update * get_param */ static struct sdp_session global_sdp_sess; #define declare_find_function(x) \ static struct x##_item* find_##x(str *name, struct x##_item*** prev) { \ struct x##_item* p;\ struct x##_item** dummy;\ if (!prev) \ prev = &dummy;\ for (p = x##s, *prev = &x##s; p; *prev = &(**prev)->next, p=p->next) {\ int len, n;\ len = (name->len < p->name.len) ? name->len : p->name.len;\ n = strncasecmp(name->s, p->name.s, len);\ if (n == 0) {\ if (name->len == p->name.len) \ return p;\ else if (name->len < p->name.len)\ return NULL;\ }\ else if (n < 0) { \ return NULL;\ }\ }\ return NULL;\ } declare_find_function(host) declare_find_function(switchboard) declare_find_function(aggregation) declare_find_function(codec_set) static struct switchboard_item* find_switchboard_by_addr(struct xt_rtpproxy_switchboard_id *addr) { struct switchboard_item* p; for (p = switchboards; p; p=p->next) { if (addr->ip == p->switchboard_addr.ip && addr->port == p->switchboard_addr.port) break; } return p; } enum { PAR_EXPIRATION_TIMEOUT, PAR_TTL, PAR_LEARNING_TIMEOUT, PAR_ALWAYS_LEARN, PAR_AGGREGATION_A, PAR_AGGREGATION_B, PAR_SWITCHBOARD_A, PAR_SWITCHBOARD_B, PAR_AGGREGATION_BY_SIP_IP_A, PAR_AGGREGATION_BY_SIP_IP_B, PAR_SWITCHBOARD_BY_SIP_IP_A, PAR_SWITCHBOARD_BY_SIP_IP_B, PAR_SESSION_IDS, PAR_PROTECTED_SESSION_IDS, PAR_SDP_IP, PAR_ACTIVE_MEDIA_NUM, PAR_OLINE_USER, PAR_OLINE_ADDR, PAR_THROTTLE_MARK, PAR_THROTTLE_RTP_MAX_BYTES, PAR_THROTTLE_RTP_MAX_PACKETS, PAR_THROTTLE_RTCP_MAX_BYTES, PAR_THROTTLE_RTCP_MAX_PACKETS, PAR_CODEC_SET, PAR_AUTH_RIGHTS, PAR_REMOVE_CODEC_MASK }; enum { PAR_READ=0x01, PAR_WRITE=0x02, PAR_INT=0x04, PAR_STR=0x08, PAR_DIR=0x10 }; static struct { char *name; int id; int flags; } param_list[] = { {"expiration_timeout", PAR_EXPIRATION_TIMEOUT, PAR_READ|PAR_WRITE|PAR_INT}, {"ttl", PAR_TTL, PAR_READ|PAR_WRITE|PAR_INT}, {"learning_timeout", PAR_LEARNING_TIMEOUT, PAR_READ|PAR_WRITE|PAR_INT}, {"always_learn", PAR_ALWAYS_LEARN, PAR_READ|PAR_WRITE|PAR_INT}, {"aggregation_a", PAR_AGGREGATION_A, PAR_READ|PAR_WRITE|PAR_STR}, {"aggregation_b", PAR_AGGREGATION_B, PAR_READ|PAR_WRITE|PAR_STR|PAR_DIR}, {"switchboard_a", PAR_SWITCHBOARD_A, PAR_READ|PAR_WRITE|PAR_STR}, {"switchboard_b", PAR_SWITCHBOARD_B, PAR_READ|PAR_WRITE|PAR_STR|PAR_DIR}, {"aggregation_by_sip_ip_a", PAR_AGGREGATION_BY_SIP_IP_A, PAR_WRITE|PAR_STR}, {"aggregation_by_sip_ip_b", PAR_AGGREGATION_BY_SIP_IP_B, PAR_WRITE|PAR_STR|PAR_DIR}, {"switchboard_by_sip_ip_a", PAR_SWITCHBOARD_BY_SIP_IP_A, PAR_WRITE|PAR_STR}, {"switchboard_by_sip_ip_b", PAR_SWITCHBOARD_BY_SIP_IP_B, PAR_WRITE|PAR_STR|PAR_DIR}, {"session_ids", PAR_SESSION_IDS, PAR_READ|PAR_STR}, {"protected_session_ids", PAR_PROTECTED_SESSION_IDS, PAR_WRITE|PAR_STR}, {"sdp_ip", PAR_SDP_IP, PAR_READ|PAR_INT}, {"active_media_num", PAR_ACTIVE_MEDIA_NUM, PAR_READ|PAR_INT}, {"o_user", PAR_OLINE_USER, PAR_READ|PAR_WRITE|PAR_STR}, {"o_addr", PAR_OLINE_ADDR, PAR_READ|PAR_WRITE|PAR_STR}, {"throttle_mark", PAR_THROTTLE_MARK, PAR_READ|PAR_WRITE|PAR_INT}, {"throttle_rtp_max_bytes", PAR_THROTTLE_RTP_MAX_BYTES, PAR_READ|PAR_WRITE|PAR_INT}, {"throttle_rtp_max_packets", PAR_THROTTLE_RTP_MAX_PACKETS, PAR_READ|PAR_WRITE|PAR_INT}, {"throttle_rtcp_max_bytes", PAR_THROTTLE_RTCP_MAX_BYTES, PAR_READ|PAR_WRITE|PAR_INT}, {"throttle_rtcp_max_packets", PAR_THROTTLE_RTCP_MAX_PACKETS, PAR_READ|PAR_WRITE|PAR_INT}, {"codec_set", PAR_CODEC_SET, PAR_READ|PAR_WRITE|PAR_STR}, {"remove_codec_mask", PAR_REMOVE_CODEC_MASK, PAR_READ|PAR_WRITE|PAR_INT}, {"auth_rights", PAR_AUTH_RIGHTS, PAR_READ|PAR_INT}, { NULL } }; static int param2idx(str *name, int rw) { int i; for (i=0; param_list[i].name; i++) { if (strlen(param_list[i].name)==name->len && strncasecmp(param_list[i].name, name->s, name->len) == 0 && (rw & param_list[i].flags)) { return i; } } ERR(MODULE_NAME": param2idx: unknown param '%.*s', rw:0x%0x\n", STR_FMT(name), rw); return -1; } /** if succesfull allocated sessions available @rtpproxy.session_ids */ static int rtpproxy_alloc_update_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_delete_fixup(void** param, int param_no) { switch (param_no) { case 1: return fixup_var_str_12(param, param_no); default: return 0; } } static int rtpproxy_set_param_fixup(void** param, int param_no) { int idx; action_u_t *a; str s; switch (param_no) { case 1: s.s = (char*)*param; s.len = strlen(s.s); idx = param2idx(&s, PAR_WRITE); if (idx < 0) { return E_CFG; } *param = INT2PTR(idx); break; case 2: a = fixup_get_param(param, param_no, 1); idx = a->u.number; if (param_list[idx].flags & PAR_STR) { return fixup_var_str_12(param, param_no); } else if (param_list[idx].flags & PAR_INT) { return fixup_var_int_12(param, param_no); } break; } return 0; } static int name2media_type(str *name) { int i; for (i = 1; ilen == sdp_media_types_str[i].len && strncasecmp(name->s, sdp_media_types_str[i].s, name->len) == 0) { return i; } } return sdpmtUnknown; } struct codec_entry { str name; /* bandwidth */ int payload_type; /* -1 .. dynamic */ }; #define MAX_FIXED_PAYLOAD_TYPES 96 static struct codec_entry(*reg_codecs)[] = NULL; static int reg_codec_count = 0; static int reg_codec_alloc_count = 0; static struct { int codec_id; } fixed_payload_types[MAX_FIXED_PAYLOAD_TYPES]; /* unregistered codec .. 0 */ static int name2codec_id(str *name, int *new_codec_id) { int i, j; i = 0; j = reg_codec_count - 1; while (i <= j) { int k, r; k = (i + j)/2; r = strncasecmp((*reg_codecs)[k].name.s, name->s, ((*reg_codecs)[k].name.len < name->len)?(*reg_codecs)[k].name.len:name->len); if (r == 0 && (*reg_codecs)[k].name.len == name->len) { return k+1; } else if (r > 0 || (r == 0 && (*reg_codecs)[k].name.len > name->len)) { j = k - 1; } else { i = k + 1; } } if (new_codec_id) { *new_codec_id = i + 1; } return 0; } /* return <0 if error, otherwise codec_id */ static int register_codec(str *name) { int codec_id, new_codec_id = 0; if (!(codec_id = name2codec_id(name, &new_codec_id))) { int i; if (reg_codec_count + 1 > reg_codec_alloc_count) { void *p; reg_codec_alloc_count += 10; p = pkg_realloc(reg_codecs, sizeof((*reg_codecs)[0])*reg_codec_alloc_count); if (!p) { return E_OUT_OF_MEM; } reg_codecs = p; } /* do not count codec_id == 0 (unknown) */ for (i=reg_codec_count-1; i >= new_codec_id-1; i--) { (*reg_codecs)[i+1] = (*reg_codecs)[i]; } reg_codec_count++; (*reg_codecs)[new_codec_id-1].name = *name; codec_id = new_codec_id; } return codec_id; } enum send_rec_modifier { sdpaattr_sendonly = 1, sdpaattr_recvonly = 2, sdpaattr_sendrecv = 3, sdpaattr_inactive = 4 }; static str send_rec_modifiers[] = { STR_STATIC_INIT(""), STR_STATIC_INIT("sendonly"), STR_STATIC_INIT("recvonly"), STR_STATIC_INIT("sendrecv"), STR_STATIC_INIT("inactive"), }; struct sdp_codec { unsigned int payload_type; unsigned int codec_id; str mline_payload_type_s; str a_rtpmap_line_s; str a_fmtp_line_s; }; struct sdp_session { str oline_user_s; str oline_addr_s; unsigned int media_count; struct { int active; /* if SDP has been parsed correctly, has a IP (even 0.0.0.0), port!=0 and has supported params */ unsigned short port; unsigned int ip; str ip_s; str port_s; enum sdp_media_type media_type; enum send_rec_modifier send_rec_modifier; str send_rec_modifier_line_s; int codec_count; struct sdp_codec (*codecs)[]; } media[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* lvalue, 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; lvalue->s = (*p)+2; lvalue->len = cp-lvalue->s; while (cp < pend && (*cp == '\n' || *cp == '\r')) cp++; line->s = (*p); line->len = cp-line->s; *p = cp; return 0; } *p = cp; } return -1; }; static int name2enum(str *name, str (*list)[]) { int i; for (i = 0; (*list)[i].s != NULL; i++) { if (name->len == (*list)[i].len && strncasecmp(name->s, (*list)[i].s, name->len) == 0) { return i; } } return -1; } static int prefix2enum(str *line, str (*list)[]) { int i; for (i = 0; (*list)[i].s != NULL; i++) { if (line->len > (*list)[i].len && strncmp(line->s, (*list)[i].s, (*list)[i].len) == 0) { return i; } } 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, lvalue, cline_ip_s, body; int sess_fl, i, cline_count, codec_count; char ltype, savec; unsigned int cline_ip; enum send_rec_modifier sess_send_rec_modifier; static struct sdp_codec codecs[MAX_CODEC_NUMBER]; static str supported_protocols[] = { 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 }; enum a_attr {sdpaattr_rtpmap, sdpaattr_fmtp, sdpaattr_rtcp}; static str a_attrs[] = { STR_STATIC_INIT("rtpmap:"), STR_STATIC_INIT("fmtp:"), STR_STATIC_INIT("rtcp:"), 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", STR_FMT(&line)); 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; codec_count = 0; memset(&codecs, 0, sizeof(codecs)); sess_send_rec_modifier = 0; while (p < pend) { if (next_sdp_line(&p, pend, <ype, &lvalue, &line) < 0) break; lend = lvalue.s + lvalue.len; 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 'o': /* originator & session description: o= */ if (sess_fl != 1) { ERR(MODULE_NAME": parse_sdp_content: o= line is not in session section\n"); return -1; } for (i=0; i<6; i++) if (sess->oline_addr_s.s) { ERR(MODULE_NAME": parse_sdp_content: only one o= line allowed\n"); return -1; } cp = eat_token_end(lvalue.s, lend); sess->oline_user_s.len = cp-lvalue.s; if (!sess->oline_user_s.len) goto invalid_o; sess->oline_user_s.s = lvalue.s; lvalue.s = eat_space_end(cp, lend); for (i=0; i<4; i++) { cp = eat_token_end(lvalue.s, lend); if (cp-lvalue.s == 0) goto invalid_o; lvalue.s = eat_space_end(cp, lend); } cp = eat_token_end(lvalue.s, lend); sess->oline_addr_s.len = cp-lvalue.s; if (!sess->oline_addr_s.len) goto invalid_o; sess->oline_addr_s.s = lvalue.s; break; invalid_o: ERR(MODULE_NAME": parse_sdp_content: invalid o= line '%.*s'\n", (int) (lend-line.s), line.s); return -1; 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; } cp = eat_token_end(lvalue.s, lend); if (cp-lvalue.s != 2 || memcmp(lvalue.s, "IN", 2) != 0) { goto invalidate; } cp = eat_space_end(cp, lend); lvalue.s = cp; cp = eat_token_end(cp, lend); if (cp-lvalue.s != 3 || memcmp(lvalue.s, "IP4", 3) != 0) { goto invalidate; } cp = eat_space_end(cp, lend); lvalue.s = cp; cp = eat_token_end(cp, lend); lvalue.len = cp-lvalue.s; if (lvalue.len == 0 || q_memchr(lvalue.s, '/', lvalue.len)) { /* multicast address not supported */ goto invalidate; } if (sess_fl == 1) { cline_ip_s = lvalue; cline_ip = s2ip4(&lvalue); } else { sess->media[sess->media_count-1].ip = s2ip4(&lvalue); sess->media[sess->media_count-1].active = sess->media[sess->media_count-1].port != 0; /* IP may by specified by hostname */ sess->media[sess->media_count-1].ip_s = lvalue; } 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 */ /* media: "audio", "video", "application", "data" and "control" */ 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; sess->media[sess->media_count-1].port = 0; sess->media[sess->media_count-1].send_rec_modifier = sess_send_rec_modifier; cp = eat_token_end(lvalue.s, lend); lvalue.len = cp-lvalue.s; sess->media[sess->media_count-1].media_type = name2media_type(&lvalue);; if (!lvalue.len) { break; } cp = eat_space_end(cp, lend); lvalue.s = cp; cp = eat_token_end(cp, lend); lvalue.len = cp-lvalue.s; cp2 = q_memchr(lvalue.s, '/', lvalue.len); if (cp2) { /* strip optional number of ports, if present should be 2 */ lvalue.len = cp2-lvalue.s; } sess->media[sess->media_count-1].port_s = lvalue; if (lvalue.len == 0) { /* invalid port? */ break; } savec = lvalue.s[lvalue.len]; lvalue.s[lvalue.len] = '\0'; sess->media[sess->media_count-1].port = atol(lvalue.s); lvalue.s[lvalue.len] = savec; if (sess->media[sess->media_count-1].port == 0) { break; } cp = eat_space_end(cp, lend); lvalue.s = cp; cp = eat_token_end(cp, lend); lvalue.len = cp-lvalue.s; if (name2enum(&lvalue, &supported_protocols) >= 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; } /* get payload types */ sess->media[sess->media_count-1].codecs = (struct sdp_codec (*)[]) (codecs + codec_count); while (cp < lend) { if (codec_count >= MAX_CODEC_NUMBER) { ERR(MODULE_NAME": parse_sdp_content: max.number of codecs (%d) exceeded\n", MAX_CODEC_NUMBER); return -1; } codecs[codec_count].mline_payload_type_s.s = cp; cp = eat_space_end(cp, lend); lvalue.s = cp; cp = eat_token_end(cp, lend); codecs[codec_count].mline_payload_type_s.len = cp - codecs[codec_count].mline_payload_type_s.s; lvalue.len = cp-lvalue.s; savec = lvalue.s[lvalue.len]; lvalue.s[lvalue.len] = '\0'; codecs[codec_count].payload_type = atol(lvalue.s); if (codecs[codec_count].payload_type < MAX_FIXED_PAYLOAD_TYPES) { codecs[codec_count].codec_id = fixed_payload_types[codecs[codec_count].payload_type].codec_id; } lvalue.s[lvalue.len] = savec; for (i=0; i < sess->media[sess->media_count-1].codec_count; i++) { if (codecs[codec_count].payload_type == (*sess->media[sess->media_count-1].codecs)[i].payload_type) { ERR(MODULE_NAME": parse_sdp_content: duplicate payload type in '%.*s'\n", (int) (lend-line.s), line.s); return -1; } } codec_count++; sess->media[sess->media_count-1].codec_count++; } if (!sess->media[sess->media_count-1].codec_count) { ERR(MODULE_NAME": parse_sdp_content: no codec declared '%.*s'\n", (int) (lend-line.s), line.s); return -1; } break; default: ; } break; case 'a': i = name2enum(&lvalue, &send_rec_modifiers); if (i > 0) { switch (sess_fl) { case 1: if (sess_send_rec_modifier) { ERR(MODULE_NAME": parse_sdp_content: duplicate send/recv modifier in session '%.*s'\n", (int) (lend-line.s), line.s); return -1; } sess_send_rec_modifier = i; break; case 2: if (sess->media[sess->media_count-1].send_rec_modifier_line_s.s) { ERR(MODULE_NAME": parse_sdp_content: duplicate send/recv modifier in stream '%.*s'\n", (int) (lend-line.s), line.s); return -1; } sess->media[sess->media_count-1].send_rec_modifier = i; sess->media[sess->media_count-1].send_rec_modifier_line_s = line; default: ; } } else if (sess_fl == 2) { int payload_type; int a_attr; a_attr = prefix2enum(&lvalue, &a_attrs); if (a_attr < 0) { break; } lend = lvalue.s + lvalue.len; lvalue.s += a_attrs[a_attr].len; switch (a_attr) { case sdpaattr_rtpmap: /* a=rtpmap: /[/] , max.one a=rtpmap: per codec */ case sdpaattr_fmtp: /* a=fmtp: , max.one a=fmtp: per codec */ /* we validate only things important for us. Other thinkgs not important we leave up to UA. Not tested: - payload order of a:rtpmap corresponds to m= line - all dynamic payloads have cooresponding a:rtpmap line (for us it's unknown codec) - if static payload type corresponds to codec, i.e. e.g. if 0 is PCMU */ cp = eat_token_end(lvalue.s, lend); lvalue.len = cp-lvalue.s; savec = lvalue.s[lvalue.len]; lvalue.s[lvalue.len] = '\0'; payload_type = atol(lvalue.s); lvalue.s[lvalue.len] = savec; for (i=0; i < sess->media[sess->media_count-1].codec_count; i++) { if ((*sess->media[sess->media_count-1].codecs)[i].payload_type == payload_type) { goto found; } } ERR(MODULE_NAME": parse_sdp_content: '%.*s' payload type (%d) has not been mentioned at m= line\n", (int) (lend-line.s), line.s, payload_type); return -1; found: cp = eat_space_end(cp, lend); switch (a_attr) { case sdpaattr_rtpmap: if ((*sess->media[sess->media_count-1].codecs)[i].a_rtpmap_line_s.s) { ERR(MODULE_NAME": parse_sdp_content: '%.*s' multiple a=rtpmap lines for payload type (%d)\n", (int) (lend-line.s), line.s, payload_type); return -1; } (*sess->media[sess->media_count-1].codecs)[i].a_rtpmap_line_s = line; lvalue.s = cp; cp = eat_token2_end(cp, lend, '/'); lvalue.len = cp-lvalue.s; (*sess->media[sess->media_count-1].codecs)[i].codec_id = name2codec_id(&lvalue, NULL); break; case sdpaattr_fmtp: if ((*sess->media[sess->media_count-1].codecs)[i].a_fmtp_line_s.s) { ERR(MODULE_NAME": parse_sdp_content: '%.*s' multiple a=fmtp lines for payload type (%d)\n", (int) (lend-line.s), line.s, payload_type); return -1; } (*sess->media[sess->media_count-1].codecs)[i].a_fmtp_line_s = line; break; default: break; } break; case sdpaattr_rtcp: /* a=rtcp: port [nettype space addrtype space connection-address] */ ERR(MODULE_NAME": parse_sdp_content: a=rtcp parameter is ignored '%.*s', RTCP relaying may fail\n", (int) (lend-line.s), line.s); break; default: ; } } break; default: ; } } return 0; } /* simple wrapper to call parse_sdp_content() only once per request */ static inline int check_parse_sdp_content(struct sip_msg* msg, struct sdp_session *sess) { switch (sdp_parsed) { case -1: case 0: return sdp_parsed; default: sdp_parsed = parse_sdp_content(msg, sess); return sdp_parsed; } } static int prepare_lumps(struct sip_msg* msg, str* position, str* s) { struct lump* anchor; char *buf; if (!position->s) return 0; //ERR("'%.*s' --> '%.*s'\n", STR_FMT(position), STR_FMT(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; } if (!s || !s->len) return 0; 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 */ global_params.sdp_ip.len = 0; for (i=0; imedia_count; i++) { if (sdp_sess->media[i].active) { if (ipt_sess->sdp_media[i] < 0) { goto cline_fixed; } for (j=0; jmedia[j].active && sdp_sess->media[i].ip_s.s == sdp_sess->media[j].ip_s.s && ipt_sess->sdp_media[j] >= 0) { goto cline_fixed; } } if (global_params.sdp_ip.len == 0) { /* takes 1st ip to be rewritten, for aux purposes only */ global_params.sdp_ip = sdp_sess->media[i].ip_s; } if (sdp_sess->media[i].ip != 0) { /* we won't update 0.0.0.0 to anything because such a UA cannot receive. The session may be allocated and reused (unless expires) */ /* apply lump for ip address in c= line */ ip42s(ipt_sess->sessions[ipt_sess->sdp_media[i]].dir[!gate_a_to_b].switchboard.addr.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->sdp_media[i]<0)? 0/* disable stream */: ipt_sess->sessions[ipt_sess->sdp_media[i]].dir[!gate_a_to_b].stream[0].port, &s.len); if (prepare_lumps(msg, &sdp_sess->media[i].port_s, &s) < 0) return -1; } } /* do topo hiding if all media are disabled for c= line then set address 0.0.0.0 to hide UA location */ for (i=0; imedia_count; i++) { if (sdp_sess->media[i].ip && (!sdp_sess->media[i].active || ipt_sess->sdp_media[i] < 0)) { /* not affected but previous loop */ for (j=0; jmedia[i].ip_s.s == sdp_sess->media[j].ip_s.s) { goto cline_fixed2; /* must be already updated */ } } for (j=i+1; jmedia_count; j++) { if (sdp_sess->media[i].ip_s.s == sdp_sess->media[j].ip_s.s && /* same c= line */ sdp_sess->media[i].active && ipt_sess->sdp_media[i] >= 0) { /* has media enabled */ goto cline_fixed2; } } /* apply lump for ip address in c= line */ ip42s(0, &s); if (prepare_lumps(msg, &sdp_sess->media[i].ip_s, &s) < 0) return -1; cline_fixed2: ; } } if (sdp_sess->oline_addr_s.s) { /* o= line exists */ if (global_params.oline_user.len) { if (prepare_lumps(msg, &sdp_sess->oline_user_s, &global_params.oline_user) < 0) return -1; } if (global_params.oline_addr.len) { if (prepare_lumps(msg, &sdp_sess->oline_addr_s, &global_params.oline_addr) < 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; if (sess->sdp_media_count) { if (sess->switchboard) { memcpy(p, sess->switchboard->name.s, sess->switchboard->name.len); p += sess->switchboard->name.len; } *p = ':'; p++; for (i=0; isdp_media_count; i++) { if (sess->sdp_media[i] >= 0) { p += sprintf(p, "%u/%u", sess->sessions[sess->sdp_media[i]].dir[0].sess_id, sess->sessions[sess->sdp_media[i]].sh.created ); } *p = ','; p++; } p--; *p = '\0'; } session_ids->s = buf; session_ids->len = p - buf; } /* switchboardname [":" [sess_id "/" created] [ * ( "," [sess_id "/" created] )] ] */ /* sessids are placed at dir[0] */ static int unserialize_ipt_session(str* session_ids, struct ipt_session* sess) { char *p, *pend, savec; str s; unsigned int sess_id, created, i; memset(sess, 0, sizeof(*sess)); if (session_ids->len == 0) { return 0; } 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, NULL); global_params.switchboard[0] = sess->switchboard; if (s.len && !sess->switchboard) { /* empty switchboard is stored if all sessions are forced as disabled (port==0), SDP streams are empty but we need test medias later*/ ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', switchboard '%.*s' not found\n", STR_FMT(session_ids), STR_FMT(&s)); return -1; } if (p == pend) return 0; if (*p != ':') { ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', colon expected near '%.*s'\n", STR_FMT(session_ids), PTR2INT(pend-p), p); return -1; } do { if (sess->sdp_media_count >= MAX_MEDIA_NUMBER) { ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', max.media number (%d) exceeded\n", STR_FMT(session_ids), MAX_MEDIA_NUMBER); return -1; } sess->sdp_media[sess->sdp_media_count] = -1; p++; if (p < pend && *p != ',') { s.s = p; while (p < pend && (*p >= '0' && *p <= '9')) p++; s.len = p-s.s; if (s.len == 0 || p == pend || *p != '/') { ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', '/' expected near '%.*s'\n", STR_FMT(session_ids), PTR2INT(pend-p), p); return -1; } savec = s.s[s.len]; s.s[s.len] = '\0'; sess_id = atol(s.s); s.s[s.len] = savec; p++; s.s = p; while (p < pend && (*p >= '0' && *p <= '9')) p++; s.len = p-s.s; if (s.len == 0 || (p != pend && *p != ',')) { ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', comma expected near '%.*s'\n", STR_FMT(session_ids), PTR2INT(pend-p), p); return -1; } savec = s.s[s.len]; s.s[s.len] = '\0'; created = atol(s.s); s.s[s.len] = savec; for (i=0; isdp_media_count; i++) { if (sess->sdp_media[i] >= 0 && sess->sessions[sess->sdp_media[i]].dir[0].sess_id == sess_id) { if (sess->sessions[sess->sdp_media[i]].sh.created != created) { ERR(MODULE_NAME": unserialize_ipt_session: '%.*s', sess-id/created mismatch '%u/(%u!=%u)'\n", STR_FMT(session_ids), sess_id, sess->sessions[sess->sdp_media[i]].sh.created, created); return -1; } sess->sdp_media[sess->sdp_media_count] = sess->sdp_media[i]; goto cont; } } sess->sessions[sess->session_count].dir[0].switchboard.addr = sess->switchboard->switchboard_addr; sess->sessions[sess->session_count].dir[0].sess_id = sess_id; sess->sessions[sess->session_count].sh.created = created; sess->sdp_media[sess->sdp_media_count] = sess->session_count; sess->session_count++; } cont: sess->sdp_media_count++; } while (p < pend); return 0; } static inline int check_host_err(struct host_item *hi, int ret) { switch (hi->handle.err_no) { case XT_RTPPROXY_ERR_CANNOT_OPEN_SOCKET: case XT_RTPPROXY_ERR_RPC: atomic_set_int(&hi->stat->last_error_stamp, (int) time(NULL)); break; default: atomic_set_int(&hi->stat->last_ok_stamp, (int) time(NULL)); } return ret; } static inline int check_open_handle(struct host_item* hi) { if (!hi->handle_is_opened) { if (hi->local) { if (check_host_err(hi, xt_RTPPROXY_open(&hi->handle, xt_rtpproxy_LOCAL, NULL)) < 0) goto err; } else { if (check_host_err(hi, xt_RTPPROXY_open(&hi->handle, xt_rtpproxy_REMOTE, &hi->rpc_params)) < 0) goto err; } hi->handle_is_opened = 1; } return 0; err: ERR(MODULE_NAME": %s (%d)\n", hi->handle.err_str, hi->handle.err_no); return -1; } static void delete_ipt_sessions(struct host_item* hi, struct ipt_session* ipt_sess, struct ipt_session *ipt_surviving_sess) { int i; for (i=0; i < ipt_sess->session_count; i++) { if (ipt_sess->switchboard == ipt_surviving_sess->switchboard) { int j; for (j=0; j < ipt_surviving_sess->session_count; j++) { if (ipt_sess->sessions[i].dir[0].sess_id == ipt_surviving_sess->sessions[j].dir[0].sess_id && ipt_sess->sessions[i].sh.created == ipt_surviving_sess->sessions[j].sh.created ) { /* we do non need test also created */ goto skip_del; } } } ipt_sess->sessions[i].sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_DESTROY; skip_del: ; } //ERR("DEBUG_RTPPROXY: module: delete_ipt_sessions: xt_RTPPROXY_update_sessions(%d)\n", ipt_sess->session_count); if (check_host_err(hi, xt_RTPPROXY_update_sessions(&hi->handle, ipt_sess->session_count, &ipt_sess->sessions)) < 0) { ERR(MODULE_NAME": delete_ipt_sessions: xt_RTPPROXY_update_session error: %s (%d)\n", hi->handle.err_str, hi->handle.err_no); /* what to do ? */ } } #define GATE_FLAG 0x01 #define UPDATE_SDP_ONLY_FLAG 0x02 /* gate_a_to_b has index 0, gate_b_to_a 1 */ #define GATE_A_TO_B(flags) (((flags) & GATE_FLAG) == 0) /* SDP (sdp_session) -> ipt RTP proxy session [dir == 0] */ inline static void fill_in_session(int flags, int media_idx, struct sdp_session *sdp_sess, struct xt_rtpproxy_sockopt_session *in_session) { int j; for (j=0; j<2; j++) { if (sdp_sess) { in_session->dir[GATE_A_TO_B(flags)].stream[j].flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_ADDR; in_session->dir[GATE_A_TO_B(flags)].stream[j].source.ip = sdp_sess->media[media_idx].ip; in_session->dir[GATE_A_TO_B(flags)].stream[j].source.port = sdp_sess->media[media_idx].port+j; } if (global_params.learning_timeout > 0) { in_session->dir[GATE_A_TO_B(flags)].stream[j].flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_LEARNING_TIMEOUT; in_session->dir[GATE_A_TO_B(flags)].stream[j].learning_timeout = global_params.learning_timeout; } } if (global_params.always_learn >= 0) { in_session->dir[GATE_A_TO_B(flags)].always_learn = global_params.always_learn!=0; in_session->dir[GATE_A_TO_B(flags)].flags |= XT_RTPPROXY_SOCKOPT_FLAG_ALWAYS_LEARN; } if (global_params.expiration_timeout > 0) { in_session->sh.expires_timeout = global_params.expiration_timeout; in_session->sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_EXPIRES; } if (global_params.ttl >= 0) { in_session->sh.ttl = global_params.ttl; in_session->sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_TTL; } } inline static void fill_in_session_throttle(int flags, int media_idx, struct xt_rtpproxy_sockopt_session *in_session) { int j; if (global_params.throttle.mark > 0) { for (j=0; j<2; j++) { in_session->dir[GATE_A_TO_B(flags)].stream[j].throttle.mark = global_params.throttle.mark; in_session->dir[GATE_A_TO_B(flags)].stream[j].flags |= XT_RTPPROXY_SOCKOPT_FLAG_THROTTLE_MARK; } } for (j=0; j<2; j++) { if (global_params.codec_set && (global_params.codec_set->media_types[global_sdp_sess.media[media_idx].media_type].throttle.bandwidth[j].packets > 0 || global_params.codec_set->media_types[global_sdp_sess.media[media_idx].media_type].throttle.bandwidth[j].bytes > 0) ) { in_session->dir[GATE_A_TO_B(flags)].stream[j].throttle.max_bandwidth = global_params.codec_set->media_types[global_sdp_sess.media[media_idx].media_type].throttle.bandwidth[j]; in_session->dir[GATE_A_TO_B(flags)].stream[j].flags |= XT_RTPPROXY_SOCKOPT_FLAG_THROTTLE_BANDWIDTH; } else if (global_params.throttle.bandwidth[j].bytes > 0 || global_params.throttle.bandwidth[j].packets > 0 ) { in_session->dir[GATE_A_TO_B(flags)].stream[j].throttle.max_bandwidth = global_params.throttle.bandwidth[j]; in_session->dir[GATE_A_TO_B(flags)].stream[j].flags |= XT_RTPPROXY_SOCKOPT_FLAG_THROTTLE_BANDWIDTH; } } } static int rtpproxy_alloc(struct sip_msg* msg, char* _flags, char* _dummy) { int flags; struct ipt_session ipt_sess; struct host_item* hi = NULL; struct xt_rtpproxy_switchboard_id aggregated_switchboards[MAX_AGGREGATED_NUMBER]; time_t stamp; xt_rtpproxy_sockopt_count cnt[2]; str s; int i, aggr_fl, reuse_existing_count; if (get_int_fparam(&flags, msg, (fparam_t*) _flags) < 0) { return -1; } if (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; ERR("RTPPROXY_DEBUG: sdp.media_count: %d, flags: %d\n", global_sdp_sess.media_count, flags); if (global_params.protected_sess.switchboard) { /* any protected ? */ /* get session source address from kernel module and compare with SDP content */ for (i = 0; i < global_params.protected_sess.session_count; i++) { global_params.protected_sess.sessions[i].sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_INFO; } if (check_open_handle(global_params.protected_sess.switchboard->host) < 0) { return -1; } ERR("RTPPROXY_DEBUG: xt_RTPPROXY_update_sessions(sess#:%d, sdp#:%d, XT_RTPPROXY_SOCKOPT_FLAG_SESSION_INFO)\n", global_params.protected_sess.session_count, global_params.protected_sess.sdp_media_count); if (check_host_err(global_params.protected_sess.switchboard->host, xt_RTPPROXY_update_sessions(&global_params.protected_sess.switchboard->host->handle, global_params.protected_sess.session_count, &global_params.protected_sess.sessions)) < 0) { ERR(MODULE_NAME": rtpproxy_alloc: xt_RTPPROXY_update_session error when retrieving sessions: %s (%d)\n", global_params.protected_sess.switchboard->host->handle.err_str, global_params.protected_sess.switchboard->host->handle.err_no ); return -1; } } reuse_existing_count = 0; memset(&ipt_sess, 0, sizeof(ipt_sess)); for (i = 0; i < global_sdp_sess.media_count; i++) { ipt_sess.sdp_media[i] = -1; if (global_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 (global_sdp_sess.media[j].active) { if (global_sdp_sess.media[i].ip == global_sdp_sess.media[j].ip && global_sdp_sess.media[i].port == global_sdp_sess.media[j].port) { if (global_sdp_sess.media[i].ip != 0) { ipt_sess.sdp_media[i] = ipt_sess.sdp_media[j]; goto cont; } else if (i < global_params.protected_sess.sdp_media_count) { int k, l; k = global_params.protected_sess.sdp_media[i]; l = global_params.protected_sess.sdp_media[j]; if ((global_params.protected_sess.sessions[k].sh.flags & XT_RTPPROXY_SOCKOPT_FLAG_NOT_FOUND) == 0 && global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.ip == global_params.protected_sess.sessions[l].dir[GATE_A_TO_B(flags)].stream[0].source.ip && global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.port == global_params.protected_sess.sessions[l].dir[GATE_A_TO_B(flags)].stream[0].source.port) { /* if ip == 0, for example phone goes on-hold we'll take IP from protected sessions if possible */ ipt_sess.sdp_media[i] = ipt_sess.sdp_media[j]; goto cont; } } } } } /* if there are existing sessions then we take those instead of allocation new ones */ /* we can match 1:1 existing media streams against SDP sessions provided by SDP */ if (i < global_params.protected_sess.sdp_media_count) { int k; k = global_params.protected_sess.sdp_media[i]; ERR("RTPPROXY_DEBUG: protected.sess media:%d -> sess:%d, flags: %d\n", i, k, global_params.protected_sess.sessions[k].sh.flags); if ((global_params.protected_sess.sessions[k].sh.flags & XT_RTPPROXY_SOCKOPT_FLAG_NOT_FOUND) == 0) { switch (global_params.protected_sess.sessions[k].sh.state) { case xt_rtpproxy_INIT1: case xt_rtpproxy_INIT2: case xt_rtpproxy_FORWARD1: case xt_rtpproxy_FORWARD2: /* is original ip:port of existion session equal to ip:port provided by SDP ? RTP test is sufficient */ /* Workaround: if a phone (Sipura, X-Lite, ...) goes on-hold then c= line address is set to 0.0.0.0. It's not correct because RTCP media can't tricle. It would not force new RTP session allocation. So we won't update SDP c= line but get sees_id from protected sess to reuse it when on-hold terminates. But when on-hold is too long and session expires then new session will be allocated */ ERR("DEBUG_RTPPROXY: module: CMP %x=%x & %d=%d\n", global_sdp_sess.media[i].ip, global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.ip, global_sdp_sess.media[i].port, global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.port); if ((global_sdp_sess.media[i].ip == 0 || global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.ip == 0 || global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.ip == global_sdp_sess.media[i].ip) && /* global_sdp_sess.media[i].port always because active && */ global_params.protected_sess.sessions[k].dir[GATE_A_TO_B(flags)].stream[0].source.port == global_sdp_sess.media[i].port) { /* keep all reused sess at the beginning of list, i.e. make slot */ ERR("RTPPROXY_DEBUG: REUSE!\n"); for (j=ipt_sess.session_count; j > 0; j--) { ipt_sess.sessions[j] = ipt_sess.sessions[j-1]; } for (j=0; j < i; j++) { if (ipt_sess.sdp_media[j] >= 0) { ipt_sess.sdp_media[j]++; } } /* put it at slot [0], copy data from existing session */ for (j=0; j<2; j++) { ipt_sess.sessions[0].dir[j] = global_params.protected_sess.sessions[k].dir[j]; } ipt_sess.sessions[0].sh = global_params.protected_sess.sessions[k].sh; ipt_sess.sdp_media[i] = 0; reuse_existing_count++; goto skip_fill; } break; default: ; } } } if (global_sdp_sess.media[i].ip == 0) { switch (global_sdp_sess.media[i].send_rec_modifier) { case sdpaattr_sendonly: case sdpaattr_sendrecv: /* it's error because it cannot receive anything but client are weird */ break; /* they can send RTP/RTCP, not recommended in RFC3264, maybe allow only when learning possible */ default: /* do not allocate session for on-hold stream unless reused, disable stream (sdp_media[i]) < 0 */ ERR("DEBUG_RTPPROXY: module: do not allocate session for on-hold stream unless reused\n"); goto cont; } } fill_in_session(flags, i, &global_sdp_sess, ipt_sess.sessions+ipt_sess.session_count); fill_in_session_throttle(flags, i, ipt_sess.sessions+ipt_sess.session_count); ipt_sess.sdp_media[i] = ipt_sess.session_count; skip_fill: ipt_sess.session_count++; } cont: ; } ipt_sess.sdp_media_count = global_sdp_sess.media_count; ERR("RTPPROXY_DEBUG: session_count: %d, reuse_existing_count: %d\n", global_sdp_sess.media_count, reuse_existing_count); if (ipt_sess.session_count > reuse_existing_count) { stamp = time(NULL); if (reuse_existing_count > 0) { /* we need allocate sessions at the same switchboard as already being existed */ aggr_fl = 0; hi = global_params.protected_sess.switchboard->host; ipt_sess.switchboard = global_params.protected_sess.switchboard; for (i=reuse_existing_count; i < ipt_sess.session_count; i++) { int j; for (j=0; j<2; j++) { ipt_sess.sessions[i].dir[j].switchboard.addr = ipt_sess.sessions[0].dir[j].switchboard.addr; } } } else { for (i=0; i<2; i++) { if (!global_params.switchboard[i] && !global_params.aggregation[i]) { ERR(MODULE_NAME": rtpproxy_alloc: aggregation/switchboard not set (dir:%d)\n", i); return -1; } } aggr_fl = global_params.aggregation[0] || global_params.aggregation[1]; if (aggr_fl) { struct switchboard_item *si; /* calculate switchboard weights. There is minor problem when weight are calculated some time before RPC commands are performed, i.e. if a remote RPC server become unavailable then more processes may spend time waiting for unresponsive machine even it's been discovered by parallel process. */ for (si=switchboards; si; si=si->next) { unsigned int w; int a, f; time_t ok_stamp, err_stamp; a = f = 0; ok_stamp = atomic_get_int(&si->host->stat->last_ok_stamp); err_stamp = atomic_get_int(&si->host->stat->last_error_stamp); if (rpc_heartbeat_timeout > 0 && err_stamp > ok_stamp && (stamp-err_stamp) >= rpc_heartbeat_timeout) { /* set max. priority to force remote rtpproxy rpc call, i.e. test if is alive or dead */ ok_stamp = err_stamp = 0; } if (err_stamp > ok_stamp) { /* lowest priority */ /* prefer older error */ w = time(NULL) - err_stamp + 1; if (w > 999) w = 999; } else if (ok_stamp == 0) { /* not yet acquired, highest */ w = 100000000 + (kam_rand() & 0xFFFF); /* randomize not yet asked or being hartbeated */ } else { /* middle */ w = 1000; a = atomic_get(&si->stat->alloc); f = atomic_get(&si->stat->free); if ((a + f) > 0) { /* prefer switchboards having more free slots */ w += (1000*f)/(a+f); } } si->weight = w; //ERR(MODULE_NAME": rtpproxy_alloc: switchboard '%.*s' (ok_stamp: %u, err_stamp: %u, alloc: %u, free: %u, weight: %u)\n", STR_FMT(&si->name), (unsigned int) ok_stamp, (unsigned int) err_stamp, a, f, w); } hi = NULL; } else { if (global_params.switchboard[0]->host != global_params.switchboard[1]->host) { ERR(MODULE_NAME": rtpproxy_alloc: switchboard resides of different hosts '%.*s'!='%.*s'\n", STR_FMT(&global_params.switchboard[0]->host->name), STR_FMT(&global_params.switchboard[1]->host->name) ); return -1; } hi = global_params.switchboard[0]->host; for (i=0; i < ipt_sess.session_count; i++) { int j; for (j=0; j<2; j++) { ipt_sess.sessions[i].dir[j].switchboard.addr = global_params.switchboard[j]->switchboard_addr; } } } } try_next_host: cnt[0] = cnt[1] = 0; if (aggr_fl) { int j; hi = NULL; if (global_params.aggregation[0] && global_params.aggregation[1]) { int w = 0; /* find switchboard having max. weight */ for (i=0; iswitchboard_count; i++) { if ((*global_params.aggregation[0]->switchboards)[i]->weight > w) { /* weight==0 is skipped */ time_t err_stamp; err_stamp = atomic_get_int(&(*global_params.aggregation[0]->switchboards)[i]->host->stat->last_error_stamp); if (err_stamp >= stamp) { if (w > 0) continue; /* decrease weight to minimum, parallel process meanwhile got error */ w = 1; } else { w = (*global_params.aggregation[0]->switchboards)[i]->weight; } hi = (*global_params.aggregation[0]->switchboards)[i]->host; } } } else { for (j=0; j<2; j++) { if (!global_params.aggregation[j] && global_params.switchboard[j]->weight) { hi = global_params.switchboard[j]->host; } } } if (!hi) { ERR(MODULE_NAME": rtpproxy_alloc: cannot allocate aggregated switchboard (#1)\n"); return -1; } for (j=0; j<2; j++) { if (global_params.aggregation[j]) { struct switchboard_item *aggr_switchboards[MAX_AGGREGATED_NUMBER]; for (i=0; iswitchboard_count; i++) { if ((*global_params.aggregation[j]->switchboards)[i]->weight && (*global_params.aggregation[j]->switchboards)[i]->host == hi) { int k, l; if (cnt[0]+cnt[1] >= MAX_AGGREGATED_NUMBER) { ERR(MODULE_NAME": rtpproxy_alloc: number of aggregated switchboard exceeded limit %d\n", MAX_AGGREGATED_NUMBER); return -1; } /* put switchboard ordered by weight */ for (k=0; kweight >= (*global_params.aggregation[j]->switchboards)[i]->weight; k++); for (l=cnt[j]; l>k; l--) { aggr_switchboards[l] = aggr_switchboards[l-1]; } aggr_switchboards[k] = (*global_params.aggregation[j]->switchboards)[i]; cnt[j]++; } } if (!cnt[j]) { ERR(MODULE_NAME": rtpproxy_alloc: cannot allocate aggregated switchboard (#2)\n"); return -1; } for (i = 0; i < cnt[j]; i++) { aggregated_switchboards[j*cnt[0]+i] = aggr_switchboards[i]->switchboard_addr; } } else { if (cnt[0]+cnt[1] >= MAX_AGGREGATED_NUMBER) { ERR(MODULE_NAME": rtpproxy_alloc: number of aggregated switchboard exceeded limit %d\n", MAX_AGGREGATED_NUMBER); return -1; } aggregated_switchboards[cnt[0]+cnt[1]] = global_params.switchboard[j]->switchboard_addr; cnt[j]++; } } for (j=0; j<2; j++) { if (global_params.aggregation[j]) { for (i=0; iswitchboard_count; i++) { if ((*global_params.aggregation[j]->switchboards)[i]->host == hi) { /* done, do not process in next round again */ (*global_params.aggregation[j]->switchboards)[i]->weight = 0; } } } else { global_params.switchboard[j]->weight = 0; } } } if (reuse_existing_count < ipt_sess.session_count) { /* allocation required ? */ if (check_open_handle(hi) < 0) { if (aggr_fl) { goto try_next_host; } return -1; } ERR("DEBUG_RTPPROXY: module: rtpproxy_alloc: xt_RTPPROXY_alloc_sessions(%d/%d/%d), host: '%.*s', flags: %d\n", cnt[0], cnt[1], ipt_sess.session_count, STR_FMT(&hi->name), flags); if (check_host_err(hi, xt_RTPPROXY_alloc_sessions(&hi->handle, cnt[0], &aggregated_switchboards, cnt[1], (void*) &aggregated_switchboards[cnt[0]], ipt_sess.session_count-reuse_existing_count, /* allocate only non-reused sessions */ &ipt_sess.sessions+reuse_existing_count )) < 0) { ERR(MODULE_NAME": rtpproxy_alloc: xt_RTPPROXY_alloc_session error: %s (%d)\n", hi->handle.err_str, hi->handle.err_no); if (aggr_fl) { goto try_next_host; } return -1; } } } if (update_sdp_content(msg, GATE_A_TO_B(flags), &global_sdp_sess, &ipt_sess) < 0) { delete_ipt_sessions(hi, &ipt_sess, &global_params.protected_sess); return -1; } if (ipt_sess.session_count) { ipt_sess.switchboard = find_switchboard_by_addr(&ipt_sess.sessions[0].dir[0].switchboard.addr); if (!ipt_sess.switchboard) { BUG(MODULE_NAME": rtpproxy_alloc: switchboard-a definition not found\n"); return -1; } //ERR("DEBUG_RTPPROXY: module: rtpproxy_alloc: switchboard-a '%.*s'\n", STR_FMT(&ipt_sess.switchboard->name)); global_params.switchboard[0] = ipt_sess.switchboard; global_params.switchboard[1] = find_switchboard_by_addr(&ipt_sess.sessions[0].dir[1].switchboard.addr); if (!global_params.switchboard[1]) { BUG(MODULE_NAME": rtpproxy_alloc: switchboard-b definition not found\n"); return -1; } //ERR("DEBUG_RTPPROXY: module: rtpproxy_alloc: switchboard-b '%.*s'\n", STR_FMT(&global_params.switchboard[1]->name)); atomic_set(&global_params.switchboard[0]->stat->free, ipt_sess.sessions[0].dir[0].switchboard.free); atomic_set(&global_params.switchboard[0]->stat->alloc, ipt_sess.sessions[0].dir[0].switchboard.alloc); if (global_params.switchboard[0] != global_params.switchboard[1]) { atomic_set(&global_params.switchboard[1]->stat->free, ipt_sess.sessions[0].dir[1].switchboard.free); atomic_set(&global_params.switchboard[1]->stat->alloc, ipt_sess.sessions[0].dir[1].switchboard.alloc); } } else { ipt_sess.switchboard = global_params.protected_sess.switchboard; /* we need still keep the same switchboard */ global_params.switchboard[0] = ipt_sess.switchboard; } serialize_ipt_session(&ipt_sess, &s); global_params.session_ids = 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 ipt_session ipt_sess; 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 (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; if (ipt_sess.sdp_media_count != global_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.sdp_media_count, global_sdp_sess.media_count); return -1; } /* first we check for unexpected duplicate source ports */ for (i = 0; i < global_sdp_sess.media_count; i++) { if (ipt_sess.sdp_media[i] >= 0 && global_sdp_sess.media[i].active) { int j; for (j = i+1; j < global_sdp_sess.media_count; j++) { if (ipt_sess.sdp_media[j] >= 0 && global_sdp_sess.media[j].active) { /* if two media streams have equal source address XOR have equal session */ if ( (global_sdp_sess.media[i].ip == global_sdp_sess.media[j].ip && global_sdp_sess.media[i].port == global_sdp_sess.media[j].port) ^ (ipt_sess.sdp_media[i] == ipt_sess.sdp_media[j]) ) { ERR(MODULE_NAME": rtpproxy_update: media (%d,%d) violation number\n", i, j); return -1; } } } } } if (flags & UPDATE_SDP_ONLY_FLAG) { /* get session source address from kernel module, do not update RTP session, only updateSDP content */ for (i = 0; i < ipt_sess.session_count; i++) { ipt_sess.sessions[i].sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_INFO; } } else { /* first we check sessions to delete, the sessions can be "undeleted" if other media still uses session */ for (i = 0; i < global_sdp_sess.media_count; i++) { if (ipt_sess.sdp_media[i] >= 0) { if (!global_sdp_sess.media[i].active) { ipt_sess.sessions[ipt_sess.sdp_media[i]].sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_DESTROY; ipt_sess.sdp_media[i] = -1; } } } for (i = 0; i < global_sdp_sess.media_count; i++) { if (ipt_sess.sdp_media[i] >= 0) { if (global_sdp_sess.media[i].active) { fill_in_session(flags, i, &global_sdp_sess, ipt_sess.sessions+ipt_sess.sdp_media[i]); fill_in_session_throttle(flags, i, ipt_sess.sessions+ipt_sess.sdp_media[i]); ipt_sess.sessions[ipt_sess.sdp_media[i]].sh.flags &= ~XT_RTPPROXY_SOCKOPT_FLAG_SESSION_DESTROY; } } } /* we cannot also delete sessions which have been reused from other session set */ for (i = 0; i < ipt_sess.session_count; i++) { if (ipt_sess.sessions[i].sh.flags & XT_RTPPROXY_SOCKOPT_FLAG_SESSION_DESTROY) { if (ipt_sess.switchboard == global_params.protected_sess.switchboard) { int j; for (j=0; j < global_params.protected_sess.session_count; j++) { if (ipt_sess.sessions[i].dir[0].sess_id == global_params.protected_sess.sessions[j].dir[0].sess_id && ipt_sess.sessions[i].sh.created == global_params.protected_sess.sessions[j].sh.created ) { ipt_sess.sessions[i].sh.flags &= ~XT_RTPPROXY_SOCKOPT_FLAG_SESSION_DESTROY; ipt_sess.sessions[i].sh.flags |= XT_RTPPROXY_SOCKOPT_FLAG_SESSION_INFO; /* or we can remove from sessions TODO */ break; } } } } } } //ERR("DEBUG_RTPPROXY: module: rtpproxy_update: xt_RTPPROXY_update_sessions(%d), flags:%d, switchboard:%p, sess:%.*s\n", ipt_sess.session_count, flags, ipt_sess.switchboard, STR_FMT(&session_ids)); global_params.switchboard[0] = ipt_sess.switchboard; if (ipt_sess.switchboard) { if (check_open_handle(ipt_sess.switchboard->host) < 0) { return -1; } if (check_host_err(ipt_sess.switchboard->host, xt_RTPPROXY_update_sessions(&ipt_sess.switchboard->host->handle, ipt_sess.session_count, &ipt_sess.sessions)) < 0) { ERR(MODULE_NAME": rtpproxy_update: xt_RTPPROXY_update_session error: %s (%d)\n", ipt_sess.switchboard->host->handle.err_str, ipt_sess.switchboard->host->handle.err_no ); /* delete all sessions ? */ return -1; } global_params.switchboard[1] = find_switchboard_by_addr(&ipt_sess.sessions[0].dir[1].switchboard.addr); } else { /* disable media from answer too as we did it in request, port = 0 */ global_params.switchboard[1] = NULL; } if (update_sdp_content(msg, GATE_A_TO_B(flags), &global_sdp_sess, &ipt_sess) < 0) { /* delete all sessions ? */ return -1; } serialize_ipt_session(&ipt_sess, &session_ids); global_params.session_ids = session_ids; /* 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; 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 (!ipt_sess.switchboard) { return 1; } for (i = 0; i < ipt_sess.sdp_media_count; i++) { if (ipt_sess.sdp_media[i] >= 0) { fill_in_session(flags, i, NULL, ipt_sess.sessions+ipt_sess.sdp_media[i]); /* throttle not affected */ } } //ERR("DEBUG_RTPPROXY: module: rtpproxy_adjust_timeout: xt_RTPPROXY_update_sessions(%d), flags:%d, sess:%.*s\n", ipt_sess.session_count, flags, STR_FMT(&session_ids)); if (check_open_handle(ipt_sess.switchboard->host) < 0) { return -1; } if (check_host_err(ipt_sess.switchboard->host, xt_RTPPROXY_update_sessions(&ipt_sess.switchboard->host->handle, ipt_sess.session_count, &ipt_sess.sessions)) < 0) { ERR(MODULE_NAME": rtpproxy_adjust_timeout: xt_RTPPROXY_adjust_timeout error: %s (%d)\n", ipt_sess.switchboard->host->handle.err_str, ipt_sess.switchboard->host->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 (!session_ids.len) return 1; if (unserialize_ipt_session(&session_ids, &ipt_sess) < 0) { return -1; } //ERR("DEBUG_RTPPROXY: module: rtpproxy_delete: sess:%.*s\n", STR_FMT(&session_ids)); if (!ipt_sess.switchboard) { return 1; /* nothing to delete */ } if (check_open_handle(ipt_sess.switchboard->host) < 0) { return -1; } delete_ipt_sessions(ipt_sess.switchboard->host, &ipt_sess, &global_params.protected_sess); /* do not serialize sessions because it affect static buffer and more valuable values disappears */ return 1; } static int rtpproxy_authorize_media(struct sip_msg* msg, char* _dummy1, char* _dummy2) { unsigned int media_count[MAX_MEDIA_NUMBER]; int i; if (!global_params.codec_set) return 1; if (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; global_params.auth_rights = 0; memset(&media_count, 0, sizeof(media_count)); for (i=0; imedia_types[global_sdp_sess.media[i].media_type].throttle.max_streams; if (fl) { goto remove_stream; } for (j=0; jmline_payload_type_s.s) continue; r = (*global_params.codec_set->media_types[global_sdp_sess.media[i].media_type].codec_rights)[c->codec_id]; if (r) { if (r > global_params.auth_rights) { global_params.auth_rights = r; } if (r & global_params.remove_codec_mask) { /* remove codec */ n++; if (n < global_sdp_sess.media[i].codec_count) { /* if it's last remainng codec then leave it and remove stream */ if (prepare_lumps(msg, &c->mline_payload_type_s, NULL) < 0) return -1; c->mline_payload_type_s.s = NULL; /* mark as removed */ if (prepare_lumps(msg, &c->a_rtpmap_line_s, NULL) < 0) return -1; if (prepare_lumps(msg, &c->a_fmtp_line_s, NULL) < 0) return -1; } } } } remove_stream: if (n == global_sdp_sess.media[i].codec_count || fl) { /* remove the stream */ static str zero_s = STR_STATIC_INIT("0"); if (prepare_lumps(msg, & global_sdp_sess.media[i].port_s, &zero_s) < 0) return -1; global_sdp_sess.media[i].active = 0; continue; } media_count[global_sdp_sess.media[i].media_type]++; } return 1; } static int rtpproxy_set_param(struct sip_msg* msg, char* _idx, char* _value) { int idx, dir; unsigned int ip; idx = PTR2INT(_idx); union { str s; int i; } u; dir = (param_list[idx].flags & PAR_DIR) != 0; if (param_list[idx].flags & PAR_INT) { if (get_int_fparam(&u.i, msg, (fparam_t*) _value) < 0) { return -1; } } else { if (get_str_fparam(&u.s, msg, (fparam_t*) _value) < 0) { return -1; } } switch (param_list[idx].id) { case PAR_EXPIRATION_TIMEOUT: global_params.expiration_timeout = u.i; break; case PAR_TTL: global_params.ttl = u.i; break; case PAR_LEARNING_TIMEOUT: global_params.learning_timeout = u.i; break; case PAR_ALWAYS_LEARN: global_params.always_learn = u.i; break; case PAR_SWITCHBOARD_A: case PAR_SWITCHBOARD_B: if (!(global_params.switchboard[dir] = find_switchboard(&u.s, NULL))) return -1; break; case PAR_SWITCHBOARD_BY_SIP_IP_A: case PAR_SWITCHBOARD_BY_SIP_IP_B: ip = s2ip4(&u.s); for (global_params.switchboard[dir] = switchboards; global_params.switchboard[dir]; global_params.switchboard[dir] = global_params.switchboard[dir]->next) { if (ip == global_params.switchboard[dir]->sip_ip) { global_params.aggregation[dir] = NULL; /* invalidate aggregation */ return 1; } } return -1; case PAR_AGGREGATION_A: case PAR_AGGREGATION_B: if (!(global_params.aggregation[dir] = find_aggregation(&u.s, NULL))) return -1; break; case PAR_AGGREGATION_BY_SIP_IP_A: case PAR_AGGREGATION_BY_SIP_IP_B: ip = s2ip4(&u.s); for (global_params.aggregation[dir] = aggregations; global_params.aggregation[dir]; global_params.aggregation[dir] = global_params.aggregation[dir]->next) { if (ip == global_params.aggregation[dir]->sip_ip) { global_params.switchboard[dir] = NULL; /* invalidate switchboard */ return 1; } } return -1; case PAR_THROTTLE_MARK: global_params.throttle.mark = u.i; break; case PAR_THROTTLE_RTP_MAX_BYTES: case PAR_THROTTLE_RTCP_MAX_BYTES: global_params.throttle.bandwidth[param_list[idx].id==PAR_THROTTLE_RTCP_MAX_BYTES].bytes = u.i; break; case PAR_THROTTLE_RTP_MAX_PACKETS: case PAR_THROTTLE_RTCP_MAX_PACKETS: global_params.throttle.bandwidth[param_list[idx].id==PAR_THROTTLE_RTCP_MAX_PACKETS].packets = u.i; break; case PAR_CODEC_SET: if (!(global_params.codec_set = find_codec_set(&u.s, NULL))) return -1; break; case PAR_REMOVE_CODEC_MASK: global_params.remove_codec_mask = u.i; break; case PAR_OLINE_USER: { static char buf[30]; if (u.s.len > sizeof(buf)) { return -1; } global_params.oline_user.len = u.s.len; if (u.s.len) { memcpy(buf, u.s.s, u.s.len); global_params.oline_user.s = buf; } break; } case PAR_OLINE_ADDR: { static char buf[30]; if (u.s.len > sizeof(buf)) { return -1; } global_params.oline_addr.len = u.s.len; if (u.s.len) { memcpy(buf, u.s.s, u.s.len); global_params.oline_addr.s = buf; } break; } case PAR_PROTECTED_SESSION_IDS: memset(&global_params.protected_sess, 0, sizeof(global_params.protected_sess)); if (!u.s.len) break; if (unserialize_ipt_session(&u.s, &global_params.protected_sess) < 0) { return -1; } if (!global_params.protected_sess.session_count) { global_params.protected_sess.switchboard = NULL; } break; default: ; } return 1; } /* @select implementation */ static int sel_rtpproxy(str* res, select_t* s, struct sip_msg* msg) { /* dummy */ union {int i; str s;} u; int dir, idx; u.s.len = 0; if (msg == NULL && res == NULL) { /* fixup call */ int idx; idx = param2idx(&s->params[1].v.s, PAR_READ); if (idx < 0) { return -1; } s->params[1].v.i = idx; s->params[1].type = SEL_PARAM_DIV; return 1; } idx = s->params[1].v.i; dir = (param_list[idx].flags & PAR_DIR) != 0; switch (param_list[idx].id) { case PAR_EXPIRATION_TIMEOUT: u.i = global_params.expiration_timeout; break; case PAR_TTL: u.i = global_params.ttl; break; case PAR_LEARNING_TIMEOUT: u.i = global_params.learning_timeout; break; case PAR_ALWAYS_LEARN: u.i = global_params.always_learn; break; case PAR_SWITCHBOARD_A: case PAR_SWITCHBOARD_B: if (global_params.switchboard[dir]) u.s = global_params.switchboard[dir]->name; break; case PAR_AGGREGATION_A: case PAR_AGGREGATION_B: if (global_params.aggregation[dir]) u.s = global_params.aggregation[dir]->name; break; case PAR_SDP_IP: u.s = global_params.sdp_ip; break; case PAR_ACTIVE_MEDIA_NUM: { int i; if (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; u.i = 0; for (i = 0; i < global_sdp_sess.media_count; i++) { if (global_sdp_sess.media[i].active) { u.i++; } } break; } case PAR_OLINE_USER: if (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; u.s = global_sdp_sess.oline_user_s; break; case PAR_OLINE_ADDR: if (check_parse_sdp_content(msg, &global_sdp_sess) < 0) return -1; u.s = global_sdp_sess.oline_addr_s; break; case PAR_SESSION_IDS: u.s = global_params.session_ids; break; case PAR_THROTTLE_MARK: u.i = global_params.throttle.mark; break; case PAR_THROTTLE_RTP_MAX_BYTES: case PAR_THROTTLE_RTCP_MAX_BYTES: u.i = global_params.throttle.bandwidth[param_list[idx].id==PAR_THROTTLE_RTCP_MAX_BYTES].bytes; break; case PAR_THROTTLE_RTP_MAX_PACKETS: case PAR_THROTTLE_RTCP_MAX_PACKETS: u.i = global_params.throttle.bandwidth[param_list[idx].id==PAR_THROTTLE_RTCP_MAX_PACKETS].packets; break; case PAR_CODEC_SET: if (global_params.codec_set) u.s = global_params.codec_set->name; break; case PAR_AUTH_RIGHTS: u.i = global_params.auth_rights; break; case PAR_REMOVE_CODEC_MASK: u.i = global_params.remove_codec_mask; break; default: ; } if (param_list[idx].flags & PAR_STR) { if (!u.s.len) return 1; *res = u.s; } else { return uint_to_static_buffer(res, u.i); } return 0; } select_row_t sel_declaration[] = { { NULL, SEL_PARAM_STR, STR_STATIC_INIT(MODULE_NAME), sel_rtpproxy, CONSUME_NEXT_STR | FIXUP_CALL }, { NULL, SEL_PARAM_INT, STR_NULL, NULL, 0} }; static int mod_pre_script_cb(struct sip_msg *msg, unsigned int flags, void *param) { memset(&global_params, 0, sizeof(global_params)); global_params.always_learn = -1; global_params.ttl = -1; sdp_parsed = 999; /* any number not in (-1,0) */ return 1; } static struct { enum {iptrtpproxy_default=0x01, iptrtpproxy_switchboard=0x02, iptrtpproxy_host=0x04} flag; str name; struct { struct { struct xt_rtpproxy_switchboard_id addr; str host; } switchboard; struct xt_rtpproxy_connection_rpc_params host; } dflt, parsed; } parse_config_vals; static int cfg_parse_addr(void* param, cfg_parser_t* st, unsigned int flags) { str val; char buff[50]; val.s = buff; val.len = sizeof(buff)-1; if (cfg_parse_str(&val, st, CFG_EXTENDED_ALPHA|CFG_STR_STATIC) < 0) return -1; *(uint32_t*)param = s2ip4(&val); if (*(uint32_t*)param == 0) { ERR(MODULE_NAME": parse_addr: bad ip address '%.*s'\n", STR_FMT(&val)); return -1; } return 0; } static int cfg_parse_uint16(void* param, cfg_parser_t* st, unsigned int flags) { int val; if (cfg_parse_int(&val, st, 0) < 0) return -1; *(uint16_t *) param = val; return 0; } static int cfg_parse_default(void* param, cfg_parser_t* st, unsigned int flags) { int ret; cfg_token_t t; str val, tok; cfg_option_t* opt; char buf[MAX_TOKEN_LEN]; tok.s = buf; if (st->cur_opt->val.len >= sizeof(buf)-1) goto skip; memcpy(tok.s, st->cur_opt->val.s, st->cur_opt->val.len); tok.len = st->cur_opt->val.len; /* we need process here options containing dash '-' because of too strict parser */ while (1) { ret = cfg_get_token(&t, st, 0); if (ret < 0) return ret; if (ret > 0) return 0; if (t.type == '=') break; if (tok.len+t.val.len >= sizeof(buf)-1) goto skip; memcpy(tok.s+tok.len, t.val.s, t.val.len); tok.len += t.val.len; } tok.s[tok.len] = '\0'; if ((opt = cfg_lookup_token(st->options+1/*2nd pass*/, &tok)) && ((opt->flags & CFG_DEFAULT)== 0)) { st->cur_opt = &t; if (opt->f(opt->param, st, opt->flags) < 0) return -1; return 0; } skip: if (cfg_parse_str(&val, st, CFG_EXTENDED_ALPHA) < 0) return -1; return 0; } static int safe_parsed_values() { #define PROC_DEFAULT(_f_, _def_) \ if (!parse_config_vals.parsed._f_) {\ parse_config_vals.parsed._f_ = parse_config_vals.dflt._f_?parse_config_vals.dflt._f_:_def_; \ } if (parse_config_vals.flag & iptrtpproxy_default) { if (parse_config_vals.flag & iptrtpproxy_switchboard) parse_config_vals.dflt.switchboard = parse_config_vals.parsed.switchboard; else if (parse_config_vals.flag & iptrtpproxy_host) parse_config_vals.dflt.host = parse_config_vals.parsed.host; } else if (parse_config_vals.flag) { int fl, max_len, i; struct switchboard_item **prev_si = NULL; struct host_item **prev_hi = NULL; char *s; if (parse_config_vals.flag & iptrtpproxy_switchboard) { fl = find_switchboard(&parse_config_vals.name, &prev_si) != NULL; s = "switchboard"; max_len = MAX_SWITCHBOARD_NAME_LEN; } else { struct host_item *p; p = find_host(&parse_config_vals.name, &prev_hi); fl = p != NULL; s = "host"; max_len = HOST_NAME_MAX; if (p && p->local) { pkg_free(parse_config_vals.name.s); goto local; } } if (fl) { ERR(MODULE_NAME": safe_parsed_values: %s name '%.*s' already declared\n", s, STR_FMT(&parse_config_vals.name)); return -1; } for (i=0; i max_len) { ERR(MODULE_NAME": safe_parsed_values: %s name '%.*s' is too long (%d>%d)\n", s, STR_FMT(&parse_config_vals.name), parse_config_vals.name.len, max_len); return -1; } if (parse_config_vals.flag & iptrtpproxy_switchboard) { struct switchboard_item *si; si = pkg_malloc(sizeof(*si)); if (!si) goto out_of_mem; memset(si, 0, sizeof(*si)); si->name = parse_config_vals.name; si->next = (*prev_si); (*prev_si) = si; PROC_DEFAULT(switchboard.addr.ip, 0); PROC_DEFAULT(switchboard.addr.port, 0); if (parse_config_vals.parsed.switchboard.host.len) { si->hostname = parse_config_vals.parsed.switchboard.host; } else { si->hostname = parse_config_vals.dflt.switchboard.host; } if (!si->hostname.len) { si->hostname.s = iptrtpproxy_cfg_hostname; si->hostname.len = strlen(si->hostname.s); } si->switchboard_addr = parse_config_vals.parsed.switchboard.addr; } else { struct host_item *hi; hi = pkg_malloc(sizeof(*hi)); if (!hi) goto out_of_mem; memset(hi, 0, sizeof(*hi)); hi->name = parse_config_vals.name; hi->next = (*prev_hi); (*prev_hi) = hi; PROC_DEFAULT(host.addr, 0); PROC_DEFAULT(host.port, 0); PROC_DEFAULT(host.request_size, XT_RTPPROXY_RPC_DEFAULT_REQUEST_SIZE); PROC_DEFAULT(host.reply_size, XT_RTPPROXY_RPC_DEFAULT_REPLY_SIZE); PROC_DEFAULT(host.total_timeout, XT_RTPPROXY_RPC_DEFAULT_TOTAL_TIMEOUT); PROC_DEFAULT(host.udp_retry_timeout, XT_RTPPROXY_RPC_DEFAULT_UDP_REPLY_TIMEOUT); hi->rpc_params = parse_config_vals.parsed.host; } } local: memset(&parse_config_vals.parsed, 0, sizeof(parse_config_vals.parsed)); memset(&parse_config_vals.name, 0, sizeof(parse_config_vals.name)); return 0; out_of_mem: ERR(MODULE_NAME": safe_parsed_values: not enough pkg memory\n"); return -1; } static cfg_option_t section_switchboard_options[] = { /* 1st pass */ {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default}, /* 2nd pass */ {"addr", .f = cfg_parse_addr, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.switchboard.addr.ip}, {"port", .f = cfg_parse_uint16, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.switchboard.addr.port}, {"host", .f = cfg_parse_str, .flags = CFG_CASE_SENSITIVE|CFG_STR_PKGMEM, .param = &parse_config_vals.parsed.switchboard.host}, {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default} }; static cfg_option_t protos[] = { {"udp", .param = &parse_config_vals.parsed.host.proto, .val = xt_rtpproxy_connection_UDP}, {"tcp", .param = &parse_config_vals.parsed.host.proto, .val = xt_rtpproxy_connection_TCP}, {NULL, .flags = 0} }; static cfg_option_t section_host_options[] = { /* 1st pass */ {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default}, /* 2nd pass */ {"rpc-addr", .f = cfg_parse_addr, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.addr}, {"rpc-port", .f = cfg_parse_uint16, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.port}, {"rpc-proto", .f = cfg_parse_enum, .flags = CFG_CASE_SENSITIVE, .param = protos}, {"rpc-request-size", .f = cfg_parse_int, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.request_size}, {"rpc-reply-size", .f = cfg_parse_int, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.reply_size}, {"rpc-total-timeout", .f = cfg_parse_int, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.total_timeout}, {"rpc-udp-retry-timeout", .f = cfg_parse_int, .flags = CFG_CASE_SENSITIVE, .param = &parse_config_vals.parsed.host.udp_retry_timeout}, {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default} }; static cfg_option_t section_dummy_options[] = { /* 1st pass */ {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default}, /* 2nd pass */ {NULL, .flags = CFG_DEFAULT, .f = cfg_parse_default} }; #define DEFAULT_SECTION "default" #define SWITCHBOARD_PREFIX "switchboard" #define HOST_PREFIX "host" static int parse_section_name(void* param, cfg_parser_t* st, unsigned int flags) { cfg_token_t t; int ret, fl; ret = safe_parsed_values(); if (ret != 0) return ret; cfg_set_options(st, section_dummy_options); 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 = iptrtpproxy_default; else if (t.val.len == (sizeof(SWITCHBOARD_PREFIX)-1) && strncmp(t.val.s, SWITCHBOARD_PREFIX, t.val.len) == 0) { fl = iptrtpproxy_switchboard; } else if (t.val.len == (sizeof(HOST_PREFIX)-1) && strncmp(t.val.s, HOST_PREFIX, t.val.len) == 0) { fl = iptrtpproxy_host; } else goto skip; ret = cfg_get_token(&t, st, 0); if (ret != 0) return ret; if (t.type != ':') goto skip; ret = cfg_parse_section(&parse_config_vals.name, st, CFG_STR_PKGMEM); if (ret != 0) return ret; if (fl==iptrtpproxy_default) { if (parse_config_vals.name.len == (sizeof(SWITCHBOARD_PREFIX)-1) && strncmp(parse_config_vals.name.s, SWITCHBOARD_PREFIX, parse_config_vals.name.len) == 0) { fl |= iptrtpproxy_switchboard; } else if (parse_config_vals.name.len == (sizeof(HOST_PREFIX)-1) && strncmp(parse_config_vals.name.s, HOST_PREFIX, parse_config_vals.name.len) == 0) { fl |= iptrtpproxy_host; } else { goto skip; } if (parse_config_vals.name.s) { pkg_free(parse_config_vals.name.s); parse_config_vals.name.s = NULL; } } cfg_set_options(st, section_dummy_options); if (fl) { if (fl & iptrtpproxy_switchboard) { cfg_set_options(st, section_switchboard_options); } else if (fl & iptrtpproxy_host) { cfg_set_options(st, section_host_options); } parse_config_vals.flag = fl; } 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; static char buf[HOST_NAME_MAX+1]; struct switchboard_item *si; if (!iptrtpproxy_cfg_hostname || !strlen(iptrtpproxy_cfg_hostname)) { if (gethostname(buf, sizeof(buf)-1) < 0) { ERR(MODULE_NAME"parse_iptrtpproxy_cfg: gethostname error\n"); return E_CFG; } iptrtpproxy_cfg_hostname = buf; } hosts = pkg_malloc(sizeof(*hosts)); if (!hosts) return -1; memset(hosts, 0, sizeof(*hosts)); hosts->name.s = iptrtpproxy_cfg_hostname; hosts->name.len = strlen(hosts->name.s); hosts->local = 1; 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_section_name, NULL); memset(&parse_config_vals, 0, sizeof(parse_config_vals)); if (sr_cfg_parse(parser)) { return -1; } cfg_parser_close(parser); if (safe_parsed_values() < 0) { return -1; } for (si = switchboards; si; si = si->next) { si->host = find_host(&si->hostname, NULL); if (!si->host) { ERR(MODULE_NAME"parse_iptrtpproxy_cfg: host '%.*s' not found.\n", STR_FMT(&si->hostname)); return -1; } si->sip_ip = si->switchboard_addr.ip; } return 0; } static struct { char *name; int payload_type; unsigned int media_type; int clock_rate; int channels; } def_codecs [] = { {"PCMU", 0, 1<next) { str ips[2]; char buf1[17]; si->stat = shm_malloc(sizeof(*si->stat)); if (!si->stat) return E_OUT_OF_MEM; memset(si->stat, 0, sizeof(*si->stat)); ip42s(si->switchboard_addr.ip, ips+0); strncpy(buf1, ips[0].s, sizeof(buf1)-1); ips[0].s = buf1; ip42s(si->sip_ip, ips+1); INFO(MODULE_NAME": mod_init: switchboard_name=%.*s;addr=%.*s;port=%d;sip-addr=%.*s;hostname=%.*s\n", STR_FMT(&si->name), STR_FMT(ips+0), si->switchboard_addr.port, STR_FMT(ips+1), STR_FMT(&si->hostname) ); } for (ai = aggregations; ai; ai=ai->next) { str ips[1]; ip42s(ai->sip_ip, ips+0); INFO(MODULE_NAME": mod_init: aggregation '%.*s';sip-addr=%.*s\n", STR_FMT(&ai->name), STR_FMT(ips+0) ); for (i=0; iswitchboard_count; i++) { ERR(MODULE_NAME": mod_init: '%.*s'\n", STR_FMT(&(*ai->switchboards)[i]->name)); } } for (hi = hosts; hi; hi=hi->next) { str ips; hi->stat = shm_malloc(sizeof(*hi->stat)); if (!hi->stat) return E_OUT_OF_MEM; memset(hi->stat, 0, sizeof(*hi->stat)); ip42s(hi->rpc_params.addr, &ips); INFO(MODULE_NAME": mod_init: host_name=%.*s;rpc-addr=%.*s;rpc-port=%d;rpc-proto=%d,request-size=%d,reply-size=%d,total-timeout=%d,udp-retry-timeout=%d\n", STR_FMT(&hi->name), STR_FMT(&ips), hi->rpc_params.port, hi->rpc_params.proto, hi->rpc_params.request_size, hi->rpc_params.reply_size, hi->rpc_params.total_timeout, hi->rpc_params.udp_retry_timeout ); } if (!reg_codecs) { int r; if ((r = declare_def_codecs()) < 0) { return r; } } memset(&fixed_payload_types, 0, sizeof(fixed_payload_types)); i = 0; while (def_codecs[i].name && def_codecs[i].payload_type >= 0) { str s; int codec_id; s.s = def_codecs[i].name; s.len = strlen(s.s); codec_id = name2codec_id(&s, NULL); if (!codec_id) { BUG(MODULE_NAME": mod_init: def.codec '%s' not found\n", s.s); return -1; } fixed_payload_types[def_codecs[i].payload_type].codec_id = codec_id; i++; } #if 0 for (i=0; inext) { int media_type; INFO(MODULE_NAME": mod_init: codec_set='%.*s'\n", STR_FMT(&ci->name)); for (media_type=0; media_typemedia_types[media_type].throttle.max_streams, ci->media_types[media_type].throttle.bandwidth[0].bytes, ci->media_types[media_type].throttle.bandwidth[1].bytes, ci->media_types[media_type].throttle.bandwidth[0].packets, ci->media_types[media_type].throttle.bandwidth[1].packets ); if (ci->media_types[media_type].throttle.max_streams != 0) { for (i=0; imedia_types[media_type].codec_rights)[i] > 0) { if (i > 0) INFO(MODULE_NAME": mod_init: codec='%.*s', right=%d\n", STR_FMT(&(*reg_codecs)[i-1].name), (*ci->media_types[media_type].codec_rights)[i]); else INFO(MODULE_NAME": mod_init: codec='?', right=%d\n", (*ci->media_types[media_type].codec_rights)[i]); } } } } } /*register_script_cb(mod_pre_script_cb, REQ_TYPE_CB | RPL_TYPE_CB| PRE_SCRIPT_CB, 0);*/ register_script_cb(mod_pre_script_cb, REQUEST_CB | ONREPLY_CB| PRE_SCRIPT_CB, 0); register_select_table(sel_declaration); return 0; } static void mod_cleanup(void) { struct host_item *hi; struct switchboard_item *si; for (si = switchboards; si; si = si->next) { if (si->stat) { shm_free(si->stat); si->stat = NULL; } } for (hi = hosts; hi; hi = hi->next) { if (hi->handle_is_opened) { xt_RTPPROXY_close(&hi->handle); hi->handle_is_opened = 0; } if (hi->stat) { shm_free(hi->stat); hi->stat = NULL; } } } 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 <= 1) { iptrtpproxy_cfg_flag = 2; iptrtpproxy_cfg_filename = * (str*) val; if (parse_iptrtpproxy_cfg() == 0) return 0; } else { switch (iptrtpproxy_cfg_flag) { case 2: ERR(MODULE_NAME": declare_config: 'config' param may be used only once\n"); break; case 3: ERR(MODULE_NAME": declare_config: 'config' param may not be used after 'switchboard'\n"); break; default: BUG(MODULE_NAME": declare_config: unexpected 'iptrtpproxy_cfg_flag' value %d\n", iptrtpproxy_cfg_flag); } } return E_CFG; } static int declare_hostname(modparam_t type, void* val) { if (!val) return 0; if (iptrtpproxy_cfg_flag == 0) { iptrtpproxy_cfg_hostname = (char*) val; return 0; } else { switch (iptrtpproxy_cfg_flag) { case 1: ERR(MODULE_NAME": declare_hostname: 'hostname' param may be used only once\n"); break; case 2: case 3: ERR(MODULE_NAME": declare_hostname: 'hostname' param may not be used after 'switchboard' or 'config'\n"); break; default: BUG(MODULE_NAME": declare_hostname: unexpected 'iptrtpproxy_cfg_flag' value %d\n", iptrtpproxy_cfg_flag); } } return E_CFG; } struct parse_param_item { char *name; unsigned int id; }; /* returns index to items, -1 if error or param not found */ static int parse_next_param(char **s, struct parse_param_item (*params)[], str *val) { str p; int i; char *c; eat_spaces(*s); c = *s; while ( is_alpha(*c) ) { c++; } if (c == *s) { ERR(MODULE_NAME": parse_next_param: param name expected near '%s'\n", *s); return -1; } p.s = *s; p.len = c-*s; eat_spaces(c); *s = c; if (*c != '=') { ERR(MODULE_NAME": parse_next_param: equal char expected near '%s'\n", *s); return -1; } 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); *s = c; for (i=0; (*params)[i].name; i++) { if (strlen((*params)[i].name)==p.len && strncasecmp((*params)[i].name, p.s, p.len) == 0) { return i; } } ERR(MODULE_NAME": parse_next_param: unknown param name '%.*s'\n", STR_FMT(&p)); return -1; } static int declare_switchboard_param(modparam_t type, void* val) { char *s; int all_flag; struct switchboard_item *si = NULL; enum param_id { par_Name = 0x000001, par_Aggregation = 0x000002, par_SipAddr = 0x000004 }; static struct parse_param_item params[] = { {.name = "name", .id = par_Name}, {.name = "sip-addr", .id = par_SipAddr}, {.name = "aggregation", .id = par_Aggregation}, {.name = 0, .id = 0} }; if (!val) return 0; if (iptrtpproxy_cfg_flag <= 1) { iptrtpproxy_cfg_flag = 3; if (parse_iptrtpproxy_cfg() < 0) return E_CFG; } s = val; all_flag = -1; eat_spaces(s); if (!*s) return 0; /* parse param: name=;aggregation=;sip-addr= */ while (*s) { str val; int idx; idx = parse_next_param(&s, ¶ms, &val); if (idx < 0) goto err_E_CFG; if (all_flag >= 0 && params[idx].id == par_Name) { ERR(MODULE_NAME": declare_switchboard_param: name must be the first param\n"); goto err_E_CFG; } if (params[idx].id == par_Name) { all_flag = 0; si = find_switchboard(&val, NULL); if (!si) { if (val.len == 1 && val.s[0] == '*') all_flag = 1; else { ERR(MODULE_NAME": declare_switchboard_param: switchboard '%.*s' not found\n", STR_FMT(&val)); goto err_E_CFG; } } } else { if (all_flag) si = switchboards; while (si) { switch (params[idx].id) { case par_Name: break; case par_Aggregation: { struct aggregation_item *ai; struct aggregation_item **prev_ai; int i; ai = find_aggregation(&val, &prev_ai); if (!ai) { ai = pkg_malloc(sizeof(*ai)); if (!ai) return E_OUT_OF_MEM; memset(ai, 0, sizeof(*ai)); ai->name = val; ai->next = (*prev_ai); (*prev_ai) = ai; } for (i=0; iswitchboard_count; i++) { if ((*ai->switchboards)[i] == si) goto aggr_found; } if (!ai->sip_ip) { ai->sip_ip = si->sip_ip; } ai->switchboards = pkg_realloc(ai->switchboards, sizeof((*ai->switchboards)[0])*(ai->switchboard_count+1)); if (!ai->switchboards) return E_OUT_OF_MEM; (*ai->switchboards)[ai->switchboard_count] = si; ai->switchboard_count++; aggr_found: break; } case par_SipAddr: si->sip_ip = s2ip4(&val); if (si->sip_ip == 0) { goto err_E_CFG; } break; default: BUG(MODULE_NAME": declare_switchboard_param: unknown id '%x\n", idx); goto err_E_CFG; } if (!all_flag) break; si = si->next; } } } if (all_flag) { return 0; } 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 int declare_codec(modparam_t type, void* val) { int r; str *s; s = val; if (!s || !s->len) return 0; if (codec_sets) { ERR(MODULE_NAME": declare_codec: codec declaration cannot follow codec set declaration\n"); return E_CFG; } if (!reg_codecs) { int r; if ((r = declare_def_codecs()) < 0) { return r; } } r = register_codec(s); if (r < 0) return r; return 0; } static int declare_codec_set(modparam_t type, void* val) { char *s; unsigned int cur_rights = 0; unsigned int cur_media_type = 0; struct codec_set_item *ci = NULL; enum param_id { par_Name = 0x000001, par_Rights = 0x000002, par_Codecs = 0x000003, par_MediaType = 0x000004, par_MaxStreams = 0x000005, par_ThrottleRTPBytes = 0x000100, par_ThrottleRTCPBytes = 0x000101, par_ThrottleRTPPackets = 0x000200, par_ThrottleRTCPPackets = 0x000201 }; static struct parse_param_item params[] = { {.name = "name", .id = par_Name}, {.name = "media_type", .id = par_MediaType}, {.name = "rights", .id = par_Rights}, {.name = "codecs", .id = par_Codecs}, {.name = "max_streams", .id = par_MaxStreams}, {.name = "rtp_bytes", .id = par_ThrottleRTPBytes}, {.name = "rtcp_bytes", .id = par_ThrottleRTCPBytes}, {.name = "rtp_packets", .id = par_ThrottleRTPPackets}, {.name = "rtcp_packets", .id = par_ThrottleRTCPPackets}, {.name = 0, .id = 0} }; if (!val) return 0; if (!reg_codecs) { int r; if ((r = declare_def_codecs()) < 0) { return r; } } s = val; eat_spaces(s); if (!*s) return 0; /* parse param: name=;media_type=audio,video;rights=;codecs=,,..;max_streams=2 */ while (*s) { str val; int idx, media_type; char *p, *pend; struct codec_set_item **prev_ci; idx = parse_next_param(&s, ¶ms, &val); if (idx < 0) return E_CFG; if (params[idx].id != par_Name && !ci) { ERR(MODULE_NAME": declare_codec_set: name must be the first param\n"); return E_CFG; } switch (params[idx].id) { case par_Name: ci = find_codec_set(&val, &prev_ci); if (!ci) { int media_type; ci = pkg_malloc(sizeof(*ci)); if (!ci) return E_OUT_OF_MEM; memset(ci, 0, sizeof(*ci)); for (media_type=0; media_typemedia_types[media_type].codec_rights = pkg_malloc(sizeof((*ci->media_types[media_type].codec_rights)[0])*(reg_codec_count+1)); if (!ci->media_types[media_type].codec_rights) return E_OUT_OF_MEM; memset(ci->media_types[media_type].codec_rights, 0, sizeof((*ci->media_types[media_type].codec_rights)[0])*(reg_codec_count+1)); ci->media_types[media_type].throttle.max_streams = -1; } ci->name = val; ci->next = (*prev_ci); (*prev_ci) = ci; } break; case par_MediaType: cur_media_type = 0; if (val.len == 1 && val.s[0] == '*') { for (media_type=0; media_typemedia_types[media_type].throttle.max_streams = atol(val.s); } } break; case par_ThrottleRTPBytes: case par_ThrottleRTCPBytes: val.s[val.len] = '\0'; /* we need not save value, it's already parsed */ for (media_type=0; media_typemedia_types[media_type].throttle.bandwidth[params[idx].id&1].bytes = atol(val.s); } } break; case par_ThrottleRTPPackets: case par_ThrottleRTCPPackets: val.s[val.len] = '\0'; /* we need not save value, it's already parsed */ for (media_type=0; media_typemedia_types[media_type].throttle.bandwidth[params[idx].id&1].packets = atol(val.s); } } break; case par_Codecs: if (val.len == 1 && val.s[0] == '*') { int codec_id; for (media_type=0; media_typemedia_types[media_type].codec_rights)[codec_id] = cur_rights; } } } } else { p = val.s; pend = p + val.len; while (p < pend) { str s2; s2.s = p; while (p < pend && is_alpha(*p)) p++; s2.len = p - s2.s; for (media_type=0; media_typemedia_types[media_type].codec_rights)[name2codec_id(&s2, NULL)] = cur_rights; } } while (p < pend && !is_alpha(*p)) p++; } } break; default: BUG(MODULE_NAME": declare_codec_set: unknown id '%x\n", params[idx].id); return E_CFG; } } return 0; } static cmd_export_t cmds[] = { {MODULE_NAME "_alloc", rtpproxy_alloc, 1, rtpproxy_alloc_update_fixup, REQUEST_ROUTE | ONREPLY_ROUTE }, {MODULE_NAME "_update", rtpproxy_update, 2, rtpproxy_alloc_update_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE }, {MODULE_NAME "_adjust_timeout", rtpproxy_adjust_timeout, 2, rtpproxy_alloc_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 "_set_param", rtpproxy_set_param, 2, rtpproxy_set_param_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE }, {MODULE_NAME "_authorize_media", rtpproxy_authorize_media, 0, NULL, REQUEST_ROUTE | ONREPLY_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}, {"hostname", PARAM_STRING | PARAM_USE_FUNC, &declare_hostname}, {"codec_set", PARAM_STRING | PARAM_USE_FUNC, &declare_codec_set}, {"rpc_heartbeat_timeout", PARAM_INT, &rpc_heartbeat_timeout}, {"declare_codec", PARAM_STR | PARAM_USE_FUNC, &declare_codec}, {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 */ #ifdef IPT_RTPPROXY_IPTABLES_API #include #undef IPT_RTPPROXY_IPTABLES_API #else #include #endif #include #ifdef xtables_error /* iptables 1.4.8 */ struct xtables_globals *xt_params = NULL; int xtables_check_inverse(const char option[], int *invert, int *optind, int argc, char **argv) { return FALSE; } void xtables_register_target(struct xtables_target *me) { } #else /* xtables_error */ #ifdef _IPTABLES_COMMON_H /* old iptables API, it uses iptables_common.h (instead of xtables.h) included from iptables.h */ /* #ifndef XTABLES_VERSION ... optional test */ #define IPT_RTPPROXY_IPTABLES_API 1 #endif #ifdef IPT_RTPPROXY_IPTABLES_API void register_target(struct iptables_target *me) { } #else void xtables_register_target(struct xtables_target *me) { } #endif #if IPT_RTPPROXY_IPTABLES_API void exit_error(enum exittype status, char *msg, ...) #else void exit_error(enum exittype status, const char *msg, ...) #endif { va_list args; va_start(args, msg); // ERR(msg/*, args*/); /* TODO: how to pass ... to macro? */ ERR(MODULE_NAME": %s", msg); va_end(args); } int check_inverse(const char option[], int *invert, int *optind, int argc) { return 0; } #endif /* xtables_error */ #endif