diff options
author | Quentin Young <qlyoung@users.noreply.github.com> | 2019-06-07 17:23:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-07 17:23:16 +0200 |
commit | 9f9307c95369f667d209a5a6bd2fd24f44af1cb6 (patch) | |
tree | 3fd696fa7642094d30625a3073c50c49d4db8fe0 | |
parent | Merge pull request #4486 from idryzhov/master (diff) | |
parent | tests: exercise frr_inet_ntop() (diff) | |
download | frr-9f9307c95369f667d209a5a6bd2fd24f44af1cb6.tar.xz frr-9f9307c95369f667d209a5a6bd2fd24f44af1cb6.zip |
Merge pull request #4480 from opensourcerouting/inet_ntop
inet_ntop but faster
-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 750942822..9ce91e336 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 000000000..d47a0b697 --- /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 50ff1feec..4e6adced7 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 7177165e4..4c6a51d47 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 000000000..180605996 --- /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 000000000..2526f53db --- /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 1d29a418c..41f1a4873 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 \ |