Merged revisions 304245 via svnmerge from
authorMatthew Nicholson <mnicholson@digium.com>
Wed, 26 Jan 2011 20:44:47 +0000 (20:44 +0000)
committerMatthew Nicholson <mnicholson@digium.com>
Wed, 26 Jan 2011 20:44:47 +0000 (20:44 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.8

................
  r304245 | mnicholson | 2011-01-26 14:43:27 -0600 (Wed, 26 Jan 2011) | 20 lines

  Merged revisions 304244 via svnmerge from
  https://origsvn.digium.com/svn/asterisk/branches/1.6.2

  ................
    r304244 | mnicholson | 2011-01-26 14:42:16 -0600 (Wed, 26 Jan 2011) | 13 lines

    Merged revisions 304241 via svnmerge from
    https://origsvn.digium.com/svn/asterisk/branches/1.4

    ........
      r304241 | mnicholson | 2011-01-26 14:38:22 -0600 (Wed, 26 Jan 2011) | 6 lines

      This patch modifies chan_sip to route responses to the address the request came from.  It also modifies chan_sip to respect the maddr parameter in the Via header.

      ABE-2664

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

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@304246 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
include/asterisk/netsock2.h
main/netsock2.c

index 183b2da..d0c0fff 100644 (file)
@@ -7153,17 +7153,20 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
        /* 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;
+               struct sip_via *via;
                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);
+               via = parse_via(get_header(req, "Via"));
+               if (via) {
+                       /* 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(via->branch) && !strncasecmp(via->branch, "z9hG4bK", 7)) {
+                               ast_string_field_set(p, initviabranch, via->branch);
+                               ast_string_field_set(p, initviasentby, via->sent_by);
+                       }
+                       free_via(via);
                }
 
                /* Store initial incoming cseq. An error in sscanf here is ignored.  There is no approperiate
@@ -7275,6 +7278,38 @@ struct sip_pvt *sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
        return p;
 }
 
+static int process_via(struct sip_pvt *p, const struct sip_request *req)
+{
+       struct sip_via *via = parse_via(get_header(req, "Via"));
+
+       if (!via) {
+               ast_log(LOG_ERROR, "error processing via header\n");
+               return -1;
+       }
+
+       if (via->maddr) {
+               if (ast_sockaddr_resolve_first(&p->sa, via->maddr, PARSE_PORT_FORBID)) {
+                       ast_log(LOG_WARNING, "Can't find address for maddr '%s'\n", via->maddr);
+                       ast_log(LOG_ERROR, "error processing via header\n");
+                       free_via(via);
+                       return -1;
+               }
+
+               if (via->port) {
+                       ast_sockaddr_set_port(&p->sa, via->port);
+               } else {
+                       ast_sockaddr_set_port(&p->sa, STANDARD_SIP_PORT);
+               }
+
+               if (ast_sockaddr_is_ipv4_multicast(&p->sa)) {
+                       setsockopt(sipsock, IPPROTO_IP, IP_MULTICAST_TTL, &via->ttl, sizeof(via->ttl));
+               }
+       }
+
+       free_via(via);
+       return 0;
+}
+
 /* \brief arguments used for Request/Response to matching */
 struct match_req_args {
        int method;
@@ -7572,6 +7607,7 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                        dialog_find_multiple,
                        &tmp_dialog,
                        "pedantic ao2_find in dialogs");
+               struct sip_via *via = NULL;
 
                args.method = req->method;
                args.callid = NULL; /* we already matched this. */
@@ -7580,7 +7616,11 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                args.seqno = seqno;
                /* 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);
+               via = parse_via(get_header(req, "Via"));
+               if (via) {
+                       args.viasentby = via->sent_by;
+                       args.viabranch = via->branch;
+               }
                /* 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"))) {
@@ -7604,6 +7644,7 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                                sip_pvt_lock(sip_pvt_ptr);
                                ao2_iterator_destroy(iterator);
                                dialog_unref(fork_pvt, "unref fork_pvt");
+                               free_via(via);
                                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.
@@ -7612,6 +7653,7 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                                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");
+                               free_via(via);
                                return NULL;
                        case SIP_REQ_FORKED:
                                dialog_unref(fork_pvt, "throwing way pvt to fork off of.");
@@ -7633,10 +7675,13 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a
                        if (fork_pvt->method == SIP_INVITE) {
                                forked_invite_init(req, args.totag, fork_pvt, addr);
                                dialog_unref(fork_pvt, "throwing way old forked pvt");
+                               free_via(via);
                                return NULL;
                        }
                        fork_pvt = dialog_unref(fork_pvt, "throwing way pvt to fork off of");
                }
+
+               free_via(via);
        } /* end of pedantic mode Request/Reponse to Dialog matching */
 
        /* See if the method is capable of creating a dialog */
@@ -9686,6 +9731,15 @@ static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg
                ast_string_field_set(p, url, NULL);
        }
 
