<depend>res_features</depend>
***/
+/*! \page sip_session_timers SIP Session Timers in Asterisk Chan_sip
+
+ The SIP Session-Timers is an extension of the SIP protocol that allows end-points and proxies to
+ refresh a session periodically. The sessions are kept alive by sending a RE-INVITE or UPDATE
+ request at a negotiated interval. If a session refresh fails then all the entities that support Session-
+ Timers clear their internal session state. In addition, UAs generate a BYE request in order to clear
+ the state in the proxies and the remote UA (this is done for the benefit of SIP entities in the path
+ that do not support Session-Timers).
+
+ The Session-Timers can be configured on a system-wide, per-user, or per-peer basis. The peruser/
+ per-peer settings override the global settings. The following new parameters have been
+ added to the sip.conf file.
+ session-timers=["accept", "originate", "refuse"]
+ session-expires=[integer]
+ session-minse=[integer]
+ session-refresher=["uas", "uac"]
+
+ The session-timers parameter in sip.conf defines the mode of operation of SIP session-timers feature in
+ Asterisk. The Asterisk can be configured in one of the following three modes:
+
+ 1. Accept :: In the "accept" mode, the Asterisk server honors session-timers requests
+ made by remote end-points. A remote end-point can request Asterisk to engage
+ session-timers by either sending it an INVITE request with a "Supported: timer"
+ header in it or by responding to Asterisk's INVITE with a 200 OK that contains
+ Session-Expires: header in it. In this mode, the Asterisk server does not
+ request session-timers from remote end-points. This is the default mode.
+ 2. Originate :: In the "originate" mode, the Asterisk server requests the remote
+ end-points to activate session-timers in addition to honoring such requests
+ made by the remote end-pints. In order to get as much protection as possible
+ against hanging SIP channels due to network or end-point failures, Asterisk
+ resends periodic re-INVITEs even if a remote end-point does not support
+ the session-timers feature.
+ 3. Refuse :: In the "refuse" mode, Asterisk acts as if it does not support session-
+ timers for inbound or outbound requests. If a remote end-point requests
+ session-timers in a dialog, then Asterisk ignores that request unless it's
+ noted as a requirement (Require: header), in which case the INVITE is
+ rejected with a 420 Bad Extension response.
+
+*/
#include "asterisk.h"
#define INITIAL_CSEQ 101 /*!< our initial sip sequence number */
+#define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */
+#define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */
+
/*! \brief Global jitterbuffer configuration - by default, jb is disabled */
static struct ast_jb_conf default_jbconf =
{
/* fatal - no chance to proceed */
};
+/*! \brief Modes in which Asterisk can be configured to run SIP Session-Timers */
+enum st_mode {
+ SESSION_TIMER_MODE_INVALID = 0, /*!< Invalid value */
+ SESSION_TIMER_MODE_ACCEPT, /*!< Honor inbound Session-Timer requests */
+ SESSION_TIMER_MODE_ORIGINATE, /*!< Originate outbound and honor inbound requests */
+ SESSION_TIMER_MODE_REFUSE /*!< Ignore inbound Session-Timers requests */
+};
+
+/*! \brief The entity playing the refresher role for Session-Timers */
+enum st_refresher {
+ SESSION_TIMER_REFRESHER_AUTO, /*!< Negotiated */
+ SESSION_TIMER_REFRESHER_UAC, /*!< Session is refreshed by the UAC */
+ SESSION_TIMER_REFRESHER_UAS /*!< Session is refreshed by the UAS */
+};
+
+
/*! \brief definition of a sip proxy server
*
* For outbound proxies, this is allocated in the SIP peer dynamically or
#define SIP_OPT_NOREFERSUB (1 << 14)
#define SIP_OPT_HISTINFO (1 << 15)
#define SIP_OPT_RESPRIORITY (1 << 16)
+#define SIP_OPT_UNKNOWN (1 << 17)
+
/*! \brief List of well-known SIP options. If we get this in a require,
we should check the list and answer accordingly. */
{ SIP_OPT_REPLACES, SUPPORTED, "replace" },
/* RFC3262: PRACK 100% reliability */
{ SIP_OPT_100REL, NOT_SUPPORTED, "100rel" },
- /* RFC4028: SIP Session Timers */
- { SIP_OPT_TIMER, NOT_SUPPORTED, "timer" },
+ /* RFC4028: SIP Session-Timers */
+ { SIP_OPT_TIMER, SUPPORTED, "timer" },
/* RFC3959: SIP Early session support */
{ SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" },
/* RFC3911: SIP Join header support */
#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY"
/*! \brief SIP Extensions we support */
-#define SUPPORTED_EXTENSIONS "replaces"
+#define SUPPORTED_EXTENSIONS "replaces, timer"
/*! \brief Standard SIP port from RFC 3261. DO NOT CHANGE THIS */
#define STANDARD_SIP_PORT 5060
static struct ast_flags global_flags[2] = {{0}}; /*!< global SIP_ flags */
static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */
+static enum st_mode global_st_mode; /*!< Mode of operation for Session-Timers */
+static enum st_refresher global_st_refresher; /*!< Session-Timer refresher */
+static int global_min_se; /*!< Lowest threshold for session refresh interval */
+static int global_max_se; /*!< Highest threshold for session refresh interval */
+
+
AST_MUTEX_DEFINE_STATIC(netlock);
/*! \brief Protect the monitoring thread, so only one process can kill or start it, and not
enum referstatus status; /*!< REFER status */
};
+
+/*! \brief Structure that encapsulates all attributes related to running
+ * SIP Session-Timers feature on a per dialog basis.
+ */
+struct sip_st_dlg {
+ int st_active; /*!< Session-Timers on/off */
+ int st_interval; /*!< Session-Timers negotiated session refresh interval */
+ int st_schedid; /*!< Session-Timers ast_sched scheduler id */
+ enum st_refresher st_ref; /*!< Session-Timers session refresher */
+ int st_expirys; /*!< Session-Timers number of expirys */
+ int st_active_peer_ua; /*!< Session-Timers on/off in peer UA */
+ int st_cached_min_se; /*!< Session-Timers cached Min-SE */
+ int st_cached_max_se; /*!< Session-Timers cached Session-Expires */
+ enum st_mode st_cached_mode; /*!< Session-Timers cached M.O. */
+ enum st_refresher st_cached_ref; /*!< Session-Timers cached refresher */
+};
+
+
+/*! \brief Structure that encapsulates all attributes related to configuration
+ * of SIP Session-Timers feature on a per user/peer basis.
+ */
+struct sip_st_cfg {
+ enum st_mode st_mode_oper; /*!< Mode of operation for Session-Timers */
+ enum st_refresher st_ref; /*!< Session-Timer refresher */
+ int st_min_se; /*!< Lowest threshold for session refresh interval */
+ int st_max_se; /*!< Highest threshold for session refresh interval */
+};
+
+
+
+
/*! \brief sip_pvt: structures used for each SIP dialog, ie. a call, a registration, a subscribe.
* Created and initialized by sip_alloc(), the descriptor goes into the list of
* descriptors (dialoglist).
int timer_t1; /*!< SIP timer T1, ms rtt */
int timer_b; /*!< SIP timer B, ms */
unsigned int sipoptions; /*!< Supported SIP options on the other end */
+ unsigned int reqsipoptions; /*!< Required SIP options on the other end */
struct ast_codec_pref prefs; /*!< codec prefs */
int capability; /*!< Special capability (codec) */
int jointcapability; /*!< Supported capability at both ends (codecs) */
char tag[11]; /*!< Our tag for this session */
int sessionid; /*!< SDP Session ID */
int sessionversion; /*!< SDP Session Version */
+ int sessionversion_remote; /*!< Remote UA's SDP Session Version */
+ int session_modify; /*!< Session modification request true/false */
struct sockaddr_in sa; /*!< Our peer */
struct sockaddr_in redirip; /*!< Where our RTP should be going if not to us */
struct sockaddr_in vredirip; /*!< Where our Video RTP should be going if not to us */
before strolling to the Grokyzpå
(A bit unsure of this, please correct if
you know more) */
+ struct sip_st_dlg *stimer; /*!< SIP Session-Timers */
};
+
/*! Max entires in the history list for a sip_pvt */
#define MAX_HISTORY_ENTRIES 50
struct ast_variable *chanvars; /*!< Variables to set for channel created by user */
int maxcallbitrate; /*!< Maximum Bitrate for a video call */
int autoframing;
+ struct sip_st_cfg stimer; /*!< SIP Session-Timers */
};
/*!
struct ast_variable *chanvars; /*!< Variables to set for channel created by user */
struct sip_pvt *mwipvt; /*!< Subscription for MWI */
int autoframing;
- int timer_t1; /*!< The maximum T1 value for the peer */
- int timer_b; /*!< The maximum timer B (transaction timeouts) */
+ struct sip_st_cfg stimer; /*!< SIP Session-Timers */
+ int timer_t1; /*!< The maximum T1 value for the peer */
+ int timer_b; /*!< The maximum timer B (transaction timeouts) */
};
static int transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req);
static int transmit_response_reliable(struct sip_pvt *p, const char *msg, const struct sip_request *req);
static int transmit_response_with_date(struct sip_pvt *p, const char *msg, const struct sip_request *req);
-static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp);
static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported);
static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale);
static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable);
static int transmit_request(struct sip_pvt *p, int sipmethod, int inc, enum xmittype reliable, int newbranch);
static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch);
static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init);
-static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version);
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp);
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration);
static int transmit_info_with_vidupdate(struct sip_pvt *p);
static int transmit_message_with_text(struct sip_pvt *p, const char *text);
static void add_noncodec_to_sdp(const struct sip_pvt *p, int format, int sample_rate,
struct ast_str **m_buf, struct ast_str **a_buf,
int debug);
-static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p);
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp);
static void do_setnat(struct sip_pvt *p, int natflags);
static void stop_media_flows(struct sip_pvt *p);
static struct ast_udptl *sip_get_udptl_peer(struct ast_channel *chan);
static int sip_set_udptl_peer(struct ast_channel *chan, struct ast_udptl *udptl);
+/*------ Session-Timers functions --------- */
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp);
+static int proc_session_timer(const void *vp);
+static void stop_session_timer(struct sip_pvt *p);
+static void start_session_timer(struct sip_pvt *p);
+static void restart_session_timer(struct sip_pvt *p);
+static const char *strefresher2str(enum st_refresher r);
+static int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref);
+static int parse_minse(const char *p_hdrval, int *const p_interval);
+static int st_get_se(struct sip_pvt *, int max);
+static enum st_refresher st_get_refresher(struct sip_pvt *);
+static enum st_mode st_get_mode(struct sip_pvt *);
+static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p);
+
+
/*! \brief Definition of this channel for PBX channel registration */
static const struct ast_channel_tech sip_tech = {
.type = "SIP",
break;
}
}
+
+ /* This function is used to parse both Suported: and Require: headers.
+ Let the caller of this function know that an unknown option tag was
+ encountered, so that if the UAC requires it then the request can be
+ rejected with a 420 response. */
+ if (!found)
+ profile |= SIP_OPT_UNKNOWN;
+
if (!found && sipdebug) {
if (!strncasecmp(next, "x-", 2))
ast_debug(3, "Found private SIP option, not supported: %s\n", next);
if (p->do_history)
append_history(p, "SchedDestroy", "%d ms", ms);
p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p));
+
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > 0)
+ stop_session_timer(p);
}
/*! \brief Cancel destruction of SIP dialog.
break;
case AST_STATE_UP:
if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
- transmit_reinvite_with_sdp(p, FALSE);
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
}
static void sip_destroy_user(struct sip_user *user)
{
ast_debug(3, "Destroying user object from memory: %s\n", user->name);
+
ast_free_ha(user->ha);
if (user->chanvars) {
ast_variables_destroy(user->chanvars);
p->registry = registry_unref(p->registry);
}
+ /* Destroy Session-Timers if allocated */
+ if (p->stimer) {
+ if (p->stimer->st_active == TRUE && p->stimer->st_schedid > -1)
+ ast_sched_del(sched, p->stimer->st_schedid);
+ ast_free(p->stimer);
+ }
+
/* Unlink us from the owner if we have one */
if (p->owner) {
if (lockowner)
ast_debug(2,"T38State change to %d on channel %s\n", p->t38.state, ast->name);
res = transmit_response_with_t38_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
} else
- res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL);
+ res = transmit_response_with_sdp(p, "200 OK", &p->initreq, XMIT_CRITICAL, FALSE);
}
sip_pvt_unlock(p);
return res;
if ((ast->_state != AST_STATE_UP) &&
!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
- transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
}
p->lastrtptx = time(NULL);
if ((ast->_state != AST_STATE_UP) &&
!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
- transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
}
p->lastrtptx = time(NULL);
if ((ast->_state != AST_STATE_UP) &&
!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
- transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
}
p->lastrtptx = time(NULL);
!ast_test_flag(&p->flags[0], SIP_PROGRESS_SENT) &&
!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
p->invitestate = INV_EARLY_MEDIA;
- transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE);
+ transmit_response_with_sdp(p, "183 Session Progress", &p->initreq, XMIT_UNRELIABLE, FALSE);
ast_set_flag(&p->flags[0], SIP_PROGRESS_SENT);
break;
}
if (!p->pendinginvite) {
ast_debug(3, "Sending reinvite on SIP (%s) for T.38 negotiation.\n",ast->name);
p->t38.state = T38_LOCAL_REINVITE;
- transmit_reinvite_with_sdp(p, TRUE);
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
ast_debug(2, "T38 state changed to %d on channel %s\n", p->t38.state, ast->name);
}
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
snprintf(tagbuf, len, "as%08lx", ast_random());
}
+/*! \brief Allocate Session-Timers struct w/in dialog */
+static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p)
+{
+ struct sip_st_dlg *stp;
+
+ if (p->stimer) {
+ ast_log(LOG_ERROR, "Session-Timer struct already allocated\n");
+ return p->stimer;
+ }
+
+ if (!(stp = ast_calloc(1, sizeof(struct sip_st_dlg))))
+ return NULL;
+
+ p->stimer = stp;
+
+ stp->st_schedid = -1; /* Session-Timers ast_sched scheduler id */
+
+ return p->stimer;
+}
+
/*! \brief Allocate sip_pvt structure, set defaults and link in the container.
* Returns a reference to the object so whoever uses it later must
* remember to release the reference.
p->autokillid = -1;
p->subscribed = NONE;
p->stateid = -1;
+ p->sessionversion_remote = -1;
+ p->session_modify = TRUE;
+ p->stimer = NULL;
p->prefs = default_prefs; /* Set default codecs for this call */
if (intended_method != SIP_OPTIONS) { /* Peerpoke has it's own system */
}
ast_string_field_set(p, context, default_context);
+
/* Add to active dialog list */
dialoglist_lock();
p->next = dialoglist;
const char *m; /* SDP media offer */
const char *c;
const char *a;
+ const char *o; /* Pointer to o= line */
+ char *o_copy; /* Copy of o= line */
+ char *token;
char host[258];
int len = -1;
int portno = -1; /*!< RTP Audio port number */
int last_rtpmap_codec=0;
char buf[BUFSIZ];
+ int rua_version;
if (!p->rtp) {
ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n");
/* Update our last rtprx when we receive an SDP, too */
p->lastrtprx = p->lastrtptx = time(NULL); /* XXX why both ? */
+ /* Store the SDP version number of remote UA. This will allow us to
+ distinguish between session modifications and session refreshes. If
+ the remote UA does not send an incremented SDP version number in a
+ subsequent RE-INVITE then that means its not changing media session.
+ The RE-INVITE may have been sent to update connected party, remote
+ target or to refresh the session (Session-Timers). Asterisk must not
+ change media session and increment its own version number in answer
+ SDP in this case. */
+
+ o = get_sdp(req, "o");
+ if (ast_strlen_zero(o)) {
+ ast_log(LOG_WARNING, "SDP sytax error. SDP without an o= line\n");
+ return -1;
+ }
+
+ o_copy = ast_strdupa(o);
+ token = strsep(&o_copy, " "); /* Skip username */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line username\n");
+ return -1;
+ }
+ token = strsep(&o_copy, " "); /* Skip session-id */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line session-id\n");
+ return -1;
+ }
+ token = strsep(&o_copy, " "); /* Version */
+ if (!o_copy) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line\n");
+ return -1;
+ }
+ if (!sscanf(token, "%d", &rua_version)) {
+ ast_log(LOG_WARNING, "SDP sytax error in o= line version\n");
+ return -1;
+ }
+
+ if (p->sessionversion_remote < 0 || p->sessionversion_remote != rua_version) {
+ p->sessionversion_remote = rua_version;
+ p->session_modify = TRUE;
+ } else if (p->sessionversion_remote == rua_version) {
+ p->session_modify = FALSE;
+ ast_debug(2, "SDP version number same as previous SDP\n");
+ return 0;
+ }
/* Try to find first media stream */
m = get_sdp(req, "m");
add_header(resp, "User-Agent", global_useragent);
add_header(resp, "Allow", ALLOWED_METHODS);
add_header(resp, "Supported", SUPPORTED_EXTENSIONS);
+
+ /* Add Session-Timers related headers if the feature is active for this session */
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE) {
+ char se_hdr[256];
+ snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval,
+ strefresher2str(p->stimer->st_ref));
+ add_header(resp, "Require", "timer");
+ add_header(resp, "Session-Expires", se_hdr);
+ }
+
if (msg[0] == '2' && (p->method == SIP_SUBSCRIBE || p->method == SIP_REGISTER)) {
/* For registration responses, we also need expiry and
contact info */
ast_string_field_set(p, url, NULL);
}
+ /* Add Session-Timers related headers if the feature is active for this session */
+ if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_active_peer_ua == TRUE) {
+ char se_hdr[256];
+ snprintf(se_hdr, sizeof(se_hdr), "%d;refresher=%s", p->stimer->st_interval,
+ strefresher2str(p->stimer->st_ref));
+ add_header(req, "Require", "timer");
+ add_header(req, "Session-Expires", se_hdr);
+ snprintf(se_hdr, sizeof(se_hdr), "%d", st_get_se(p, FALSE));
+ add_header(req, "Min-SE", se_hdr);
+ }
+
return 0;
}
return send_response(p, &resp, XMIT_UNRELIABLE, 0);
}
+/*! \brief Transmit 422 response with Min-SE header (Session-Timers) */
+static int transmit_response_with_minse(struct sip_pvt *p, const char *msg, const struct sip_request *req, int minse_int)
+{
+ struct sip_request resp;
+ char minse_str[20];
+
+ respprep(&resp, p, msg, req);
+ append_date(&resp);
+
+ snprintf(minse_str, sizeof(minse_str), "%d", minse_int);
+ add_header(&resp, "Min-SE", minse_str);
+
+ add_header_contentLength(&resp, 0);
+ return send_response(p, &resp, XMIT_UNRELIABLE, 0);
+}
+
+
/*! \brief Transmit response, Make sure you get an ACK
This is only used for responses to INVITEs, where we need to make sure we get an ACK
*/
#define SDP_SAMPLE_RATE(x) (x == AST_FORMAT_G722) ? 16000 : 8000
-/*! \brief Add Session Description Protocol message */
-static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p)
+/*! \brief Add Session Description Protocol message
+
+ If oldsdp is TRUE, then the SDP version number is not incremented. This mechanism
+ is used in Session-Timers where RE-INVITEs are used for refreshing SIP sessions
+ without modifying the media session in any way.
+*/
+static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int oldsdp)
{
int len = 0;
int alreadysent = 0;
if (!p->sessionid) {
p->sessionid = (int)ast_random();
p->sessionversion = p->sessionid;
- } else
- p->sessionversion++;
+ } else {
+ if (oldsdp == FALSE)
+ p->sessionversion++;
+ }
capability = p->jointcapability;
/*! \brief Used for 200 OK and 183 early media
\return Will return XMIT_ERROR for network errors.
*/
-static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable)
+static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp)
{
struct sip_request resp;
int seqno;
ast_rtp_codec_setpref(p->rtp, &p->prefs);
}
try_suggested_sip_codec(p);
- add_sdp(&resp, p);
+ add_sdp(&resp, p, oldsdp);
} else
ast_log(LOG_ERROR, "Can't add SDP to response, since we have no RTP session allocated. Call-ID %s\n", p->callid);
if (reliable && !p->pendinginvite)
If t38version is TRUE, we send T38 SDP for re-invite from audio/video to
T38 UDPTL transmission on the channel
+
+ If oldsdp is TRUE then the SDP version number is not incremented. This
+ is needed for Session-Timers so we can send a re-invite to refresh the
+ SIP session without modifying the media session.
*/
-static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version)
+static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int oldsdp)
{
struct sip_request req;
reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1);
-
+
add_header(&req, "Allow", ALLOWED_METHODS);
add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
- if (sipdebug)
- add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
+ if (sipdebug) {
+ if (oldsdp == TRUE)
+ add_header(&req, "X-asterisk-Info", "SIP re-invite (Session-Timers)");
+ else
+ add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
+ }
+
if (p->do_history)
append_history(p, "ReInv", "Re-invite sent");
if (t38version)
add_t38_sdp(&req, p);
else
- add_sdp(&req, p);
+ add_sdp(&req, p, oldsdp);
+
/* Use this as the basis */
initialize_initreq(p, &req);
p->lastinvite = p->ocseq;
- ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Change direction of this dialog */
+ ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Change direction of this dialog */
+
return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
}
add_header(&req, "Require", "replaces");
}
+ /* Add Session-Timers related headers */
+ if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+ char i2astr[10];
+
+ if (!p->stimer->st_interval)
+ p->stimer->st_interval = st_get_se(p, TRUE);
+
+ p->stimer->st_active = TRUE;
+
+ snprintf(i2astr, sizeof(i2astr), "%d", p->stimer->st_interval);
+ add_header(&req, "Session-Expires", i2astr);
+ add_header(&req, "Min-SE", i2astr);
+ }
+
add_header(&req, "Allow", ALLOWED_METHODS);
add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
if (p->options && p->options->addsipheaders && p->owner) {
ast_debug(1, "T38 is in state %d on channel %s\n", p->t38.state, p->owner ? p->owner->name : "<none>");
add_t38_sdp(&req, p);
} else if (p->rtp)
- add_sdp(&req, p);
+ add_sdp(&req, p, FALSE);
} else {
add_header_contentLength(&req, 0);
}
/*! \brief Report Peer status in character string
* \return 0 if peer is unreachable, 1 if peer is online, -1 if unmonitored
*/
+
+
+/* Session-Timer Modes */
+static struct _map_x_s stmodes[] = {
+ { SESSION_TIMER_MODE_ACCEPT, "Accept"},
+ { SESSION_TIMER_MODE_ORIGINATE, "Originate"},
+ { SESSION_TIMER_MODE_REFUSE, "Refuse"},
+ { -1, NULL},
+};
+
+static const char *stmode2str(enum st_mode m)
+{
+ return map_x_s(stmodes, m, "Unknown");
+}
+
+static enum st_mode str2stmode(const char *s)
+{
+ return map_s_x(stmodes, s, -1);
+}
+
+/* Session-Timer Refreshers */
+static struct _map_x_s strefreshers[] = {
+ { SESSION_TIMER_REFRESHER_AUTO, "auto"},
+ { SESSION_TIMER_REFRESHER_UAC, "uac"},
+ { SESSION_TIMER_REFRESHER_UAS, "uas"},
+ { -1, NULL},
+};
+
+static const char *strefresher2str(enum st_refresher r)
+{
+ return map_x_s(strefreshers, r, "Unknown");
+}
+
+static enum st_refresher str2strefresher(const char *s)
+{
+ return map_s_x(strefreshers, s, -1);
+}
+
+
static int peer_status(struct sip_peer *peer, char *status, int statuslen)
{
int res = 0;
return 0;
}
-
-
/*! \brief Show one peer in detail */
static char *sip_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
print_codec_to_cli(fd, &peer->prefs);
ast_cli(fd, ")\n");
- ast_cli(fd, " Auto-Framing: %s \n", cli_yesno(peer->autoframing));
+ ast_cli(fd, " Auto-Framing : %s \n", cli_yesno(peer->autoframing));
ast_cli(fd, " 100 on REG : %s\n", ast_test_flag(&peer->flags[1], SIP_PAGE2_REGISTERTRYING) ? "Yes" : "No");
ast_cli(fd, " Status : ");
peer_status(peer, status, sizeof(status));
for (v = peer->chanvars ; v ; v = v->next)
ast_cli(fd, " %s = %s\n", v->name, v->value);
}
+
+ ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper));
+ ast_cli(fd, " Sess-Refresh : %s\n", strefresher2str(peer->stimer.st_ref));
+ ast_cli(fd, " Sess-Expires : %d secs\n", peer->stimer.st_max_se);
+ ast_cli(fd, " Min-Sess : %d secs\n", peer->stimer.st_min_se);
ast_cli(fd,"\n");
unref_peer(peer);
} else if (peer && type == 1) { /* manager listing */
astman_append(s, "SIP-UserPhone: %s\r\n", (ast_test_flag(&peer->flags[0], SIP_USEREQPHONE)?"Y":"N"));
astman_append(s, "SIP-VideoSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_VIDEOSUPPORT)?"Y":"N"));
astman_append(s, "SIP-TextSupport: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_TEXTSUPPORT)?"Y":"N"));
+ astman_append(s, "SIP-Sess-Timers: %s\r\n", stmode2str(peer->stimer.st_mode_oper));
+ astman_append(s, "SIP-Sess-Refresh: %s\r\n", strefresher2str(peer->stimer.st_ref));
+ astman_append(s, "SIP-Sess-Expires: %d\r\n", peer->stimer.st_max_se);
+ astman_append(s, "SIP-Sess-Min: %d\r\n", peer->stimer.st_min_se);
/* - is enumerated */
astman_append(s, "SIP-DTMFmode: %s\r\n", dtmfmode2str(ast_test_flag(&peer->flags[0], SIP_DTMF)));
print_group(a->fd, user->pickupgroup, 0);
ast_cli(a->fd, " Callerid : %s\n", ast_callerid_merge(cbuf, sizeof(cbuf), user->cid_name, user->cid_num, "<unspecified>"));
ast_cli(a->fd, " ACL : %s\n", cli_yesno(user->ha != NULL));
+ ast_cli(a->fd, " Sess-Timers : %s\n", stmode2str(user->stimer.st_mode_oper));
+ ast_cli(a->fd, " Sess-Refresh : %s\n", strefresher2str(user->stimer.st_ref));
+ ast_cli(a->fd, " Sess-Expires : %d secs\n", user->stimer.st_max_se);
+ ast_cli(a->fd, " Sess-Min-SE : %d secs\n", user->stimer.st_min_se);
+
ast_cli(a->fd, " Codec Order : (");
print_codec_to_cli(a->fd, &user->prefs);
ast_cli(a->fd, ")\n");
for (v = user->chanvars ; v ; v = v->next)
ast_cli(a->fd, " %s = %s\n", v->name, v->value);
}
+
ast_cli(a->fd,"\n");
+
unref_user(user);
} else {
ast_cli(a->fd,"User %s not found.\n", a->argv[3]);
ast_cli(a->fd, " Auto-Framing: %s\n", cli_yesno(global_autoframing));
ast_cli(a->fd, " Outb. proxy: %s %s\n", ast_strlen_zero(global_outboundproxy.name) ? "<not set>" : global_outboundproxy.name,
global_outboundproxy.force ? "(forced)" : "");
+ ast_cli(a->fd, " Session Timers: %s\n", stmode2str(global_st_mode));
+ ast_cli(a->fd, " Session Refresher: %s\n", strefresher2str (global_st_refresher));
+ ast_cli(a->fd, " Session Expires: %d secs\n", global_max_se);
+ ast_cli(a->fd, " Session Min-SE: %d secs\n", global_min_se);
ast_cli(a->fd, " Timer T1: %d\n", global_t1);
ast_cli(a->fd, " Timer T1 minimum: %d\n", global_t1min);
ast_cli(a->fd, " Timer B: %d\n", global_timer_b);
if (cur->sipoptions & sip_options[x].id)
ast_cli(a->fd, "%s ", sip_options[x].text);
}
+ ast_cli(a->fd, "\n");
} else
ast_cli(a->fd, "(none)\n");
+
+ if (!cur->stimer)
+ ast_cli(a->fd, " Session-Timer: Uninitiallized\n");
+ else {
+ ast_cli(a->fd, " Session-Timer: %s\n", cur->stimer->st_active ? "Active" : "Inactive");
+ if (cur->stimer->st_active == TRUE) {
+ ast_cli(a->fd, " S-Timer Interval: %d\n", cur->stimer->st_interval);
+ ast_cli(a->fd, " S-Timer Refresher: %s\n", strefresher2str(cur->stimer->st_ref));
+ ast_cli(a->fd, " S-Timer Expirys: %d\n", cur->stimer->st_expirys);
+ ast_cli(a->fd, " S-Timer Sched Id: %d\n", cur->stimer->st_schedid);
+ ast_cli(a->fd, " S-Timer Peer Sts: %s\n", cur->stimer->st_active_peer_ua ? "Active" : "Inactive");
+ ast_cli(a->fd, " S-Timer Cached Min-SE: %d\n", cur->stimer->st_cached_min_se);
+ ast_cli(a->fd, " S-Timer Cached SE: %d\n", cur->stimer->st_cached_max_se);
+ ast_cli(a->fd, " S-Timer Cached Ref: %s\n", strefresher2str(cur->stimer->st_cached_ref));
+ ast_cli(a->fd, " S-Timer Cached Mode: %s\n", stmode2str(cur->stimer->st_cached_mode));
+ }
+ }
+
ast_cli(a->fd, "\n\n");
+
found++;
}
}
} else {
ast_debug(2, "Sending pending reinvite on '%s'\n", p->callid);
/* Didn't get to reinvite yet, so do it now */
- transmit_reinvite_with_sdp(p, FALSE);
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE);
}
}
int xmitres = 0;
int reinvite = (p->owner && p->owner->_state == AST_STATE_UP);
struct ast_channel *bridgepeer = NULL;
+ char *p_hdrval;
+ int rtn;
if (reinvite)
ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid);
if (!req->ignore)
ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
}
+
+ /* Check for Session-Timers related headers */
+ if (st_get_mode(p) != SESSION_TIMER_MODE_REFUSE && p->outgoing_call == TRUE && !reinvite) {
+ p_hdrval = (char*)get_header(req, "Session-Expires");
+ if (!ast_strlen_zero(p_hdrval)) {
+ /* UAS supports Session-Timers */
+ enum st_refresher tmp_st_ref = SESSION_TIMER_REFRESHER_AUTO;
+ int tmp_st_interval = 0;
+ rtn = parse_session_expires(p_hdrval, &tmp_st_interval, &tmp_st_ref);
+ if (rtn != 0) {
+ ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
+ }
+ if (tmp_st_ref == SESSION_TIMER_REFRESHER_UAC ||
+ tmp_st_ref == SESSION_TIMER_REFRESHER_UAS) {
+ p->stimer->st_ref = tmp_st_ref;
+ }
+ if (tmp_st_interval) {
+ p->stimer->st_interval = tmp_st_interval;
+ }
+ p->stimer->st_active = TRUE;
+ p->stimer->st_active_peer_ua = TRUE;
+ start_session_timer(p);
+ } else {
+ /* UAS doesn't support Session-Timers */
+ if (st_get_mode(p) == SESSION_TIMER_MODE_ORIGINATE) {
+ p->stimer->st_ref = SESSION_TIMER_REFRESHER_UAC;
+ p->stimer->st_active_peer_ua = FALSE;
+ start_session_timer(p);
+ }
+ }
+ }
+
+
/* If I understand this right, the branch is different for a non-200 ACK only */
p->invitestate = INV_TERMINATED;
xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE);
ast_queue_control(p->owner, AST_CONTROL_CONGESTION);
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
break;
+
+ case 422: /* Session-Timers: Session interval too small */
+ xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, FALSE);
+ ast_string_field_set(p, theirtag, NULL);
+ proc_422_rsp(p, req);
+ break;
+
case 487: /* Cancelled transaction */
/* We have sent CANCEL on an outbound INVITE
This transaction is already scheduled to be killed by sip_hangup().
p->needdestroy = 1;
}
break;
+
+ case 422: /* Session-Timers: Session Interval Too Small */
+ if (sipmethod == SIP_INVITE) {
+ handle_response_invite(p, resp, rest, req, seqno);
+ }
+ break;
+
case 481: /* Call leg does not exist */
if (sipmethod == SIP_INVITE) {
handle_response_invite(p, resp, rest, req, seqno);
/* We should answer something here. If we are here, the
call we are replacing exists, so an accepted
can't harm */
- transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE);
+ transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
/* Do something more clever here */
ast_channel_unlock(c);
sip_pvt_unlock(p->refer->refer_call);
Targetcall is not touched by the masq */
/* Answer the incoming call and set channel to UP state */
- transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE);
+ transmit_response_with_sdp(p, "200 OK", req, XMIT_RELIABLE, FALSE);
ast_setstate(c, AST_STATE_UP);
unsigned int required_profile = 0;
struct ast_channel *c = NULL; /* New channel */
int reinvite = 0;
+ int rtn;
+
+ const char *p_uac_se_hdr; /* UAC's Session-Expires header string */
+ const char *p_uac_min_se; /* UAC's requested Min-SE interval (char string) */
+ int uac_max_se = -1; /* UAC's Session-Expires in integer format */
+ int uac_min_se = -1; /* UAC's Min-SE in integer format */
+ int st_active = FALSE; /* Session-Timer on/off boolean */
+ int st_interval = 0; /* Session-Timer negotiated refresh interval */
+ enum st_refresher st_ref; /* Session-Timer session refresher */
+ int dlg_min_se = -1;
+ st_ref = SESSION_TIMER_REFRESHER_AUTO;
/* Find out what they support */
if (!p->sipoptions) {
required = get_header(req, "Require");
if (!ast_strlen_zero(required)) {
required_profile = parse_sip_options(NULL, required);
- if (required_profile && required_profile != SIP_OPT_REPLACES) {
- /* At this point we only support REPLACES */
+ if (required_profile && required_profile != SIP_OPT_REPLACES && required_profile != SIP_OPT_TIMER) {
+ /* At this point we only support REPLACES and Session-Timer */
transmit_response_with_unsupported(p, "420 Bad extension (unsupported)", req, required);
ast_log(LOG_WARNING,"Received SIP INVITE with unsupported required extension: %s\n", required);
p->invitestate = INV_COMPLETED;
}
}
+ /* The option tags may be present in Supported: or Require: headers.
+ Include the Require: option tags for further processing as well */
+ p->sipoptions |= required_profile;
+ p->reqsipoptions = required_profile;
+
/* Check if this is a loop */
if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner && (p->owner->_state != AST_STATE_UP)) {
/* This is a call to ourself. Send ourselves an error code and stop
}
}
-
/* Check if this is an INVITE that sets up a new dialog or
a re-invite in an existing dialog */
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
return 0;
} else {
+
/* If no extension was specified, use the s one */
/* Basically for calling to IP/Host name only */
if (ast_strlen_zero(p->exten))
else
ast_debug(2, "Got a SIP re-transmit of INVITE for call %s\n", p->callid);
}
+
reinvite = 1;
c = p->owner;
}
+ /* Session-Timers */
+ if (p->sipoptions == SIP_OPT_TIMER) {
+ /* The UAC has requested session-timers for this session. Negotiate
+ the session refresh interval and who will be the refresher */
+ ast_debug(2, "Incoming INVITE with 'timer' option enabled\n");
+
+ /* Allocate Session-Timers struct w/in the dialog */
+ if (!p->stimer)
+ sip_st_alloc(p);
+
+ /* Parse the Session-Expires header */
+ p_uac_se_hdr = get_header(req, "Session-Expires");
+ if (!ast_strlen_zero(p_uac_se_hdr)) {
+ rtn = parse_session_expires(p_uac_se_hdr, &uac_max_se, &st_ref);
+ if (rtn != 0) {
+ transmit_response(p, "400 Session-Expires Invalid Syntax", req);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ }
+
+ /* Parse the Min-SE header */
+ p_uac_min_se = get_header(req, "Min-SE");
+ if (!ast_strlen_zero(p_uac_min_se)) {
+ rtn = parse_minse(p_uac_min_se, &uac_min_se);
+ if (rtn != 0) {
+ transmit_response(p, "400 Min-SE Invalid Syntax", req);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ }
+
+ dlg_min_se = st_get_se(p, FALSE);
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ACCEPT:
+ case SESSION_TIMER_MODE_ORIGINATE:
+ if (uac_max_se > 0 && uac_max_se < dlg_min_se) {
+ transmit_response_with_minse(p, "422 Session Interval Too Small", req, dlg_min_se);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+
+ p->stimer->st_active_peer_ua = TRUE;
+ st_active = TRUE;
+ if (st_ref == SESSION_TIMER_REFRESHER_AUTO) {
+ st_ref = st_get_refresher(p);
+ }
+
+ if (uac_max_se > 0) {
+ int dlg_max_se = st_get_se(p, TRUE);
+ if (dlg_max_se >= uac_min_se) {
+ st_interval = (uac_max_se < dlg_max_se) ? uac_max_se : dlg_max_se;
+ } else {
+ st_interval = uac_max_se;
+ }
+ } else {
+ st_interval = uac_min_se;
+ }
+ break;
+
+ case SESSION_TIMER_MODE_REFUSE:
+ if (p->reqsipoptions == SIP_OPT_TIMER) {
+ transmit_response_with_unsupported(p, "420 Option Disabled", req, required);
+ ast_log(LOG_WARNING,"Received SIP INVITE with supported but disabled option: %s\n", required);
+ p->invitestate = INV_COMPLETED;
+ if (!p->lastinvite) {
+ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+ }
+ return -1;
+ }
+ break;
+
+ default:
+ ast_log(LOG_ERROR, "Internal Error %d at %s:%d\n", st_get_mode(p), __FILE__, __LINE__);
+ break;
+ }
+ } else {
+ /* The UAC did not request session-timers. Asterisk (UAS), will now decide
+ (based on session-timer-mode in sip.conf) whether to run session-timers for
+ this session or not. */
+ switch (st_get_mode(p)) {
+ case SESSION_TIMER_MODE_ORIGINATE:
+ st_active = TRUE;
+ st_interval = st_get_se(p, TRUE);
+ st_ref = SESSION_TIMER_REFRESHER_UAS;
+ p->stimer->st_active_peer_ua = FALSE;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (reinvite == 0) {
+ /* Session-Timers: Start session refresh timer based on negotiation/config */
+ if (st_active == TRUE) {
+ p->stimer->st_active = TRUE;
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ start_session_timer(p);
+ }
+ } else {
+ if (p->stimer->st_active == TRUE) {
+ /* Session-Timers: A re-invite request sent within a dialog will serve as
+ a refresh request, no matter whether the re-invite was sent for refreshing
+ the session or modifying it.*/
+ ast_debug (2, "Restarting session-timers on a refresh - %s\n", p->callid);
+
+ /* The UAC may be adjusting the session-timers mid-session */
+ if (st_interval > 0) {
+ p->stimer->st_interval = st_interval;
+ p->stimer->st_ref = st_ref;
+ }
+
+ restart_session_timer(p);
+ if (p->stimer->st_expirys > 0) {
+ p->stimer->st_expirys--;
+ }
+ }
+ }
+
if (!req->ignore && p)
p->lastinvite = seqno;
}
}
/* Respond to normal re-invite */
- if (sendok)
+ if (sendok) {
/* If this is not a re-invite or something to ignore - it's critical */
- transmit_response_with_sdp(p, "200 OK", req, (reinvite || req->ignore) ? XMIT_UNRELIABLE : XMIT_CRITICAL);
+ transmit_response_with_sdp(p, "200 OK", req, (reinvite || req->ignore) ? XMIT_UNRELIABLE : XMIT_CRITICAL, p->session_modify == TRUE ? FALSE:TRUE);
+ }
}
p->invitestate = INV_TERMINATED;
break;
}
stop_media_flows(p); /* Immediately stop RTP, VRTP and UDPTL as applicable */
+ stop_session_timer(p); /* Stop Session-Timer */
if (!ast_strlen_zero(get_header(req, "Also"))) {
ast_log(LOG_NOTICE, "Client '%s' using deprecated BYE/Also transfer method. Ask vendor to support REFER instead\n",
return 0;
}
+
+/*! \brief Session-Timers: Restart session timer */
+static void restart_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in restart_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ if (p->stimer->st_active == TRUE) {
+ if (ast_sched_del(sched, p->stimer->st_schedid) != 0) {
+ ast_log(LOG_WARNING, "ast_sched_del failed: %d - %s\n", p->stimer->st_schedid, p->callid);
+ }
+
+ ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
+ start_session_timer(p);
+ }
+}
+
+
+/*! \brief Session-Timers: Stop session timer */
+static void stop_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in stop_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ if (p->stimer->st_active == TRUE) {
+ p->stimer->st_active = FALSE;
+ ast_sched_del(sched, p->stimer->st_schedid);
+ ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
+ }
+}
+
+
+/*! \brief Session-Timers: Start session timer */
+static void start_session_timer(struct sip_pvt *p)
+{
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in start_session_timer - %s\n", p->callid);
+ return;
+ }
+
+ p->stimer->st_schedid = ast_sched_add(sched, p->stimer->st_interval * 1000 / 2, proc_session_timer, p);
+ if (p->stimer->st_schedid < 0) {
+ ast_log(LOG_ERROR, "ast_sched_add failed.\n");
+ }
+ ast_debug(2, "Session timer started: %d - %s\n", p->stimer->st_schedid, p->callid);
+}
+
+
+/*! \brief Session-Timers: Process session refresh timeout event */
+static int proc_session_timer(const void *vp)
+{
+ struct sip_pvt *p = (struct sip_pvt *) vp;
+ int sendreinv = FALSE;
+
+ if (!p->stimer) {
+ ast_log(LOG_WARNING, "Null stimer in proc_session_timer - %s\n", p->callid);
+ return 0;
+ }
+
+ ast_debug(2, "Session timer expired: %d - %s\n", p->stimer->st_schedid, p->callid);
+
+ if (!p->owner) {
+ if (p->stimer->st_active == TRUE) {
+ stop_session_timer(p);
+ }
+ return 0;
+ }
+
+ if ((p->stimer->st_active != TRUE) || (p->owner->_state != AST_STATE_UP)) {
+ return 0;
+ }
+
+ switch (p->stimer->st_ref) {
+ case SESSION_TIMER_REFRESHER_UAC:
+ if (p->outgoing_call == TRUE) {
+ sendreinv = TRUE;
+ }
+ break;
+ case SESSION_TIMER_REFRESHER_UAS:
+ if (p->outgoing_call != TRUE) {
+ sendreinv = TRUE;
+ }
+ break;
+ default:
+ ast_log(LOG_ERROR, "Unknown session refresher %d\n", p->stimer->st_ref);
+ return -1;
+ }
+
+ if (sendreinv == TRUE) {
+ transmit_reinvite_with_sdp(p, FALSE, TRUE);
+ } else {
+ p->stimer->st_expirys++;
+ if (p->stimer->st_expirys >= 2) {
+ ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid);
+ stop_session_timer(p);
+
+ while (p->owner && ast_channel_trylock(p->owner)) {
+ sip_pvt_unlock(p);
+ usleep(1);
+ sip_pvt_lock(p);
+ }
+
+ ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
+ ast_channel_unlock(p->owner);
+ }
+ }
+ return 1;
+}
+
+
+/* Session-Timers: Function for parsing Min-SE header */
+int parse_minse (const char *p_hdrval, int *const p_interval)
+{
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "Null Min-SE header\n");
+ return -1;
+ }
+
+ *p_interval = 0;
+ p_hdrval = ast_skip_blanks(p_hdrval);
+ if (!sscanf(p_hdrval, "%d", p_interval)) {
+ ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+ return -1;
+ }
+
+ ast_debug(2, "Received Min-SE: %d\n", *p_interval);
+ return 0;
+}
+
+
+/* Session-Timers: Function for parsing Session-Expires header */
+int parse_session_expires(const char *p_hdrval, int *const p_interval, enum st_refresher *const p_ref)
+{
+ char *p_token;
+ int ref_idx;
+ char *p_se_hdr;
+
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "Null Session-Expires header\n");
+ return -1;
+ }
+
+ *p_ref = SESSION_TIMER_REFRESHER_AUTO;
+ *p_interval = 0;
+
+ p_se_hdr = ast_strdupa(p_hdrval);
+ p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+ while ((p_token = strsep(&p_se_hdr, ";"))) {
+ p_token = ast_skip_blanks(p_token);
+ if (!sscanf(p_token, "%d", p_interval)) {
+ ast_log(LOG_WARNING, "Parsing of Session-Expires failed\n");
+ return -1;
+ }
+
+ ast_debug(2, "Session-Expires: %d\n", *p_interval);
+
+ if (!p_se_hdr)
+ continue;
+
+ ref_idx = strlen("refresher=");
+ if (!strncasecmp(p_se_hdr, "refresher=", ref_idx)) {
+ p_se_hdr += ref_idx;
+ p_se_hdr = ast_skip_blanks(p_se_hdr);
+
+ if (!strncasecmp(p_se_hdr, "uac", strlen("uac"))) {
+ *p_ref = SESSION_TIMER_REFRESHER_UAC;
+ ast_debug(2, "Refresher: UAC\n");
+ } else if (!strncasecmp(p_se_hdr, "uas", strlen("uas"))) {
+ *p_ref = SESSION_TIMER_REFRESHER_UAS;
+ ast_debug(2, "Refresher: UAS\n");
+ } else {
+ ast_log(LOG_WARNING, "Invalid refresher value %s\n", p_se_hdr);
+ return -1;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/*! \brief Handle 422 response to INVITE with session-timer requested
+
+ Session-Timers: An INVITE originated by Asterisk that asks for session-timers support
+ from the UAS can result into a 422 response. This is how a UAS or an intermediary proxy
+ server tells Asterisk that the session refresh interval offered by Asterisk is too low
+ for them. The proc_422_rsp() function handles a 422 response. It extracts the Min-SE
+ header that comes back in 422 and sends a new INVITE accordingly. */
+static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp)
+{
+ int rtn;
+ const char *p_hdrval;
+ int minse;
+
+ p_hdrval = get_header(rsp, "Min-SE");
+ if (ast_strlen_zero(p_hdrval)) {
+ ast_log(LOG_WARNING, "422 response without a Min-SE header %s\n", p_hdrval);
+ return;
+ }
+ rtn = parse_minse(p_hdrval, &minse);
+ if (rtn != 0) {
+ ast_log(LOG_WARNING, "Parsing of Min-SE header failed %s\n", p_hdrval);
+ return;
+ }
+ p->stimer->st_interval = minse;
+ transmit_invite(p, SIP_INVITE, 1, 2);
+}
+
+
+/*! \brief Get Max or Min SE (session timer expiry)
+ \param max if true, get max se, otherwise min se
+*/
+int st_get_se(struct sip_pvt *p, int max)
+{
+ if (max == TRUE) {
+ if (p->stimer->st_cached_max_se) {
+ return p->stimer->st_cached_max_se;
+ } else {
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_max_se = up->stimer.st_max_se;
+ return (p->stimer->st_cached_max_se);
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_max_se = pp->stimer.st_max_se;
+ return (p->stimer->st_cached_max_se);
+ }
+ }
+ }
+ p->stimer->st_cached_max_se = global_max_se;
+ return (p->stimer->st_cached_max_se);
+ } else {
+ if (p->stimer->st_cached_min_se) {
+ return p->stimer->st_cached_min_se;
+ } else {
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_min_se = up->stimer.st_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_min_se = pp->stimer.st_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+ }
+ }
+ p->stimer->st_cached_min_se = global_min_se;
+ return (p->stimer->st_cached_min_se);
+ }
+}
+
+
+/*! \brief Get the entity (UAC or UAS) that's acting as the session-timer refresher
+ \param sip_pvt pointer to the SIP dialog
+*/
+enum st_refresher st_get_refresher(struct sip_pvt *p)
+{
+ if (p->stimer->st_cached_ref != SESSION_TIMER_REFRESHER_AUTO)
+ return p->stimer->st_cached_ref;
+
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_ref = up->stimer.st_ref;
+ return up->stimer.st_ref;
+ }
+ }
+
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_ref = pp->stimer.st_ref;
+ return pp->stimer.st_ref;
+ }
+ }
+
+ p->stimer->st_cached_ref = global_st_refresher;
+ return global_st_refresher;
+}
+
+
+/*! \brief Get the session-timer mode
+ \param sip_pvt pointer to the SIP dialog
+*/
+enum st_mode st_get_mode(struct sip_pvt *p)
+{
+ if (!p->stimer)
+ sip_st_alloc(p);
+
+ if (p->stimer->st_cached_mode != SESSION_TIMER_MODE_INVALID)
+ return p->stimer->st_cached_mode;
+
+ if (p->username) {
+ struct sip_user *up = find_user(p->username, 1);
+ if (up) {
+ p->stimer->st_cached_mode = up->stimer.st_mode_oper;
+ return up->stimer.st_mode_oper;
+ }
+ }
+ if (p->peername) {
+ struct sip_peer *pp = find_peer(p->peername, NULL, 1);
+ if (pp) {
+ p->stimer->st_cached_mode = pp->stimer.st_mode_oper;
+ return pp->stimer.st_mode_oper;
+ }
+ }
+
+ p->stimer->st_cached_mode = global_st_mode;
+ return global_st_mode;
+}
+
+
/*! \brief React to lack of answer to Qualify poke */
static int sip_poke_noanswer(const void *data)
{
if (!peer->maxms || !peer->addr.sin_addr.s_addr) {
/* IF we have no IP, or this isn't to be monitored, return
- imeediately after clearing things out */
+ immediately after clearing things out */
if (peer->pokeexpire > -1)
ast_sched_del(sched, peer->pokeexpire);
peer->lastms = 0;
if (global_callcounter)
user->call_limit=999;
user->prefs = default_prefs;
+ user->stimer.st_mode_oper = global_st_mode; /* Session-Timers */
+ user->stimer.st_ref = global_st_refresher;
+ user->stimer.st_min_se = global_min_se;
+ user->stimer.st_max_se = global_max_se;
+
/* set default context */
strcpy(user->context, default_context);
strcpy(user->language, default_language);
user->maxcallbitrate = atoi(v->value);
if (user->maxcallbitrate < 0)
user->maxcallbitrate = default_maxcallbitrate;
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_mode_oper = global_st_mode;
+ } else {
+ user->stimer.st_mode_oper = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &user->stimer.st_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_max_se = global_max_se;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &user->stimer.st_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_min_se = global_min_se;
+ }
+ if (user->stimer.st_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ user->stimer.st_min_se = global_min_se;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ user->stimer.st_ref = global_st_refresher;
+ } else {
+ user->stimer.st_ref = i;
+ }
}
+
/* We can't just report unknown options here because this may be a
* type=friend entry. All user options are valid for a peer, but not
* the other way around. */
peer->pickupgroup = 0;
peer->maxms = default_qualify;
peer->prefs = default_prefs;
+ peer->stimer.st_mode_oper = global_st_mode; /* Session-Timers */
+ peer->stimer.st_ref = global_st_refresher;
+ peer->stimer.st_min_se = global_min_se;
+ peer->stimer.st_max_se = global_max_se;
+
peer->timer_t1 = global_t1;
peer->timer_b = global_timer_b;
clear_peer_mailboxes(peer);
peer->maxcallbitrate = atoi(v->value);
if (peer->maxcallbitrate < 0)
peer->maxcallbitrate = default_maxcallbitrate;
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_mode_oper = global_st_mode;
+ } else {
+ peer->stimer.st_mode_oper = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &peer->stimer.st_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_max_se = global_max_se;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &peer->stimer.st_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_min_se = global_min_se;
+ }
+ if (peer->stimer.st_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ peer->stimer.st_min_se = global_min_se;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ peer->stimer.st_ref = global_st_refresher;
+ } else {
+ peer->stimer.st_ref = i;
+ }
}
}
if (!sip_cfg.ignore_regexpire && peer->host_dynamic && realtime) {
ast_set_flag(&global_flags[1], SIP_PAGE2_ALLOWOVERLAP); /* Default for peers, users: TRUE */
sip_cfg.peer_rtupdate = TRUE;
+ global_st_mode = SESSION_TIMER_MODE_ACCEPT; /* Session-Timers */
+ global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+ global_min_se = DEFAULT_MIN_SE;
+ global_max_se = DEFAULT_MAX_SE;
+
/* Initialize some reasonable defaults at SIP reload (used both for channel and as default for peers and users */
ast_copy_string(default_context, DEFAULT_CONTEXT, sizeof(default_context));
default_subscribecontext[0] = '\0';
default_maxcallbitrate = DEFAULT_MAX_CALL_BITRATE;
} else if (!strcasecmp(v->name, "matchexterniplocally")) {
global_matchexterniplocally = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "session-timers")) {
+ int i = (int) str2stmode(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-timers '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_st_mode = SESSION_TIMER_MODE_ACCEPT;
+ } else {
+ global_st_mode = i;
+ }
+ } else if (!strcasecmp(v->name, "session-expires")) {
+ if (sscanf(v->value, "%d", &global_max_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-expires '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_max_se = DEFAULT_MAX_SE;
+ }
+ } else if (!strcasecmp(v->name, "session-minse")) {
+ if (sscanf(v->value, "%d", &global_min_se) != 1) {
+ ast_log(LOG_WARNING, "Invalid session-minse '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_min_se = DEFAULT_MIN_SE;
+ }
+ if (global_min_se < 90) {
+ ast_log(LOG_WARNING, "session-minse '%s' at line %d of %s is not allowed to be < 90 secs\n", v->value, v->lineno, config);
+ global_min_se = DEFAULT_MIN_SE;
+ }
+ } else if (!strcasecmp(v->name, "session-refresher")) {
+ int i = (int) str2strefresher(v->value);
+ if (i < 0) {
+ ast_log(LOG_WARNING, "Invalid session-refresher '%s' at line %d of %s\n", v->value, v->lineno, config);
+ global_st_refresher = SESSION_TIMER_REFRESHER_UAS;
+ } else {
+ global_st_refresher = i;
+ }
}
}
if (!ast_test_flag(&p->flags[0], SIP_GOTREFER)) {
if (!p->pendinginvite) {
ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
- transmit_reinvite_with_sdp(p, TRUE);
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(udptl ? p->udptlredirip.sin_addr : p->ourip.sin_addr), udptl ? ntohs(p->udptlredirip.sin_port) : 0);
ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
else
ast_debug(3, "Sending reinvite on SIP '%s' - It's UDPTL soon redirected to us (IP %s)\n", p->callid, ast_inet_ntoa(p->ourip.sin_addr));
- transmit_reinvite_with_sdp(p, TRUE);
+ transmit_reinvite_with_sdp(p, TRUE, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
if (flag)
ast_debug(3, "Deferring reinvite on SIP '%s' - It's UDPTL will be redirected to IP %s:%d\n", p->callid, ast_inet_ntoa(p->udptlredirip.sin_addr), ntohs(p->udptlredirip.sin_port));
ast_debug(1, "Early remote bridge setting SIP '%s' - Sending media to %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
} else if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
ast_debug(3, "Sending reinvite on SIP '%s' - It's audio soon redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
- transmit_reinvite_with_sdp(p, FALSE);
+ transmit_reinvite_with_sdp(p, FALSE, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_debug(3, "Deferring reinvite on SIP '%s' - It's audio will be redirected to IP %s\n", p->callid, ast_inet_ntoa(rtp ? p->redirip.sin_addr : p->ourip.sin_addr));
/* We have a pending Invite. Send re-invite when we're done with the invite */