mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1781 lines
62 KiB
1781 lines
62 KiB
/*
|
|
* Copyright (C) 2010-2011 Stefan Sayer
|
|
*
|
|
* This file is part of SEMS, a free SIP media server.
|
|
*
|
|
* SEMS 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 SEMS 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
|
|
*
|
|
* SEMS 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 "SBCCallProfile.h"
|
|
#include "SBC.h"
|
|
#include <algorithm>
|
|
|
|
#include "log.h"
|
|
#include "AmUtils.h"
|
|
#include "AmPlugIn.h"
|
|
#include "AmConfig.h"
|
|
|
|
#include "SBCCallControlAPI.h"
|
|
#include "RTPParameters.h"
|
|
#include "SDPFilter.h"
|
|
#include "RegisterCache.h"
|
|
|
|
#include "sip/pcap_logger.h"
|
|
|
|
typedef vector<SdpPayload>::iterator PayloadIterator;
|
|
static string payload2str(const SdpPayload &p);
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
// helper defines for parameter evaluation
|
|
|
|
#define REPLACE_VALS req, app_param, ruri_parser, from_parser, to_parser
|
|
|
|
// FIXME: r_type in replaceParameters is just for debug output?
|
|
|
|
#define REPLACE_STR(what) do { \
|
|
what = ctx.replaceParameters(what, #what, req); \
|
|
DBG(#what " = '%s'\n", what.c_str()); \
|
|
} while(0)
|
|
|
|
#define REPLACE_NONEMPTY_STR(what) do { \
|
|
if (!what.empty()) { \
|
|
REPLACE_STR(what); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define REPLACE_NUM(what, dst_num) do { \
|
|
if (!what.empty()) { \
|
|
what = ctx.replaceParameters(what, #what, req); \
|
|
unsigned int num; \
|
|
if (str2i(what, num)) { \
|
|
ERROR(#what " '%s' not understood\n", what.c_str()); \
|
|
return false; \
|
|
} \
|
|
DBG(#what " = '%s'\n", what.c_str()); \
|
|
dst_num = num; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define REPLACE_BOOL(what, dst_value) do { \
|
|
if (!what.empty()) { \
|
|
what = ctx.replaceParameters(what, #what, req); \
|
|
if (!what.empty()) { \
|
|
if (!str2bool(what, dst_value)) { \
|
|
ERROR(#what " '%s' not understood\n", what.c_str()); \
|
|
return false; \
|
|
} \
|
|
} \
|
|
DBG(#what " = '%s'\n", dst_value ? "yes" : "no"); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define REPLACE_IFACE_RTP(what, iface) do { \
|
|
if (!what.empty()) { \
|
|
what = ctx.replaceParameters(what, #what, req); \
|
|
DBG("set " #what " to '%s'\n", what.c_str()); \
|
|
if (!what.empty()) { \
|
|
EVALUATE_IFACE_RTP(what, iface); \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
#define EVALUATE_IFACE_RTP(what, iface) do { \
|
|
if (what == "default") iface = 0; \
|
|
else { \
|
|
map<string,unsigned short>::iterator name_it = \
|
|
AmConfig::RTP_If_names.find(what); \
|
|
if (name_it != AmConfig::RTP_If_names.end()) \
|
|
iface = name_it->second; \
|
|
else { \
|
|
ERROR("selected " #what " '%s' does not exist as a media interface. " \
|
|
"Please check the 'interfaces' " \
|
|
"parameter in the main configuration file.", \
|
|
what.c_str()); \
|
|
return false; \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
#define REPLACE_IFACE_SIP(what, iface) do { \
|
|
if (!what.empty()) { \
|
|
what = ctx.replaceParameters(what, #what, req); \
|
|
DBG("set " #what " to '%s'\n", what.c_str()); \
|
|
if (!what.empty()) { \
|
|
if (what == "default") iface = 0; \
|
|
else { \
|
|
map<string,unsigned short>::iterator name_it = \
|
|
AmConfig::SIP_If_names.find(what); \
|
|
if (name_it != AmConfig::RTP_If_names.end()) \
|
|
iface = name_it->second; \
|
|
else { \
|
|
ERROR("selected " #what " '%s' does not exist as a signaling" \
|
|
" interface. " \
|
|
"Please check the 'interfaces' " \
|
|
"parameter in the main configuration file.", \
|
|
what.c_str()); \
|
|
return false; \
|
|
} \
|
|
} \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SBCCallProfile::readFromConfiguration(const string& name,
|
|
const string profile_file_name) {
|
|
AmConfigReader cfg;
|
|
if (cfg.loadFile(profile_file_name)) {
|
|
ERROR("reading SBC call profile from '%s'\n", profile_file_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
profile_file = profile_file_name;
|
|
|
|
ruri = cfg.getParameter("RURI");
|
|
ruri_host = cfg.getParameter("RURI_host");
|
|
from = cfg.getParameter("From");
|
|
to = cfg.getParameter("To");
|
|
//contact = cfg.getParameter("Contact");
|
|
|
|
callid = cfg.getParameter("Call-ID");
|
|
|
|
transparent_dlg_id = cfg.getParameter("transparent_dlg_id") == "yes";
|
|
dlg_nat_handling = cfg.getParameter("dlg_nat_handling") == "yes";
|
|
|
|
force_outbound_proxy = cfg.getParameter("force_outbound_proxy") == "yes";
|
|
outbound_proxy = cfg.getParameter("outbound_proxy");
|
|
|
|
aleg_force_outbound_proxy = cfg.getParameter("aleg_force_outbound_proxy") == "yes";
|
|
aleg_outbound_proxy = cfg.getParameter("aleg_outbound_proxy");
|
|
|
|
next_hop = cfg.getParameter("next_hop");
|
|
next_hop_1st_req = cfg.getParameter("next_hop_1st_req") == "yes";
|
|
patch_ruri_next_hop = cfg.getParameter("patch_ruri_next_hop") == "yes";
|
|
next_hop_fixed = cfg.getParameter("next_hop_fixed") == "yes";
|
|
|
|
aleg_next_hop = cfg.getParameter("aleg_next_hop");
|
|
|
|
allow_subless_notify = cfg.getParameter("allow_subless_notify", "yes") == "yes";
|
|
|
|
if (!readFilter(cfg, "header_filter", "header_list", headerfilter, false))
|
|
return false;
|
|
|
|
if (!readFilter(cfg, "message_filter", "message_list", messagefilter, false))
|
|
return false;
|
|
|
|
if (!readFilter(cfg, "sdp_filter", "sdpfilter_list", sdpfilter, true))
|
|
return false;
|
|
|
|
have_aleg_sdpfilter = cfg.hasParameter("aleg_sdp_filter");
|
|
if (have_aleg_sdpfilter) {
|
|
if (!readFilter(cfg, "aleg_sdp_filter", "aleg_sdpfilter_list", aleg_sdpfilter, true))
|
|
return false;
|
|
}
|
|
|
|
if (!readFilter(cfg, "media_filter", "mediafilter_list", mediafilter, true))
|
|
return false;
|
|
|
|
anonymize_sdp = cfg.getParameter("sdp_anonymize", "no") == "yes";
|
|
|
|
// SDP alines filter
|
|
if (!readFilter(cfg, "sdp_alines_filter", "sdp_alinesfilter_list",
|
|
sdpalinesfilter, false))
|
|
return false;
|
|
|
|
sst_enabled = cfg.getParameter("enable_session_timer");
|
|
if (cfg.hasParameter("enable_aleg_session_timer")) {
|
|
sst_aleg_enabled = cfg.getParameter("enable_aleg_session_timer");
|
|
} else {
|
|
sst_aleg_enabled = sst_enabled;
|
|
}
|
|
|
|
#define CP_SST_CFGVAR(cfgprefix, cfgkey, dstcfg) \
|
|
if (cfg.hasParameter(cfgprefix cfgkey)) { \
|
|
dstcfg.setParameter(cfgkey, cfg.getParameter(cfgprefix cfgkey)); \
|
|
} else if (cfg.hasParameter(cfgkey)) { \
|
|
dstcfg.setParameter(cfgkey, cfg.getParameter(cfgkey)); \
|
|
} else if (SBCFactory::instance()->cfg.hasParameter(cfgkey)) { \
|
|
dstcfg.setParameter(cfgkey, SBCFactory::instance()-> \
|
|
cfg.getParameter(cfgkey)); \
|
|
}
|
|
|
|
if (sst_enabled.size() && sst_enabled != "no") {
|
|
if (NULL == SBCFactory::instance()->session_timer_fact) {
|
|
ERROR("session_timer module not loaded thus SST not supported, but "
|
|
"required for profile '%s' (%s)\n", name.c_str(), profile_file_name.c_str());
|
|
return false;
|
|
}
|
|
|
|
sst_b_cfg.setParameter("enable_session_timer", "yes");
|
|
// create sst_cfg with values from aleg_*
|
|
CP_SST_CFGVAR("", "session_expires", sst_b_cfg);
|
|
CP_SST_CFGVAR("", "minimum_timer", sst_b_cfg);
|
|
CP_SST_CFGVAR("", "maximum_timer", sst_b_cfg);
|
|
CP_SST_CFGVAR("", "session_refresh_method", sst_b_cfg);
|
|
CP_SST_CFGVAR("", "accept_501_reply", sst_b_cfg);
|
|
}
|
|
|
|
if (sst_aleg_enabled.size() && sst_aleg_enabled != "no") {
|
|
sst_a_cfg.setParameter("enable_session_timer", "yes");
|
|
// create sst_a_cfg superimposing values from aleg_*
|
|
CP_SST_CFGVAR("aleg_", "session_expires", sst_a_cfg);
|
|
CP_SST_CFGVAR("aleg_", "minimum_timer", sst_a_cfg);
|
|
CP_SST_CFGVAR("aleg_", "maximum_timer", sst_a_cfg);
|
|
CP_SST_CFGVAR("aleg_", "session_refresh_method", sst_a_cfg);
|
|
CP_SST_CFGVAR("aleg_", "accept_501_reply", sst_a_cfg);
|
|
}
|
|
#undef CP_SST_CFGVAR
|
|
|
|
fix_replaces_inv = cfg.getParameter("fix_replaces_inv");
|
|
fix_replaces_ref = cfg.getParameter("fix_replaces_ref");;
|
|
|
|
auth_enabled = cfg.getParameter("enable_auth", "no") == "yes";
|
|
auth_credentials.user = cfg.getParameter("auth_user");
|
|
auth_credentials.pwd = cfg.getParameter("auth_pwd");
|
|
|
|
auth_aleg_enabled = cfg.getParameter("enable_aleg_auth", "no") == "yes";
|
|
auth_aleg_credentials.user = cfg.getParameter("auth_aleg_user");
|
|
auth_aleg_credentials.pwd = cfg.getParameter("auth_aleg_pwd");
|
|
|
|
uas_auth_bleg_enabled = cfg.getParameter("enable_bleg_uas_auth", "no") == "yes";
|
|
uas_auth_bleg_credentials.realm = cfg.getParameter("uas_auth_bleg_realm");
|
|
uas_auth_bleg_credentials.user = cfg.getParameter("uas_auth_bleg_user");
|
|
uas_auth_bleg_credentials.pwd = cfg.getParameter("uas_auth_bleg_pwd");
|
|
|
|
if (!cfg.getParameter("call_control").empty()) {
|
|
vector<string> cc_sections = explode(cfg.getParameter("call_control"), ",");
|
|
for (vector<string>::iterator it =
|
|
cc_sections.begin(); it != cc_sections.end(); it++) {
|
|
DBG("reading call control '%s'\n", it->c_str());
|
|
cc_interfaces.push_back(CCInterface(*it));
|
|
CCInterface& cc_if = cc_interfaces.back();
|
|
cc_if.cc_module = cfg.getParameter(*it + "_module");
|
|
|
|
AmArg mandatory_values;
|
|
|
|
// check if module is loaded and if, get mandatory config values
|
|
if (cc_if.cc_module.find('$') == string::npos &&
|
|
cc_if.cc_name.find('$') == string::npos) {
|
|
AmDynInvokeFactory* df = AmPlugIn::instance()->getFactory4Di(cc_if.cc_module);
|
|
if (NULL == df) {
|
|
ERROR("Call control module '%s' used in call profile "
|
|
"'%s' is not loaded\n", cc_if.cc_module.c_str(), name.c_str());
|
|
return false;
|
|
}
|
|
AmDynInvoke* di = df->getInstance();
|
|
AmArg args;
|
|
try {
|
|
di->invoke(CC_INTERFACE_MAND_VALUES_METHOD, args, mandatory_values);
|
|
} catch (AmDynInvoke::NotImplemented& ni) { }
|
|
}
|
|
|
|
size_t cc_name_prefix_len = it->length()+1;
|
|
|
|
// read interface values
|
|
for (std::map<string,string>::const_iterator cfg_it =
|
|
cfg.begin(); cfg_it != cfg.end(); cfg_it++) {
|
|
if (cfg_it->first.substr(0, cc_name_prefix_len) != *it+"_")
|
|
continue;
|
|
|
|
if (cfg_it->first.size() <= cc_name_prefix_len ||
|
|
cfg_it->first == *it+"_module")
|
|
continue;
|
|
|
|
cc_if.cc_values[cfg_it->first.substr(cc_name_prefix_len)] = cfg_it->second;
|
|
}
|
|
|
|
if (isArgArray(mandatory_values)) {
|
|
for (size_t i=0;i<mandatory_values.size();i++) {
|
|
if (!isArgCStr(mandatory_values[i])) continue;
|
|
if (cc_if.cc_values.find(mandatory_values[i].asCStr()) == cc_if.cc_values.end()) {
|
|
ERROR("value '%s' for SBC profile '%s' in '%s' not defined. set %s_%s=...\n",
|
|
mandatory_values[i].asCStr(), name.c_str(), profile_file_name.c_str(),
|
|
it->c_str(), mandatory_values[i].asCStr());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vector<string> reply_translations_v =
|
|
explode(cfg.getParameter("reply_translations"), "|");
|
|
for (vector<string>::iterator it =
|
|
reply_translations_v.begin(); it != reply_translations_v.end(); it++) {
|
|
// expected: "603=>488 Not acceptable here"
|
|
vector<string> trans_components = explode(*it, "=>");
|
|
if (trans_components.size() != 2) {
|
|
ERROR("%s: entry '%s' in reply_translations could not be understood.\n",
|
|
name.c_str(), it->c_str());
|
|
ERROR("expected 'from_code=>to_code reason'\n");
|
|
return false;
|
|
}
|
|
|
|
unsigned int from_code, to_code;
|
|
if (str2i(trans_components[0], from_code)) {
|
|
ERROR("%s: code '%s' in reply_translations not understood.\n",
|
|
name.c_str(), trans_components[0].c_str());
|
|
return false;
|
|
}
|
|
unsigned int s_pos = 0;
|
|
string to_reply = trans_components[1];
|
|
while (s_pos < to_reply.length() && to_reply[s_pos] != ' ')
|
|
s_pos++;
|
|
if (str2i(to_reply.substr(0, s_pos), to_code)) {
|
|
ERROR("%s: code '%s' in reply_translations not understood.\n",
|
|
name.c_str(), to_reply.substr(0, s_pos).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (s_pos < to_reply.length())
|
|
s_pos++;
|
|
// DBG("got translation %u => %u %s\n",
|
|
// from_code, to_code, to_reply.substr(s_pos).c_str());
|
|
reply_translations[from_code] = make_pair(to_code, to_reply.substr(s_pos));
|
|
}
|
|
|
|
// append_headers=P-Received-IP: $si\r\nP-Received-Port:$sp\r\n
|
|
append_headers = cfg.getParameter("append_headers");
|
|
append_headers_req = cfg.getParameter("append_headers_req");
|
|
aleg_append_headers_req = cfg.getParameter("aleg_append_headers_req");
|
|
|
|
refuse_with = cfg.getParameter("refuse_with");
|
|
|
|
rtprelay_enabled = cfg.getParameter("enable_rtprelay");
|
|
aleg_force_symmetric_rtp = cfg.getParameter("rtprelay_force_symmetric_rtp");
|
|
force_symmetric_rtp = aleg_force_symmetric_rtp;
|
|
msgflags_symmetric_rtp = cfg.getParameter("rtprelay_msgflags_symmetric_rtp") == "yes";
|
|
|
|
rtprelay_interface = cfg.getParameter("rtprelay_interface");
|
|
aleg_rtprelay_interface = cfg.getParameter("aleg_rtprelay_interface");
|
|
|
|
rtprelay_transparent_seqno =
|
|
cfg.getParameter("rtprelay_transparent_seqno", "yes") == "yes";
|
|
rtprelay_transparent_ssrc =
|
|
cfg.getParameter("rtprelay_transparent_ssrc", "yes") == "yes";
|
|
rtprelay_dtmf_filtering =
|
|
cfg.getParameter("rtprelay_dtmf_filtering", "no") == "yes";
|
|
rtprelay_dtmf_detection =
|
|
cfg.getParameter("rtprelay_dtmf_detection", "no") == "yes";
|
|
|
|
outbound_interface = cfg.getParameter("outbound_interface");
|
|
aleg_outbound_interface = cfg.getParameter("aleg_outbound_interface");
|
|
|
|
contact.displayname = cfg.getParameter("contact_displayname");
|
|
contact.user = cfg.getParameter("contact_user");
|
|
contact.host = cfg.getParameter("contact_host");
|
|
contact.port = cfg.getParameter("contact_port");
|
|
|
|
contact.hiding = cfg.getParameter("enable_contact_hiding", "no") == "yes";
|
|
contact.hiding_prefix = cfg.getParameter("contact_hiding_prefix");
|
|
contact.hiding_vars = cfg.getParameter("contact_hiding_vars");
|
|
|
|
if (!codec_prefs.readConfig(cfg)) return false;
|
|
if (!transcoder.readConfig(cfg)) return false;
|
|
hold_settings.readConfig(cfg);
|
|
|
|
msg_logger_path = cfg.getParameter("msg_logger_path");
|
|
log_rtp = cfg.getParameter("log_rtp","no") == "yes";
|
|
log_sip = cfg.getParameter("log_sip","yes") == "yes";
|
|
|
|
reg_caching = cfg.getParameter("enable_reg_caching","no") == "yes";
|
|
min_reg_expires = cfg.getParameterInt("min_reg_expires",0);
|
|
max_ua_expires = cfg.getParameterInt("max_ua_expires",0);
|
|
|
|
max_491_retry_time = cfg.getParameterInt("max_491_retry_time", 2000);
|
|
|
|
md5hash = "<unknown>";
|
|
if (!cfg.getMD5(profile_file_name, md5hash)){
|
|
ERROR("calculating MD5 of file %s\n", profile_file_name.c_str());
|
|
}
|
|
|
|
INFO("SBC: loaded SBC profile '%s' - MD5: %s\n", name.c_str(), md5hash.c_str());
|
|
|
|
if (!refuse_with.empty()) {
|
|
INFO("SBC: refusing calls with '%s'\n", refuse_with.c_str());
|
|
} else {
|
|
INFO("SBC: RURI = '%s'\n", ruri.c_str());
|
|
INFO("SBC: RURI-host = '%s'\n", ruri_host.c_str());
|
|
INFO("SBC: From = '%s'\n", from.c_str());
|
|
INFO("SBC: To = '%s'\n", to.c_str());
|
|
// if (!contact.empty()) {
|
|
// INFO("SBC: Contact = '%s'\n", contact.c_str());
|
|
// }
|
|
if (!callid.empty()) {
|
|
INFO("SBC: Call-ID = '%s'\n", callid.c_str());
|
|
}
|
|
|
|
INFO("SBC: force outbound proxy: %s\n", force_outbound_proxy?"yes":"no");
|
|
INFO("SBC: outbound proxy = '%s'\n", outbound_proxy.c_str());
|
|
|
|
if (!outbound_interface.empty()) {
|
|
INFO("SBC: outbound interface = '%s'\n", outbound_interface.c_str());
|
|
}
|
|
|
|
if (!aleg_outbound_interface.empty()) {
|
|
INFO("SBC: A leg outbound interface = '%s'\n", aleg_outbound_interface.c_str());
|
|
}
|
|
|
|
INFO("SBC: A leg force outbound proxy: %s\n", aleg_force_outbound_proxy?"yes":"no");
|
|
INFO("SBC: A leg outbound proxy = '%s'\n", aleg_outbound_proxy.c_str());
|
|
|
|
if (!next_hop.empty()) {
|
|
INFO("SBC: next hop = %s (%s, %s)\n", next_hop.c_str(),
|
|
next_hop_1st_req ? "1st req" : "all reqs", next_hop_fixed?"fixed":"not fixed");
|
|
}
|
|
|
|
if (!aleg_next_hop.empty()) {
|
|
INFO("SBC: A leg next hop = %s\n", aleg_next_hop.c_str());
|
|
}
|
|
|
|
string filter_type; size_t filter_elems;
|
|
filter_type = headerfilter.size() ?
|
|
FilterType2String(headerfilter.back().filter_type) : "disabled";
|
|
filter_elems = headerfilter.size() ? headerfilter.back().filter_list.size() : 0;
|
|
INFO("SBC: header filter is %s, %zd items in list\n",
|
|
filter_type.c_str(), filter_elems);
|
|
|
|
filter_type = messagefilter.size() ?
|
|
FilterType2String(messagefilter.back().filter_type) : "disabled";
|
|
filter_elems = messagefilter.size() ? messagefilter.back().filter_list.size() : 0;
|
|
INFO("SBC: message filter is %s, %zd items in list\n",
|
|
filter_type.c_str(), filter_elems);
|
|
|
|
filter_type = sdpfilter.size() ?
|
|
FilterType2String(sdpfilter.back().filter_type) : "disabled";
|
|
filter_elems = sdpfilter.size() ? sdpfilter.back().filter_list.size() : 0;
|
|
INFO("SBC: SDP filter is %sabled, %s, %zd items in list, %sanonymizing SDP\n",
|
|
sdpfilter.size()?"en":"dis", filter_type.c_str(), filter_elems,
|
|
anonymize_sdp?"":"not ");
|
|
|
|
if (have_aleg_sdpfilter) {
|
|
filter_type = aleg_sdpfilter.size() ?
|
|
FilterType2String(aleg_sdpfilter.back().filter_type) : "disabled";
|
|
filter_elems = aleg_sdpfilter.size() ? aleg_sdpfilter.back().filter_list.size() : 0;
|
|
INFO("SBC: separate A-leg SDP filter is %sabled, %s, %zd items in list\n",
|
|
sdpfilter.size()?"en":"dis", filter_type.c_str(), filter_elems);
|
|
} else {
|
|
INFO("SBC: separate A-leg SDP filter is disabled (same SDP filter for both legs)\n");
|
|
}
|
|
|
|
filter_type = sdpalinesfilter.size() ?
|
|
FilterType2String(sdpalinesfilter.back().filter_type) : "disabled";
|
|
filter_elems = sdpalinesfilter.size() ? sdpalinesfilter.back().filter_list.size() : 0;
|
|
INFO("SBC: SDP alines-filter is %sabled, %s, %zd items in list\n",
|
|
sdpalinesfilter.size()?"en":"dis", filter_type.c_str(), filter_elems);
|
|
|
|
filter_type = mediafilter.size() ?
|
|
FilterType2String(mediafilter.back().filter_type) : "disabled";
|
|
filter_elems = mediafilter.size() ? mediafilter.back().filter_list.size() : 0;
|
|
INFO("SBC: SDP filter is %sabled, %s, %zd items in list\n",
|
|
mediafilter.size()?"en":"dis", filter_type.c_str(), filter_elems);
|
|
|
|
INFO("SBC: fixing Replaces in INVITE: '%s'\n", fix_replaces_inv.c_str());
|
|
INFO("SBC: fixing Replaces in REFER: '%s'\n", fix_replaces_ref.c_str());
|
|
|
|
|
|
INFO("SBC: RTP relay enabled: '%s'\n", rtprelay_enabled.c_str());
|
|
if (!rtprelay_enabled.empty() && rtprelay_enabled != "no") {
|
|
if (!force_symmetric_rtp.empty()) {
|
|
INFO("SBC: RTP force symmetric RTP: %s\n",
|
|
force_symmetric_rtp.c_str());
|
|
}
|
|
if (msgflags_symmetric_rtp) {
|
|
INFO("SBC: P-MsgFlags symmetric RTP detection enabled\n");
|
|
}
|
|
if (!aleg_rtprelay_interface.empty()) {
|
|
INFO("SBC: RTP Relay interface A leg '%s'\n", aleg_rtprelay_interface.c_str());
|
|
}
|
|
if (!rtprelay_interface.empty()) {
|
|
INFO("SBC: RTP Relay interface B leg '%s'\n", rtprelay_interface.c_str());
|
|
}
|
|
|
|
INFO("SBC: RTP Relay %s seqno\n",
|
|
rtprelay_transparent_seqno?"transparent":"opaque");
|
|
INFO("SBC: RTP Relay %s SSRC\n",
|
|
rtprelay_transparent_ssrc?"transparent":"opaque");
|
|
INFO("SBC: RTP Relay RTP DTMF filtering %sabled\n",
|
|
rtprelay_dtmf_filtering?"en":"dis");
|
|
INFO("SBC: RTP Relay RTP DTMF detection %sabled\n",
|
|
rtprelay_dtmf_detection?"en":"dis");
|
|
}
|
|
|
|
INFO("SBC: SST on A leg enabled: '%s'\n", sst_aleg_enabled.empty() ?
|
|
"no" : sst_aleg_enabled.c_str());
|
|
if (sst_aleg_enabled.size() && sst_aleg_enabled != "no") {
|
|
INFO("SBC: session_expires=%s\n",
|
|
sst_a_cfg.getParameter("session_expires").c_str());
|
|
INFO("SBC: minimum_timer=%s\n",
|
|
sst_a_cfg.getParameter("minimum_timer").c_str());
|
|
INFO("SBC: maximum_timer=%s\n",
|
|
sst_a_cfg.getParameter("maximum_timer").c_str());
|
|
INFO("SBC: session_refresh_method=%s\n",
|
|
sst_a_cfg.getParameter("session_refresh_method").c_str());
|
|
INFO("SBC: accept_501_reply=%s\n",
|
|
sst_a_cfg.getParameter("accept_501_reply").c_str());
|
|
}
|
|
INFO("SBC: SST on B leg enabled: '%s'\n", sst_enabled.empty() ?
|
|
"no" : sst_enabled.c_str());
|
|
if (sst_enabled.size() && sst_enabled != "no") {
|
|
INFO("SBC: session_expires=%s\n",
|
|
sst_b_cfg.getParameter("session_expires").c_str());
|
|
INFO("SBC: minimum_timer=%s\n",
|
|
sst_b_cfg.getParameter("minimum_timer").c_str());
|
|
INFO("SBC: maximum_timer=%s\n",
|
|
sst_b_cfg.getParameter("maximum_timer").c_str());
|
|
INFO("SBC: session_refresh_method=%s\n",
|
|
sst_b_cfg.getParameter("session_refresh_method").c_str());
|
|
INFO("SBC: accept_501_reply=%s\n",
|
|
sst_b_cfg.getParameter("accept_501_reply").c_str());
|
|
}
|
|
|
|
INFO("SBC: SIP auth %sabled\n", auth_enabled?"en":"dis");
|
|
INFO("SBC: SIP auth for A leg %sabled\n", auth_aleg_enabled?"en":"dis");
|
|
INFO("SBC: SIP UAS auth for B leg %sabled\n", uas_auth_bleg_enabled?"en":"dis");
|
|
|
|
if (cc_interfaces.size()) {
|
|
string cc_if_names;
|
|
for (CCInterfaceListIteratorT it =
|
|
cc_interfaces.begin(); it != cc_interfaces.end(); it++) {
|
|
cc_if_names = it->cc_name+",";
|
|
cc_if_names.erase(cc_if_names.size()-1,1);
|
|
INFO("SBC: Call Control: %s\n", cc_if_names.c_str());
|
|
}
|
|
}
|
|
|
|
if (reply_translations.size()) {
|
|
string reply_trans_codes;
|
|
for(map<unsigned int, std::pair<unsigned int, string> >::iterator it=
|
|
reply_translations.begin(); it != reply_translations.end(); it++)
|
|
reply_trans_codes += int2str(it->first)+", ";
|
|
reply_trans_codes.erase(reply_trans_codes.length()-2);
|
|
INFO("SBC: reply translation for %s\n", reply_trans_codes.c_str());
|
|
}
|
|
}
|
|
|
|
if (append_headers.size()) {
|
|
INFO("SBC: append headers '%s'\n", append_headers.c_str());
|
|
}
|
|
|
|
INFO("SBC: reg-caching: '%s'\n", reg_caching ? "yes" : "no");
|
|
INFO("SBC: min_reg_expires: %i\n", min_reg_expires);
|
|
INFO("SBC: max_ua_expires: %i\n", max_ua_expires);
|
|
|
|
codec_prefs.infoPrint();
|
|
transcoder.infoPrint();
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool payloadDescsEqual(const vector<PayloadDesc> &a, const vector<PayloadDesc> &b)
|
|
{
|
|
// not sure if this function is really needed (seems that vectors can be
|
|
// compared using builtin operator== but anyway ...)
|
|
if (a.size() != b.size()) return false;
|
|
vector<PayloadDesc>::const_iterator ia = a.begin();
|
|
vector<PayloadDesc>::const_iterator ib = b.begin();
|
|
for (; ia != a.end(); ++ia, ++ib) {
|
|
if (!(*ia == *ib)) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SBCCallProfile::operator==(const SBCCallProfile& rhs) const {
|
|
bool res =
|
|
ruri == rhs.ruri &&
|
|
ruri_host == rhs.ruri_host &&
|
|
from == rhs.from &&
|
|
to == rhs.to &&
|
|
//contact == rhs.contact &&
|
|
callid == rhs.callid &&
|
|
outbound_proxy == rhs.outbound_proxy &&
|
|
force_outbound_proxy == rhs.force_outbound_proxy &&
|
|
aleg_outbound_proxy == rhs.aleg_outbound_proxy &&
|
|
aleg_force_outbound_proxy == rhs.aleg_force_outbound_proxy &&
|
|
next_hop == rhs.next_hop &&
|
|
next_hop_1st_req == rhs.next_hop_1st_req &&
|
|
next_hop_fixed == rhs.next_hop_fixed &&
|
|
patch_ruri_next_hop == rhs.patch_ruri_next_hop &&
|
|
aleg_next_hop == rhs.aleg_next_hop &&
|
|
headerfilter == rhs.headerfilter &&
|
|
//headerfilter_list == rhs.headerfilter_list &&
|
|
messagefilter == rhs.messagefilter &&
|
|
//messagefilter_list == rhs.messagefilter_list &&
|
|
//sdpfilter_enabled == rhs.sdpfilter_enabled &&
|
|
sdpfilter == rhs.sdpfilter &&
|
|
aleg_sdpfilter == rhs.aleg_sdpfilter &&
|
|
mediafilter == rhs.mediafilter &&
|
|
sst_enabled == rhs.sst_enabled &&
|
|
sst_aleg_enabled == rhs.sst_aleg_enabled &&
|
|
auth_enabled == rhs.auth_enabled &&
|
|
auth_aleg_enabled == rhs.auth_aleg_enabled &&
|
|
reply_translations == rhs.reply_translations &&
|
|
append_headers == rhs.append_headers &&
|
|
refuse_with == rhs.refuse_with &&
|
|
rtprelay_enabled == rhs.rtprelay_enabled &&
|
|
force_symmetric_rtp == rhs.force_symmetric_rtp &&
|
|
msgflags_symmetric_rtp == rhs.msgflags_symmetric_rtp;
|
|
|
|
if (auth_enabled) {
|
|
res = res &&
|
|
auth_credentials.user == rhs.auth_credentials.user &&
|
|
auth_credentials.pwd == rhs.auth_credentials.pwd;
|
|
}
|
|
if (auth_aleg_enabled) {
|
|
res = res &&
|
|
auth_aleg_credentials.user == rhs.auth_aleg_credentials.user &&
|
|
auth_aleg_credentials.pwd == rhs.auth_aleg_credentials.pwd;
|
|
}
|
|
res = res && (codec_prefs == rhs.codec_prefs);
|
|
res = res && (transcoder == rhs.transcoder);
|
|
return res;
|
|
}
|
|
|
|
string stringset_print(const set<string>& s) {
|
|
string res;
|
|
for (set<string>::const_iterator i=s.begin(); i != s.end(); i++)
|
|
res += *i+" ";
|
|
return res;
|
|
}
|
|
|
|
string SBCCallProfile::print() const {
|
|
string res =
|
|
"SBC call profile dump: ~~~~~~~~~~~~~~~~~\n";
|
|
res += "ruri: " + ruri + "\n";
|
|
res += "ruri_host: " + ruri_host + "\n";
|
|
res += "from: " + from + "\n";
|
|
res += "to: " + to + "\n";
|
|
// res += "contact: " + contact + "\n";
|
|
res += "callid: " + callid + "\n";
|
|
res += "outbound_proxy: " + outbound_proxy + "\n";
|
|
res += "force_outbound_proxy: " + string(force_outbound_proxy?"true":"false") + "\n";
|
|
res += "aleg_outbound_proxy: " + aleg_outbound_proxy + "\n";
|
|
res += "aleg_force_outbound_proxy: " + string(aleg_force_outbound_proxy?"true":"false") + "\n";
|
|
res += "next_hop: " + next_hop + "\n";
|
|
res += "next_hop_1st_req: " + string(next_hop_1st_req ? "true":"false") + "\n";
|
|
res += "next_hop_fixed: " + string(next_hop_fixed ? "true":"false") + "\n";
|
|
res += "aleg_next_hop: " + aleg_next_hop + "\n";
|
|
// res += "headerfilter: " + string(FilterType2String(headerfilter)) + "\n";
|
|
// res += "headerfilter_list: " + stringset_print(headerfilter_list) + "\n";
|
|
// res += "messagefilter: " + string(FilterType2String(messagefilter)) + "\n";
|
|
// res += "messagefilter_list: " + stringset_print(messagefilter_list) + "\n";
|
|
// res += "sdpfilter_enabled: " + string(sdpfilter_enabled?"true":"false") + "\n";
|
|
// res += "sdpfilter: " + string(FilterType2String(sdpfilter)) + "\n";
|
|
// res += "sdpfilter_list: " + stringset_print(sdpfilter_list) + "\n";
|
|
// res += "sdpalinesfilter: " + string(FilterType2String(sdpalinesfilter)) + "\n";
|
|
// res += "sdpalinesfilter_list: " + stringset_print(sdpalinesfilter_list) + "\n";
|
|
res += "sst_enabled: " + sst_enabled + "\n";
|
|
res += "sst_aleg_enabled: " + sst_aleg_enabled + "\n";
|
|
res += "auth_enabled: " + string(auth_enabled?"true":"false") + "\n";
|
|
res += "auth_user: " + auth_credentials.user+"\n";
|
|
res += "auth_pwd: " + auth_credentials.pwd+"\n";
|
|
res += "auth_aleg_enabled: " + string(auth_aleg_enabled?"true":"false") + "\n";
|
|
res += "auth_aleg_user: " + auth_aleg_credentials.user+"\n";
|
|
res += "auth_aleg_pwd: " + auth_aleg_credentials.pwd+"\n";
|
|
res += "rtprelay_enabled: " + rtprelay_enabled + "\n";
|
|
res += "force_symmetric_rtp: " + force_symmetric_rtp;
|
|
res += "msgflags_symmetric_rtp: " + string(msgflags_symmetric_rtp?"true":"false") + "\n";
|
|
|
|
res += codec_prefs.print();
|
|
res += transcoder.print();
|
|
|
|
if (reply_translations.size()) {
|
|
string reply_trans_codes;
|
|
for(map<unsigned int, std::pair<unsigned int, string> >::const_iterator it=
|
|
reply_translations.begin(); it != reply_translations.end(); it++)
|
|
reply_trans_codes += int2str(it->first)+"=>"+
|
|
int2str(it->second.first)+" " + it->second.second+", ";
|
|
reply_trans_codes.erase(reply_trans_codes.length()-2);
|
|
|
|
res += "reply_trans_codes: " + reply_trans_codes +"\n";
|
|
}
|
|
res += "append_headers: " + append_headers + "\n";
|
|
res += "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";
|
|
return res;
|
|
}
|
|
|
|
static bool isTranscoderNeeded(const AmSipRequest& req, vector<PayloadDesc> &caps,
|
|
bool default_value)
|
|
{
|
|
const AmMimeBody* body = req.body.hasContentType(SIP_APPLICATION_SDP);
|
|
if (!body) return default_value;
|
|
|
|
AmSdp sdp;
|
|
int res = sdp.parse((const char *)body->getPayload());
|
|
if (res != 0) {
|
|
DBG("SDP parsing failed!\n");
|
|
return default_value;
|
|
}
|
|
|
|
// not nice, but we need to compare codec names and thus normalized SDP is
|
|
// required
|
|
normalizeSDP(sdp, false, "");
|
|
|
|
// go through payloads and try find one of the supported ones
|
|
for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
|
|
for (vector<SdpPayload>::iterator p = m->payloads.begin(); p != m->payloads.end(); ++p) {
|
|
for (vector<PayloadDesc>::iterator i = caps.begin(); i != caps.end(); ++i) {
|
|
if (i->match(*p)) return false; // found compatible codec
|
|
}
|
|
}
|
|
}
|
|
|
|
return true; // no compatible codec found, transcoding needed
|
|
}
|
|
|
|
void SBCCallProfile::eval_sst_config(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req,
|
|
AmConfigReader& sst_cfg)
|
|
{
|
|
|
|
#define SST_CFG_PARAM_COUNT 5 // Change if you add/remove params in below
|
|
|
|
static const char* _sst_cfg_params[] = {
|
|
"session_expires",
|
|
"minimum_timer",
|
|
"maximum_timer",
|
|
"session_refresh_method",
|
|
"accept_501_reply",
|
|
};
|
|
|
|
for(unsigned int i=0; i<SST_CFG_PARAM_COUNT; i++) {
|
|
if (sst_cfg.hasParameter(_sst_cfg_params[i])) {
|
|
string newval =
|
|
ctx.replaceParameters(sst_cfg.getParameter(_sst_cfg_params[i]),
|
|
_sst_cfg_params[i],req);
|
|
if (newval.empty()) {
|
|
sst_cfg.eraseParameter(_sst_cfg_params[i]);
|
|
} else{
|
|
sst_cfg.setParameter(_sst_cfg_params[i],newval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SBCCallProfile::evaluate(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req)
|
|
{
|
|
REPLACE_NONEMPTY_STR(ruri);
|
|
REPLACE_NONEMPTY_STR(ruri_host);
|
|
REPLACE_NONEMPTY_STR(from);
|
|
REPLACE_NONEMPTY_STR(to);
|
|
REPLACE_NONEMPTY_STR(callid);
|
|
|
|
REPLACE_NONEMPTY_STR(dlg_contact_params);
|
|
REPLACE_NONEMPTY_STR(bleg_dlg_contact_params);
|
|
|
|
REPLACE_NONEMPTY_STR(outbound_proxy);
|
|
REPLACE_NONEMPTY_STR(next_hop);
|
|
|
|
if (!transcoder.evaluate(ctx,req)) return false;
|
|
|
|
REPLACE_BOOL(rtprelay_enabled, rtprelay_enabled_value);
|
|
|
|
if (rtprelay_enabled_value || transcoder.isActive()) {
|
|
// evaluate other RTP relay related params only if enabled
|
|
REPLACE_BOOL(force_symmetric_rtp, force_symmetric_rtp_value);
|
|
REPLACE_BOOL(aleg_force_symmetric_rtp, aleg_force_symmetric_rtp_value);
|
|
|
|
// enable symmetric RTP by P-MsgFlags?
|
|
// SBC need not to know if it is from P-MsgFlags or from profile parameter
|
|
if (msgflags_symmetric_rtp) {
|
|
string str_msg_flags = getHeader(req.hdrs,"P-MsgFlags", true);
|
|
unsigned int msg_flags = 0;
|
|
if(reverse_hex2int(str_msg_flags,msg_flags)){
|
|
ERROR("while parsing 'P-MsgFlags' header\n");
|
|
msg_flags = 0;
|
|
}
|
|
if (msg_flags & FL_FORCE_ACTIVE) {
|
|
DBG("P-MsgFlags indicates forced symmetric RTP (passive mode)");
|
|
force_symmetric_rtp_value = true;
|
|
aleg_force_symmetric_rtp_value = true;
|
|
}
|
|
}
|
|
|
|
REPLACE_IFACE_RTP(rtprelay_interface, rtprelay_interface_value);
|
|
REPLACE_IFACE_RTP(aleg_rtprelay_interface, aleg_rtprelay_interface_value);
|
|
}
|
|
|
|
REPLACE_BOOL(sst_enabled, sst_enabled_value);
|
|
if (sst_enabled_value) {
|
|
AmConfigReader& sst_cfg = sst_b_cfg;
|
|
eval_sst_config(ctx,req,sst_cfg);
|
|
}
|
|
|
|
REPLACE_NONEMPTY_STR(append_headers);
|
|
|
|
if (auth_enabled) {
|
|
auth_credentials.user = ctx.replaceParameters(auth_credentials.user,
|
|
"auth_user", req);
|
|
auth_credentials.pwd = ctx.replaceParameters(auth_credentials.pwd,
|
|
"auth_pwd", req);
|
|
}
|
|
|
|
if (auth_aleg_enabled) {
|
|
auth_aleg_credentials.user = ctx.replaceParameters(auth_aleg_credentials.user,
|
|
"auth_aleg_user", req);
|
|
auth_aleg_credentials.pwd = ctx.replaceParameters(auth_aleg_credentials.pwd,
|
|
"auth_aleg_pwd", req);
|
|
}
|
|
|
|
if (uas_auth_bleg_enabled) {
|
|
uas_auth_bleg_credentials.realm =
|
|
ctx.replaceParameters(uas_auth_bleg_credentials.realm, "uas_auth_bleg_realm", req);
|
|
uas_auth_bleg_credentials.user =
|
|
ctx.replaceParameters(uas_auth_bleg_credentials.user, "uas_auth_bleg_user", req);
|
|
uas_auth_bleg_credentials.pwd =
|
|
ctx.replaceParameters(uas_auth_bleg_credentials.pwd, "uas_auth_bleg_pwd", req);
|
|
}
|
|
|
|
fix_replaces_inv = ctx.replaceParameters(fix_replaces_inv, "fix_replaces_inv", req);
|
|
fix_replaces_ref = ctx.replaceParameters(fix_replaces_ref, "fix_replaces_ref", req);
|
|
|
|
REPLACE_IFACE_SIP(outbound_interface, outbound_interface_value);
|
|
|
|
if (!codec_prefs.evaluate(ctx,req)) return false;
|
|
if (!hold_settings.evaluate(ctx,req)) return false;
|
|
|
|
// TODO: activate filter if transcoder or codec_prefs is set?
|
|
/* if ((!aleg_payload_order.empty() || !bleg_payload_order.empty()) && (!sdpfilter_enabled)) {
|
|
sdpfilter_enabled = true;
|
|
sdpfilter = Transparent;
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SBCCallProfile::evaluateOutboundInterface() {
|
|
if (outbound_interface == "default") {
|
|
outbound_interface_value = 0;
|
|
} else {
|
|
map<string,unsigned short>::iterator name_it =
|
|
AmConfig::SIP_If_names.find(outbound_interface);
|
|
if (name_it != AmConfig::RTP_If_names.end()) {
|
|
outbound_interface_value = name_it->second;
|
|
} else {
|
|
ERROR("selected outbound_interface '%s' does not exist as a signaling"
|
|
" interface. "
|
|
"Please check the 'interfaces' "
|
|
"parameter in the main configuration file.",
|
|
outbound_interface.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SBCCallProfile::evaluateRTPRelayInterface() {
|
|
EVALUATE_IFACE_RTP(rtprelay_interface, rtprelay_interface_value);
|
|
return true;
|
|
}
|
|
|
|
bool SBCCallProfile::evaluateRTPRelayAlegInterface() {
|
|
EVALUATE_IFACE_RTP(aleg_rtprelay_interface, aleg_rtprelay_interface_value);
|
|
return true;
|
|
}
|
|
|
|
static int apply_outbound_interface(const string& oi, AmBasicSipDialog& dlg)
|
|
{
|
|
if (oi == "default")
|
|
dlg.setOutboundInterface(0);
|
|
else {
|
|
map<string,unsigned short>::iterator name_it = AmConfig::SIP_If_names.find(oi);
|
|
if (name_it != AmConfig::SIP_If_names.end()) {
|
|
dlg.setOutboundInterface(name_it->second);
|
|
} else {
|
|
ERROR("selected [aleg_]outbound_interface '%s' "
|
|
"does not exist as an interface. "
|
|
"Please check the 'interfaces' "
|
|
"parameter in the main configuration file.",
|
|
oi.c_str());
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SBCCallProfile::apply_a_routing(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req,
|
|
AmBasicSipDialog& dlg) const
|
|
{
|
|
if (!aleg_outbound_interface.empty()) {
|
|
string aleg_oi =
|
|
ctx.replaceParameters(aleg_outbound_interface,
|
|
"aleg_outbound_interface", req);
|
|
|
|
if(apply_outbound_interface(aleg_oi,dlg) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!aleg_next_hop.empty()) {
|
|
|
|
string aleg_nh = ctx.replaceParameters(aleg_next_hop,
|
|
"aleg_next_hop", req);
|
|
|
|
DBG("set next hop ip to '%s'\n", aleg_nh.c_str());
|
|
dlg.setNextHop(aleg_nh);
|
|
}
|
|
else {
|
|
dlg.nat_handling = dlg_nat_handling;
|
|
if(dlg_nat_handling && req.first_hop) {
|
|
string nh = req.remote_ip + ":"
|
|
+ int2str(req.remote_port)
|
|
+ "/" + req.trsp;
|
|
dlg.setNextHop(nh);
|
|
dlg.setNextHop1stReq(false);
|
|
}
|
|
}
|
|
|
|
if (!aleg_outbound_proxy.empty()) {
|
|
string aleg_op =
|
|
ctx.replaceParameters(aleg_outbound_proxy, "aleg_outbound_proxy", req);
|
|
dlg.outbound_proxy = aleg_op;
|
|
dlg.force_outbound_proxy = aleg_force_outbound_proxy;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SBCCallProfile::apply_b_routing(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req,
|
|
AmBasicSipDialog& dlg) const
|
|
{
|
|
if (!outbound_interface.empty()) {
|
|
string oi =
|
|
ctx.replaceParameters(outbound_interface, "outbound_interface", req);
|
|
|
|
if(apply_outbound_interface(oi,dlg) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!next_hop.empty()) {
|
|
|
|
string nh = ctx.replaceParameters(next_hop, "next_hop", req);
|
|
|
|
DBG("set next hop to '%s' (1st_req=%s,fixed=%s)\n",
|
|
nh.c_str(), next_hop_1st_req?"true":"false",
|
|
next_hop_fixed?"true":"false");
|
|
dlg.setNextHop(nh);
|
|
dlg.setNextHop1stReq(next_hop_1st_req);
|
|
dlg.setNextHopFixed(next_hop_fixed);
|
|
}
|
|
|
|
DBG("patch_ruri_next_hop = %i",patch_ruri_next_hop);
|
|
dlg.setPatchRURINextHop(patch_ruri_next_hop);
|
|
|
|
if (!outbound_proxy.empty()) {
|
|
string op = ctx.replaceParameters(outbound_proxy, "outbound_proxy", req);
|
|
dlg.outbound_proxy = op;
|
|
dlg.force_outbound_proxy = force_outbound_proxy;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int SBCCallProfile::apply_common_fields(ParamReplacerCtx& ctx,
|
|
AmSipRequest& req) const
|
|
{
|
|
if(!ruri.empty()) {
|
|
req.r_uri = ctx.replaceParameters(ruri, "RURI", req);
|
|
}
|
|
|
|
if (!ruri_host.empty()) {
|
|
string ruri_h = ctx.replaceParameters(ruri_host, "RURI-host", req);
|
|
|
|
ctx.ruri_parser.uri = req.r_uri;
|
|
if (!ctx.ruri_parser.parse_uri()) {
|
|
WARN("Error parsing R-URI '%s'\n", ctx.ruri_parser.uri.c_str());
|
|
return -1;
|
|
}
|
|
else {
|
|
ctx.ruri_parser.uri_port.clear();
|
|
ctx.ruri_parser.uri_host = ruri_host;
|
|
req.r_uri = ctx.ruri_parser.uri_str();
|
|
}
|
|
}
|
|
|
|
if(!from.empty()) {
|
|
req.from = ctx.replaceParameters(from, "From", req);
|
|
}
|
|
|
|
if(!to.empty()) {
|
|
req.to = ctx.replaceParameters(to, "To", req);
|
|
}
|
|
|
|
if(!callid.empty()){
|
|
req.callid = ctx.replaceParameters(callid, "Call-ID", req);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SBCCallProfile::replace_cc_values(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req,
|
|
AmArg* values)
|
|
{
|
|
for (CCInterfaceListIteratorT cc_it = cc_interfaces.begin();
|
|
cc_it != cc_interfaces.end(); cc_it++) {
|
|
|
|
CCInterface& cc_if = *cc_it;
|
|
|
|
DBG("processing replacements for call control interface '%s'\n",
|
|
cc_if.cc_name.c_str());
|
|
|
|
for (map<string, string>::iterator it = cc_if.cc_values.begin();
|
|
it != cc_if.cc_values.end(); it++) {
|
|
|
|
it->second = ctx.replaceParameters(it->second, it->first.c_str(), req);
|
|
if(values) (*values)[it->first] = it->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
int SBCCallProfile::refuse(ParamReplacerCtx& ctx, const AmSipRequest& req) const
|
|
{
|
|
string m_refuse_with = ctx.replaceParameters(refuse_with, "refuse_with", req);
|
|
if (m_refuse_with.empty()) {
|
|
ERROR("refuse_with empty after replacing (was '%s' in profile %s)\n",
|
|
refuse_with.c_str(), profile_file.c_str());
|
|
return -1;
|
|
}
|
|
|
|
size_t spos = m_refuse_with.find(' ');
|
|
unsigned int refuse_with_code;
|
|
if (spos == string::npos || spos == m_refuse_with.size() ||
|
|
str2i(m_refuse_with.substr(0, spos), refuse_with_code)) {
|
|
ERROR("invalid refuse_with '%s'->'%s' in %s. Expected <code> <reason>\n",
|
|
refuse_with.c_str(), m_refuse_with.c_str(), profile_file.c_str());
|
|
return -1;
|
|
}
|
|
|
|
string refuse_with_reason = m_refuse_with.substr(spos+1);
|
|
string hdrs = ctx.replaceParameters(append_headers, "append_headers", req);
|
|
//TODO: hdrs = remove_empty_headers(hdrs);
|
|
if (hdrs.size()>2) assertEndCRLF(hdrs);
|
|
|
|
DBG("refusing call with %u %s\n", refuse_with_code, refuse_with_reason.c_str());
|
|
AmSipDialog::reply_error(req, refuse_with_code, refuse_with_reason, hdrs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fixupCCInterface(const string& val, CCInterface& cc_if)
|
|
{
|
|
DBG("instantiating CC interface from '%s'\n", val.c_str());
|
|
size_t spos, last = val.length() - 1;
|
|
if (val.length() == 0) {
|
|
spos = string::npos;
|
|
cc_if.cc_module = "";
|
|
} else {
|
|
spos = val.find(";", 0);
|
|
cc_if.cc_module = val.substr(0, spos);
|
|
}
|
|
DBG(" module='%s'\n", cc_if.cc_module.c_str());
|
|
while (spos < last) {
|
|
size_t epos = val.find("=", spos + 1);
|
|
if (epos == string::npos) {
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos + 1), ""));
|
|
DBG(" '%s'='%s'\n", val.substr(spos + 1).c_str(), "");
|
|
return;
|
|
}
|
|
if (epos == last) {
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos + 1, epos - spos - 1), ""));
|
|
DBG(" '%s'='%s'\n", val.substr(spos + 1, epos - spos - 1).c_str(), "");
|
|
return;
|
|
}
|
|
// if value starts with " char, it continues until another " is found
|
|
if (val[epos + 1] == '"') {
|
|
if (epos + 1 == last) {
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos+1, epos-spos-1), ""));
|
|
|
|
DBG(" '%s'='%s'\n",
|
|
val.substr(spos+1, epos-spos-1).c_str(), "");
|
|
return;
|
|
}
|
|
size_t qpos = val.find('"', epos + 2);
|
|
if (qpos == string::npos) {
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos+1, epos-spos-1),
|
|
val.substr(epos + 2)));
|
|
DBG(" '%s'='%s'\n",
|
|
val.substr(spos+1, epos-spos-1).c_str(),
|
|
val.substr(epos + 2).c_str());
|
|
|
|
return;
|
|
}
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos+1, epos-spos-1),
|
|
val.substr(epos+2, qpos-epos-2)));
|
|
DBG(" '%s'='%s'\n",
|
|
val.substr(spos+1, epos-spos-1).c_str(),
|
|
val.substr(epos+2, qpos-epos-2).c_str());
|
|
|
|
if (qpos < last) {
|
|
spos = val.find(";", qpos + 1);
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
size_t new_spos = val.find(";", epos + 1);
|
|
if (new_spos == string::npos) {
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos+1, epos-spos-1),
|
|
val.substr(epos+1)));
|
|
|
|
DBG(" '%s'='%s'\n",
|
|
val.substr(spos+1, epos-spos-1).c_str(),
|
|
val.substr(epos+1).c_str());
|
|
|
|
return;
|
|
}
|
|
|
|
cc_if.cc_values.insert(make_pair(val.substr(spos+1, epos-spos-1),
|
|
val.substr(epos+1, new_spos-epos-1)));
|
|
|
|
DBG(" '%s'='%s'\n",
|
|
val.substr(spos+1, epos-spos-1).c_str(),
|
|
val.substr(epos+1, new_spos-epos-1).c_str());
|
|
|
|
spos = new_spos;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBCCallProfile::eval_cc_list(ParamReplacerCtx& ctx, const AmSipRequest& req)
|
|
{
|
|
unsigned int cc_dynif_count = 0;
|
|
|
|
// fix up replacements in cc list
|
|
CCInterfaceListIteratorT cc_rit = cc_interfaces.begin();
|
|
while (cc_rit != cc_interfaces.end()) {
|
|
CCInterfaceListIteratorT curr_if = cc_rit;
|
|
cc_rit++;
|
|
// CCInterfaceListIteratorT next_cc = cc_rit+1;
|
|
if (curr_if->cc_name.find('$') != string::npos) {
|
|
curr_if->cc_name = ctx.replaceParameters(curr_if->cc_name,
|
|
"cc_interfaces", req);
|
|
vector<string> dyn_ccinterfaces = explode(curr_if->cc_name, ",");
|
|
if (!dyn_ccinterfaces.size()) {
|
|
DBG("call_control '%s' did not produce any call control instances\n",
|
|
curr_if->cc_name.c_str());
|
|
cc_interfaces.erase(curr_if);
|
|
} else {
|
|
// fill first CC interface (replacement item)
|
|
vector<string>::iterator it = dyn_ccinterfaces.begin();
|
|
curr_if->cc_name = "cc_dyn_"+int2str(cc_dynif_count++);
|
|
fixupCCInterface(trim(*it, " \t"), *curr_if);
|
|
it++;
|
|
|
|
// insert other CC interfaces (in order!)
|
|
while (it != dyn_ccinterfaces.end()) {
|
|
CCInterfaceListIteratorT new_cc =
|
|
cc_interfaces.insert(cc_rit, CCInterface());
|
|
fixupCCInterface(trim(*it, " \t"), *new_cc);
|
|
new_cc->cc_name = "cc_dyn_"+int2str(cc_dynif_count++);
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** removes headers with empty values from headers list separated by "\r\n" */
|
|
static string remove_empty_headers(const string& s)
|
|
{
|
|
string res(s), hdr;
|
|
size_t start = 0, end = 0, len = 0, col = 0;
|
|
DBG("SBCCallProfile::remove_empty_headers '%s'", s.c_str());
|
|
|
|
if (res.empty())
|
|
return res;
|
|
|
|
do {
|
|
end = res.find_first_of("\n", start);
|
|
len = (end == string::npos ? res.size() - start : end - start + 1);
|
|
hdr = res.substr(start, len);
|
|
col = hdr.find_first_of(':');
|
|
|
|
if (col && hdr.find_first_not_of(": \r\n", col) == string::npos) {
|
|
// remove empty header
|
|
WARN("Ignored empty header: %s\n", res.substr(start, len).c_str());
|
|
res.erase(start, len);
|
|
// start remains the same
|
|
}
|
|
else {
|
|
if (string::npos == col)
|
|
WARN("Malformed append header: %s\n", hdr.c_str());
|
|
start = end + 1;
|
|
}
|
|
} while (end != string::npos && start < res.size());
|
|
|
|
return res;
|
|
}
|
|
|
|
static void fix_append_hdr_list(const AmSipRequest& req, ParamReplacerCtx& ctx,
|
|
string& append_hdr, const char* field_name)
|
|
{
|
|
append_hdr = ctx.replaceParameters(append_hdr, field_name, req);
|
|
append_hdr = remove_empty_headers(append_hdr);
|
|
if (append_hdr.size()>2) assertEndCRLF(append_hdr);
|
|
}
|
|
|
|
void SBCCallProfile::fix_append_hdrs(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req)
|
|
{
|
|
fix_append_hdr_list(req, ctx, append_headers, "append_headers");
|
|
fix_append_hdr_list(req, ctx, append_headers_req,"append_headers_req");
|
|
fix_append_hdr_list(req, ctx, aleg_append_headers_req,"aleg_append_headers_req");
|
|
}
|
|
|
|
|
|
void SBCCallProfile::fix_reg_contact(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req,
|
|
AmUriParser& contact) const
|
|
{
|
|
string user = contact.uri_user;
|
|
string host = contact.uri_host;
|
|
string port = contact.uri_port;
|
|
|
|
if (!this->contact.displayname.empty()) {
|
|
contact.display_name =
|
|
ctx.replaceParameters(this->contact.displayname, "Contact DN", req);
|
|
}
|
|
if (!this->contact.user.empty()) {
|
|
contact.uri_user =
|
|
ctx.replaceParameters(this->contact.user, "Contact User", req);
|
|
}
|
|
if (!this->contact.host.empty()) {
|
|
contact.uri_host =
|
|
ctx.replaceParameters(this->contact.host, "Contact host", req);
|
|
}
|
|
if (!this->contact.port.empty()) {
|
|
contact.uri_port =
|
|
ctx.replaceParameters(this->contact.port, "Contact port", req);
|
|
}
|
|
}
|
|
|
|
string SBCCallProfile::retarget(const string& alias)
|
|
{
|
|
// REG-Cache lookup
|
|
AliasEntry alias_entry;
|
|
if(!RegisterCache::instance()->findAliasEntry(alias, alias_entry)) {
|
|
throw AmSession::Exception(404,"User not found");
|
|
}
|
|
string new_r_uri = alias_entry.contact_uri;
|
|
DBG("setting from registration cache: r_uri='%s'\n",new_r_uri.c_str());
|
|
|
|
// fix NAT
|
|
string nh = alias_entry.source_ip;
|
|
if(alias_entry.source_port != 5060)
|
|
nh += ":" + int2str(alias_entry.source_port);
|
|
|
|
DBG("setting from registration cache: next_hop='%s'\n", nh.c_str());
|
|
next_hop = nh;
|
|
|
|
// sticky interface
|
|
DBG("setting from registration cache: outbound_interface='%s'\n",
|
|
AmConfig::SIP_Ifs[alias_entry.local_if].name.c_str());
|
|
outbound_interface = AmConfig::SIP_Ifs[alias_entry.local_if].name;
|
|
outbound_interface_value = alias_entry.local_if;
|
|
|
|
return new_r_uri;
|
|
}
|
|
|
|
string SBCCallProfile::retarget(const string& alias, AmBasicSipDialog& dlg) const
|
|
{
|
|
// REG-Cache lookup
|
|
AliasEntry alias_entry;
|
|
if(!RegisterCache::instance()->findAliasEntry(alias, alias_entry)) {
|
|
DBG("No alias entry found for alias '%s', replying with 404\n", alias.c_str());
|
|
throw AmSession::Exception(404,"User not found");
|
|
}
|
|
string new_r_uri = alias_entry.contact_uri;
|
|
DBG("setting from registration cache: r_uri='%s'\n",new_r_uri.c_str());
|
|
|
|
// fix NAT
|
|
string nh = alias_entry.source_ip;
|
|
if(alias_entry.source_port != 5060)
|
|
nh += ":" + int2str(alias_entry.source_port);
|
|
|
|
DBG("setting from registration cache: next_hop='%s'\n", nh.c_str());
|
|
dlg.setNextHop(nh);
|
|
|
|
// sticky interface
|
|
DBG("setting from registration cache: outbound_interface='%s'\n",
|
|
AmConfig::SIP_Ifs[alias_entry.local_if].name.c_str());
|
|
dlg.setOutboundInterface(alias_entry.local_if);
|
|
|
|
return new_r_uri;
|
|
}
|
|
|
|
static bool readPayloadList(std::vector<PayloadDesc> &dst, const std::string &src)
|
|
{
|
|
dst.clear();
|
|
vector<string> elems = explode(src, ",");
|
|
for (vector<string>::iterator it=elems.begin(); it != elems.end(); ++it) {
|
|
PayloadDesc payload;
|
|
if (!payload.read(*it)) return false;
|
|
dst.push_back(payload);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool readPayload(SdpPayload &p, const string &src)
|
|
{
|
|
vector<string> elems = explode(src, "/");
|
|
|
|
if (elems.size() < 1) return false;
|
|
|
|
if (elems.size() > 2) str2int(elems[2], p.encoding_param);
|
|
if (elems.size() > 1) str2int(elems[1], p.clock_rate);
|
|
else p.clock_rate = 8000; // default value
|
|
p.encoding_name = elems[0];
|
|
|
|
string pname = p.encoding_name;
|
|
transform(pname.begin(), pname.end(), pname.begin(), ::tolower);
|
|
|
|
// fix static payload type numbers
|
|
// (http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xml)
|
|
for (int i = 0; i < IANA_RTP_PAYLOADS_SIZE; i++) {
|
|
string s = IANA_RTP_PAYLOADS[i].payload_name;
|
|
transform(s.begin(), s.end(), s.begin(), ::tolower);
|
|
if (p.encoding_name == s &&
|
|
(unsigned)p.clock_rate == IANA_RTP_PAYLOADS[i].clock_rate &&
|
|
(p.encoding_param == -1 || ((unsigned)p.encoding_param == IANA_RTP_PAYLOADS[i].channels)))
|
|
p.payload_type = i;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static string payload2str(const SdpPayload &p)
|
|
{
|
|
string s(p.encoding_name);
|
|
s += "/";
|
|
s += int2str(p.clock_rate);
|
|
return s;
|
|
}
|
|
|
|
static bool read(const std::string &src, vector<SdpPayload> &codecs)
|
|
{
|
|
vector<string> elems = explode(src, ",");
|
|
|
|
AmPlugIn* plugin = AmPlugIn::instance();
|
|
|
|
for (vector<string>::iterator it=elems.begin(); it != elems.end(); ++it) {
|
|
SdpPayload p;
|
|
if (!readPayload(p, trim(*it, " "))) return false;
|
|
int payload_id = plugin->getDynPayload(p.encoding_name, p.clock_rate, 0);
|
|
amci_payload_t* payload = plugin->payload(payload_id);
|
|
if(!payload) {
|
|
ERROR("Ignoring unknown payload found in call profile: '%s/%i'\n",
|
|
p.encoding_name.c_str(), p.clock_rate);
|
|
} else {
|
|
p.sdp_format_parameters = plugin->getSdpFormatParameters(payload->codec_id, true, "");
|
|
|
|
if(payload_id < DYNAMIC_PAYLOAD_TYPE_START)
|
|
p.payload_type = payload->payload_id;
|
|
else
|
|
p.payload_type = -1;
|
|
|
|
codecs.push_back(p);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SBCCallProfile::CodecPreferences::orderSDP(AmSdp& sdp, bool a_leg)
|
|
{
|
|
// get order of payloads for the other leg!
|
|
std::vector<PayloadDesc> &payload_order = a_leg ? bleg_payload_order: aleg_payload_order;
|
|
|
|
if (payload_order.size() < 1) return; // nothing to do - no predefined order
|
|
|
|
DBG("ordering SDP\n");
|
|
for (vector<SdpMedia>::iterator m_it =
|
|
sdp.media.begin(); m_it != sdp.media.end(); ++m_it) {
|
|
SdpMedia& media = *m_it;
|
|
|
|
unsigned pos = 0;
|
|
unsigned idx;
|
|
unsigned cnt = media.payloads.size();
|
|
|
|
// TODO: optimize
|
|
// for each predefined payloads in their order
|
|
for (vector<PayloadDesc>::iterator i = payload_order.begin(); i != payload_order.end(); ++i) {
|
|
// try to find this payload in SDP
|
|
// (not needed to go through already sorted members)
|
|
for (idx = pos; idx < cnt; idx++) {
|
|
if (i->match(media.payloads[idx])) {
|
|
// found, insert found element at pos and delete the occurence on idx
|
|
// (can not swap these elements to avoid changing order of codecs
|
|
// which are not in the payload_order list)
|
|
if (idx != pos) {
|
|
media.payloads.insert(media.payloads.begin() + pos, media.payloads[idx]);
|
|
media.payloads.erase(media.payloads.begin() + idx + 1);
|
|
}
|
|
|
|
++pos; // next payload index
|
|
// do not terminate the inner loop because one PayloadDesc can match
|
|
// more payloads!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SBCCallProfile::CodecPreferences::shouldOrderPayloads(bool a_leg)
|
|
{
|
|
// returns true if order of payloads for the other leg is set! (i.e. if we
|
|
// have to order payloads)
|
|
if (a_leg) return !bleg_payload_order.empty();
|
|
else return !aleg_payload_order.empty();
|
|
}
|
|
|
|
bool SBCCallProfile::CodecPreferences::readConfig(AmConfigReader &cfg)
|
|
{
|
|
// store string values for later evaluation
|
|
bleg_payload_order_str = cfg.getParameter("codec_preference");
|
|
bleg_prefer_existing_payloads_str = cfg.getParameter("prefer_existing_codecs");
|
|
|
|
aleg_payload_order_str = cfg.getParameter("codec_preference_aleg");
|
|
aleg_prefer_existing_payloads_str = cfg.getParameter("prefer_existing_codecs_aleg");
|
|
|
|
return true;
|
|
}
|
|
|
|
void SBCCallProfile::CodecPreferences::infoPrint() const
|
|
{
|
|
INFO("SBC: A leg codec preference: %s\n", aleg_payload_order_str.c_str());
|
|
INFO("SBC: A leg prefer existing codecs: %s\n", aleg_prefer_existing_payloads_str.c_str());
|
|
INFO("SBC: B leg codec preference: %s\n", bleg_payload_order_str.c_str());
|
|
INFO("SBC: B leg prefer existing codecs: %s\n", bleg_prefer_existing_payloads_str.c_str());
|
|
}
|
|
|
|
bool SBCCallProfile::CodecPreferences::operator==(const CodecPreferences& rhs) const
|
|
{
|
|
if (!payloadDescsEqual(aleg_payload_order, rhs.aleg_payload_order)) return false;
|
|
if (!payloadDescsEqual(bleg_payload_order, rhs.bleg_payload_order)) return false;
|
|
if (aleg_prefer_existing_payloads != rhs.aleg_prefer_existing_payloads) return false;
|
|
if (bleg_prefer_existing_payloads != rhs.bleg_prefer_existing_payloads) return false;
|
|
return true;
|
|
}
|
|
|
|
string SBCCallProfile::CodecPreferences::print() const
|
|
{
|
|
string res;
|
|
|
|
res += "codec_preference: ";
|
|
for (vector<PayloadDesc>::const_iterator i = bleg_payload_order.begin(); i != bleg_payload_order.end(); ++i) {
|
|
if (i != bleg_payload_order.begin()) res += ",";
|
|
res += i->print();
|
|
}
|
|
res += "\n";
|
|
|
|
res += "prefer_existing_codecs: ";
|
|
if (bleg_prefer_existing_payloads) res += "yes\n";
|
|
else res += "no\n";
|
|
|
|
res += "codec_preference_aleg: ";
|
|
for (vector<PayloadDesc>::const_iterator i = aleg_payload_order.begin(); i != aleg_payload_order.end(); ++i) {
|
|
if (i != aleg_payload_order.begin()) res += ",";
|
|
res += i->print();
|
|
}
|
|
res += "\n";
|
|
|
|
res += "prefer_existing_codecs_aleg: ";
|
|
if (aleg_prefer_existing_payloads) res += "yes\n";
|
|
else res += "no\n";
|
|
|
|
return res;
|
|
}
|
|
|
|
bool SBCCallProfile::CodecPreferences::evaluate(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req)
|
|
{
|
|
REPLACE_BOOL(aleg_prefer_existing_payloads_str, aleg_prefer_existing_payloads);
|
|
REPLACE_BOOL(bleg_prefer_existing_payloads_str, bleg_prefer_existing_payloads);
|
|
|
|
REPLACE_NONEMPTY_STR(aleg_payload_order_str);
|
|
REPLACE_NONEMPTY_STR(bleg_payload_order_str);
|
|
|
|
if (!readPayloadList(bleg_payload_order, bleg_payload_order_str)) return false;
|
|
if (!readPayloadList(aleg_payload_order, aleg_payload_order_str)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SBCCallProfile::TranscoderSettings::readTranscoderMode(const std::string &src)
|
|
{
|
|
static const string always("always");
|
|
static const string never("never");
|
|
static const string on_missing_compatible("on_missing_compatible");
|
|
|
|
if (src == always) { transcoder_mode = Always; return true; }
|
|
if (src == never) { transcoder_mode = Never; return true; }
|
|
if (src == on_missing_compatible) { transcoder_mode = OnMissingCompatible; return true; }
|
|
if (src.empty()) { transcoder_mode = Never; return true; } // like default value
|
|
ERROR("unknown value of enable_transcoder option: %s\n", src.c_str());
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SBCCallProfile::TranscoderSettings::readDTMFMode(const std::string &src)
|
|
{
|
|
static const string always("always");
|
|
static const string never("never");
|
|
static const string lowfi_codec("lowfi_codec");
|
|
|
|
if (src == always) { dtmf_mode = DTMFAlways; return true; }
|
|
if (src == never) { dtmf_mode = DTMFNever; return true; }
|
|
if (src == lowfi_codec) { dtmf_mode = DTMFLowFiCodecs; return true; }
|
|
if (src.empty()) { dtmf_mode = DTMFNever; return true; } // like default value
|
|
ERROR("unknown value of dtmf_transcoding_mode option: %s\n", src.c_str());
|
|
|
|
return false;
|
|
}
|
|
|
|
void SBCCallProfile::TranscoderSettings::infoPrint() const
|
|
{
|
|
INFO("SBC: transcoder audio codecs: %s\n", audio_codecs_str.c_str());
|
|
INFO("SBC: callee codec capabilities: %s\n", callee_codec_capabilities_str.c_str());
|
|
INFO("SBC: enable transcoder: %s\n", transcoder_mode_str.c_str());
|
|
INFO("SBC: norelay audio codecs: %s\n", audio_codecs_norelay_str.c_str());
|
|
INFO("SBC: norelay audio codecs (aleg): %s\n", audio_codecs_norelay_aleg_str.c_str());
|
|
}
|
|
|
|
bool SBCCallProfile::TranscoderSettings::readConfig(AmConfigReader &cfg)
|
|
{
|
|
// store string values for later evaluation
|
|
audio_codecs_str = cfg.getParameter("transcoder_codecs");
|
|
callee_codec_capabilities_str = cfg.getParameter("callee_codeccaps");
|
|
transcoder_mode_str = cfg.getParameter("enable_transcoder");
|
|
dtmf_mode_str = cfg.getParameter("dtmf_transcoding");
|
|
lowfi_codecs_str = cfg.getParameter("lowfi_codecs");
|
|
audio_codecs_norelay_str = cfg.getParameter("prefer_transcoding_for_codecs");
|
|
audio_codecs_norelay_aleg_str = cfg.getParameter("prefer_transcoding_for_codecs_aleg");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SBCCallProfile::TranscoderSettings::operator==(const TranscoderSettings& rhs) const
|
|
{
|
|
bool res = (transcoder_mode == rhs.transcoder_mode);
|
|
res = res && (enabled == rhs.enabled);
|
|
res = res && (payloadDescsEqual(callee_codec_capabilities, rhs.callee_codec_capabilities));
|
|
res = res && (audio_codecs == rhs.audio_codecs);
|
|
return res;
|
|
}
|
|
|
|
string SBCCallProfile::TranscoderSettings::print() const
|
|
{
|
|
string res("transcoder audio codecs:");
|
|
for (vector<SdpPayload>::const_iterator i = audio_codecs.begin(); i != audio_codecs.end(); ++i) {
|
|
res += " ";
|
|
res += payload2str(*i);
|
|
}
|
|
|
|
res += "\ncallee codec capabilities:";
|
|
for (vector<PayloadDesc>::const_iterator i = callee_codec_capabilities.begin();
|
|
i != callee_codec_capabilities.end(); ++i)
|
|
{
|
|
res += " ";
|
|
res += i->print();
|
|
}
|
|
|
|
string s("?");
|
|
switch (transcoder_mode) {
|
|
case Always: s = "always"; break;
|
|
case Never: s = "never"; break;
|
|
case OnMissingCompatible: s = "on_missing_compatible"; break;
|
|
}
|
|
res += "\nenable transcoder: " + s;
|
|
|
|
res += "\ntranscoder currently enabled: ";
|
|
if (enabled) res += "yes\n";
|
|
else res += "no\n";
|
|
|
|
return res;
|
|
}
|
|
|
|
bool SBCCallProfile::TranscoderSettings::evaluate(ParamReplacerCtx& ctx,
|
|
const AmSipRequest& req)
|
|
{
|
|
REPLACE_NONEMPTY_STR(transcoder_mode_str);
|
|
REPLACE_NONEMPTY_STR(audio_codecs_str);
|
|
REPLACE_NONEMPTY_STR(audio_codecs_norelay_str);
|
|
REPLACE_NONEMPTY_STR(audio_codecs_norelay_aleg_str);
|
|
REPLACE_NONEMPTY_STR(callee_codec_capabilities_str);
|
|
REPLACE_NONEMPTY_STR(lowfi_codecs_str);
|
|
|
|
if (!read(audio_codecs_str, audio_codecs)) return false;
|
|
if (!read(audio_codecs_norelay_str, audio_codecs_norelay)) return false;
|
|
if (!read(audio_codecs_norelay_aleg_str, audio_codecs_norelay_aleg)) return false;
|
|
|
|
if (!readPayloadList(callee_codec_capabilities, callee_codec_capabilities_str))
|
|
return false;
|
|
|
|
if (!readTranscoderMode(transcoder_mode_str)) return false;
|
|
|
|
if (!readDTMFMode(dtmf_mode_str)) return false;
|
|
|
|
if (!read(lowfi_codecs_str, lowfi_codecs)) return false;
|
|
|
|
// enable transcoder according to transcoder mode and optionally request's SDP
|
|
switch (transcoder_mode) {
|
|
case Always: enabled = true; break;
|
|
case Never: enabled = false; break;
|
|
case OnMissingCompatible:
|
|
enabled = isTranscoderNeeded(req, callee_codec_capabilities,
|
|
true /* if SDP can't be analyzed, enable transcoder */);
|
|
break;
|
|
}
|
|
|
|
DBG("transcoder is %s\n", enabled ? "enabled": "disabled");
|
|
|
|
if (enabled && audio_codecs.empty()) {
|
|
ERROR("transcoder is enabled but no transcoder codecs selected ... disabling it\n");
|
|
enabled = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SBCCallProfile::create_logger(const AmSipRequest& req)
|
|
{
|
|
if (msg_logger_path.empty()) return;
|
|
|
|
ParamReplacerCtx ctx(this);
|
|
string log_path = ctx.replaceParameters(msg_logger_path, "msg_logger_path", req);
|
|
if (log_path.empty()) return;
|
|
|
|
file_msg_logger *log = new pcap_logger();
|
|
|
|
if(log->open(log_path.c_str()) != 0) {
|
|
// open error
|
|
delete log;
|
|
return;
|
|
}
|
|
|
|
// opened successfully
|
|
logger.reset(log);
|
|
}
|
|
|
|
msg_logger* SBCCallProfile::get_logger(const AmSipRequest& req)
|
|
{
|
|
if (!logger.get() && !msg_logger_path.empty()) create_logger(req);
|
|
return logger.get();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool PayloadDesc::match(const SdpPayload &p) const
|
|
{
|
|
string enc_name = p.encoding_name;
|
|
transform(enc_name.begin(), enc_name.end(), enc_name.begin(), ::tolower);
|
|
|
|
if ((name.size() > 0) && (name != enc_name)) return false;
|
|
if (clock_rate && (p.clock_rate > 0) && clock_rate != (unsigned)p.clock_rate) return false;
|
|
return true;
|
|
}
|
|
|
|
bool PayloadDesc::read(const std::string &s)
|
|
{
|
|
vector<string> elems = explode(s, "/");
|
|
if (elems.size() > 1) {
|
|
name = elems[0];
|
|
str2i(elems[1], clock_rate);
|
|
}
|
|
else if (elems.size() > 0) {
|
|
name = elems[0];
|
|
clock_rate = 0;
|
|
}
|
|
transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
return true;
|
|
}
|
|
|
|
string PayloadDesc::print() const
|
|
{
|
|
std::string s(name);
|
|
s += " / ";
|
|
if (!clock_rate) s += "whatever rate";
|
|
else s += int2str(clock_rate);
|
|
return s;
|
|
}
|
|
|
|
bool PayloadDesc::operator==(const PayloadDesc &other) const
|
|
{
|
|
if (name != other.name) return false;
|
|
if (clock_rate != other.clock_rate) return false;
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SBCCallProfile::HoldSettings::readConfig(AmConfigReader &cfg)
|
|
{
|
|
// store string values for later evaluation
|
|
aleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_aleg");
|
|
aleg.activity_str = cfg.getParameter("hold_activity_aleg");
|
|
aleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_aleg");
|
|
|
|
bleg.mark_zero_connection_str = cfg.getParameter("hold_zero_connection_bleg");
|
|
bleg.activity_str = cfg.getParameter("hold_activity_bleg");
|
|
bleg.alter_b2b_str = cfg.getParameter("hold_alter_b2b_bleg");
|
|
}
|
|
|
|
bool SBCCallProfile::HoldSettings::HoldParams::setActivity(const string &s)
|
|
{
|
|
if (s == "sendrecv") activity = sendrecv;
|
|
else if (s == "sendonly") activity = sendonly;
|
|
else if (s == "recvonly") activity = recvonly;
|
|
else if (s == "inactive") activity = inactive;
|
|
else {
|
|
ERROR("unsupported hold stream activity: %s\n", s.c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SBCCallProfile::HoldSettings::evaluate(ParamReplacerCtx& ctx, const AmSipRequest& req)
|
|
{
|
|
REPLACE_BOOL(aleg.mark_zero_connection_str, aleg.mark_zero_connection);
|
|
REPLACE_STR(aleg.activity_str);
|
|
REPLACE_BOOL(aleg.alter_b2b_str, aleg.alter_b2b);
|
|
|
|
REPLACE_BOOL(bleg.mark_zero_connection_str, bleg.mark_zero_connection);
|
|
REPLACE_STR(bleg.activity_str);
|
|
REPLACE_BOOL(bleg.alter_b2b_str, bleg.alter_b2b);
|
|
|
|
if (!aleg.activity_str.empty() && !aleg.setActivity(aleg.activity_str)) return false;
|
|
if (!bleg.activity_str.empty() && !bleg.setActivity(bleg.activity_str)) return false;
|
|
|
|
return true;
|
|
}
|