[asterisk-commits] mmichelson: trunk r195589 - in /trunk: channels/ configs/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue May 19 15:59:41 CDT 2009


Author: mmichelson
Date: Tue May 19 15:59:38 2009
New Revision: 195589

URL: http://svn.asterisk.org/svn-view/asterisk?view=rev&rev=195589
Log:
Add basic support for handling connected line-related UPDATE requests.

SIP purists may want to look the other way...

When COLP/CONP support for SIP was committed, there was a condition under 
which Asterisk may transmit a SIP UPDATE in order to communicate the change 
in connected line information. The issue here is that while we could send a 
SIP UPDATE message, we were not prepared to receive such an UPDATE and would 
always responde with a 501 when we received an UPDATE.

The situation was a bit rough. We really want to be able to receive UPDATEs 
having to do with connected line changes, but the amount of effort involved 
in properly supporting RFC 3311 was staggering. This commit represents a 
compromise.

First, it was decided that it is important to only send a SIP UPDATE to 
an endpoint that is able to handle one. So, now we have added parsing of 
the Allow header into SIP. We store the allowed methods on SIP peers so 
that when we communicate with them, we already will know what we can and 
cannot send to them. We will parse the peer's allowed methods when he registers
with us. If the peer is not the type to register with us, but the qualify option 
is enabled, then we will use the response to the OPTIONS request we send 
the peer to determine the peer's allowed methods. When the peer's registration 
expires, or when qualify deems the peer to be unreachable, we clear the allowed 
methods from the peer.

For an actual call, we will copy the peer's allowed methods to the sip_pvt 
representing the call leg. If we are communicating with an endpoint which is 
not a peer, then we will just parse the Allow header from the first message 
we receive during the call and store the information in the sip_pvt.

If, during communication with a peer, we receive a 501 response, then we will 
make sure to save the fact that we cannot use that method when communicating 
with that peer.

Now, with all that infrastructure in place, the only actual place we use this 
information currently is when attempting to send a connected line change using 
an UPDATE request. If we cannot send the change immediately using an UPDATE, 
we will set the SIP_NEEDREINVITE flag so that we can send a REINVITE as soon 
as it is allowed.

The second part of the changes here is for Asterisk to accept UPDATE requests 
that have connected line changes. Since we are not fully supporting RFC 3311, 
Asterisk will NOT place the UPDATE method in Allow headers it sends. Instead, 
if you are communicating with what you know to be another Asterisk box, you may 
set the rpid_update parameter in sip.conf so that we will send UPDATEs to that 
Asterisk box. When we send a connected line update, we set a custom header 
called "X-Asterisk-rpid-update."

On the receiving end, if Asterisk receives an UPDATE that does not have the 
"X-Asterisk-rpid-update" header present, then Asterisk will respond with a 501 
since media-changing UPDATEs are not supported. We should never get such 
UPDATEs, since as was stated earlier, Asterisk does not put UPDATE in its Allow
header. If the custom header is present in the received UPDATE, though, then we 
will check the incoming request for connected line updates and queue the update
on the channel where the change occurred.

ABE-1840
ABE-1822


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

Modified: trunk/channels/chan_sip.c
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/channels/chan_sip.c?view=diff&rev=195589&r1=195588&r2=195589
==============================================================================
--- trunk/channels/chan_sip.c (original)
+++ trunk/channels/chan_sip.c Tue May 19 15:59:38 2009
@@ -1423,6 +1423,7 @@
 /* realtime flags */
 #define SIP_PAGE2_RTCACHEFRIENDS	(1 << 0)	/*!< GP: Should we keep RT objects in memory for extended time? */
 #define SIP_PAGE2_RTAUTOCLEAR		(1 << 2)	/*!< GP: Should we clean memory from peers after expiry? */
+#define SIP_PAGE2_RPID_UPDATE		(1 << 3)
 /* Space for addition of other realtime flags in the future */
 #define SIP_PAGE2_STATECHANGEQUEUE	(1 << 9)	/*!< D: Unsent state pending change exists */
 
@@ -1460,7 +1461,7 @@
 	SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | \
 	SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \
 	SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \
-	SIP_PAGE2_RPID_IMMEDIATE)
+	SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE)
 
 /*@}*/ 
 
@@ -1780,6 +1781,11 @@
 	int hangupcause;			/*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */
 
 	struct sip_subscription_mwi *mwi;       /*!< If this is a subscription MWI dialog, to which subscription */
