Merged revisions 324484 via svnmerge from
authorTerry Wilson <twilson@digium.com>
Wed, 22 Jun 2011 19:12:24 +0000 (19:12 +0000)
committerTerry Wilson <twilson@digium.com>
Wed, 22 Jun 2011 19:12:24 +0000 (19:12 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.8

........
  r324484 | twilson | 2011-06-22 13:52:04 -0500 (Wed, 22 Jun 2011) | 20 lines

  Stop sending IPv6 link-local scope-ids in SIP messages

  The idea behind the patch listed below was used, but in a more targeted manner.
  There are now address stringification functions for addresses that are meant to
  be sent to a remote party. Link-local scope-ids only make sense on the machine
  from which they originate and so are stripped in the new functions.

  There is also a host sanitization function added to chan_sip which is used
  for when peer and dialog tohost fields or sip_registry hostnames are used to
  craft a SIP message.

  Also added are some basic unit tests for netsock2 address parsing.

  (closes issue ASTERISK-17711)
  Reported by: ch_djalel
  Patches:
        asterisk-1.8.3.2-ipv6_ll_scope.patch uploaded by ch_djalel (license 1251)

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

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

channels/chan_sip.c
include/asterisk/netsock2.h
main/netsock2.c
tests/test_netsock2.c [new file with mode: 0644]

index d9672bf..83d74c4 100644 (file)
@@ -3398,7 +3398,7 @@ static void build_via(struct sip_pvt *p)
        /* z9hG4bK is a magic cookie.  See RFC 3261 section 8.1.1.7 */
        snprintf(p->via, sizeof(p->via), "SIP/2.0/%s %s;branch=z9hG4bK%08x%s",
                 get_transport_pvt(p),
-                ast_sockaddr_stringify(&p->ourip),
+                ast_sockaddr_stringify_remote(&p->ourip),
                 (int) p->branch, rport);
 }
 
@@ -5215,7 +5215,7 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
        dialog->disallowed_methods = peer->disallowed_methods;
        ast_cc_copy_config_params(dialog->cc_params, peer->cc_params);
        if (ast_strlen_zero(dialog->tohost))
