summaryrefslogtreecommitdiff
path: root/channels/chan_sip.c
diff options
context:
space:
mode:
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 */