+       /* default to routing the response to the address where the request
+        * came from.  Since we don't have a transport layer, we do this here.
+        */
+       p->sa = p->recv;
+
+       if (process_via(p, req)) {
+               ast_log(LOG_WARNING, "error processing via header, will send response to originating address\n");
+       }
+
        return 0;
 }
 
index 8c8c59a..bb18314 100644 (file)
@@ -166,9 +166,43 @@ 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
+ * \brief Parse a Via header
+ *
+ * This function parses the Via header and processes it according to section
+ * 18.2 of RFC 3261 and RFC 3581. Since we don't have a transport layer, we
+ * only care about the maddr and ttl parms.  The received and rport params are
+ * not parsed.
+ *
+ * \note This function fails to parse some odd combinations of SWS in parameter
+ * lists.
+ *
+ * \code
+ * VIA syntax. RFC 3261 section 25.1
+ * Via               =  ( "Via" / "v" ) HCOLON via-parm *(COMMA via-parm)
+ * via-parm          =  sent-protocol LWS sent-by *( SEMI via-params )
+ * via-params        =  via-ttl / via-maddr
+ *                   / via-received / via-branch
+ *                   / via-extension
+ * via-ttl           =  "ttl" EQUAL ttl
+ * via-maddr         =  "maddr" EQUAL host
+ * via-received      =  "received" EQUAL (IPv4address / IPv6address)
+ * via-branch        =  "branch" EQUAL token
+ * via-extension     =  generic-param
+ * sent-protocol     =  protocol-name SLASH protocol-version
+ *                   SLASH transport
+ * protocol-name     =  "SIP" / token
+ * protocol-version  =  token
+ * transport         =  "UDP" / "TCP" / "TLS" / "SCTP"
+ *                   / other-transport
+ * sent-by           =  host [ COLON port ]
+ * ttl               =  1*3DIGIT ; 0 to 255
+ * \endcode
+ */
+struct sip_via *parse_via(const char *header);
+
+/*
+ * \brief Free parsed Via data.
  */
-void get_viabranch(char *via, char **sent_by, char **branch);
+void free_via(struct sip_via *v);
+
 #endif
index 385d966..57c155e 100644 (file)
@@ -789,6 +789,17 @@ struct sip_route {
        char hop[0];
 };
 
