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.
sems/core/plug-in/session_timer/SessionTimer.cpp

432 lines
12 KiB

/*
* Copyright (C) 2002-2003 Fhg Fokus
*
* 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. This program is released under
* the GPL with the additional exemption that compiling, linking,
* and/or using OpenSSL is allowed.
*
* 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 "SessionTimer.h"
#include "AmUtils.h"
#include "UserTimer.h"
#include "AmSipHeaders.h"
EXPORT_SESSION_EVENT_HANDLER_FACTORY(SessionTimerFactory, MOD_NAME);
int SessionTimerFactory::onLoad()
{
return 0;
}
bool SessionTimerFactory::onInvite(const AmSipRequest& req, AmConfigReader& cfg)
{
return checkSessionExpires(req, cfg);
}
AmSessionEventHandler* SessionTimerFactory::getHandler(AmSession* s)
{
return new SessionTimer(s);
}
SessionTimer::SessionTimer(AmSession* s)
:AmSessionEventHandler(),
s(s),
session_interval(0),
session_refresher(refresh_remote),
accept_501_reply(true)
{
}
bool SessionTimer::process(AmEvent* ev)
{
assert(ev);
AmTimeoutEvent* timeout_ev = dynamic_cast<AmTimeoutEvent*>(ev);
if (timeout_ev) {
DBG("received timeout Event with ID %d\n", timeout_ev->data.get(0).asInt());
onTimeoutEvent(timeout_ev);
return true;
}
return false;
}
bool SessionTimer::onSipRequest(const AmSipRequest& req)
{
updateTimer(s,req);
return false;
}
bool SessionTimer::onSipReply(const AmSipReply& reply, int old_dlg_status,
const string& trans_method)
{
updateTimer(s,reply);
return false;
}
bool SessionTimer::onSendRequest(const string& method,
const string& content_type,
const string& body,
string& hdrs,
int flags,
unsigned int cseq)
{
string m_hdrs = SIP_HDR_COLSP(SIP_HDR_SUPPORTED) "timer" CRLF;
if ((method != SIP_METH_INVITE) && (method != SIP_METH_UPDATE))
goto end;
m_hdrs += SIP_HDR_COLSP(SIP_HDR_SESSION_EXPIRES) + int2str(session_interval) +CRLF
+ SIP_HDR_COLSP(SIP_HDR_MIN_SE) + int2str(min_se) + CRLF;
end:
hdrs += m_hdrs;
return false;
}
bool SessionTimer::onSendReply(const AmSipRequest& req,
unsigned int code,const string& reason,
const string& content_type,const string& body,
string& hdrs,
int flags)
{
if (((req.method != SIP_METH_INVITE) && (req.method != SIP_METH_UPDATE)) ||
(code < 200) || (code >= 300))
return false;
string m_hdrs = SIP_HDR_COLSP(SIP_HDR_SUPPORTED) "timer" CRLF;
// only in 2xx responses to INV/UPD
m_hdrs += SIP_HDR_COLSP(SIP_HDR_SESSION_EXPIRES) +
int2str(session_interval) + ";refresher="+
(session_refresher_role==UAC ? "uac":"uas")+CRLF;
if (((session_refresher_role==UAC) && (session_refresher==refresh_remote))
|| ((session_refresher_role==UAS) && remote_timer_aware))
m_hdrs += SIP_HDR_COLSP(SIP_HDR_REQUIRE) "timer" CRLF;
hdrs += m_hdrs;
return false;
}
int SessionTimer::configure(AmConfigReader& conf)
{
if(session_timer_conf.readFromConfig(conf))
return -1;
session_interval = session_timer_conf.getSessionExpires();
min_se = session_timer_conf.getMinimumTimer();
DBG("Configured session with EnableSessionTimer = %s, "
"SessionExpires = %u, MinimumTimer = %u\n",
session_timer_conf.getEnableSessionTimer() ? "yes":"no",
session_timer_conf.getSessionExpires(),
session_timer_conf.getMinimumTimer()
);
if (conf.hasParameter("session_refresh_method")) {
string refresh_method_s = conf.getParameter("session_refresh_method");
if (refresh_method_s == "UPDATE") {
s->refresh_method = AmSession::REFRESH_UPDATE;
} else if (refresh_method_s == "UPDATE_FALLBACK_INVITE") {
s->refresh_method = AmSession::REFRESH_UPDATE_FB_REINV;
} else if (refresh_method_s == "INVITE") {
s->refresh_method = AmSession::REFRESH_REINVITE;
} else {
ERROR("unknown setting for 'session_refresh_method' config option.\n");
return -1;
}
DBG("set session refresh method: %d.\n", s->refresh_method);
}
if (conf.getParameter("accept_501_reply")=="no")
accept_501_reply = false;
return 0;
}
/**
* check if UAC requests too low Session-Expires
* (<locally configured Min-SE)
* Throws SessionIntervalTooSmallException if too low
*/
bool SessionTimerFactory::checkSessionExpires(const AmSipRequest& req, AmConfigReader& cfg)
{
AmSessionTimerConfig sst_cfg;
if (sst_cfg.readFromConfig(cfg)) {
return false;
}
string session_expires = getHeader(req.hdrs, SIP_HDR_SESSION_EXPIRES,
SIP_HDR_SESSION_EXPIRES_COMPACT, true);
if (session_expires.length()) {
unsigned int i_se;
if (!str2i(strip_header_params(session_expires), i_se)) {
if (i_se < sst_cfg.getMinimumTimer()) {
throw AmSession::Exception(422, "Session Interval Too Small",
SIP_HDR_COLSP(SIP_HDR_MIN_SE)+
int2str(sst_cfg.getMinimumTimer())+CRLF);
}
} else {
WARN("parsing session expires '%s' failed\n", session_expires.c_str());
throw AmSession::Exception(400,"Bad Request");
}
}
return true;
}
void SessionTimer::updateTimer(AmSession* s, const AmSipRequest& req) {
if((req.method == SIP_METH_INVITE)||(req.method == SIP_METH_UPDATE)){
remote_timer_aware =
key_in_list(getHeader(req.hdrs, SIP_HDR_SUPPORTED),"timer", true);
// determine session interval
string sess_expires_hdr = getHeader(req.hdrs, SIP_HDR_SESSION_EXPIRES,
SIP_HDR_SESSION_EXPIRES_COMPACT, true);
bool rem_has_sess_expires = false;
unsigned int rem_sess_expires=0;
if (!sess_expires_hdr.empty()) {
if (str2i(strip_header_params(sess_expires_hdr),
rem_sess_expires)) {
WARN("error while parsing " SIP_HDR_SESSION_EXPIRES " header value '%s'\n",
strip_header_params(sess_expires_hdr).c_str()); // exception?
} else {
rem_has_sess_expires = true;
}
}
// get Min-SE
unsigned int i_minse = min_se;
string min_se_hdr = getHeader(req.hdrs, SIP_HDR_MIN_SE, true);
if (!min_se_hdr.empty()) {
if (str2i(strip_header_params(min_se_hdr),
i_minse)) {
WARN("error while parsing " SIP_HDR_MIN_SE " header value '%s'\n",
strip_header_params(min_se_hdr).c_str()); // exception?
}
}
// calculate actual se
session_interval = session_timer_conf.getSessionExpires();
if (i_minse > min_se)
min_se = i_minse;
if (rem_has_sess_expires && (rem_sess_expires < min_se)) {
session_interval = min_se;
} else {
if (rem_has_sess_expires && (rem_sess_expires < session_interval))
session_interval = rem_sess_expires;
}
DBG("using actual session interval %u\n", session_interval);
// determine session refresher -- cf rfc4028 Table 2
// only if the remote party supports timer and asks
// to be refresher we will let the remote party do it.
// if remote supports timer and does not specify,
// could also be refresher=uac
if ((remote_timer_aware) && (!sess_expires_hdr.empty()) &&
(get_header_param(sess_expires_hdr, "refresher") == "uac")) {
DBG("session refresher will be remote UAC.\n");
session_refresher = refresh_remote;
session_refresher_role = UAC;
} else {
DBG("session refresher will be local UAS.\n");
session_refresher = refresh_local;
session_refresher_role = UAS;
}
removeTimers(s);
setTimers(s);
} else if (req.method == "BYE") { // remove all timers?
removeTimers(s);
}
}
void SessionTimer::updateTimer(AmSession* s, const AmSipReply& reply)
{
if (!session_timer_conf.getEnableSessionTimer())
return;
// only update timer on positive reply, or 501 if config'd
if (((reply.code < 200) || (reply.code >= 300)) &&
(!(accept_501_reply && reply.code == 501)))
return;
// determine session interval
string sess_expires_hdr = getHeader(reply.hdrs, SIP_HDR_SESSION_EXPIRES,
SIP_HDR_SESSION_EXPIRES_COMPACT, true);
session_refresher = refresh_local;
session_refresher_role = UAC;
if (!sess_expires_hdr.empty()) {
unsigned int sess_i_tmp = 0;
if (str2i(strip_header_params(sess_expires_hdr),
sess_i_tmp)) {
WARN("error while parsing " SIP_HDR_SESSION_EXPIRES " header value '%s'\n",
strip_header_params(sess_expires_hdr).c_str()); // exception?
} else {
// this is forbidden by rfc, but to be sure against 'rogue' proxy/uas
if (sess_i_tmp < min_se) {
session_interval = min_se;
} else {
session_interval = sess_i_tmp;
}
}
if (get_header_param(sess_expires_hdr, "refresher") == "uas") {
session_refresher = refresh_remote;
session_refresher_role = UAS;
}
}
removeTimers(s);
setTimers(s);
}
void SessionTimer::setTimers(AmSession* s)
{
// set session timer
DBG("Setting session interval timer: %ds, tag '%s'\n", session_interval,
s->getLocalTag().c_str());
s->setTimer(ID_SESSION_INTERVAL_TIMER, session_interval);
// set session refresh action timer, after half the expiration
if (session_refresher == refresh_local) {
DBG("Setting session refresh timer: %ds, tag '%s'\n", session_interval/2,
s->getLocalTag().c_str());
s->setTimer(ID_SESSION_REFRESH_TIMER, session_interval/2);
}
}
void SessionTimer::retryRefreshTimer(AmSession* s) {
DBG("Retrying session refresh timer: T-2s, tag '%s' \n",
s->getLocalTag().c_str());
s->setTimer(ID_SESSION_REFRESH_TIMER, 2);
}
void SessionTimer::removeTimers(AmSession* s)
{
s->removeTimer(ID_SESSION_REFRESH_TIMER);
s->removeTimer(ID_SESSION_INTERVAL_TIMER);
}
void SessionTimer::onTimeoutEvent(AmTimeoutEvent* timeout_ev)
{
int timer_id = timeout_ev->data.get(0).asInt();
if (timer_id == ID_SESSION_REFRESH_TIMER) {
if (session_refresher == refresh_local) {
DBG("Session Timer: initiating session refresh\n");
if (!s->refresh()) {
retryRefreshTimer(s);
}
} else {
DBG("need session refresh but remote session is refresher\n");
}
} else if (timer_id == ID_SESSION_INTERVAL_TIMER) {
s->onSessionTimeout();
} else {
DBG("unknown timeout event received.\n");
}
return;
}
AmSessionTimerConfig::AmSessionTimerConfig()
: EnableSessionTimer(DEFAULT_ENABLE_SESSION_TIMER),
SessionExpires(SESSION_EXPIRES),
MinimumTimer(MINIMUM_TIMER)
{
}
AmSessionTimerConfig::~AmSessionTimerConfig()
{
}
int AmSessionTimerConfig::readFromConfig(AmConfigReader& cfg)
{
// enable_session_timer
if(cfg.hasParameter("enable_session_timer")){
if(!setEnableSessionTimer(cfg.getParameter("enable_session_timer"))){
ERROR("invalid enable_session_timer specified\n");
return -1;
}
}
// session_expires
if(cfg.hasParameter("session_expires")){
if(!setSessionExpires(cfg.getParameter("session_expires"))){
ERROR("invalid session_expires specified\n");
return -1;
}
}
// minimum_timer
if(cfg.hasParameter("minimum_timer")){
if(!setMinimumTimer(cfg.getParameter("minimum_timer"))){
ERROR("invalid minimum_timer specified\n");
return -1;
}
}
return 0;
}
int AmSessionTimerConfig::setEnableSessionTimer(const string& enable) {
if ( strcasecmp(enable.c_str(), "yes") == 0 ) {
EnableSessionTimer = 1;
} else if ( strcasecmp(enable.c_str(), "no") == 0 ) {
EnableSessionTimer = 0;
} else {
return 0;
}
return 1;
}
int AmSessionTimerConfig::setSessionExpires(const string& se) {
if(sscanf(se.c_str(),"%u",&SessionExpires) != 1) {
return 0;
}
DBG("setSessionExpires(%i)\n",SessionExpires);
return 1;
}
int AmSessionTimerConfig::setMinimumTimer(const string& minse) {
if(sscanf(minse.c_str(),"%u",&MinimumTimer) != 1) {
return 0;
}
DBG("setMinimumTimer(%i)\n",MinimumTimer);
return 1;
}