+	/*! The SIP methods allowed on this dialog. We get this information from the Allow header present in 
+	 * the peer's REGISTER. If peer does not register with us, then we will use the first transaction we
+	 * have with this peer to determine its allowed methods.
+	 */
+	unsigned int allowed_methods;
 }; 
 
 
@@ -1966,6 +1972,7 @@
 	
 	/*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */
 	enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */
+	unsigned int allowed_methods;
 };
 
 
@@ -2546,6 +2553,8 @@
 static const char *gettag(const struct sip_request *req, const char *header, char *tagbuf, int tagbufsize);
 static int find_sip_method(const char *msg);
 static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported);
+static unsigned int parse_allowed_methods(struct sip_request *req);
+static unsigned int set_pvt_allowed_methods(struct sip_pvt *pvt, struct sip_request *req);
 static int parse_request(struct sip_request *req);
 static const char *get_header(const struct sip_request *req, const char *name);
 static const char *referstatus2str(enum referstatus rstatus) attribute_pure;
@@ -2609,6 +2618,7 @@
 
 /*------Request handling functions */
 static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock);
+static int handle_request_update(struct sip_pvt *p, struct sip_request *req);
 static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, struct sockaddr_in *sin, int *recount, char *e, int *nounlock);
 static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, int debug, int seqno, int *nounlock);
 static int handle_request_bye(struct sip_pvt *p, struct sip_request *req);
@@ -4915,6 +4925,7 @@
 	dialog->rtptimeout = peer->rtptimeout;
 	dialog->peerauth = peer->auth;
 	dialog->maxcallbitrate = peer->maxcallbitrate;