+/*! \brief Structure to store Via information */
+struct sip_via {
+       char *via;
+       const char *protocol;
+       const char *sent_by;
+       const char *branch;
+       const char *maddr;
+       unsigned int port;
+       unsigned char ttl;
+};
+
 /*! \brief Domain data structure.
        \note In the future, we will connect this to a configuration tree specific
        for this domain
index aaf1f56..ecea91f 100644 (file)
@@ -2243,123 +2243,159 @@ AST_TEST_DEFINE(sip_uri_cmp_test)
        return test_res;
 }
 
-void get_viabranch(char *via, char **sent_by, char **branch)
+void free_via(struct sip_via *v)
 {
-       char *tmp;
-
-       if (sent_by) {
-               *sent_by = NULL;
-       }
-       if (branch) {
-               *branch = NULL;
-       }
-       if (ast_strlen_zero(via)) {
+       if (!v) {
                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 (v->via) {
+               ast_free(v->via);
        }
-       if (ast_strlen_zero(via)) {
-               return;
+
+       ast_free(v);
+}
+
+struct sip_via *parse_via(const char *header)
+{
+       struct sip_via *v = ast_calloc(1, sizeof(*v));
+       char *via, *parm;
+
+       if (!v) {
+               return NULL;
        }
 
-       /* chop off sent-protocol */
-       via = ast_skip_blanks(via);
-       strsep(&via, " \t\r\n");
+       v->via = ast_strdup(header);
+       v->ttl = 1;
+
+       via = v->via;
+
        if (ast_strlen_zero(via)) {
-               return;
+               ast_log(LOG_ERROR, "received request without a Via header\n");
+               free_via(v);
+               return NULL;
        }
 
-       /* chop off sent-by */
-       via = ast_skip_blanks(via);
-       *sent_by = strsep(&via, "; \t\r\n");
-       if (ast_strlen_zero(via)) {
-               return;
+       /* seperate the first via-parm */
+       via = strsep(&via, ",");
+
+       /* chop off sent-protocol */
+       v->protocol = strsep(&via, " \t\r\n");
+       if (ast_strlen_zero(v->protocol)) {
+               ast_log(LOG_ERROR, "missing sent-protocol in Via header\n");
+               free_via(v);
+               return NULL;
        }
+       v->protocol = ast_skip_blanks(v->protocol);
 
-       /* 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);
+       if (via) {
+               via = ast_skip_blanks(via);
+       }
 
-               /* chop off the branch parameter */
-               *branch = strsep(&via, "; \t\r\n");
+       /* chop off sent-by */
+       v->sent_by = strsep(&via, "; \t\r\n");
+       if (ast_strlen_zero(v->sent_by)) {
+               ast_log(LOG_ERROR, "missing sent-by in Via header\n");
+               free_via(v);
+               return NULL;
+       }
+       v->sent_by = ast_skip_blanks(v->sent_by);
+
+       /* store the port */
+       if ((parm = strchr(v->sent_by, ':'))) {
+               char *endptr;
+
+               v->port = strtol(++parm, &endptr, 10);
+       }
+
+       /* evaluate any via-parms */
+       while ((parm = strsep(&via, "; \t\r\n"))) {
+               char *c;
+               if ((c = strstr(parm, "maddr="))) {
+                       v->maddr = ast_skip_blanks(c + sizeof("maddr=") - 1);
+               } else if ((c = strstr(parm, "branch="))) {
+                       v->branch = ast_skip_blanks(c + sizeof("branch=") - 1);
+               } else if ((c = strstr(parm, "ttl="))) {
+                       char *endptr;
+                       c = ast_skip_blanks(c + sizeof("ttl=") - 1);
+                       v->ttl = strtol(c, &endptr, 10);
+
+                       /* make sure we got a valid ttl value */
+                       if (c == endptr) {
+                               v->ttl = 1;
+                       }
+               }
        }
+
+       return v;
 }
 