-               ast_string_field_set(dialog, tohost, ast_sockaddr_stringify_host(&dialog->sa));
+               ast_string_field_set(dialog, tohost, ast_sockaddr_stringify_host_remote(&dialog->sa));
        if (!ast_strlen_zero(peer->fromdomain)) {
                ast_string_field_set(dialog, fromdomain, peer->fromdomain);
                if (!dialog->initreq.headers) {
@@ -7361,7 +7361,7 @@ static char *generate_uri(struct sip_pvt *pvt, char *buf, size_t size)
         * use the handy random string generation function we already have
         */
        ast_str_append(&uri, 0, "%s", generate_random_string(buf, size));
-       ast_str_append(&uri, 0, "@%s", ast_sockaddr_stringify(&pvt->ourip));
+       ast_str_append(&uri, 0, "@%s", ast_sockaddr_stringify_remote(&pvt->ourip));
        ast_copy_string(buf, ast_str_buffer(uri), size);
        return buf;
 }
@@ -7371,7 +7371,7 @@ static void build_callid_pvt(struct sip_pvt *pvt)
 {
        char buf[33];
 
-       const char *host = S_OR(pvt->fromdomain, ast_sockaddr_stringify(&pvt->ourip));
+       const char *host = S_OR(pvt->fromdomain, ast_sockaddr_stringify_remote(&pvt->ourip));
        
        ast_string_field_build(pvt, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
 
@@ -7382,7 +7382,7 @@ static void build_callid_registry(struct sip_registry *reg, const struct ast_soc
 {
        char buf[33];
 
-       const char *host = S_OR(fromdomain, ast_sockaddr_stringify_host(ourip));
+       const char *host = S_OR(fromdomain, ast_sockaddr_stringify_host_remote(ourip));
 
        ast_string_field_build(reg, callid, "%s@%s", generate_random_string(buf, sizeof(buf)), host);
 }
@@ -9850,13 +9850,13 @@ static int copy_via_headers(struct sip_pvt *p, struct sip_request *req, const st
 
                                /* Add rport to first VIA header if requested */
                                snprintf(new, sizeof(new), "%s;received=%s;rport=%d%s%s",
-                                       leftmost, ast_sockaddr_stringify_addr(&p->recv),
+                                       leftmost, ast_sockaddr_stringify_addr_remote(&p->recv),
                                        ast_sockaddr_port(&p->recv),
                                        others ? "," : "", others ? others : "");
                        } else {
                                /* We should *always* add a received to the topmost via */
                                snprintf(new, sizeof(new), "%s;received=%s%s%s",
-                                       leftmost, ast_sockaddr_stringify_addr(&p->recv),
+                                       leftmost, ast_sockaddr_stringify_addr_remote(&p->recv),
                                        others ? "," : "", others ? others : "");
                        }
                        oh = new;       /* the header to copy */
@@ -10730,7 +10730,7 @@ static int add_rpid(struct sip_request *req, struct sip_pvt *p)
                return 0;
        if (ast_strlen_zero(lid_name))
                lid_name = lid_num;
-       fromdomain = S_OR(p->fromdomain, ast_sockaddr_stringify_host(&p->ourip));
+       fromdomain = S_OR(p->fromdomain, ast_sockaddr_stringify_host_remote(&p->ourip));
 
        lid_num = ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user);
 
@@ -11211,12 +11211,12 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
                 p->sessionid, p->sessionversion,
                 (ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ?
                        "IP6" : "IP4",
-                ast_sockaddr_stringify_addr(&dest));
+                ast_sockaddr_stringify_addr_remote(&dest));
 
        snprintf(connection, sizeof(connection), "c=IN %s %s\r\n",
                 (ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ?
                        "IP6" : "IP4",
-                ast_sockaddr_stringify_addr(&dest));
+                ast_sockaddr_stringify_addr_remote(&dest));
 
        if (add_audio) {
                if (ast_test_flag(&p->flags[1], SIP_PAGE2_CALL_ONHOLD) == SIP_PAGE2_CALL_ONHOLD_ONEDIR) {
@@ -11399,7 +11399,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
                if (!ast_sockaddr_cmp(&udptldest, &dest)) {
                        ast_str_append(&m_modem, 0, "c=IN %s %s\r\n",
                                        (ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ?
-                                       "IP6" : "IP4", ast_sockaddr_stringify_addr(&udptldest));
+                                       "IP6" : "IP4", ast_sockaddr_stringify_addr_remote(&udptldest));
                }
 
                ast_str_append(&a_modem, 0, "a=T38FaxVersion:%d\r\n", p->t38.our_parms.version);
@@ -11754,10 +11754,10 @@ static void build_contact(struct sip_pvt *p)
 
        if (p->socket.type == SIP_TRANSPORT_UDP) {
                ast_string_field_build(p, our_contact, "<sip:%s%s%s>", user,
-                       ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify(&p->ourip));
+                       ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&p->ourip));
        } else {
                ast_string_field_build(p, our_contact, "<sip:%s%s%s;transport=%s>", user,
-                       ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify(&p->ourip),
+                       ast_strlen_zero(user) ? "" : "@", ast_sockaddr_stringify_remote(&p->ourip),
                        get_transport(p->socket.type));
        }
 }
@@ -11798,7 +11798,7 @@ static void initreqprep(struct sip_request *req, struct sip_pvt *p, int sipmetho
 
        snprintf(p->lastmsg, sizeof(p->lastmsg), "Init: %s", sip_methods[sipmethod].text);
 
-       d = S_OR(p->fromdomain, ast_sockaddr_stringify_host(&p->ourip));
+       d = S_OR(p->fromdomain, ast_sockaddr_stringify_host_remote(&p->ourip));
        if (p->owner) {
                if ((ast_party_id_presentation(&p->owner->connected.id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) {
                        l = p->owner->connected.id.number.valid ? p->owner->connected.id.number.str : NULL;
@@ -11962,11 +11962,11 @@ static void add_diversion_header(struct sip_request *req, struct sip_pvt *pvt)
        if (!pvt->owner->redirecting.from.name.valid
                || ast_strlen_zero(diverting_name)) {
                snprintf(header_text, sizeof(header_text), "<sip:%s@%s>;reason=%s", diverting_number,
-                               ast_sockaddr_stringify_host(&pvt->ourip), reason);
+                               ast_sockaddr_stringify_host_remote(&pvt->ourip), reason);
        } else {
                snprintf(header_text, sizeof(header_text), "\"%s\" <sip:%s@%s>;reason=%s",
                                diverting_name, diverting_number,
-                               ast_sockaddr_stringify_host(&pvt->ourip), reason);
+                               ast_sockaddr_stringify_host_remote(&pvt->ourip), reason);
        }
 
        add_header(req, "Diversion", header_text);
@@ -12593,7 +12593,7 @@ static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs,
        struct sip_request req;
        struct ast_str *out = ast_str_alloca(500);
        int ourport = (p->fromdomainport) ? p->fromdomainport : ast_sockaddr_port(&p->ourip);
-       const char *domain = S_OR(p->fromdomain, ast_sockaddr_stringify_host(&p->ourip));
+       const char *domain = S_OR(p->fromdomain, ast_sockaddr_stringify_host_remote(&p->ourip));
        const char *exten = S_OR(vmexten, default_vmexten);
 
        initreqprep(&req, p, SIP_NOTIFY, NULL);
@@ -12923,6 +12923,19 @@ static int sip_reg_timeout(const void *data)
        return 0;
 }
 
+static const char *sip_sanitized_host(const char *host)
+{
+       struct ast_sockaddr addr = { { 0, 0, }, };
+
+       /* peer/sip_pvt->tohost and sip_registry->hostname should never have a port
+        * in them, so we use PARSE_PORT_FORBID here. If this lookup fails, we return
+        * the original host which is most likely a host name and not an IP. */
+       if (!ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID)) {
+               return host;
+       }
+       return ast_sockaddr_stringify_host_remote(&addr);
+}
+
 /*! \brief Transmit register to SIP proxy or UA
  * auth = NULL on the initial registration (from sip_reregister())
  */
@@ -13076,19 +13089,19 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
                ast_debug(1, "Scheduled a registration timeout for %s id  #%d \n", r->hostname, r->timeout);
        }
 
-       snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", r->username, S_OR(r->regdomain,p->tohost), p->tag);
+       snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)), p->tag);
        if (!ast_strlen_zero(p->theirtag)) {
-               snprintf(to, sizeof(to), "<sip:%s@%s>;tag=%s", r->username, S_OR(r->regdomain,p->tohost), p->theirtag);
+               snprintf(to, sizeof(to), "<sip:%s@%s>;tag=%s", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)), p->theirtag);
        } else {
-               snprintf(to, sizeof(to), "<sip:%s@%s>", r->username, S_OR(r->regdomain,p->tohost));
+               snprintf(to, sizeof(to), "<sip:%s@%s>", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)));
        }
 
        /* Fromdomain is what we are registering to, regardless of actual
           host name from SRV */
        if (portno && portno != STANDARD_SIP_PORT) {
-               snprintf(addr, sizeof(addr), "sip:%s:%d", S_OR(p->fromdomain,S_OR(r->regdomain,r->hostname)), portno);
+               snprintf(addr, sizeof(addr), "sip:%s:%d", S_OR(p->fromdomain,S_OR(r->regdomain, sip_sanitized_host(r->hostname))), portno);
        } else {
-               snprintf(addr, sizeof(addr), "sip:%s", S_OR(p->fromdomain,S_OR(r->regdomain,r->hostname)));
+               snprintf(addr, sizeof(addr), "sip:%s", S_OR(p->fromdomain,S_OR(r->regdomain, sip_sanitized_host(r->hostname))));
        }
 
        ast_string_field_set(p, uri, addr);
