From: Nick Mathewson Date: Fri, 2 Jan 2009 20:46:12 +0000 (+0000) Subject: New functions in evutil to clone inet_pton and inet_ntop, with tests. X-Git-Tag: release-2.0.1-alpha~192 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0d9d5cfe22c89391a1ee25e2104cbf1fd6e1fcfb;p=libevent New functions in evutil to clone inet_pton and inet_ntop, with tests. Adapted from Tor code. svn:r983 --- diff --git a/ChangeLog b/ChangeLog index c89e46f0..b6b4ee1b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -133,7 +133,8 @@ Changes in current version: o Set the 0x20 bit on outgoing alphabetic characters in DNS requests randomly, and insist on a match in replies. This helps resist DNS poisoning attacks. o Make the http connection close detection work properly with bufferevents and fix a potential memory leak associated with it. o Restructure the event backends so that they do not need to keep track of events themselves, as a side effect multiple events can use the same fd or signal. - + o Add generic implementations for parsing and emiting IPv6 addresses on platforms that do not have inet_ntop and/or inet_pton. + Changes in 1.4.0: o allow \r or \n individually to separate HTTP headers instead of the standard "\r\n"; from Charles Kerr. o demote most http warnings to debug messages diff --git a/configure.in b/configure.in index bd274a6a..3d69625e 100644 --- a/configure.in +++ b/configure.in @@ -53,7 +53,7 @@ AM_CONDITIONAL(ZLIB_REGRESS, [test "$have_zlib" != "no"]) dnl Checks for header files. AC_HEADER_STDC -AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h stddef.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/param.h sys/ioctl.h sys/select.h sys/devpoll.h port.h netinet/in6.h sys/socket.h sys/uio.h) +AC_CHECK_HEADERS(fcntl.h stdarg.h inttypes.h stdint.h stddef.h poll.h signal.h unistd.h sys/epoll.h sys/time.h sys/queue.h sys/event.h sys/param.h sys/ioctl.h sys/select.h sys/devpoll.h port.h netinet/in6.h sys/socket.h sys/uio.h arpa/inet.h) if test "x$ac_cv_header_sys_queue_h" = "xyes"; then AC_MSG_CHECKING(for TAILQ_FOREACH in sys/queue.h) AC_EGREP_CPP(yes, @@ -146,7 +146,7 @@ AC_C_INLINE AC_HEADER_TIME dnl Checks for library functions. -AC_CHECK_FUNCS(gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop signal sigaction strtoll) +AC_CHECK_FUNCS(gettimeofday vasprintf fcntl clock_gettime strtok_r strsep getaddrinfo getnameinfo strlcpy inet_ntop inet_pton signal sigaction strtoll inet_aton) AC_CHECK_SIZEOF(long) diff --git a/evutil.c b/evutil.c index 33f42e1b..bf161be2 100644 --- a/evutil.c +++ b/evutil.c @@ -50,6 +50,14 @@ #endif #include #include +#include +#include +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif #ifndef _EVENT_HAVE_GETTIMEOFDAY #include @@ -59,6 +67,8 @@ #include "event2/util.h" #include "log.h" +#include "strlcpy-internal.h" + int evutil_socketpair(int family, int type, int protocol, evutil_socket_t fd[2]) { @@ -339,3 +349,221 @@ evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap) return r; #endif } + +#define USE_INTERNAL_NTOP +#define USE_INTERNAL_PTON + +const char * +evutil_inet_ntop(int af, const void *src, char *dst, size_t len) +{ +#if defined(_EVENT_HAVE_INET_NTOP) && !defined(USE_INTERNAL_NTOP) + return inet_ntop(af, src, dst, len); +#else + if (af == AF_INET) { + const struct in_addr *in = src; + const ev_uint32_t a = ntohl(in->s_addr); + int r; + r = evutil_snprintf(dst, len, "%d.%d.%d.%d", + (int)(ev_uint8_t)((a>>24)&0xff), + (int)(ev_uint8_t)((a>>16)&0xff), + (int)(ev_uint8_t)((a>>8 )&0xff), + (int)(ev_uint8_t)((a )&0xff)); + if (r<0||r>=len) + return NULL; + else + return dst; +#ifdef AF_INET6 + } else if (af == AF_INET6) { + const struct in6_addr *addr = src; + char buf[64], *cp; + int longestGapLen = 0, longestGapPos = -1, i, + curGapPos = -1, curGapLen = 0; + ev_uint16_t words[8]; + for (i = 0; i < 8; ++i) { + words[i] = + (((ev_uint16_t)addr->s6_addr[2*i])<<8) + addr->s6_addr[2*i+1]; + } + if (words[0] == 0 && words[1] == 0 && words[2] == 0 && words[3] == 0 && + words[4] == 0 && ((words[5] == 0 && words[6] && words[7]) || + (words[5] == 0xffff))) { + /* This is an IPv4 address. */ + if (words[5] == 0) { + evutil_snprintf(buf, sizeof(buf), "::%d.%d.%d.%d", + addr->s6_addr[12], addr->s6_addr[13], + addr->s6_addr[14], addr->s6_addr[15]); + } else { + evutil_snprintf(buf, sizeof(buf), "::%x:%d.%d.%d.%d", words[5], + addr->s6_addr[12], addr->s6_addr[13], + addr->s6_addr[14], addr->s6_addr[15]); + } + if (strlen(buf) > len) + return NULL; + strlcpy(dst, buf, len); + return dst; + } + i = 0; + while (i < 8) { + if (words[i] == 0) { + curGapPos = i++; + curGapLen = 1; + while (i<8 && words[i] == 0) { + ++i; ++curGapLen; + } + if (curGapLen > longestGapLen) { + longestGapPos = curGapPos; + longestGapLen = curGapLen; + } + } else { + ++i; + } + } + if (longestGapLen<=1) + longestGapPos = -1; + + cp = buf; + for (i = 0; i < 8; ++i) { + if (words[i] == 0 && longestGapPos == i) { + if (i == 0) + *cp++ = ':'; + *cp++ = ':'; + while (i < 8 && words[i] == 0) + ++i; + --i; /* to compensate for loop increment. */ + } else { + evutil_snprintf(cp, + sizeof(buf)-(cp-buf), "%x", (unsigned)words[i]); + cp += strlen(cp); + if (i != 7) + *cp++ = ':'; + } + } + *cp = '\0'; + if (strlen(buf) > len) + return NULL; + strlcpy(dst, buf, len); + return dst; +#endif + } else { + return NULL; + } +#endif +} + +#define ISDIGIT(c) isdigit((int)(unsigned char)(c)) +#define ISXDIGIT(c) isxdigit((int)(unsigned char)(c)) + +int +evutil_inet_pton(int af, const char *src, void *dst) +{ +#if defined(_EVENT_HAVE_INET_PTON) && !defined(USE_INTERNAL_PTON) + return inet_pton(af, src, dst); +#else + if (af == AF_INET) { +#ifdef _EVENT_HAVE_INET_ATON + return inet_aton(src, dst); +#else + ev_uint32_t r; + struct in_addr *out = dst; + if (strcmp(c, "255.255.255.255") == 0) { + out->s_addr = 0xffffffffu; + } else { + r = inet_addr(c); + if (r == INADDR_NONE) + return 0; + addr->s_addr = r; + } + return 1; +#endif +#ifdef AF_INET6 + } else if (af == AF_INET6) { + struct in6_addr *out = dst; + uint16_t words[8]; + int gapPos = -1, i, setWords=0; + const char *dot = strchr(src, '.'); + const char *eow; /* end of words. */ + if (dot == src) + return 0; + else if (!dot) + eow = src+strlen(src); + else { + int byte1,byte2,byte3,byte4; + char more; + for (eow = dot-1; eow >= src && ISDIGIT(*eow); --eow) + ; + ++eow; + + /* We use "scanf" because some platform inet_aton()s are too lax + * about IPv4 addresses of the form "1.2.3" */ + if (sscanf(eow, "%d.%d.%d.%d%c", + &byte1,&byte2,&byte3,&byte4,&more) != 4) + return 0; + + if (byte1 > 255 || byte1 < 0 || + byte2 > 255 || byte2 < 0 || + byte3 > 255 || byte3 < 0 || + byte4 > 255 || byte4 < 0) + return 0; + + words[6] = (byte1<<8) | byte2; + words[7] = (byte3<<8) | byte4; + setWords += 2; + } + + i = 0; + while (src < eow) { + if (i > 7) + return 0; + if (ISXDIGIT(*src)) { + char *next; + long r = strtol(src, &next, 16); + if (next > 4+src) + return 0; + if (next == src) + return 0; + if (r<0 || r>65536) + return 0; + + words[i++] = (uint16_t)r; + setWords++; + src = next; + if (*src != ':' && src != eow) + return 0; + ++src; + } else if (*src == ':' && i > 0 && gapPos==-1) { + gapPos = i; + ++src; + } else if (*src == ':' && i == 0 && src[1] == ':' && gapPos==-1) { + gapPos = i; + src += 2; + } else { + return 0; + } + } + + if (setWords > 8 || + (setWords == 8 && gapPos != -1) || + (setWords < 8 && gapPos == -1)) + return 0; + + if (gapPos >= 0) { + int nToMove = setWords - (dot ? 2 : 0) - gapPos; + int gapLen = 8 - setWords; + // assert(nToMove >= 0); + if (nToMove < 0) + return -1; /* should be impossible */ + memmove(&words[gapPos+gapLen], &words[gapPos], + sizeof(uint16_t)*nToMove); + memset(&words[gapPos], 0, sizeof(uint16_t)*gapLen); + } + for (i = 0; i < 8; ++i) { + out->s6_addr[2*i ] = words[i] >> 8; + out->s6_addr[2*i+1] = words[i] & 0xff; + } + + return 1; +#endif + } else { + return -1; + } +#endif +} diff --git a/include/event2/util.h b/include/event2/util.h index e082b49c..ddbe689f 100644 --- a/include/event2/util.h +++ b/include/event2/util.h @@ -213,6 +213,9 @@ int evutil_snprintf(char *buf, size_t buflen, const char *format, ...) ; int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap); +const char *evutil_inet_ntop(int af, const void *src, char *dst, size_t len); +int evutil_inet_pton(int af, const char *src, void *dst); + #ifdef __cplusplus } #endif diff --git a/test/Makefile.am b/test/Makefile.am index 7b24502f..8b7ee326 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -18,6 +18,7 @@ test_time_SOURCES = test-time.c test_time_LDADD = ../libevent_core.la regress_SOURCES = regress.c regress.h regress_http.c regress_dns.c \ regress_rpc.c regress.gen.c regress.gen.h regress_et.c \ + regress_util.c \ $(regress_pthread_SOURCES) $(regress_zlib_SOURCES) if PTHREAD_REGRESS regress_pthread_SOURCES = regress_pthread.c diff --git a/test/regress.c b/test/regress.c index bbb89582..b5da2ec1 100644 --- a/test/regress.c +++ b/test/regress.c @@ -2307,6 +2307,7 @@ main (int argc, char **argv) test_evutil_strtoll(); test_evutil_snprintf(); + util_suite(); test_periodictimeout(); diff --git a/test/regress.h b/test/regress.h index cb92c9ea..c84b1c43 100644 --- a/test/regress.h +++ b/test/regress.h @@ -38,6 +38,8 @@ void rpc_suite(void); void dns_suite(void); +void util_suite(void); + void regress_pthread(void); void regress_zlib(void); diff --git a/test/regress_util.c b/test/regress_util.c new file mode 100644 index 00000000..5d9b4597 --- /dev/null +++ b/test/regress_util.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2009 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifndef WIN32 +#include +#include +#include +#include +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif +#include +#include +#include + +#include "event2/util.h" + +void util_suite(void); + +enum entry_status { NORMAL, CANONICAL, BAD }; + +/* This is a big table of results we expect from generating and parsing */ +static struct ipv4_entry { + const char *addr; + ev_uint32_t res; + enum entry_status status; +} ipv4_entries[] = { + { "1.2.3.4", 0x01020304u, CANONICAL }, + { "255.255.255.255", 0xffffffffu, CANONICAL }, + { "256.0.0.0", 0, BAD }, + { "ABC", 0, BAD }, + { "1.2.3.4.5", 0, BAD }, + { "176.192.208.244", 0xb0c0d0f4, CANONICAL }, + { NULL, 0, BAD }, +}; + +static struct ipv6_entry { + const char *addr; + ev_uint32_t res[4]; + enum entry_status status; +} ipv6_entries[] = { + { "::", { 0, 0, 0, 0, }, CANONICAL }, + { "0:0:0:0:0:0:0:0", { 0, 0, 0, 0, }, NORMAL }, + { "::1", { 0, 0, 0, 1, }, CANONICAL }, + { "ffff:1::", { 0xffff0001u, 0, 0, 0, }, CANONICAL }, + { "ffff:0000::", { 0xffff0000u, 0, 0, 0, }, NORMAL }, + { "ffff::1234", { 0xffff0000u, 0, 0, 0x1234, }, CANONICAL }, + { "0102::1.2.3.4", {0x01020000u, 0, 0, 0x01020304u }, NORMAL }, + { "::9:c0a8:1:1", { 0, 0, 0x0009c0a8u, 0x00010001u }, CANONICAL }, + { "::ffff:1.2.3.4", { 0, 0, 0x000ffffu, 0x01020304u }, CANONICAL }, + { "FFFF::", { 0xffff0000u, 0, 0, 0 }, NORMAL }, + { "foobar.", { 0, 0, 0, 0 }, BAD }, + { "foobar", { 0, 0, 0, 0 }, BAD }, + { "fo:obar", { 0, 0, 0, 0 }, BAD }, + { "ffff", { 0, 0, 0, 0 }, BAD }, + { "fffff::", { 0, 0, 0, 0 }, BAD }, + { "fffff::", { 0, 0, 0, 0 }, BAD }, + { "1:2:33333:4::", { 0, 0, 0, 0 }, BAD }, + { "1:2:3:4:5:6:7:8:9", { 0, 0, 0, 0 }, BAD }, + { "1::2::3", { 0, 0, 0, 0 }, BAD }, + { ":::1", { 0, 0, 0, 0 }, BAD }, + { NULL, { 0, 0, 0, 0, }, BAD }, +}; + +static void +regress_ipv4_parse(void) +{ + int i; + int ok = 1; + printf("Testing IPv4 parsing..."); + + for (i = 0; ipv4_entries[i].addr; ++i) { + char written[128]; + struct ipv4_entry *ent = &ipv4_entries[i]; + struct in_addr in; + int r; + r = evutil_inet_pton(AF_INET, ent->addr, &in); + if (r == 0) { + if (ent->status != BAD) { + printf("%s did not parse, but it's a good address!\n", + ent->addr); + ok = 0; + } + continue; + } + if (ent->status == BAD) { + printf("%s parsed, but we expected an error\n", ent->addr); + ok = 0; + continue; + } + if (ntohl(in.s_addr) != ent->res) { + printf("%s parsed to %lx, but we expected %lx\n", ent->addr, + (unsigned long)ntohl(in.s_addr), + (unsigned long)ent->res); + ok = 0; + continue; + } + if (ent->status == CANONICAL) { + const char *w = evutil_inet_ntop(AF_INET, &in, written, + sizeof(written)); + if (!w) { + printf("Tried to write out %s; got NULL.\n", ent->addr); + ok = 0; + continue; + } + if (strcmp(written, ent->addr)) { + printf("Tried to write out %s; got %s\n", ent->addr, written); + ok = 0; + continue; + } + } + + } + if (!ok) { + printf("FAILED\n"); + exit(1); + } + printf("OK\n"); +} + +static void +regress_ipv6_parse(void) +{ +#ifdef AF_INET6 + int i, j; + int ok = 1; + printf("Testing IPv6 parsing..."); + + for (i = 0; ipv6_entries[i].addr; ++i) { + char written[128]; + struct ipv6_entry *ent = &ipv6_entries[i]; + struct in6_addr in6; + int r; + r = evutil_inet_pton(AF_INET6, ent->addr, &in6); + if (r == 0) { + if (ent->status != BAD) { + printf("%s did not parse, but it's a good address!\n", + ent->addr); + ok = 0; + } + continue; + } + if (ent->status == BAD) { + printf("%s parsed, but we expected an error\n", ent->addr); + ok = 0; + continue; + } + for (j = 0; j < 4; ++j) { + /* Can't use s6_addr32 here; some don't have it. */ + ev_uint32_t u = + (in6.s6_addr[j*4 ] << 24) | + (in6.s6_addr[j*4+1] << 16) | + (in6.s6_addr[j*4+2] << 8) | + (in6.s6_addr[j*4+3]); + if (u != ent->res[j]) { + printf("%s did not parse as expected.\n", ent->addr); + ok = 0; + continue; + } + } + if (ent->status == CANONICAL) { + const char *w = evutil_inet_ntop(AF_INET6, &in6, written, + sizeof(written)); + if (!w) { + printf("Tried to write out %s; got NULL.\n", ent->addr); + ok = 0; + continue; + } + if (strcmp(written, ent->addr)) { + printf("Tried to write out %s; got %s\n", ent->addr, written); + ok = 0; + continue; + } + } + + } + if (!ok) { + printf("FAILED\n"); + exit(1); + } + printf("OK\n"); +#else + print("Skipping IPv6 address parsing.\n"); +#endif +} + + +void +util_suite(void) +{ + regress_ipv4_parse(); + regress_ipv6_parse(); +}