summaryrefslogtreecommitdiffhomepage
path: root/src/nxt_sockaddr.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/nxt_sockaddr.c973
1 files changed, 973 insertions, 0 deletions
diff --git a/src/nxt_sockaddr.c b/src/nxt_sockaddr.c
new file mode 100644
index 00000000..b2515ef9
--- /dev/null
+++ b/src/nxt_sockaddr.c
@@ -0,0 +1,973 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_main.h>
+
+
+#if (NXT_INET6)
+static u_char *nxt_inet6_ntop(u_char *addr, u_char *buf, u_char *end);
+#endif
+
+static nxt_int_t nxt_job_sockaddr_unix_parse(nxt_job_sockaddr_parse_t *jbs);
+static nxt_int_t nxt_job_sockaddr_inet6_parse(nxt_job_sockaddr_parse_t *jbs);
+static nxt_int_t nxt_job_sockaddr_inet_parse(nxt_job_sockaddr_parse_t *jbs);
+
+
+nxt_sockaddr_t *
+nxt_sockaddr_alloc(nxt_mem_pool_t *mp, socklen_t len)
+{
+ nxt_sockaddr_t *sa;
+
+ /*
+ * The current struct sockaddr's define 32-bit fields at maximum
+ * and may define 64-bit AF_INET6 fields in the future. Alignment
+ * of memory allocated by nxt_mem_zalloc() is enough for these fields.
+ * If 128-bit alignment will be required then nxt_mem_malloc() and
+ * nxt_memzero() should be used instead.
+ */
+ sa = nxt_mem_zalloc(mp, offsetof(nxt_sockaddr_t, u) + len);
+
+ if (nxt_fast_path(sa != NULL)) {
+ nxt_socklen_set(sa, len);
+ }
+
+ return sa;
+}
+
+
+nxt_sockaddr_t *
+nxt_sockaddr_create(nxt_mem_pool_t *mp, struct sockaddr *sockaddr,
+ socklen_t len)
+{
+ size_t size, copy;
+ nxt_sockaddr_t *sa;
+
+ size = len;
+ copy = len;
+
+#if (NXT_HAVE_UNIX_DOMAIN)
+
+ /*
+ * Unspecified Unix domain sockaddr_un form and length are very
+ * platform depended (see comment in unix/socket.h). Here they are
+ * normalized to the sockaddr_un with single zero byte sun_path[].
+ */
+
+ if (size <= offsetof(struct sockaddr_un, sun_path)) {
+ /*
+ * Small socket length means a short unspecified Unix domain
+ * socket address:
+ *
+ * getsockname() and getpeername() on OpenBSD prior to 5.3
+ * return zero length and does not update a passed sockaddr
+ * buffer at all.
+ *
+ * Linux returns length equal to 2, i.e. sockaddr_un without
+ * sun_path[], unix(7):
+ *
+ * unnamed: A stream socket that has not been bound
+ * to a pathname using bind(2) has no name. Likewise,
+ * the two sockets created by socketpair(2) are unnamed.
+ * When the address of an unnamed socket is returned by
+ * getsockname(2), getpeername(2), and accept(2), its
+ * length is sizeof(sa_family_t), and sun_path should
+ * not be inspected.
+ */
+ size = offsetof(struct sockaddr_un, sun_path) + 1;
+
+#if !(NXT_LINUX)
+
+ } else if (sockaddr->sa_family == AF_UNIX && sockaddr->sa_data[0] == '\0') {
+ /*
+ * Omit nonsignificant zeros of the unspecified Unix domain socket
+ * address. This test is disabled for Linux since Linux abstract
+ * socket address also starts with zero. However Linux unspecified
+ * Unix domain socket address is short and is handled above.
+ */
+ size = offsetof(struct sockaddr_un, sun_path) + 1;
+ copy = size;
+
+#endif
+ }
+
+#endif /* NXT_HAVE_UNIX_DOMAIN */
+
+ sa = nxt_sockaddr_alloc(mp, size);
+
+ if (nxt_fast_path(sa != NULL)) {
+
+ nxt_memcpy(&sa->u.sockaddr, sockaddr, copy);
+
+#if (NXT_SOCKADDR_SA_LEN)
+
+ /* Update shortcut sockaddr length overwritten by nxt_memcpy(). */
+ nxt_socklen_set(sa, size);
+
+#endif
+
+#if (NXT_HAVE_UNIX_DOMAIN && NXT_OPENBSD)
+
+ if (len == 0) {
+ sa->u.sockaddr.sa_family = AF_UNIX;
+ }
+
+#endif
+ }
+
+ return sa;
+}
+
+
+nxt_sockaddr_t *
+nxt_sockaddr_copy(nxt_mem_pool_t *mp, nxt_sockaddr_t *src)
+{
+ size_t len;
+ nxt_sockaddr_t *dst;
+
+ len = offsetof(nxt_sockaddr_t, u) + nxt_socklen(src);
+
+ dst = nxt_mem_alloc(mp, len);
+
+ if (nxt_fast_path(dst != NULL)) {
+ nxt_memcpy(dst, src, len);
+ }
+
+ return dst;
+}
+
+
+nxt_sockaddr_t *
+nxt_getsockname(nxt_mem_pool_t *mp, nxt_socket_t s)
+{
+ int ret;
+ socklen_t socklen;
+ nxt_sockaddr_buf_t sockaddr;
+
+ socklen = NXT_SOCKADDR_LEN;
+
+ ret = getsockname(s, &sockaddr.buf, &socklen);
+
+ if (nxt_fast_path(ret == 0)) {
+ return nxt_sockaddr_create(mp, &sockaddr.buf, socklen);
+ }
+
+ nxt_thread_log_error(NXT_LOG_ERR, "getsockname(%d) failed %E",
+ s, nxt_errno);
+
+ return NULL;
+}
+
+
+nxt_int_t
+nxt_sockaddr_text(nxt_mem_pool_t *mp, nxt_sockaddr_t *sa, nxt_bool_t port)
+{
+ size_t len;
+ u_char *p;
+ u_char buf[NXT_SOCKADDR_STR_LEN + NXT_SOCKPORT_STR_LEN];
+
+ len = NXT_SOCKADDR_STR_LEN + NXT_SOCKPORT_STR_LEN;
+
+ len = nxt_sockaddr_ntop(sa, buf, buf + len, port);
+
+ p = nxt_mem_alloc(mp, len);
+
+ if (nxt_fast_path(p != NULL)) {
+
+ sa->text = p;
+ sa->text_len = len;
+ nxt_memcpy(p, buf, len);
+
+ return NXT_OK;
+ }
+
+ return NXT_ERROR;
+}
+
+
+uint32_t
+nxt_sockaddr_port(nxt_sockaddr_t *sa)
+{
+ uint32_t port;
+
+ switch (sa->u.sockaddr.sa_family) {
+
+#if (NXT_INET6)
+
+ case AF_INET6:
+ port = sa->u.sockaddr_in6.sin6_port;
+ break;
+
+#endif
+
+#if (NXT_HAVE_UNIX_DOMAIN)
+
+ case AF_UNIX:
+ return 0;
+
+#endif
+
+ default:
+ port = sa->u.sockaddr_in.sin_port;
+ break;
+ }
+
+ return ntohs((uint16_t) port);
+}
+
+
+nxt_bool_t
+nxt_sockaddr_cmp(nxt_sockaddr_t *sa1, nxt_sockaddr_t *sa2)
+{
+ if (nxt_socklen(sa1) != nxt_socklen(sa2)) {
+ return 0;
+ }
+
+ if (sa1->type != sa2->type) {
+ return 0;
+ }
+
+ if (sa1->u.sockaddr.sa_family != sa2->u.sockaddr.sa_family) {
+ return 0;
+ }
+
+ /*
+ * sockaddr struct's cannot be compared in whole since kernel
+ * may fill some fields in inherited sockaddr struct's.
+ */
+
+ switch (sa1->u.sockaddr.sa_family) {
+
+#if (NXT_INET6)
+
+ case AF_INET6:
+ if (sa1->u.sockaddr_in6.sin6_port != sa2->u.sockaddr_in6.sin6_port) {
+ return 0;
+ }
+
+ if (nxt_memcmp(&sa1->u.sockaddr_in6.sin6_addr,
+ &sa2->u.sockaddr_in6.sin6_addr, 16)
+ != 0)
+ {
+ return 0;
+ }
+
+ return 1;
+
+#endif
+
+#if (NXT_HAVE_UNIX_DOMAIN)
+
+ case AF_UNIX:
+ {
+ size_t len;
+
+ len = nxt_socklen(sa1) - offsetof(struct sockaddr_un, sun_path);
+
+ if (nxt_memcmp(&sa1->u.sockaddr_un.sun_path,
+ &sa2->u.sockaddr_un.sun_path, len)
+ != 0)
+ {
+ return 0;
+ }
+
+ return 1;
+ }
+
+#endif
+
+ default: /* AF_INET */
+ if (sa1->u.sockaddr_in.sin_port != sa2->u.sockaddr_in.sin_port) {
+ return 0;
+ }
+
+ if (sa1->u.sockaddr_in.sin_addr.s_addr
+ != sa2->u.sockaddr_in.sin_addr.s_addr)
+ {
+ return 0;
+ }
+
+ return 1;
+ }
+}
+
+
+size_t
+nxt_sockaddr_ntop(nxt_sockaddr_t *sa, u_char *buf, u_char *end, nxt_bool_t port)
+{
+ u_char *p;
+
+ switch (sa->u.sockaddr.sa_family) {
+
+ case AF_INET:
+ p = (u_char *) &sa->u.sockaddr_in.sin_addr;
+
+ if (port) {
+ p = nxt_sprintf(buf, end, "%ud.%ud.%ud.%ud:%d",
+ p[0], p[1], p[2], p[3],
+ ntohs(sa->u.sockaddr_in.sin_port));
+ } else {
+ p = nxt_sprintf(buf, end, "%ud.%ud.%ud.%ud",
+ p[0], p[1], p[2], p[3]);
+ }
+
+ return p - buf;
+
+#if (NXT_INET6)
+
+ case AF_INET6:
+ p = buf;
+
+ if (port) {
+ *p++ = '[';
+ }
+
+ p = nxt_inet6_ntop(sa->u.sockaddr_in6.sin6_addr.s6_addr, p, end);
+
+ if (port) {
+ p = nxt_sprintf(p, end, "]:%d",
+ ntohs(sa->u.sockaddr_in6.sin6_port));
+ }
+
+ return p - buf;
+#endif
+
+#if (NXT_HAVE_UNIX_DOMAIN)
+
+ case AF_UNIX:
+
+#if (NXT_LINUX)
+
+ p = (u_char *) sa->u.sockaddr_un.sun_path;
+
+ if (p[0] == '\0') {
+ int len;
+
+ /* Linux abstract socket address has no trailing zero. */
+
+ len = nxt_socklen(sa) - offsetof(struct sockaddr_un, sun_path) - 1;
+ p = nxt_sprintf(buf, end, "unix:\\0%*s", len, p + 1);
+
+ } else {
+ p = nxt_sprintf(buf, end, "unix:%s", p);
+ }
+
+#else /* !(NXT_LINUX) */
+
+ p = nxt_sprintf(buf, end, "unix:%s", sa->u.sockaddr_un.sun_path);
+
+#endif
+
+ return p - buf;
+
+#endif /* NXT_HAVE_UNIX_DOMAIN */
+
+ default:
+ return 0;
+ }
+}
+
+
+#if (NXT_INET6)
+
+static u_char *
+nxt_inet6_ntop(u_char *addr, u_char *buf, u_char *end)
+{
+ u_char *p;
+ size_t zero_groups, last_zero_groups, ipv6_bytes;
+ nxt_uint_t i, zero_start, last_zero_start;
+
+ if (buf + NXT_INET6_ADDR_STR_LEN > end) {
+ return buf;
+ }
+
+ zero_start = 8;
+ zero_groups = 0;
+ last_zero_start = 8;
+ last_zero_groups = 0;
+
+ for (i = 0; i < 16; i += 2) {
+
+ if (addr[i] == 0 && addr[i + 1] == 0) {
+
+ if (last_zero_groups == 0) {
+ last_zero_start = i;
+ }
+
+ last_zero_groups++;
+
+ } else {
+ if (zero_groups < last_zero_groups) {
+ zero_groups = last_zero_groups;
+ zero_start = last_zero_start;
+ }
+
+ last_zero_groups = 0;
+ }
+ }
+
+ if (zero_groups < last_zero_groups) {
+ zero_groups = last_zero_groups;
+ zero_start = last_zero_start;
+ }
+
+ ipv6_bytes = 16;
+ p = buf;
+
+ if (zero_start == 0) {
+
+ /* IPv4-mapped address */
+ if ((zero_groups == 5 && addr[10] == 0xff && addr[11] == 0xff)
+ /* IPv4-compatible address */
+ || (zero_groups == 6)
+ /* not IPv6 loopback address */
+ || (zero_groups == 7 && addr[14] != 0 && addr[15] != 1))
+ {
+ ipv6_bytes = 12;
+ }
+
+ *p++ = ':';
+ }
+
+ for (i = 0; i < ipv6_bytes; i += 2) {
+
+ if (i == zero_start) {
+ /* Output maximum number of consecutive zero groups as "::". */
+ i += (zero_groups - 1) * 2;
+ *p++ = ':';
+ continue;
+ }
+
+ p = nxt_sprintf(p, end, "%uxd", (addr[i] << 8) + addr[i + 1]);
+
+ if (i < 14) {
+ *p++ = ':';
+ }
+ }
+
+ if (ipv6_bytes == 12) {
+ p = nxt_sprintf(p, end, "%ud.%ud.%ud.%ud",
+ addr[12], addr[13], addr[14], addr[15]);
+ }
+
+ return p;
+}
+
+#endif
+
+
+void
+nxt_job_sockaddr_parse(nxt_job_sockaddr_parse_t *jbs)
+{
+ u_char *p;
+ size_t len;
+ nxt_int_t ret;
+ nxt_work_handler_t handler;
+
+ nxt_job_set_name(&jbs->resolve.job, "job sockaddr parse");
+
+ len = jbs->addr.len;
+ p = jbs->addr.data;
+
+ if (len > 6 && nxt_memcmp(p, (u_char *) "unix:", 5) == 0) {
+ ret = nxt_job_sockaddr_unix_parse(jbs);
+
+ } else if (len != 0 && *p == '[') {
+ ret = nxt_job_sockaddr_inet6_parse(jbs);
+
+ } else {
+ ret = nxt_job_sockaddr_inet_parse(jbs);
+ }
+
+ switch (ret) {
+
+ case NXT_OK:
+ handler = jbs->resolve.ready_handler;
+ break;
+
+ case NXT_ERROR:
+ handler = jbs->resolve.error_handler;
+ break;
+
+ default: /* NXT_AGAIN */
+ return;
+ }
+
+ nxt_job_return(nxt_thread(), &jbs->resolve.job, handler);
+}
+
+
+static nxt_int_t
+nxt_job_sockaddr_unix_parse(nxt_job_sockaddr_parse_t *jbs)
+{
+#if (NXT_HAVE_UNIX_DOMAIN)
+ size_t len, socklen;
+ u_char *path;
+ nxt_mem_pool_t *mp;
+ nxt_sockaddr_t *sa;
+
+ /*
+ * Actual sockaddr_un length can be lesser or even larger than defined
+ * struct sockaddr_un length (see comment in unix/nxt_socket.h). So
+ * limit maximum Unix domain socket address length by defined sun_path[]
+ * length because some OSes accept addresses twice larger than defined
+ * struct sockaddr_un. Also reserve space for a trailing zero to avoid
+ * ambiguity, since many OSes accept Unix domain socket addresses
+ * without a trailing zero.
+ */
+ const size_t max_len = sizeof(struct sockaddr_un)
+ - offsetof(struct sockaddr_un, sun_path) - 1;
+
+ /* cutting "unix:" */
+ len = jbs->addr.len - 5;
+ path = jbs->addr.data + 5;
+
+ if (len > max_len) {
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "unix domain socket \"%V\" name is too long",
+ &jbs->addr);
+ return NXT_ERROR;
+ }
+
+ socklen = offsetof(struct sockaddr_un, sun_path) + len + 1;
+
+#if (NXT_LINUX)
+
+ /*
+ * Linux unix(7):
+ *
+ * abstract: an abstract socket address is distinguished by the fact
+ * that sun_path[0] is a null byte ('\0'). The socket's address in
+ * this namespace is given by the additional bytes in sun_path that
+ * are covered by the specified length of the address structure.
+ * (Null bytes in the name have no special significance.)
+ */
+ if (path[0] == '\0') {
+ socklen--;
+ }
+
+#endif
+
+ mp = jbs->resolve.job.mem_pool;
+
+ jbs->resolve.sockaddrs = nxt_mem_alloc(mp, sizeof(void *));
+
+ if (nxt_fast_path(jbs->resolve.sockaddrs != NULL)) {
+ sa = nxt_sockaddr_alloc(mp, socklen);
+
+ if (nxt_fast_path(sa != NULL)) {
+ jbs->resolve.count = 1;
+ jbs->resolve.sockaddrs[0] = sa;
+
+ sa->u.sockaddr_un.sun_family = AF_UNIX;
+ nxt_memcpy(sa->u.sockaddr_un.sun_path, path, len);
+
+ return NXT_OK;
+ }
+ }
+
+ return NXT_ERROR;
+
+#else /* !(NXT_HAVE_UNIX_DOMAIN) */
+
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "unix domain socket \"%V\" is not supported",
+ &jbs->addr);
+ return NXT_ERROR;
+
+#endif
+}
+
+
+static nxt_int_t
+nxt_job_sockaddr_inet6_parse(nxt_job_sockaddr_parse_t *jbs)
+{
+#if (NXT_INET6)
+ u_char *p, *addr, *addr_end;
+ size_t len;
+ nxt_int_t port;
+ nxt_mem_pool_t *mp;
+ nxt_sockaddr_t *sa;
+ struct in6_addr *in6_addr;
+
+ len = jbs->addr.len - 1;
+ addr = jbs->addr.data + 1;
+
+ addr_end = nxt_memchr(addr, ']', len);
+
+ if (addr_end == NULL) {
+ goto invalid_address;
+ }
+
+ mp = jbs->resolve.job.mem_pool;
+
+ jbs->resolve.sockaddrs = nxt_mem_alloc(mp, sizeof(void *));
+
+ if (nxt_slow_path(jbs->resolve.sockaddrs == NULL)) {
+ return NXT_ERROR;
+ }
+
+ sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in6));
+
+ if (nxt_slow_path(sa == NULL)) {
+ return NXT_ERROR;
+ }
+
+ jbs->resolve.count = 1;
+ jbs->resolve.sockaddrs[0] = sa;
+
+ in6_addr = &sa->u.sockaddr_in6.sin6_addr;
+
+ if (nxt_inet6_addr(in6_addr, addr, addr_end - addr) != NXT_OK) {
+ goto invalid_address;
+ }
+
+ p = addr_end + 1;
+ len = (addr + len) - p;
+
+ if (len == 0) {
+ jbs->no_port = 1;
+ port = jbs->resolve.port;
+ goto found;
+ }
+
+ if (*p == ':') {
+ port = nxt_int_parse(p + 1, len - 1);
+
+ if (port >= 1 && port <= 65535) {
+ port = htons((in_port_t) port);
+ goto found;
+ }
+ }
+
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "invalid port in \"%V\"", &jbs->addr);
+
+ return NXT_ERROR;
+
+found:
+
+ sa->u.sockaddr_in6.sin6_family = AF_INET6;
+ sa->u.sockaddr_in6.sin6_port = (in_port_t) port;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(in6_addr)) {
+ jbs->wildcard = 1;
+ }
+
+ return NXT_OK;
+
+invalid_address:
+
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "invalid IPv6 address in \"%V\"", &jbs->addr);
+ return NXT_ERROR;
+
+#else
+
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "IPv6 socket \"%V\" is not supported", &jbs->addr);
+ return NXT_ERROR;
+
+#endif
+}
+
+
+static nxt_int_t
+nxt_job_sockaddr_inet_parse(nxt_job_sockaddr_parse_t *jbs)
+{
+ u_char *p, *host;
+ size_t len;
+ in_addr_t addr;
+ nxt_int_t port;
+ nxt_mem_pool_t *mp;
+ nxt_sockaddr_t *sa;
+
+ addr = INADDR_ANY;
+
+ len = jbs->addr.len;
+ host = jbs->addr.data;
+
+ p = nxt_memchr(host, ':', len);
+
+ if (p == NULL) {
+
+ /* single value port, address, or host name */
+
+ port = nxt_int_parse(host, len);
+
+ if (port > 0) {
+ if (port < 1 || port > 65535) {
+ goto invalid_port;
+ }
+
+ /* "*:XX" */
+ port = htons((in_port_t) port);
+ jbs->resolve.port = (in_port_t) port;
+
+ } else {
+ jbs->no_port = 1;
+
+ addr = nxt_inet_addr(host, len);
+
+ if (addr == INADDR_NONE) {
+ jbs->resolve.name.len = len;
+ jbs->resolve.name.data = host;
+
+ nxt_job_resolve(&jbs->resolve);
+ return NXT_AGAIN;
+ }
+
+ /* "x.x.x.x" */
+ port = jbs->resolve.port;
+ }
+
+ } else {
+
+ /* x.x.x.x:XX or host:XX */
+
+ p++;
+ len = (host + len) - p;
+ port = nxt_int_parse(p, len);
+
+ if (port < 1 || port > 65535) {
+ goto invalid_port;
+ }
+
+ port = htons((in_port_t) port);
+
+ len = (p - 1) - host;
+
+ if (len != 1 || host[0] != '*') {
+ addr = nxt_inet_addr(host, len);
+
+ if (addr == INADDR_NONE) {
+ jbs->resolve.name.len = len;
+ jbs->resolve.name.data = host;
+ jbs->resolve.port = (in_port_t) port;
+
+ nxt_job_resolve(&jbs->resolve);
+ return NXT_AGAIN;
+ }
+
+ /* "x.x.x.x:XX" */
+ }
+ }
+
+ mp = jbs->resolve.job.mem_pool;
+
+ jbs->resolve.sockaddrs = nxt_mem_alloc(mp, sizeof(void *));
+ if (nxt_slow_path(jbs->resolve.sockaddrs == NULL)) {
+ return NXT_ERROR;
+ }
+
+ sa = nxt_sockaddr_alloc(mp, sizeof(struct sockaddr_in));
+
+ if (nxt_fast_path(sa != NULL)) {
+ jbs->resolve.count = 1;
+ jbs->resolve.sockaddrs[0] = sa;
+
+ jbs->wildcard = (addr == INADDR_ANY);
+
+ sa->u.sockaddr_in.sin_family = AF_INET;
+ sa->u.sockaddr_in.sin_port = (in_port_t) port;
+ sa->u.sockaddr_in.sin_addr.s_addr = addr;
+
+ return NXT_OK;
+ }
+
+ return NXT_ERROR;
+
+invalid_port:
+
+ nxt_thread_log_error(jbs->resolve.log_level,
+ "invalid port in \"%V\"", &jbs->addr);
+
+ return NXT_ERROR;
+}
+
+
+in_addr_t
+nxt_inet_addr(u_char *buf, size_t len)
+{
+ u_char c, *end;
+ in_addr_t addr;
+ nxt_uint_t digit, octet, dots;
+
+ addr = 0;
+ octet = 0;
+ dots = 0;
+
+ end = buf + len;
+
+ while (buf < end) {
+
+ c = *buf++;
+
+ digit = c - '0';
+ /* values below '0' become large unsigned integers */
+
+ if (digit < 10) {
+ octet = octet * 10 + digit;
+ continue;
+ }
+
+ if (c == '.' && octet < 256) {
+ addr = (addr << 8) + octet;
+ octet = 0;
+ dots++;
+ continue;
+ }
+
+ return INADDR_NONE;
+ }
+
+ if (dots == 3 && octet < 256) {
+ addr = (addr << 8) + octet;
+ return htonl(addr);
+ }
+
+ return INADDR_NONE;
+}
+
+
+#if (NXT_INET6)
+
+nxt_int_t
+nxt_inet6_addr(struct in6_addr *in6_addr, u_char *buf, size_t len)
+{
+ u_char c, *addr, *zero_start, *ipv4, *dst, *src, *end;
+ nxt_uint_t digit, group, nibbles, groups_left;
+
+ if (len == 0) {
+ return NXT_ERROR;
+ }
+
+ end = buf + len;
+
+ if (buf[0] == ':') {
+ buf++;
+ }
+
+ addr = in6_addr->s6_addr;
+ zero_start = NULL;
+ groups_left = 8;
+ nibbles = 0;
+ group = 0;
+ ipv4 = NULL;
+
+ while (buf < end) {
+ c = *buf++;
+
+ if (c == ':') {
+ if (nibbles != 0) {
+ ipv4 = buf;
+
+ *addr++ = (u_char) (group >> 8);
+ *addr++ = (u_char) (group & 0xff);
+ groups_left--;
+
+ if (groups_left != 0) {
+ nibbles = 0;
+ group = 0;
+ continue;
+ }
+
+ } else {
+ if (zero_start == NULL) {
+ ipv4 = buf;
+ zero_start = addr;
+ continue;
+ }
+ }
+
+ return NXT_ERROR;
+ }
+
+ if (c == '.' && nibbles != 0) {
+
+ if (groups_left < 2 || ipv4 == NULL) {
+ return NXT_ERROR;
+ }
+
+ group = nxt_inet_addr(ipv4, end - ipv4);
+ if (group == INADDR_NONE) {
+ return NXT_ERROR;
+ }
+
+ group = ntohl(group);
+
+ *addr++ = (u_char) ((group >> 24) & 0xff);
+ *addr++ = (u_char) ((group >> 16) & 0xff);
+ groups_left--;
+
+ /* the low 16-bit are copied below */
+ break;
+ }
+
+ nibbles++;
+
+ if (nibbles > 4) {
+ return NXT_ERROR;
+ }
+
+ group <<= 4;
+
+ digit = c - '0';
+ /* values below '0' become large unsigned integers */
+
+ if (digit < 10) {
+ group += digit;
+ continue;
+ }
+
+ c |= 0x20;
+ digit = c - 'a';
+ /* values below 'a' become large unsigned integers */
+
+ if (digit < 6) {
+ group += 10 + digit;
+ continue;
+ }
+
+ return NXT_ERROR;
+ }
+
+ if (nibbles == 0 && zero_start == NULL) {
+ return NXT_ERROR;
+ }
+
+ *addr++ = (u_char) (group >> 8);
+ *addr++ = (u_char) (group & 0xff);
+ groups_left--;
+
+ if (groups_left != 0) {
+
+ if (zero_start != NULL) {
+
+ /* moving part before consecutive zero groups to the end */
+
+ groups_left *= 2;
+ src = addr - 1;
+ dst = src + groups_left;
+
+ while (src >= zero_start) {
+ *dst-- = *src--;
+ }
+
+ nxt_memzero(zero_start, groups_left);
+
+ return NXT_OK;
+ }
+
+ } else {
+ if (zero_start == NULL) {
+ return NXT_OK;
+ }
+ }
+
+ return NXT_ERROR;
+}
+
+#endif