@@ -19068,7 +19081,7 @@ static int build_reply_digest(struct sip_pvt *p, int method, char* digest, int d
        else if (!ast_strlen_zero(p->uri))
                ast_copy_string(uri, p->uri, sizeof(uri));
        else
-               snprintf(uri, sizeof(uri), "sip:%s@%s", p->username, ast_sockaddr_stringify_host(&p->sa));
+               snprintf(uri, sizeof(uri), "sip:%s@%s", p->username, ast_sockaddr_stringify_host_remote(&p->sa));
 
        snprintf(cnonce, sizeof(cnonce), "%08lx", ast_random());
 
@@ -26072,7 +26085,7 @@ static int sip_poke_peer(struct sip_peer *peer, int force)
        if (!ast_strlen_zero(peer->tohost))
                ast_string_field_set(p, tohost, peer->tohost);
        else
-               ast_string_field_set(p, tohost, ast_sockaddr_stringify_host(&peer->addr));
+               ast_string_field_set(p, tohost, ast_sockaddr_stringify_host_remote(&peer->addr));
 
        /* Recalculate our side, and recalculate Call ID */
        ast_sip_ouraddrfor(&p->sa, &p->ourip, p);
index c5c08cf..afb1ea9 100644 (file)
@@ -152,8 +152,13 @@ int ast_sockaddr_cmp_addr(const struct ast_sockaddr *a, const struct ast_sockadd
 #define AST_SOCKADDR_STR_ADDR          (1 << 0)
 #define AST_SOCKADDR_STR_PORT          (1 << 1)
 #define AST_SOCKADDR_STR_BRACKETS      (1 << 2)
-#define AST_SOCKADDR_STR_HOST          AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_BRACKETS
-#define AST_SOCKADDR_STR_DEFAULT       AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_PORT
+#define AST_SOCKADDR_STR_REMOTE                (1 << 3)
+#define AST_SOCKADDR_STR_HOST          (AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_BRACKETS)
+#define AST_SOCKADDR_STR_DEFAULT       (AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_PORT)
+#define AST_SOCKADDR_STR_ADDR_REMOTE     (AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_REMOTE)
+#define AST_SOCKADDR_STR_HOST_REMOTE     (AST_SOCKADDR_STR_HOST | AST_SOCKADDR_STR_REMOTE)
+#define AST_SOCKADDR_STR_DEFAULT_REMOTE  (AST_SOCKADDR_STR_DEFAULT | AST_SOCKADDR_STR_REMOTE)
+#define AST_SOCKADDR_STR_FORMAT_MASK     (AST_SOCKADDR_STR_ADDR | AST_SOCKADDR_STR_PORT | AST_SOCKADDR_STR_BRACKETS)
 
 /*!
  * \since 1.8
@@ -203,6 +208,23 @@ static inline char *ast_sockaddr_stringify(const struct ast_sockaddr *addr)
  * \since 1.8
  *
  * \brief
+ * Wrapper around ast_sockaddr_stringify_fmt() with default format
+ *
+ * \note This address will be suitable for passing to a remote machine via the
+ * application layer. For example, the scope-id on a link-local IPv6 address
+ * will be stripped.
+ *
+ * \return same as ast_sockaddr_stringify_fmt()
+ */
+static inline char *ast_sockaddr_stringify_remote(const struct ast_sockaddr *addr)
+{
+       return ast_sockaddr_stringify_fmt(addr, AST_SOCKADDR_STR_DEFAULT_REMOTE);
+}
+
+/*!
+ * \since 1.8
+ *
+ * \brief
  * Wrapper around ast_sockaddr_stringify_fmt() to return an address only
  *
  * \return same as ast_sockaddr_stringify_fmt()
@@ -216,6 +238,23 @@ static inline char *ast_sockaddr_stringify_addr(const struct ast_sockaddr *addr)
  * \since 1.8
  *
  * \brief
+ * Wrapper around ast_sockaddr_stringify_fmt() to return an address only
+ *
+ * \note This address will be suitable for passing to a remote machine via the
+ * application layer. For example, the scope-id on a link-local IPv6 address
+ * will be stripped.
+ *
+ * \return same as ast_sockaddr_stringify_fmt()
+ */
+static inline char *ast_sockaddr_stringify_addr_remote(const struct ast_sockaddr *addr)
+{
+       return ast_sockaddr_stringify_fmt(addr, AST_SOCKADDR_STR_ADDR_REMOTE);
+}
+
+/*!
+ * \since 1.8
+ *
+ * \brief
  * Wrapper around ast_sockaddr_stringify_fmt() to return an address only,
  * suitable for a URL (with brackets for IPv6).
  *
@@ -230,6 +269,24 @@ static inline char *ast_sockaddr_stringify_host(const struct ast_sockaddr *addr)
  * \since 1.8
  *
  * \brief
+ * Wrapper around ast_sockaddr_stringify_fmt() to return an address only,
+ * suitable for a URL (with brackets for IPv6).
+ *
+ * \note This address will be suitable for passing to a remote machine via the
+ * application layer. For example, the scope-id on a link-local IPv6 address
+ * will be stripped.
+ *
+ * \return same as ast_sockaddr_stringify_fmt()
+ */
+static inline char *ast_sockaddr_stringify_host_remote(const struct ast_sockaddr *addr)
+{
+       return ast_sockaddr_stringify_fmt(addr, AST_SOCKADDR_STR_HOST_REMOTE);
+}
+
+/*!
+ * \since 1.8
+ *
+ * \brief
  * Wrapper around ast_sockaddr_stringify_fmt() to return a port only
  *
  * \return same as ast_sockaddr_stringify_fmt()
@@ -413,6 +470,20 @@ int ast_sockaddr_is_ipv4_multicast(const struct ast_sockaddr *addr);
  * \since 1.8
  *
  * \brief
+ * Determine if this is a link-local IPv6 address
+ *
+ * \warning You should rarely need this function. Only use if you know what
+ * you're doing.
+ *
+ * \retval 1 This is a link-local IPv6 address.
+ * \retval 0 This is link-local IPv6 address.
+ */
+int ast_sockaddr_is_ipv6_link_local(const struct ast_sockaddr *addr);
+
+/*!
+ * \since 1.8
+ *
+ * \brief
  * Determine if this is an IPv6 address
  *
  * \warning You should rarely need this function. Only use if you know what
index d6561fb..4ac1d0f 100644 (file)
@@ -95,7 +95,14 @@ char *ast_sockaddr_stringify_fmt(const struct ast_sockaddr *sa, int format)
                return "";
        }
 
-       switch (format)  {
+       if ((format & AST_SOCKADDR_STR_REMOTE) == AST_SOCKADDR_STR_REMOTE) {
+               char *p;
+               if (ast_sockaddr_is_ipv6_link_local(sa) && (p = strchr(host, '%'))) {
+                       *p = '\0';
+               }
+       }
+
+       switch ((format & AST_SOCKADDR_STR_FORMAT_MASK))  {
        case AST_SOCKADDR_STR_DEFAULT:
                ast_str_set(&str, 0, sa_tmp->ss.ss_family == AF_INET6 ?
                                "[%s]:%s" : "%s:%s", host, port);
@@ -397,6 +404,12 @@ int ast_sockaddr_is_ipv4_multicast(const struct ast_sockaddr *addr)
        return ((ast_sockaddr_ipv4(addr) & 0xf0000000) == 0xe0000000);
 }
 
+int ast_sockaddr_is_ipv6_link_local(const struct ast_sockaddr *addr)
+{
+       const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr->ss;
+       return ast_sockaddr_is_ipv6(addr) && IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr);
+}
+
 int ast_sockaddr_is_ipv6(const struct ast_sockaddr *addr)
 {
        return addr->ss.ss_family == AF_INET6 &&
diff --git a/tests/test_netsock2.c b/tests/test_netsock2.c
new file mode 100644 (file)
index 0000000..bdc92fb
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * Terry Wilson <twilson@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Netsock2 Unit Tests
+ *
+ * \author Terry Wilson <twilson@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+       <depend>TEST_FRAMEWORK</depend>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "")
+
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/netsock2.h"
+#include "asterisk/logger.h"
+struct parse_test {
+       const char *address;
+       int expected_result;
+};
+
+AST_TEST_DEFINE(parsing)
+{
+       int res = AST_TEST_PASS;
+       struct parse_test test_vals[] = {
+               { "192.168.1.0", 1 },
+               { "10.255.255.254", 1 },
+               { "172.18.5.4", 1 },
+               { "8.8.4.4", 1 },
+               { "0.0.0.0", 1 },
+               { "127.0.0.1", 1 },
+               { "1.256.3.4", 0 },
+               { "256.0.0.1", 0 },
+               { "1.2.3.4:5060", 1 },
+               { "1.2.3.4:99999", 0},
+               { "::ffff:5.6.7.8", 1 },
+               { "fdf8:f53b:82e4::53", 1 },
+               { "fe80::200:5aee:feaa:20a2", 1 },
+               { "2001::1", 1 },
+               { "2001:0000:4136:e378:8000:63bf:3fff:fdd2", 1 },
+               { "2001:0002:6c::430", 1 },
+               { "2001:10:240:ab::a", 1 },
+               { "2002:cb0a:3cdd:1::1", 1 },
+               { "2001:db8:8:4::2", 1 }, /* Documentation only, should never be used */
+               { "ff01:0:0:0:0:0:0:2", 1 }, /* Multicast */
+               { "[fdf8:f53b:82e4::53]", 1 },
+               { "[fe80::200:5aee:feaa:20a2]", 1 },
+               { "[2001::1]", 1 },
+               { "[2001:0000:4136:e378:8000:63bf:3fff:fdd2]:5060", 1 },
+               { "2001:0000:4136:e378:8000:63bf:3fff:fdd2:5060", 0 }, /* port, but no brackets */
+               { "[2001:0000:4136:e378:8000:63bf:3fff:fdd2]:90000", 0 },
+               { "[fe80::200:5aee:feaa:20a2%eth0]", 1 }, /* link-local with scope id */
+               { "[fe80::200::abcd", 0 }, /* multiple zero expansions */
+       };
+
+       size_t x;
+       struct ast_sockaddr addr = { { 0, 0, } };
+       int parse_result;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "parsing";
+               info->category = "/main/netsock2/";
+               info->summary = "netsock2 parsing unit test";
+               info->description =
+                       "Test parsing of IPv4 and IPv6 network addresses";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       for (x = 0; x < ARRAY_LEN(test_vals); x++) {
+               if ((parse_result = ast_sockaddr_parse(&addr, test_vals[x].address, 0)) != test_vals[x].expected_result) {
+                       ast_test_status_update(test, "On '%s' expected %d but got %d\n", test_vals[x].address, test_vals[x].expected_result, parse_result);
+                       res = AST_TEST_FAIL;
+               }
+               if (parse_result) {
+                       struct ast_sockaddr tmp_addr = { { 0, 0, } };
+                       const char *tmp;
+
+                       tmp = ast_sockaddr_stringify(&addr);
+                       ast_sockaddr_parse(&tmp_addr, tmp, 0);
+                       if (ast_sockaddr_cmp_addr(&addr, &tmp_addr)) {
+                               ast_test_status_update(test, "Re-parsed stringification did not match: '%s' vs '%s'\n", ast_sockaddr_stringify(&addr), ast_sockaddr_stringify(&tmp_addr));
+                               res = AST_TEST_FAIL;
+                       }
+               }
+       }
+
+       return res;
+}
+
+static int unload_module(void)
+{
+       AST_TEST_UNREGISTER(parsing);
+       return 0;
+}
+
+static int load_module(void)
+{
+       AST_TEST_REGISTER(parsing);
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Netsock2 test module");