+	dialog->allowed_methods = peer->allowed_methods;
 	if (ast_strlen_zero(dialog->tohost))
 		ast_string_field_set(dialog, tohost, ast_inet_ntoa(dialog->sa.sin_addr));
 	if (!ast_strlen_zero(peer->fromdomain)) {
@@ -7236,6 +7247,87 @@
 	ASTOBJ_UNREF(mwi, sip_subscribe_mwi_destroy);
 	
 	return 0;
+}
+
+static void mark_method_allowed(unsigned int *allowed_methods, enum sipmethod method)
+{
+	(*allowed_methods) |= (1 << method);
+}
+
+static void mark_method_unallowed(unsigned int *allowed_methods, enum sipmethod method)
+{
+	(*allowed_methods) &= ~(1 << method);
+}
+
+static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method)
+{
+	return ((*allowed_methods) >> method) & 1;
+}
+
+/*!
+ * \brief parse the Allow header to see what methods the endpoint we
+ * are communicating with allows.
+ *
+ * We parse the allow header on incoming Registrations and save the
+ * result to the SIP peer that is registering. When the registration
+ * expires, we clear what we know about the peer's allowed methods.
+ * When the peer re-registers, we once again parse to see if the 
+ * list of allowed methods has changed.
+ *
+ * For peers that do not register, we parse the first message we receive
+ * during a call to see what is allowed, and save the information
+ * for the duration of the call.
+ * \param req The SIP request we are parsing
+ * \retval The methods allowed
+ */
+static unsigned int parse_allowed_methods(struct sip_request *req)
+{
+	char *allow = ast_strdupa(get_header(req, "Allow"));
+	char *method;
+	unsigned int allowed_methods = SIP_UNKNOWN;
+
+	if (ast_strlen_zero(allow)) {
+		/* RFC 3261 states:
+		 *
+		 * "The absence of an Allow header field MUST NOT be
+		 * interpreted to mean that the UA sending the message supports no
+		 * methods.   Rather, it implies that the UA is not providing any
+		 * information on what methods it supports."
+		 *
+		 * For simplicity, we'll assume that the peer allows all known
+		 * SIP methods if they have no Allow header. We can then clear out the necessary
+		 * bits if the peer lets us know that we have sent an unsupported method.
+		 */
+		return UINT_MAX;
+	}
+	for (method = strsep(&allow, ","); !ast_strlen_zero(method); method = strsep(&allow, ",")) {
+		int id = find_sip_method(ast_skip_blanks(method));
+		if (id == SIP_UNKNOWN) {
+			continue;
+		}
+		mark_method_allowed(&allowed_methods, id);
+	}
+	return allowed_methods;
+}
+
+/*! A wrapper for parse_allowed_methods geared toward sip_pvts
+ *
+ * This function, in addition to setting the allowed methods for a sip_pvt
+ * also will take into account the setting of the SIP_PAGE2_RPID_UPDATE flag.
+ *
+ * \param pvt The sip_pvt we are setting the allowed_methods for
+ * \param req The request which we are parsing
+ * \retval The methods alloweded by the sip_pvt
+ */
+static unsigned int set_pvt_allowed_methods(struct sip_pvt *pvt, struct sip_request *req)
+{
+	pvt->allowed_methods = parse_allowed_methods(req);
+	
+	if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_RPID_UPDATE)) {
+		mark_method_allowed(&pvt->allowed_methods, SIP_UPDATE);
+	}
+
+	return pvt->allowed_methods;
 }
 
 /*! \brief  Parse multiline SIP headers into one header
@@ -9875,8 +9967,12 @@
 			add_header(&req, "X-asterisk-Info", "SIP re-invite (External RTP bridge)");
 	}
 
+	if (ast_test_flag(&p->flags[1], SIP_SENDRPID))
+		add_rpid(&req, p);
+
 	if (p->do_history)
 		append_history(p, "ReInv", "Re-invite sent");
+
 	try_suggested_sip_codec(p);
 	if (t38version)
 		add_sdp(&req, p, oldsdp, FALSE, TRUE);
@@ -10262,7 +10358,9 @@
 
 	if (!p->initreq.headers || init > 2)
 		initialize_initreq(p, &req);
-	p->lastinvite = p->ocseq;
+	if (sipmethod == SIP_INVITE) {
+		p->lastinvite = p->ocseq;
+	}
 	return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
 }
 
@@ -10815,11 +10913,15 @@
 			p->lastinvite = p->ocseq;
 			ast_set_flag(&p->flags[0], SIP_OUTGOING);
 			send_request(p, &req, XMIT_CRITICAL, p->ocseq);
-		} else {
+		} else if (is_method_allowed(&p->allowed_methods, SIP_UPDATE)) {
 			reqprep(&req, p, SIP_UPDATE, 0, 1);
 			add_rpid(&req, p);
+			add_header(&req, "X-Asterisk-rpid-update", "Yes");
 			add_header_contentLength(&req, 0);
 			send_request(p, &req, XMIT_CRITICAL, p->ocseq);
+		} else {
+			/* We cannot send the update yet, so we have to wait until we can */
+			ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
 		}
 	} else {
 		if (ast_test_flag(&p->flags[1], SIP_PAGE2_RPID_IMMEDIATE)) {
@@ -11424,6 +11526,7 @@
 	manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
 	register_peer_exten(peer, FALSE);	/* Remove regexten */
 	ast_devstate_changed(AST_DEVICE_UNKNOWN, "SIP/%s", peer->name);
+	peer->allowed_methods = SIP_UNKNOWN;
 
 	/* Do we need to release this peer from memory? 
 		Only for realtime peers and autocreated peers
@@ -12361,6 +12464,7 @@
 	}
 
 	if (peer) {
+		ao2_lock(peer);
 		if (!peer->host_dynamic) {
 			ast_log(LOG_ERROR, "Peer '%s' is trying to register, but not configured as host=dynamic\n", peer->name);
 			res = AUTH_PEER_NOT_DYNAMIC;
@@ -12405,6 +12509,7 @@
 
 			} 
 		}
+		ao2_unlock(peer);
 	}
 	if (!peer && sip_cfg.autocreatepeer) {
 		/* Create peer if we have autocreate mode enabled */
@@ -12414,7 +12519,7 @@
 			if (peer->addr.sin_addr.s_addr) {
 				ao2_t_link(peers_by_ip, peer, "link peer into peers-by-ip table");
 			}
-			
+			ao2_lock(peer);
 			if (sip_cancel_destroy(p))
 				ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
 			switch (parse_register_contact(p, peer, req)) {
@@ -12437,6 +12542,7 @@
 				res = 0;
 				break;
 			}
+			ao2_unlock(peer);
 		}
 	}
 	if (!peer && sip_cfg.alwaysauthreject) {
@@ -12499,8 +12605,14 @@
 			break;
 		}
 	}
-	if (peer)
+	if (peer) {
+		ao2_lock(peer);
+		if (peer->allowed_methods == SIP_UNKNOWN) {
+			peer->allowed_methods = set_pvt_allowed_methods(p, req);
+		}
+		ao2_unlock(peer);
 		unref_peer(peer, "register_verify: unref_peer: tossing stack peer pointer at end of func");
+	}
 
 	return res;
 }
@@ -13479,6 +13591,11 @@
 	ast_string_field_set(p, mohsuggest, peer->mohsuggest);
 	ast_string_field_set(p, parkinglot, peer->parkinglot);
 	ast_string_field_set(p, engine, peer->engine);
