static int restart_monitor(void);
static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer);
static struct ast_variable *copy_vars(struct ast_variable *src);
+static int dialog_find_multiple(void *obj, void *arg, int flags);
/* static int sip_addrcmp(char *name, struct sockaddr_in *sin); Support for peer matching */
static int sip_refer_allocate(struct sip_pvt *p);
static int sip_notify_allocate(struct sip_pvt *p);
return NULL;
}
+ /* If this dialog is created as the result of an incoming Request. Lets store
+ * some information about that request */
if (req) {
+ char *sent_by, *branch;
+ const char *cseq = get_header(req, "Cseq");
+ unsigned int seqno;
+ /* get branch parameter from initial Request that started this dialog */
+ get_viabranch(ast_strdupa(get_header(req, "Via")), &sent_by, &branch);
+ /* only store the branch if it begins with the magic prefix "z9hG4bK", otherwise
+ * it is not useful to us to have it */
+ if (!ast_strlen_zero(branch) && !strncasecmp(branch, "z9hG4bK", 7)) {
+ ast_string_field_set(p, initviabranch, branch);
+ ast_string_field_set(p, initviasentby, sent_by);
+ }
+
+ /* Store initial incoming cseq. An error in sscanf here is ignored. There is no approperiate
+ * except not storing the number. CSeq validation must take place before dialog creation in find_call */
+ if (!ast_strlen_zero(cseq) && (sscanf(cseq, "%30u", &seqno) == 1)) {
+ p->init_icseq = seqno;
+ }
set_socket_transport(&p->socket, req->socket.type); /* Later in ast_sip_ouraddrfor we need this to choose the right ip and port for the specific transport */
} else {
set_socket_transport(&p->socket, SIP_TRANSPORT_UDP);
return p;
}
+/* \brief arguments used for Request/Response to matching */
+struct match_req_args {
+ int method;
+ const char *callid;
+ const char *totag;
+ const char *fromtag;
+ unsigned int seqno;
+
+ /* Set if the method is a Request */
+ const char *ruri;
+ const char *viabranch;
+ const char *viasentby;
+
+ /* Set this if the Authentication header is present in the Request. */
+ int authentication_present;
+};
+
+enum match_req_res {
+ SIP_REQ_MATCH,
+ SIP_REQ_NOT_MATCH,
+ SIP_REQ_LOOP_DETECTED,
+};
+
+/*
+ * \brief Match a incoming Request/Response to a dialog
+ *
+ * \retval enum match_req_res indicating if the dialog matches the arg
+ */
+static enum match_req_res match_req_to_dialog(struct sip_pvt *sip_pvt_ptr, struct match_req_args *arg)
+{
+ const char *init_ruri = REQ_OFFSET_TO_STR(&sip_pvt_ptr->initreq, rlPart2);
+
+ /*
+ * Match Tags and call-id to Dialog
+ */
+ if (!ast_strlen_zero(arg->callid) && strcmp(sip_pvt_ptr->callid, arg->callid)) {
+ /* call-id does not match. */
+ return SIP_REQ_NOT_MATCH;
+ }
+ if (arg->method == SIP_RESPONSE) {
+ /* 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)) {
+ if (ast_strlen_zero(arg->totag)) {
+ /* missing totag when they already gave us one earlier */
+ return SIP_REQ_NOT_MATCH;
+ }
+ if (strcmp(arg->totag, sip_pvt_ptr->theirtag)) {
+ /* The totag of the response does not match the one we have stored */
+ 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 (strcmp(arg->fromtag, sip_pvt_ptr->theirtag)) {
+ /* their tag does not match the one was have stored for them */
+ return SIP_REQ_NOT_MATCH;
+ }
+ /* Verify if totag is present in Request, that it matches what we gave them as our tag earlier */
+ if (!ast_strlen_zero(arg->totag) && (strcmp(arg->totag, sip_pvt_ptr->tag))) {
+ /* totag from Request does not match our tag */
+ return SIP_REQ_NOT_MATCH;
+ }
+ }
+
+ /*
+ * Compare incoming request against initial transaction.
+ *
+ * This is a best effort attempt at distinguishing forked requests from
+ * our initial transaction. If all the elements are NOT in place to evaluate
+ * this, this block is ignored and the dialog match is made regardless.
+ * Once the totag is established after the dialog is confirmed, this is not necessary.
+ *
+ * CRITERIA required for initial transaction matching.
+ *
+ * 1. Is a Request
+ * 2. Callid and theirtag match (this is done in the dialog matching block)
+ * 3. totag is NOT present
+ * 4. CSeq matchs our initial transaction's cseq number
+ * 5. pvt has init via branch parameter stored
+ */
+ if ((arg->method != SIP_RESPONSE) && /* must be a Request */
+ ast_strlen_zero(arg->totag) && /* must not have a totag */
+ (sip_pvt_ptr->init_icseq == arg->seqno) && /* the cseq must be the same as this dialogs initial cseq */
+ !ast_strlen_zero(sip_pvt_ptr->initviabranch)) { /* The dialog must have started with a RFC3261 compliant branch tag */
+
+ /* This Request matches all the criteria required for Loop/Merge detection.
+ * Now we must go down the path of comparing VIA's and RURIs. */
+ if (ast_strlen_zero(arg->viabranch) ||
+ strcmp(arg->viabranch, sip_pvt_ptr->initviabranch) ||
+ ast_strlen_zero(arg->viasentby) ||
+ strcmp(arg->viasentby, sip_pvt_ptr->initviasentby)) {
+ /* At this point, this request does not match this Dialog.*/
+
+ /* if methods are different this is just a mismatch */
+ if ((sip_pvt_ptr->method != arg->method)) {
+ return SIP_REQ_NOT_MATCH;
+ }
+
+ /* If RUIs are different, this is a forked request to a separate URI.
+ * Returning a mismatch allows this Request to be processed separately. */
+ if (sip_uri_cmp(init_ruri, arg->ruri)) {
+ /* not a match, request uris are different */
+ return SIP_REQ_NOT_MATCH;
+ }
+
+ /* Loop/Merge Detected
+ *
+ * ---Current Matches to Initial Request---
+ * request uri
+ * Call-id
+ * their-tag
+ * no totag present
+ * method
+ * cseq
+ *
+ * --- Does not Match Initial Request ---
+ * Top Via
+ *
+ * Without the same Via, this can not match our initial transaction for this dialog,
+ * but given that this Request matches everything else associated with that initial
+ * Request this is most certainly a Forked request in which we have already received
+ * part of the fork.
+ */
+ return SIP_REQ_LOOP_DETECTED;
+ }
+ } /* end of Request Via check */
+
+ /* Match Authentication Request.
+ *
+ * A Request with an Authentication header must come back with the
+ * same Request URI. Otherwise it is not a match.
+ */
+ if ((arg->method != SIP_RESPONSE) && /* Must be a Request type to even begin checking this */
+ ast_strlen_zero(arg->totag) && /* no totag is present to match */
+ arg->authentication_present && /* Authentication header is present in Request */
+ sip_uri_cmp(init_ruri, arg->ruri)) { /* Compare the Request URI of both the last Request and this new one */
+
+ /* Authentication was provided, but the Request URI did not match the last one on this dialog. */
+ return SIP_REQ_NOT_MATCH;
+ }
+
+ return SIP_REQ_MATCH;
+}
+
/*! \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.
static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *addr, const int intended_method)
{
struct sip_pvt *p = NULL;
- char *tag = ""; /* note, tag is never NULL */
char totag[128];
char fromtag[128];
const char *callid = get_header(req, "Call-ID");
const char *to = get_header(req, "To");
const char *cseq = get_header(req, "Cseq");
struct sip_pvt *sip_pvt_ptr;
-
+ unsigned int seqno;
/* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */
/* get_header always returns non-NULL so we must use ast_strlen_zero() */
if (ast_strlen_zero(callid) || ast_strlen_zero(to) ||
- ast_strlen_zero(from) || ast_strlen_zero(cseq)) {
+ ast_strlen_zero(from) || ast_strlen_zero(cseq) ||
+ (sscanf(cseq, "%30u", &seqno) != 1)) {
/* RFC 3261 section 24.4.1. Send a 400 Bad Request if the request is malformed. */
if (intended_method != SIP_RESPONSE && intended_method != SIP_ACK) {
req->has_to_tag = 1; /* Used in handle_request/response */
gettag(req, "From", fromtag, sizeof(fromtag));
- tag = (req->method == SIP_RESPONSE) ? totag : fromtag;
-
ast_debug(5, "= Looking for Call ID: %s (Checking %s) --From tag %s --To-tag %s \n", callid, req->method==SIP_RESPONSE ? "To" : "From", fromtag, totag);
/* All messages must always have From: tag */
sip_pvt_lock(sip_pvt_ptr);
return sip_pvt_ptr;
}
- } else { /* in pedantic mode! -- do the fancy linear search */
+ } else { /* in pedantic mode! -- do the fancy search */
struct sip_pvt tmp_dialog = {
.callid = callid,
};
- struct ao2_iterator *iterator = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER | OBJ_MULTIPLE,
- "pedantic ao2_find in dialogs");
- if (iterator) {
- int found = TRUE;
-
- while ((sip_pvt_ptr = ao2_iterator_next(iterator))) {
- if (req->method != SIP_REGISTER) {
- found = ast_strlen_zero(tag) || ast_strlen_zero(sip_pvt_ptr->theirtag) ||
- !ast_test_flag(&sip_pvt_ptr->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED) ||
- !strcmp(sip_pvt_ptr->theirtag, tag);
- }
- ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match",
- sip_pvt_ptr->callid, sip_pvt_ptr->theirtag, sip_pvt_ptr->tag);
- /* If we get a new request within an existing to-tag - check the to tag as well */
- if (found && req->method != SIP_RESPONSE) { /* SIP Request */
- if (sip_pvt_ptr->tag[0] == '\0' && totag[0]) {
- /* We have no to tag, but they have. Wrong dialog */
- found = FALSE;
- } else if (totag[0]) { /* Both have tags, compare them */
- if (strcmp(totag, sip_pvt_ptr->tag)) {
- found = FALSE; /* This is not our packet */
- }
- }
- if (!found)
- ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag <null> Totag %s Method %s\n",
- sip_pvt_ptr->callid, totag, sip_methods[req->method].text);
- }
- if (found) {
- sip_pvt_lock(sip_pvt_ptr);
- ao2_iterator_destroy(iterator);
- return sip_pvt_ptr;
- }
+ struct match_req_args args = { 0, };
+ int found;
+ struct ao2_iterator *iterator = ao2_t_callback(dialogs,
+ OBJ_POINTER | OBJ_MULTIPLE,
+ dialog_find_multiple,
+ &tmp_dialog,
+ "pedantic ao2_find in dialogs");
+
+ args.method = req->method;
+ args.callid = NULL; /* we already matched this. */
+ 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) {
+ const char *auth_header;
+ args.ruri = REQ_OFFSET_TO_STR(req, rlPart2);
+ get_viabranch(ast_strdupa(get_header(req, "Via")), (char **) &args.viasentby, (char **) &args.viabranch);
+ auth_header = get_header(req, "WWW-Authenticate");
+ if (!ast_strlen_zero(auth_header)) {
+ args.authentication_present = 1;
+ }
+ }
+
+ /* Iterate a list of dialogs already matched by Call-id */
+ while (iterator && (sip_pvt_ptr = ao2_iterator_next(iterator))) {
+ found = match_req_to_dialog(sip_pvt_ptr, &args);
+
+ switch (found) {
+ case SIP_REQ_MATCH:
+ sip_pvt_lock(sip_pvt_ptr);
+ ao2_iterator_destroy(iterator);
+ 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.
+ * RFC 3261 section 8.2.2.2, Indicate that we want to merge requests by sending a 482 response. */
+ 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);
+ return NULL;
+ case SIP_REQ_NOT_MATCH:
+ default:
+ dialog_unref(sip_pvt_ptr, "pvt did not match incoming SIP msg, unref from search");
}
+ }
+ if (iterator) {
ao2_iterator_destroy(iterator);
}
- }
+ } /* end of pedantic mode Request/Reponse to Dialog matching */
/* See if the method is capable of creating a dialog */
if (sip_methods[intended_method].can_create == CAN_CREATE_DIALOG) {
return ast_str_case_hash(pvt->callid);
}
+/*!
+ * \note Same as dialog_cmp_cb, except without the CMP_STOP on match
+ */
+static int dialog_find_multiple(void *obj, void *arg, int flags)
+{
+ struct sip_pvt *pvt = obj, *pvt2 = arg;
+
+ return !strcasecmp(pvt->callid, pvt2->callid) ? CMP_MATCH : 0;
+}
+
/*!
* \note The only member of the dialog used here callid string
*/
static int dialog_cmp_cb(void *obj, void *arg, int flags)
{
struct sip_pvt *pvt = obj, *pvt2 = arg;
-
+
return !strcasecmp(pvt->callid, pvt2->callid) ? CMP_MATCH | CMP_STOP : 0;
}
-
-
/*! \brief SIP Cli commands definition */
static struct ast_cli_entry cli_sip[] = {
AST_CLI_DEFINE(sip_show_channels, "List active SIP channels or subscriptions"),
return test_res;
}
+void get_viabranch(char *via, char **sent_by, char **branch)
+{
+ char *tmp;
+
+ if (sent_by) {
+ *sent_by = NULL;
+ }
+ if (branch) {
+ *branch = NULL;
+ }
+ if (ast_strlen_zero(via)) {
+ return;
+ }
+ via = ast_skip_blanks(via);
+ /*
+ * VIA syntax. RFC 3261 section 6.40.5
+ * Via = ( "Via" | "v") ":" 1#( sent-protocol sent-by *( ";" via-params ) [ comment ] )
+ * via-params = via-hidden | via-ttl | via-maddr | via-received | via-branch
+ * via-hidden = "hidden"
+ * via-ttl = "ttl" "=" ttl
+ * via-maddr = "maddr" "=" maddr
+ * via-received = "received" "=" host
+ * via-branch = "branch" "=" token
+ * sent-protocol = protocol-name "/" protocol-version "/" transport
+ * protocol-name = "SIP" | token
+ * protocol-version = token
+ * transport = "UDP" | "TCP" | token
+ * sent-by = ( host [ ":" port ] ) | ( concealed-host )
+ * concealed-host = token
+ * ttl = 1*3DIGIT ; 0 to 255
+ */
+
+ /* chop off ("Via:" | "v:") if present */
+ if (!strncasecmp(via, "Via:", 4)) {
+ via += 4;
+ } else if (!strncasecmp(via, "v:", 2)) {
+ via += 2;
+ }
+ if (ast_strlen_zero(via)) {
+ return;
+ }
+
+ /* chop off sent-protocol */
+ via = ast_skip_blanks(via);
+ strsep(&via, " \t\r\n");
+ if (ast_strlen_zero(via)) {
+ return;
+ }
+
+ /* chop off sent-by */
+ via = ast_skip_blanks(via);
+ *sent_by = strsep(&via, "; \t\r\n");
+ if (ast_strlen_zero(via)) {
+ return;
+ }
+
+ /* now see if there is a branch parameter in there */
+ if (!ast_strlen_zero(via) && (tmp = strstr(via, "branch="))) {
+ /* find the branch ID */
+ via = ast_skip_blanks(tmp + 7);
+
+ /* chop off the branch parameter */
+ *branch = strsep(&via, "; \t\r\n");
+ }
+}
+
+AST_TEST_DEFINE(get_viabranch_test)
+{
+ int res = AST_TEST_PASS;
+ int i = 1;
+ char *sent_by, *branch;
+ struct testdata {
+ char *in;
+ char *expected_branch;
+ char *expected_sent_by;
+ AST_LIST_ENTRY(testdata) list;
+ };
+ struct testdata *testdataptr;
+ static AST_LIST_HEAD_NOLOCK(testdataliststruct, testdata) testdatalist;
+ struct testdata t1 = {
+ .in = "Via: SIP/2.0/UDP host:port;branch=thebranch",
+ .expected_branch = "thebranch",
+ .expected_sent_by = "host:port"
+ };
+ struct testdata t2 = {
+ .in = "SIP/2.0/UDP host:port;branch=thebranch",
+ .expected_branch = "thebranch",
+ .expected_sent_by = "host:port"
+ };
+ struct testdata t3 = {
+ .in = "SIP/2.0/UDP host:port",
+ .expected_branch = "",
+ .expected_sent_by = "host:port"
+ };
+ struct testdata t4 = {
+ .in = "BLAH/BLAH/BLAH host:port ; branch= thebranch ;;;;;;;",
+ .expected_branch = "thebranch",
+ .expected_sent_by = "host:port"
+ };
+ struct testdata t5 = {
+ .in = "v: BLAH/BLAH/BLAH",
+ .expected_branch = "",
+ .expected_sent_by = ""
+ };
+ struct testdata t6 = {
+ .in = "BLAH/BLAH/BLAH host:port;branch=",
+ .expected_branch = "",
+ .expected_sent_by = "host:port"
+ };
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "get_viabranch_test";
+ info->category = "channels/chan_sip/";
+ info->summary = "Tests getting sent-by and branch parameter from via";
+ info->description =
+ "Runs through various test situations in which a sent-by and"
+ " branch parameter must be extracted from a VIA header";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ AST_LIST_HEAD_SET_NOLOCK(&testdatalist, &t1);
+ AST_LIST_INSERT_TAIL(&testdatalist, &t2, list);
+ AST_LIST_INSERT_TAIL(&testdatalist, &t3, list);
+ AST_LIST_INSERT_TAIL(&testdatalist, &t4, list);
+ AST_LIST_INSERT_TAIL(&testdatalist, &t5, list);
+ AST_LIST_INSERT_TAIL(&testdatalist, &t6, list);
+
+
+ AST_LIST_TRAVERSE(&testdatalist, testdataptr, list) {
+ get_viabranch(ast_strdupa(testdataptr->in), &sent_by, &branch);
+ if ((ast_strlen_zero(sent_by) && !ast_strlen_zero(testdataptr->expected_sent_by)) ||
+ (ast_strlen_zero(branch) && !ast_strlen_zero(testdataptr->expected_branch)) ||
+ (!ast_strlen_zero(sent_by) && strcmp(sent_by, testdataptr->expected_sent_by)) ||
+ (!ast_strlen_zero(branch) && strcmp(branch, testdataptr->expected_branch))) {
+ ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\" parsed sent-by = \"%s\" parsed branch = \"%s\"\n",
+ i, testdataptr->in, sent_by, branch);
+ res = AST_TEST_FAIL;
+ }
+ i++;
+ }
+ return res;
+}
+
void sip_request_parser_register_tests(void)
{
AST_TEST_REGISTER(get_calleridname_test);
AST_TEST_REGISTER(parse_contact_header_test);
AST_TEST_REGISTER(sip_parse_options_test);
AST_TEST_REGISTER(sip_uri_cmp_test);
+ AST_TEST_REGISTER(get_viabranch_test);
}
void sip_request_parser_unregister_tests(void)
{
AST_TEST_UNREGISTER(parse_contact_header_test);
AST_TEST_UNREGISTER(sip_parse_options_test);
AST_TEST_UNREGISTER(sip_uri_cmp_test);
+ AST_TEST_UNREGISTER(get_viabranch_test);
}
int sip_reqresp_parser_init(void)