summaryrefslogtreecommitdiff
path: root/channels/chan_sip.c
diff options
context:
space:
mode:
authorDavid Vossel <dvossel@digium.com>2010-11-05 21:56:38 +0000
committerDavid Vossel <dvossel@digium.com>2010-11-05 21:56:38 +0000
commit97a14899606d7b5aca241aeb463f8e25d9f17afd (patch)
tree7a76bc51ce31b24ac4264f352def7d7e4bd29ed1 /channels/chan_sip.c
parent43e8c7df2bebf87e439540c71c5167816ea3e6e0 (diff)
Perform proper handling of forked outbound INVITE requests.
RFC3261 section 12 about dialog creation says an INVITE transaction results in an established dialog once it receives the 200 OK response. It is possible to receive multiple differing 200 OK responses for a single outbound INVITE Request, and this should result in establishing multiple dialogs. This patch allows for all differing 200 OK responses to an INVITE request to establish a separate dialog, but only the first dialog is kept. All other resulting dialogs from the initial request are immediately ACKed and then immediately terminated with a BYE request. Review: https://reviewboard.asterisk.org/r/946/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@294083 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'channels/chan_sip.c')
-rw-r--r--channels/chan_sip.c122
1 files changed, 105 insertions, 17 deletions
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index c2afeb5ad..a3d1ef61f 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -7134,8 +7134,8 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
return NULL;
}
- /* If this dialog is created as the result of an incoming Request. Lets store
- * some information about that request */
+ /* If this dialog is created as a result of a request or response, lets store
+ * some information about it in the dialog. */
if (req) {
char *sent_by, *branch;
const char *cseq = get_header(req, "Cseq");
@@ -7267,6 +7267,9 @@ struct match_req_args {
const char *fromtag;
unsigned int seqno;
+ /* Set if this method is a Response */
+ int respid;
+
/* Set if the method is a Request */
const char *ruri;
const char *viabranch;
@@ -7279,7 +7282,8 @@ struct match_req_args {
enum match_req_res {
SIP_REQ_MATCH,
SIP_REQ_NOT_MATCH,
- SIP_REQ_LOOP_DETECTED,
+ SIP_REQ_LOOP_DETECTED, /* multiple incoming requests with same call-id but different branch parameters have been detected */
+ SIP_REQ_FORKED, /* An outgoing request has been forked as result of receiving two differing 200ok responses. */
};
/*
@@ -7302,6 +7306,12 @@ static enum match_req_res match_req_to_dialog(struct sip_pvt *sip_pvt_ptr, struc
return SIP_REQ_NOT_MATCH;
}
if (arg->method == SIP_RESPONSE) {
+ /* Verify fromtag of response matches the tag we gave them. */
+ if (strcmp(arg->fromtag, sip_pvt_ptr->tag)) {
+ /* fromtag from response does not match our tag */
+ return SIP_REQ_NOT_MATCH;
+ }
+
/* Verify totag if we have one stored for this dialog, but never be strict about this for
* a response until the dialog is established */
if (!ast_strlen_zero(sip_pvt_ptr->theirtag) && ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
@@ -7309,16 +7319,36 @@ static enum match_req_res match_req_to_dialog(struct sip_pvt *sip_pvt_ptr, struc
/* missing totag when they already gave us one earlier */
return SIP_REQ_NOT_MATCH;
}
+ /* compare the totag of response with the tag we have stored for them */
if (strcmp(arg->totag, sip_pvt_ptr->theirtag)) {
- /* The totag of the response does not match the one we have stored */
+ /* totag did not match what we had stored for them. */
+ char invite_branch[32] = { 0, };
+ if (sip_pvt_ptr->invite_branch) {
+ snprintf(invite_branch, sizeof(invite_branch), "z9hG4bK%08x", (int) sip_pvt_ptr->invite_branch);
+ }
+ /* Forked Request Detection
+ *
+ * If this is a 200ok response and the totags do not match, this
+ * might be a forked response to an outgoing Request. Detection of
+ * a forked response must meet the criteria below.
+ *
+ * 1. must be a 2xx Response
+ * 2. call-d equal to call-id of Request. this is done earlier
+ * 3. from-tag equal to from-tag of Request. this is done earlier
+ * 4. branch parameter equal to branch of inital Request
+ * 5. to-tag _NOT_ equal to previous 2xx response that already established the dialog.
+ */
+ if ((arg->respid == 200) &&
+ !ast_strlen_zero(invite_branch) &&
+ !ast_strlen_zero(arg->viabranch) &&
+ !strcmp(invite_branch, arg->viabranch)) {
+ return SIP_REQ_FORKED;
+ }
+
+ /* The totag did not match the one we had stored, and this is not a Forked Request. */
return SIP_REQ_NOT_MATCH;
}
}
- /* Verify fromtag of response matches the tag we gave them. */
- if (strcmp(arg->fromtag, sip_pvt_ptr->tag)) {
- /* fromtag from response does not match our tag */
- return SIP_REQ_NOT_MATCH;
- }
} else {
/* Verify the fromtag of Request matches the tag they provided earlier.
* If this is a Request with authentication credentials, forget their old
@@ -7414,6 +7444,37 @@ static enum match_req_res match_req_to_dialog(struct sip_pvt *sip_pvt_ptr, struc
return SIP_REQ_MATCH;
}
+/*! \brief This function creates a dialog to handle a forked request. This dialog
+ * exists only to properly terminiate the the forked request immediately.
+ */
+static void forked_invite_init(struct sip_request *req, const char *new_theirtag, struct sip_pvt *original, struct ast_sockaddr *addr)
+{
+ struct sip_pvt *p;
+
+ if (!(p = sip_alloc(original->callid, addr, 1, SIP_INVITE, req))) {
+ return; /* alloc error */
+ }
+ p->invitestate = INV_TERMINATED;
+ p->ocseq = original->ocseq;
+ p->branch = original->branch;
+
+ memcpy(&p->flags, &original->flags, sizeof(p->flags));
+ copy_request(&p->initreq, &original->initreq);
+ ast_string_field_set(p, theirtag, new_theirtag);
+ ast_copy_string(p->tag, original->tag, sizeof(p->tag));
+ ast_string_field_set(p, uri, original->uri);
+ ast_string_field_set(p, our_contact, original->our_contact);
+ ast_string_field_set(p, fullcontact, original->fullcontact);
+ parse_ok_contact(p, req);
+ build_route(p, req, 1);
+
+ transmit_request(p, SIP_ACK, p->ocseq, XMIT_UNRELIABLE, TRUE);
+ transmit_request(p, SIP_BYE, 0, XMIT_RELIABLE, TRUE);
+
+ pvt_set_needdestroy(p, "forked request"); /* this dialog will terminate once the BYE is responed to or times out. */
+ dialog_unref(p, "setup forked invite termination");
+}
+
/*! \brief find or create a dialog structure for an incoming SIP message.
* Connect incoming SIP message to current dialog or create new dialog structure
* Returns a reference to the sip_pvt object, remember to give it back once done.
@@ -7485,6 +7546,9 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
struct sip_pvt tmp_dialog = {
.callid = callid,
};
+ /* if a Outbound forked Request is detected, this pvt will point
+ * to the dialog the Request is forking off of. */
+ struct sip_pvt *fork_pvt = NULL;
struct match_req_args args = { 0, };
int found;
struct ao2_iterator *iterator = ao2_t_callback(dialogs,
@@ -7498,14 +7562,20 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
args.totag = totag;
args.fromtag = fromtag;
args.seqno = seqno;
-
- /* If this is a Request, set the Via and Authorization header arguments */
- if (req->method != SIP_RESPONSE) {
- args.ruri = REQ_OFFSET_TO_STR(req, rlPart2);
- get_viabranch(ast_strdupa(get_header(req, "Via")), (char **) &args.viasentby, (char **) &args.viabranch);
- if (!ast_strlen_zero(get_header(req, "Authorization")) ||
- !ast_strlen_zero(get_header(req, "Proxy-Authorization"))) {
- args.authentication_present = 1;
+ /* get via header information. */
+ args.ruri = REQ_OFFSET_TO_STR(req, rlPart2);
+ get_viabranch(ast_strdupa(get_header(req, "Via")), (char **) &args.viasentby, (char **) &args.viabranch);
+ /* determine if this is a Request with authentication credentials. */
+ if (!ast_strlen_zero(get_header(req, "Authorization")) ||
+ !ast_strlen_zero(get_header(req, "Proxy-Authorization"))) {
+ args.authentication_present = 1;
+ }
+ /* if it is a response, get the response code */
+ if (req->method == SIP_RESPONSE) {
+ const char* e = ast_skip_blanks(REQ_OFFSET_TO_STR(req, rlPart2));
+ int respid;
+ if (!ast_strlen_zero(e) && (sscanf(e, "%30d", &respid) == 1)) {
+ args.respid = respid;
}
}
@@ -7517,6 +7587,7 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
case SIP_REQ_MATCH:
sip_pvt_lock(sip_pvt_ptr);
ao2_iterator_destroy(iterator);
+ dialog_unref(fork_pvt, "unref fork_pvt");
return sip_pvt_ptr; /* return pvt with ref */
case SIP_REQ_LOOP_DETECTED:
/* This is likely a forked Request that somehow resulted in us receiving multiple parts of the fork.
@@ -7524,7 +7595,12 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
transmit_response_using_temp(callid, addr, 1, intended_method, req, "482 (Loop Detected)");
dialog_unref(sip_pvt_ptr, "pvt did not match incoming SIP msg, unref from search.");
ao2_iterator_destroy(iterator);
+ dialog_unref(fork_pvt, "unref fork_pvt");
return NULL;
+ case SIP_REQ_FORKED:
+ dialog_unref(fork_pvt, "throwing way pvt to fork off of.");
+ fork_pvt = dialog_ref(sip_pvt_ptr, "this pvt has a forked request, save this off to copy information into new dialog\n");
+ /* fall through */
case SIP_REQ_NOT_MATCH:
default:
dialog_unref(sip_pvt_ptr, "pvt did not match incoming SIP msg, unref from search");
@@ -7533,6 +7609,18 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
if (iterator) {
ao2_iterator_destroy(iterator);
}
+
+ /* Handle any possible forked requests. This must be done only after transaction matching is complete. */
+ if (fork_pvt) {
+ /* XXX right now we only support handling forked INVITE Requests. Any other
+ * forked request type must be added here. */
+ if (fork_pvt->method == SIP_INVITE) {
+ forked_invite_init(req, args.totag, fork_pvt, addr);
+ dialog_unref(fork_pvt, "throwing way old forked pvt");
+ return NULL;
+ }
+ fork_pvt = dialog_unref(fork_pvt, "throwing way pvt to fork off of");
+ }
} /* end of pedantic mode Request/Reponse to Dialog matching */
/* See if the method is capable of creating a dialog */