-AST_TEST_DEFINE(get_viabranch_test)
+AST_TEST_DEFINE(parse_via_test)
 {
        int res = AST_TEST_PASS;
        int i = 1;
-       char *sent_by, *branch;
+       struct sip_via *via;
        struct testdata {
                char *in;
+               char *expected_protocol;
                char *expected_branch;
                char *expected_sent_by;
+               char *expected_maddr;
+               unsigned int expected_port;
+               unsigned char expected_ttl;
+               int expected_null;
                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_protocol = "SIP/2.0/UDP",
+               .expected_sent_by = "host:port",
                .expected_branch = "thebranch",
-               .expected_sent_by = "host:port"
        };
-       struct testdata t3 = {
+       struct testdata t2 = {
                .in = "SIP/2.0/UDP host:port",
+               .expected_protocol = "SIP/2.0/UDP",
+               .expected_sent_by = "host:port",
                .expected_branch = "",
-               .expected_sent_by = "host:port"
+       };
+       struct testdata t3 = {
+               .in = "SIP/2.0/UDP",
+               .expected_null = 1,
        };
        struct testdata t4 = {
-               .in = "BLAH/BLAH/BLAH            host:port        ;    branch=        thebranch ;;;;;;;",
-               .expected_branch = "thebranch",
-               .expected_sent_by = "host:port"
+               .in = "BLAH/BLAH/BLAH host:port;branch=",
+               .expected_protocol = "BLAH/BLAH/BLAH",
+               .expected_sent_by = "host:port",
+               .expected_branch = "",
        };
        struct testdata t5 = {
-               .in = "v: BLAH/BLAH/BLAH",
-               .expected_branch = "",
-               .expected_sent_by = ""
+               .in = "SIP/2.0/UDP host:5060;branch=thebranch;maddr=224.0.0.1;ttl=1",
+               .expected_protocol = "SIP/2.0/UDP",
+               .expected_sent_by = "host:5060",
+               .expected_port = 5060,
+               .expected_branch = "thebranch",
+               .expected_maddr = "224.0.0.1",
+               .expected_ttl = 1,
        };
        struct testdata t6 = {
-               .in = "BLAH/BLAH/BLAH host:port;branch=",
-               .expected_branch = "",
-               .expected_sent_by = "host:port"
+               .in = "SIP/2.0/UDP      host:5060;\n   branch=thebranch;\r\n  maddr=224.0.0.1;   ttl=1",
+               .expected_protocol = "SIP/2.0/UDP",
+               .expected_sent_by = "host:5060",
+               .expected_port = 5060,
+               .expected_branch = "thebranch",
+               .expected_maddr = "224.0.0.1",
+               .expected_ttl = 1,
        };
        switch (cmd) {
        case TEST_INIT:
-               info->name = "get_viabranch_test";
+               info->name = "parse_via_test";
                info->category = "/channels/chan_sip/";
-               info->summary = "Tests getting sent-by and branch parameter from via";
+               info->summary = "Tests parsing the Via header";
                info->description =
-                               "Runs through various test situations in which a sent-by and"
-                               " branch parameter must be extracted from a VIA header";
+                               "Runs through various test situations in which various "
+                               " parameters parameter must be extracted from a VIA header";
                return AST_TEST_NOT_RUN;
        case TEST_EXECUTE:
                break;
@@ -2374,15 +2410,91 @@ AST_TEST_DEFINE(get_viabranch_test)
 
 
        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);
+               via = parse_via(testdataptr->in);
+               if (!via) {
+                       if (!testdataptr->expected_null) {
+                               ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                                       "failed to parse header\n",
+                               i, testdataptr->in);
+                               res = AST_TEST_FAIL;
+                       }
+                       i++;
+                       continue;
+               }
+
+               if (testdataptr->expected_null) {
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "successfully parased invalid via header\n",
+                       i, testdataptr->in);
+                       res = AST_TEST_FAIL;
+                       free_via(via);
+                       i++;
+                       continue;
+               }
+
+               if ((ast_strlen_zero(via->protocol) && !ast_strlen_zero(testdataptr->expected_protocol))
+                       || (!ast_strlen_zero(via->protocol) && strcmp(via->protocol, testdataptr->expected_protocol))) {
+
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed protocol = \"%s\"\n"
+                               "expected = \"%s\"\n"
+                               "failed to parse protocol\n",
+                       i, testdataptr->in, via->protocol, testdataptr->expected_protocol);
+                       res = AST_TEST_FAIL;
+               }
+
+               if ((ast_strlen_zero(via->sent_by) && !ast_strlen_zero(testdataptr->expected_sent_by))
+                       || (!ast_strlen_zero(via->sent_by) && strcmp(via->sent_by, testdataptr->expected_sent_by))) {
+
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed sent_by = \"%s\"\n"
+                               "expected = \"%s\"\n"
+                               "failed to parse sent-by\n",
+                       i, testdataptr->in, via->sent_by, testdataptr->expected_sent_by);
+                       res = AST_TEST_FAIL;
+               }
+
+               if (testdataptr->expected_port && testdataptr->expected_port != via->port) {
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed port = \"%d\"\n"
+                               "expected = \"%d\"\n"
+                               "failed to parse port\n",
+                       i, testdataptr->in, via->port, testdataptr->expected_port);
+                       res = AST_TEST_FAIL;
+               }
+
+               if ((ast_strlen_zero(via->branch) && !ast_strlen_zero(testdataptr->expected_branch))
+                       || (!ast_strlen_zero(via->branch) && strcmp(via->branch, testdataptr->expected_branch))) {
+
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed branch = \"%s\"\n"
+                               "expected = \"%s\"\n"
+                               "failed to parse branch\n",
+                       i, testdataptr->in, via->branch, testdataptr->expected_branch);
                        res = AST_TEST_FAIL;
                }
