diff options
| -rw-r--r-- | lib/compiler.h | 22 | ||||
| -rw-r--r-- | lib/ntop.c | 174 | ||||
| -rw-r--r-- | lib/subdir.am | 1 | ||||
| -rw-r--r-- | tests/.gitignore | 1 | ||||
| -rw-r--r-- | tests/lib/test_ntop.c | 87 | ||||
| -rw-r--r-- | tests/lib/test_ntop.py | 6 | ||||
| -rw-r--r-- | tests/subdir.am | 6 | 
7 files changed, 297 insertions, 0 deletions
diff --git a/lib/compiler.h b/lib/compiler.h index 7509428220..9ce91e3361 100644 --- a/lib/compiler.h +++ b/lib/compiler.h @@ -33,6 +33,9 @@ extern "C" {  #endif  # define _CONSTRUCTOR(x)  constructor(x)  # define _DEPRECATED(x) deprecated(x) +# if __has_builtin(assume) +#  define assume(x) __builtin_assume(x) +# endif  #elif defined(__GNUC__)  #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)  #  define _RET_NONNULL    , returns_nonnull @@ -44,12 +47,28 @@ extern "C" {  #endif  #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)  #  define _DEPRECATED(x) deprecated(x) +#  define assume(x) do { if (!(x)) __builtin_unreachable(); } while (0) +#endif +#if __GNUC__ < 5 +#  define __has_attribute(x) 0  #endif  #if __GNUC__ >= 7  #  define _FALLTHROUGH __attribute__((fallthrough));  #endif  #endif +#if __has_attribute(hot) +#  define _OPTIMIZE_HOT __attribute__((hot)) +#else +#  define _OPTIMIZE_HOT +#endif +#if __has_attribute(optimize) +#  define _OPTIMIZE_O3 __attribute__((optimize("3"))) +#else +#  define _OPTIMIZE_O3 +#endif +#define OPTIMIZE _OPTIMIZE_O3 _OPTIMIZE_HOT +  #if !defined(__GNUC__)  #error module code needs GCC visibility extensions  #elif __GNUC__ < 4 @@ -85,6 +104,9 @@ extern "C" {  #ifndef _DEPRECATED  #define _DEPRECATED(x) deprecated  #endif +#ifndef assume +#define assume(x) +#endif  /* pure = function does not modify memory & return value is the same if   * memory hasn't changed (=> allows compiler to optimize) diff --git a/lib/ntop.c b/lib/ntop.c new file mode 100644 index 0000000000..d47a0b697a --- /dev/null +++ b/lib/ntop.c @@ -0,0 +1,174 @@ +/* + * optimized ntop, about 10x faster than libc versions [as of 2019] + * + * Copyright (c) 2019  David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "compiler.h" + +#define pos (*posx) + +static inline void putbyte(uint8_t bytex, char **posx) +	__attribute__((always_inline)) OPTIMIZE; + +static inline void putbyte(uint8_t bytex, char **posx) +{ +	bool zero = false; +	int byte = bytex, tmp, a, b; + +	if ((tmp = byte - 200) >= 0) { +		*pos++ = '2'; +		zero = true; +		byte = tmp; +	} else if ((tmp = byte - 100) >= 0) { +		*pos++ = '1'; +		zero = true; +		byte = tmp; +	} + +	/* make sure the compiler knows the value range of "byte" */ +	assume(byte < 100 && byte >= 0); + +	b = byte % 10; +	a = byte / 10; +	if (a || zero) { +		*pos++ = '0' + a; +		*pos++ = '0' + b; +	} else +		*pos++ = '0' + b; +} + +static inline void puthex(uint16_t word, char **posx) +	__attribute__((always_inline)) OPTIMIZE; + +static inline void puthex(uint16_t word, char **posx) +{ +	const char *digits = "0123456789abcdef"; +	if (word >= 0x1000) +		*pos++ = digits[(word >> 12) & 0xf]; +	if (word >= 0x100) +		*pos++ = digits[(word >> 8) & 0xf]; +	if (word >= 0x10) +		*pos++ = digits[(word >> 4) & 0xf]; +	*pos++ = digits[word & 0xf]; +} + +#undef pos + +const char *frr_inet_ntop(int af, const void * restrict src, +			  char * restrict dst, socklen_t size) +	__attribute__((flatten)) DSO_SELF OPTIMIZE; + +const char *frr_inet_ntop(int af, const void * restrict src, +			  char * restrict dst, socklen_t size) +{ +	const uint8_t *b = src; +	/* 8 * "abcd:" for IPv6 +	 * note: the IPv4-embedded IPv6 syntax is only used for ::A.B.C.D, +	 * which isn't longer than 40 chars either.  even with ::ffff:A.B.C.D +	 * it's shorter. +	 */ +	char buf[8 * 5], *o = buf; +	size_t best = 0, bestlen = 0, curlen = 0, i; + +	switch (af) { +	case AF_INET: +inet4: +		putbyte(b[0], &o); +		*o++ = '.'; +		putbyte(b[1], &o); +		*o++ = '.'; +		putbyte(b[2], &o); +		*o++ = '.'; +		putbyte(b[3], &o); +		*o++ = '\0'; +		break; +	case AF_INET6: +		for (i = 0; i < 8; i++) { +			if (b[i * 2] || b[i * 2 + 1]) { +				if (curlen && curlen > bestlen) { +					best = i - curlen; +					bestlen = curlen; +				} +				curlen = 0; +				continue; +			} +			curlen++; +		} +		if (curlen && curlen > bestlen) { +			best = i - curlen; +			bestlen = curlen; +		} +		/* do we want ::ffff:A.B.C.D? */ +		if (best == 0 && bestlen == 6) { +			*o++ = ':'; +			*o++ = ':'; +			b += 12; +			goto inet4; +		} +		if (bestlen == 1) +			bestlen = 0; + +		for (i = 0; i < 8; i++) { +			if (bestlen && i == best) { +				if (i == 0) +					*o++ = ':'; +				*o++ = ':'; +				continue; +			} +			if (i > best && i < best + bestlen) { +				continue; +			} +			puthex((b[i * 2] << 8) | b[i * 2 + 1], &o); + +			if (i < 7) +				*o++ = ':'; +		} +		*o++ = '\0'; +		break; +	default: +		return NULL; +	} + +	i = o - buf; +	if (i > size) +		return NULL; +	/* compiler might inline memcpy if it knows the length is short, +	 * although neither gcc nor clang actually do this currently [2019] +	 */ +	assume(i <= 8 * 5); +	memcpy(dst, buf, i); +	return dst; +} + +#ifndef INET_NTOP_NO_OVERRIDE +/* we want to override libc inet_ntop, but make sure it shows up in backtraces + * as frr_inet_ntop (to avoid confusion while debugging) + */ +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) +	__attribute__((alias ("frr_inet_ntop"))) DSO_SELF; +#endif diff --git a/lib/subdir.am b/lib/subdir.am index 50ff1feecc..4e6adced74 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -56,6 +56,7 @@ lib_libfrr_la_SOURCES = \  	lib/northbound.c \  	lib/northbound_cli.c \  	lib/northbound_db.c \ +	lib/ntop.c \  	lib/openbsd-tree.c \  	lib/pid_output.c \  	lib/plist.c \ diff --git a/tests/.gitignore b/tests/.gitignore index 7177165e4a..4c6a51d475 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -30,6 +30,7 @@  /lib/test_idalloc  /lib/test_memory  /lib/test_nexthop_iter +/lib/test_ntop  /lib/test_printfrr  /lib/test_privs  /lib/test_ringbuf diff --git a/tests/lib/test_ntop.c b/tests/lib/test_ntop.c new file mode 100644 index 0000000000..1806059963 --- /dev/null +++ b/tests/lib/test_ntop.c @@ -0,0 +1,87 @@ +/* + * frr_inet_ntop() unit test + * Copyright (C) 2019  David Lamparter + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <assert.h> + +#include "tests/helpers/c/prng.h" + +/* NB: libfrr is NOT linked for this unit test! */ + +#define INET_NTOP_NO_OVERRIDE +#include "lib/ntop.c" + +int main(int argc, char **argv) +{ +	size_t i, j, k, l; +	struct in_addr i4; +	struct in6_addr i6, i6check; +	char buf1[64], buf2[64]; +	const char *rv; +	struct prng *prng; + +	prng = prng_new(0); +	/* IPv4 */ +	for (i = 0; i < 1000; i++) { +		i4.s_addr = prng_rand(prng); +		assert(frr_inet_ntop(AF_INET, &i4, buf1, sizeof(buf1))); +		assert(inet_ntop(AF_INET, &i4, buf2, sizeof(buf2))); +		assert(!strcmp(buf1, buf2)); +	} + +	/* check size limit */ +	for (i = 0; i < sizeof(buf1); i++) { +		memset(buf2, 0xcc, sizeof(buf2)); +		rv = frr_inet_ntop(AF_INET, &i4, buf2, i); +		if (i < strlen(buf1) + 1) +			assert(!rv); +		else +			assert(rv && !strcmp(buf1, buf2)); +	} + +	/* IPv6 */ +	for (i = 0; i < 10000; i++) { +		uint16_t *i6w = (uint16_t *)&i6; +		for (j = 0; j < 8; j++) +			i6w[j] = prng_rand(prng); + +		/* clear some words */ +		l = prng_rand(prng) & 7; +		for (j = 0; j < l; j++) { +			uint32_t num = __builtin_ctz(prng_rand(prng)); +			uint32_t where = prng_rand(prng) & 7; + +			for (k = where; k < where + num && k < 8; k++) +				i6w[k] = 0; +		} + +		assert(frr_inet_ntop(AF_INET6, &i6, buf1, sizeof(buf1))); +		assert(inet_ntop(AF_INET6, &i6, buf2, sizeof(buf2))); +		if (strcmp(buf1, buf2)) +			printf("%-40s (FRR) != (SYS) %-40s\n", buf1, buf2); + +		assert(inet_pton(AF_INET6, buf1, &i6check)); +		assert(!memcmp(&i6, &i6check, sizeof(i6))); +		assert(strlen(buf1) <= strlen(buf2)); +	} +	return 0; +} diff --git a/tests/lib/test_ntop.py b/tests/lib/test_ntop.py new file mode 100644 index 0000000000..2526f53db5 --- /dev/null +++ b/tests/lib/test_ntop.py @@ -0,0 +1,6 @@ +import frrtest + +class TestNtop(frrtest.TestMultiOut): +    program = './test_ntop' + +TestNtop.exit_cleanly() diff --git a/tests/subdir.am b/tests/subdir.am index 1d29a418ce..41f1a4873b 100644 --- a/tests/subdir.am +++ b/tests/subdir.am @@ -54,6 +54,7 @@ check_PROGRAMS = \  	tests/lib/test_idalloc \  	tests/lib/test_memory \  	tests/lib/test_nexthop_iter \ +	tests/lib/test_ntop \  	tests/lib/test_printfrr \  	tests/lib/test_privs \  	tests/lib/test_ringbuf \ @@ -231,6 +232,10 @@ tests_lib_test_nexthop_iter_CFLAGS = $(TESTS_CFLAGS)  tests_lib_test_nexthop_iter_CPPFLAGS = $(TESTS_CPPFLAGS)  tests_lib_test_nexthop_iter_LDADD = $(ALL_TESTS_LDADD)  tests_lib_test_nexthop_iter_SOURCES = tests/lib/test_nexthop_iter.c tests/helpers/c/prng.c +tests_lib_test_ntop_CFLAGS = $(TESTS_CFLAGS) +tests_lib_test_ntop_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_test_ntop_LDADD = # none +tests_lib_test_ntop_SOURCES = tests/lib/test_ntop.c tests/helpers/c/prng.c  tests_lib_test_printfrr_CFLAGS = $(TESTS_CFLAGS)  tests_lib_test_printfrr_CPPFLAGS = $(TESTS_CPPFLAGS)  tests_lib_test_printfrr_LDADD = $(ALL_TESTS_LDADD) @@ -322,6 +327,7 @@ EXTRA_DIST += \  	tests/lib/northbound/test_oper_data.refout \  	tests/lib/test_atomlist.py \  	tests/lib/test_nexthop_iter.py \ +	tests/lib/test_ntop.py \  	tests/lib/test_printfrr.py \  	tests/lib/test_ringbuf.py \  	tests/lib/test_srcdest_table.py \  | 
