[asterisk-commits] russell: trunk r98978 - in /trunk: ./ channels/ configs/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Jan 16 15:53:11 CST 2008


Author: russell
Date: Wed Jan 16 15:53:10 2008
New Revision: 98978

URL: http://svn.digium.com/view/asterisk?view=rev&rev=98978
Log:
Merge the changes from issue #10665 from the team/group/sip_session_timers branch.

This set of changes introduces SIP session timers support (RFC 4028).  In short,
this prevents stuck SIP sessions that were not properly torn down due to network
or endpoint failures during an established SIP session.

To quote some of the documentation supplied with the patch:
"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)."

(closes issue #10665)
Reported by: rjain
Patches:
      chan_sip.c.1.diff uploaded by rjain (license 226)
      chan_sip.c.diff uploaded by rjain (license 226)
      sip.conf.sample.diff uploaded by rjain (license 226)
      proc_422_rsp_comment.diff uploaded by rjain (license 226)
      chan_sip.c.cache.diff uploaded by rjain (license 226)
      chan_sip.memalloc uploaded by rjain (license 226)
      chan_sip.memalloc.bugfix uploaded by rjain (license 226)

      Patches tracked in team/group/sip_session_timers, with some additional fixes
      by russell and oej.

Tested by: jtodd, rjain, loloski

Modified:
    trunk/CREDITS
    trunk/channels/chan_sip.c
    trunk/configs/sip.conf.sample

Modified: trunk/CREDITS
URL: http://svn.digium.com/view/asterisk/trunk/CREDITS?view=diff&rev=98978&r1=98977&r2=98978
==============================================================================
--- trunk/CREDITS (original)
+++ trunk/CREDITS Wed Jan 16 15:53:10 2008
@@ -16,6 +16,9 @@
 nic.at - ENUM support in Asterisk
 
 Paul Bagyenda, Digital Solutions - for initial Voicetronix driver development
+
+John Todd, TalkPlus, Inc.  and JR Richardson, Ntegrated Solutions. - for funding
+    the development of SIP Session Timers support.
 
 === WISHLIST CONTRIBUTERS ===
 Jeremy McNamara - SpeeX support

Modified: trunk/channels/chan_sip.c
URL: http://svn.digium.com/view/asterisk/trunk/channels/chan_sip.c?view=diff&rev=98978&r1=98977&r2=98978
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Wed Jan 16 15:53:10 2008
@@ -86,6 +86,45 @@
         <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"
 
@@ -198,6 +237,9 @@
 
 #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 =
 {
@@ -346,6 +388,22 @@
 	REG_STATE_FAILED,	/*!< Registration failed after several tries */
 		/* 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
  *
@@ -458,6 +516,8 @@
 #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. */
@@ -472,8 +532,8 @@
 	{ 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 */
@@ -512,7 +572,7 @@
 #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
@@ -666,6 +726,12 @@
 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
@@ -1025,6 +1091,37 @@
 	int localtransfer;				/*!< Transfer to local domain? */
 	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
@@ -1098,6 +1195,7 @@
 	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) */
@@ -1118,6 +1216,8 @@
 	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 */
@@ -1167,7 +1267,9 @@
 							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
@@ -1273,6 +1375,7 @@
 	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 */
 };
 
 /*!
@@ -1359,8 +1462,9 @@
 	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) */
 };
 
 
@@ -1545,7 +1649,7 @@
 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);
@@ -1553,7 +1657,7 @@
 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);
@@ -1607,7 +1711,7 @@
 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);
 
@@ -1844,6 +1948,21 @@
 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",
@@ -2106,6 +2225,14 @@
 				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);
@@ -2526,6 +2653,9 @@
 	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.
@@ -2867,7 +2997,7 @@
 		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);	
 		}	
@@ -3298,6 +3428,7 @@
 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);
@@ -3782,6 +3913,13 @@
 		if (p->registry->call == p)
 			p->registry->call = NULL;
 		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 */
@@ -4338,7 +4476,7 @@
 			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;
@@ -4371,7 +4509,7 @@
 				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);
@@ -4388,7 +4526,7 @@
 				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);
@@ -4405,7 +4543,7 @@
 				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);
@@ -4601,7 +4739,7 @@
 		    !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;
 		}
@@ -5053,7 +5191,7 @@
 			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)) {
@@ -5105,6 +5243,26 @@
 static void make_our_tag(char *tagbuf, size_t len)
 {
 	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.
@@ -5132,6 +5290,9 @@
 	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 */
@@ -5230,6 +5391,7 @@
 		p->t38.jointcapability = p->t38.capability;
 	}
 	ast_string_field_set(p, context, default_context);
+
 
 	/* Add to active dialog list */
 	dialoglist_lock();
@@ -5646,6 +5808,9 @@
 	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 */
@@ -5687,6 +5852,7 @@
 	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");
@@ -5712,6 +5878,50 @@
 	/* 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");
@@ -6598,6 +6808,16 @@
 		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 */
@@ -6729,6 +6949,17 @@
 		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;
 }
 
@@ -6839,6 +7070,23 @@
 	add_header_contentLength(&resp, 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
@@ -7222,8 +7470,13 @@
 
 #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;
@@ -7276,8 +7529,10 @@
 	if (!p->sessionid) {
 		p->sessionid = (int)ast_random();
 		p->sessionversion = p->sessionid;
-	} else
-		p->sessionversion++;
+	} else {
+		if (oldsdp == FALSE)
+			p->sessionversion++;
+	}
 
 	capability = p->jointcapability;
 
@@ -7539,7 +7794,7 @@
 /*! \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;
@@ -7554,7 +7809,7 @@
 			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)
@@ -7611,27 +7866,38 @@
 	
 	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);
 }
 
@@ -7942,6 +8208,20 @@
 		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) {
@@ -7990,7 +8270,7 @@
 			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);
 	}
@@ -10829,6 +11109,45 @@
 /*! \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;
@@ -11509,8 +11828,6 @@
 	astman_append(s, "\r\n\r\n" );
 	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)
@@ -11667,7 +11984,7 @@
 		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));
@@ -11680,6 +11997,11 @@
 			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 */
@@ -11722,6 +12044,10 @@
 		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)));
@@ -11816,6 +12142,11 @@
 		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");
@@ -11826,7 +12157,9 @@
 			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]);
@@ -12058,6 +12391,10 @@
 	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);
@@ -12438,9 +12775,29 @@
 					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++;
 		}
 	}
@@ -13457,7 +13814,7 @@
 		} 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);	
 		}
 	}
@@ -13485,6 +13842,8 @@
 	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);
@@ -13657,6 +14016,39 @@
 			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);
@@ -13711,6 +14103,13 @@
 			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().
@@ -14214,6 +14613,13 @@
 				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);
@@ -14936,7 +15342,7 @@
 		/* 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);
@@ -14970,7 +15376,7 @@
 	   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);
 	
@@ -15069,6 +15475,17 @@

[... 808 lines stripped ...]



More information about the asterisk-commits mailing list