+
+               if ((ast_strlen_zero(via->maddr) && !ast_strlen_zero(testdataptr->expected_maddr))
+                       || (!ast_strlen_zero(via->maddr) && strcmp(via->maddr, testdataptr->expected_maddr))) {
+
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed maddr = \"%s\"\n"
+                               "expected = \"%s\"\n"
+                               "failed to parse maddr\n",
+                       i, testdataptr->in, via->maddr, testdataptr->expected_maddr);
+                       res = AST_TEST_FAIL;
+               }
+
+               if (testdataptr->expected_ttl && testdataptr->expected_ttl != via->ttl) {
+                       ast_test_status_update(test, "TEST#%d FAILED: VIA = \"%s\"\n"
+                               "parsed ttl = \"%d\"\n"
+                               "expected = \"%d\"\n"
+                               "failed to parse ttl\n",
+                       i, testdataptr->in, via->ttl, testdataptr->expected_ttl);
+                       res = AST_TEST_FAIL;
+               }
+
+               free_via(via);
                i++;
        }
        return res;
@@ -2399,7 +2511,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);
+       AST_TEST_REGISTER(parse_via_test);
 }
 void sip_request_parser_unregister_tests(void)
 {
@@ -2412,7 +2524,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);
+       AST_TEST_UNREGISTER(parse_via_test);
 }
 
 int sip_reqresp_parser_init(void)
index 73c57c5..888861c 100644 (file)
@@ -386,6 +386,20 @@ int ast_sockaddr_is_ipv4(const struct ast_sockaddr *addr);
 int ast_sockaddr_is_ipv4_mapped(const struct ast_sockaddr *addr);
 
 /*!
+ * \since 1.10
+ *
+ * \brief
+ * Determine if an IPv4 address is a multicast address
+ *
+ * \parm addr the address to check
+ *
+ * This function checks if an address is in the 224.0.0.0/4 network block.
+ *
+ * \return non-zero if this is a multicast address
+ */
+int ast_sockaddr_is_ipv4_multicast(const struct ast_sockaddr *addr);
+
+/*!
  * \since 1.8
  *
  * \brief
index e575bcf..6f55b3b 100644 (file)
@@ -381,6 +381,11 @@ int ast_sockaddr_is_ipv4_mapped(const struct ast_sockaddr *addr)
        return addr->len && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr);
 }
 
+int ast_sockaddr_is_ipv4_multicast(const struct ast_sockaddr *addr)
+{
+       return ((ast_sockaddr_ipv4(addr) & 0xf0000000) == 0xe0000000);
+}
+
 int ast_sockaddr_is_ipv6(const struct ast_sockaddr *addr)
 {
        return addr->ss.ss_family == AF_INET6 &&