+	if (peer->allowed_methods == SIP_UNKNOWN) {
+		set_pvt_allowed_methods(p, req);
+	} else {
+		p->allowed_methods = peer->allowed_methods;
+	}
 	if (peer->callingpres)	/* Peer calling pres setting will override RPID */
 		p->callingpres = peer->callingpres;
 	if (peer->maxms && peer->lastms)
@@ -17188,6 +17305,20 @@
 	return 0;
 }
 
+/*!
+ * \brief Handle authentication challenge for SIP UPDATE
+ *
+ * This function is only called upon the receipt of a 401/407 response to an UPDATE.
+ */
+static void handle_response_update(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
+{
+	if (p->options) {
+		p->options->auth_type = (resp == 401 ? WWW_AUTH : PROXY_AUTH);
+	}
+	if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, resp, SIP_UPDATE, 1)) {
+		ast_log(LOG_NOTICE, "Failed to authenticate on UPDATE to '%s'\n", get_header(&p->initreq, "From"));
+	}
+}
 
 /*! \brief Handle SIP response to INVITE dialogue */
 static void handle_response_invite(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
@@ -17204,6 +17335,21 @@
 		ast_debug(4, "SIP response %d to RE-invite on %s call %s\n", resp, outgoing ? "outgoing" : "incoming", p->callid);
 	else
 		ast_debug(4, "SIP response %d to standard invite\n", resp);
+
+	/* If this is a response to our initial INVITE, we need to set what we can use
+	 * for this peer.
+	 */
+	if (!reinvite && p->allowed_methods == SIP_UNKNOWN) {
+		struct sip_peer *peer = find_peer(p->peername, NULL, 1, FINDPEERS, FALSE);
+		if (!peer || peer->allowed_methods == SIP_UNKNOWN) {
+			set_pvt_allowed_methods(p, req);
+		} else {
+			p->allowed_methods = peer->allowed_methods;
+		}
+		if (peer) {
+			unref_peer(peer, "handle_response_invite: Getting supported methods from peer");
+		}
+	}
 
 	if (p->alreadygone) { /* This call is already gone */
 		ast_debug(1, "Got response on call that is already terminated: %s (ignoring)\n", p->callid);
@@ -17591,6 +17737,7 @@
 /* \brief Handle SIP response in SUBSCRIBE transaction */
 static void handle_response_subscribe(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int seqno)
 {
+	struct sip_peer *peer;
 	if (!p->mwi) {
 		return;
 	}
@@ -17598,6 +17745,15 @@
 	switch (resp) {
 	case 200: /* Subscription accepted */
 		ast_debug(3, "Got 200 OK on subscription for MWI\n");
+		peer = find_peer(p->peername, NULL, 1, FINDPEERS, FALSE);
+		if (!peer || peer->allowed_methods == SIP_UNKNOWN) {
+			set_pvt_allowed_methods(p, req);
+		} else {
+			p->allowed_methods = peer->allowed_methods;
+		}
+		if (peer) {
+			unref_peer(peer, "handle_response_subscribe: Getting supported methods");
+		}
 		if (p->options) {
 			ast_free(p->options);
 			p->options = NULL;
@@ -17910,6 +18066,12 @@
 		manager_event(EVENT_FLAG_SYSTEM, "PeerStatus",
 			"ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: %s\r\nTime: %d\r\n",
 			peer->name, s, pingtime);
+		if (!is_reachable) {
+			peer->allowed_methods = SIP_UNKNOWN;
+		} else {
+			set_pvt_allowed_methods(p, req);
+			peer->allowed_methods = p->allowed_methods;
+		}
 		if (is_reachable && sip_cfg.regextenonqualify)
 			register_peer_exten(peer, TRUE);
 	}
@@ -17951,6 +18113,7 @@
 	char *c_copy = ast_strdupa(c);
 	/* Skip the Cseq and its subsequent spaces */
 	const char *msg = ast_skip_blanks(ast_skip_nonblanks(c_copy));
+	struct sip_peer *peer;
 
 	if (!msg)
 		msg = "";
@@ -18051,7 +18214,9 @@
 				handle_response_subscribe(p, resp, rest, req, seqno);
 			else if (p->registry && sipmethod == SIP_REGISTER)
 				res = handle_response_register(p, resp, rest, req, seqno);
-			else if (sipmethod == SIP_BYE) {
+			else if (sipmethod == SIP_UPDATE) {
+				handle_response_update(p, resp, rest, req, seqno);
+			} else if (sipmethod == SIP_BYE) {
 				if (p->options)
 					p->options->auth_type = resp;
 				if (ast_strlen_zero(p->authname)) {
@@ -18151,6 +18316,11 @@
 			}
 			break;
 		case 501: /* Not Implemented */
+			mark_method_unallowed(&p->allowed_methods, sipmethod);
+			if ((peer = find_peer(p->peername, 0, 1, FINDPEERS, FALSE))) {
+				peer->allowed_methods = p->allowed_methods;
+				unref_peer(peer, "handle_response: marking a specific method as unallowed");
+			}
 			if (sipmethod == SIP_INVITE)
 				handle_response_invite(p, resp, rest, req, seqno);
 			else if (sipmethod == SIP_REFER)
@@ -19314,6 +19484,36 @@
 }
 
 /*!
+ * \brief bare-bones support for SIP UPDATE
+ *
+ * XXX This is not even close to being RFC 3311-compliant. We don't advertise
+ * that we support the UPDATE method, so no one should ever try sending us
+ * an UPDATE anyway. However, Asterisk can send an UPDATE to change connected
+ * line information, so we need to be prepared to handle this. The way we distinguish
+ * such an UPDATE is through the X-Asterisk-rpid-update header.
+ *
+ * Actually updating the media session may be some future work.
+ */
+static int handle_request_update(struct sip_pvt *p, struct sip_request *req)
+{
+	if (ast_strlen_zero(get_header(req, "X-Asterisk-rpid-update"))) {
+		transmit_response(p, "501 Method Not Implemented", req);
+		return 0;
+	}
+	if (get_rpid(p, req)) {
+		struct ast_party_connected_line connected;
+		ast_party_connected_line_init(&connected);
+		connected.id.number = (char *) p->cid_num;
+		connected.id.name = (char *) p->cid_name;
+		connected.id.number_presentation = p->callingpres;
+		connected.source = AST_CONNECTED_LINE_UPDATE_SOURCE_TRANSFER;
+		ast_channel_queue_connected_line_update(p->owner, &connected);
+	}
+	transmit_response(p, "200 OK", req);
+	return 0;
+}
+
+/*!
  * \brief Handle incoming INVITE request
  * \note If the INVITE has a Replaces header, it is part of an
  *	attended transfer. If so, we do not go through the dial
@@ -21381,6 +21581,9 @@
 	case SIP_NOTIFY:
 		res = handle_request_notify(p, req, sin, seqno, e);
 		break;
+	case SIP_UPDATE:
+		res = handle_request_update(p, req);
+		break;
 	case SIP_ACK:
 		/* Make sure we don't ignore this */
 		if (seqno == p->pendinginvite) {
@@ -22837,6 +23040,9 @@
 		} else if (ast_true(v->value)) {
 			ast_set_flag(&flags[0], SIP_SENDRPID_RPID);
 		}
+	} else if (!strcasecmp(v->name, "rpid_update")) {
+		ast_set_flag(&mask[1], SIP_PAGE2_RPID_UPDATE);
+		ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RPID_UPDATE);
 	} else if (!strcasecmp(v->name, "rpid_immediate")) {
 		ast_set_flag(&mask[1], SIP_PAGE2_RPID_IMMEDIATE);
 		ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RPID_IMMEDIATE);

Modified: trunk/configs/sip.conf.sample
URL: http://svn.asterisk.org/svn-view/asterisk/trunk/configs/sip.conf.sample?view=diff&rev=195589&r1=195588&r2=195589
==============================================================================
--- trunk/configs/sip.conf.sample (original)
+++ trunk/configs/sip.conf.sample Tue May 19 15:59:38 2009
@@ -232,6 +232,13 @@
                                 ; This is identical to sendrpid=yes
 ;sendrpid = pai                 ; Use the "P-Asserted-Identity" header
                                 ; to send the identity of the remote party
+;rpid_update = no               ; In certain cases, the only method by which a connected line
+                                ; change may be immediately transmitted is with a SIP UPDATE request.
+                                ; If communicating with another Asterisk server, and you wish to be able
+                                ; transmit such UPDATE messages to it, then you must enable this option.
+                                ; Otherwise, we will have to wait until we can send a reinvite to
+                                ; transmit the information.
+
 ;progressinband=never           ; If we should generate in-band ringing always
                                 ; use 'never' to never use in-band signalling, even in cases
                                 ; where some buggy devices might not render it




More information about the asterisk-commits mailing list