Merged revisions 279568 via svnmerge from
authorDavid Vossel <dvossel@digium.com>
Mon, 26 Jul 2010 20:00:52 +0000 (20:00 +0000)
committerDavid Vossel <dvossel@digium.com>
Mon, 26 Jul 2010 20:00:52 +0000 (20:00 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.8

........
  r279568 | dvossel | 2010-07-26 14:59:03 -0500 (Mon, 26 Jul 2010) | 21 lines

  transaction matching using top most Via header

  This patch modifies the way chan_sip.c does transaction to dialog
  matching.  Asterisk now stores information in the top most Via header
  of the initial incoming request and compares that against other Requests
  that have the same call-id.  This results in Asterisk being able to
  detect a forked call in which it has received multiple legs of the
  fork.  I completely stripped out the previous matching code and made
  the comparisons a little more explicit and easier to understand.  My
  comments in the code should offer all the details involving this patch.

  This patch also fixes a bug with the usage of the OBJ-MULTIPLE flag to
  find multiple dialogs with the same call-id.  Since the callback
  function was returning (CMP_MATCH | CMP_STOP) only the first item
  found was being returned.  I fixed this by making a new callback
  function for finding multiple dialogs that only returns (CMP_MATCH)
  on a match allowing for multiple items to be returned.

  Review: https://reviewboard.asterisk.org/r/776/
........

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@279569 65c4cc65-6c06-0410-ace0-fbb531ad65f3

channels/chan_sip.c
channels/sip/include/reqresp_parser.h
channels/sip/include/sip.h
channels/sip/reqresp_parser.c

index 7a7a38b..1829bc3 100644 (file)
@@ -1318,6 +1318,7 @@ static void *do_monitor(void *data);
 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);
@@ -7010,7 +7011,26 @@ 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 (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);
@@ -7114,6 +7134,156 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
        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.
@@ -7122,7 +7292,6 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
 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");
@@ -7130,11 +7299,12 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
        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) {
@@ -7155,8 +7325,6 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                        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 */
@@ -7181,46 +7349,60 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                        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) {
@@ -27670,17 +27852,25 @@ static int dialog_hash_cb(const void *obj, const int flags)
 }
 
 /*!
+ * \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"),
index 2957151..8c8c59a 100644 (file)
@@ -165,4 +165,10 @@ int sip_reqresp_parser_init(void);
  */
 void sip_reqresp_parser_exit(void);
 
+/*!
+ * \brief Parse the VIA header into it's parts.
+ *
+ * \note This will modify the string
+ */
+void get_viabranch(char *via, char **sent_by, char **branch);
 #endif
index 6da0c44..0e207ca 100644 (file)
@@ -910,6 +910,8 @@ struct sip_pvt {
        int method;                             /*!< SIP method that opened this dialog */
        AST_DECLARE_STRING_FIELDS(
                AST_STRING_FIELD(callid);       /*!< Global CallID */
+               AST_STRING_FIELD(initviabranch); /*!< The branch ID from the topmost Via header in the initial request */
+               AST_STRING_FIELD(initviasentby); /*!< The sent-by from the topmost Via header in the initial request */
                AST_STRING_FIELD(randdata);     /*!< Random data */
                AST_STRING_FIELD(accountcode);  /*!< Account code */
                AST_STRING_FIELD(realm);        /*!< Authorization realm */
@@ -958,6 +960,7 @@ struct sip_pvt {
        struct sip_socket socket;               /*!< The socket used for this dialog */
        unsigned int ocseq;                     /*!< Current outgoing seqno */
        unsigned int icseq;                     /*!< Current incoming seqno */
+       unsigned int init_icseq;                /*!< Initial incoming seqno from first request */
        ast_group_t callgroup;                  /*!< Call group */
        ast_group_t pickupgroup;                /*!< Pickup group */
        int lastinvite;                         /*!< Last Cseq of invite */
index ac9aeab..b219bf0 100644 (file)
@@ -2243,6 +2243,151 @@ AST_TEST_DEFINE(sip_uri_cmp_test)
        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);
@@ -2254,6 +2399,7 @@ void sip_request_parser_register_tests(void)
        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)
 {
@@ -2266,6 +2412,7 @@ 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)