├── simdzoneConfig.cmake.in ├── conanfile.txt ├── doc └── manual │ ├── requirements.txt │ ├── index.rst │ ├── api_reference.rst │ ├── links.rst │ ├── conf.py │ ├── building_sources.rst │ ├── getting_started.rst │ └── design_notes.rst ├── .gitignore ├── cmake ├── westmere.test.c ├── haswell.test.c └── Modules │ └── Findcmocka │ └── runner.c.in ├── tests ├── tools.h ├── fallback │ └── bits.c ├── CMakeLists.txt ├── haswell │ └── bits.c ├── westmere │ └── bits.c ├── tools.c ├── xxd.cmake ├── base32.c ├── ip4.c ├── time.c ├── bits.c ├── eui.c └── ttl.c ├── compat ├── getopt.h └── getopt.c ├── .readthedocs.yaml ├── src ├── fallback │ ├── bench.c │ ├── name.h │ ├── parser.c │ ├── text.h │ ├── bits.h │ └── scanner.h ├── haswell │ ├── bench.c │ ├── parser.c │ ├── bits.h │ ├── base32.h │ └── simd.h ├── westmere │ ├── bench.c │ ├── parser.c │ ├── bits.h │ ├── base32.h │ ├── simd.h │ └── time.h ├── config.h.in ├── generic │ ├── ilnp64.h │ ├── ip4.h │ ├── nxt.h │ ├── nsap.h │ ├── nsec.h │ ├── eui.h │ ├── apl.h │ ├── time.h │ ├── text.h │ ├── caa.h │ ├── cert.h │ ├── ttl.h │ ├── number.h │ ├── base32.h │ ├── name.h │ ├── algorithm.h │ └── ip6.h ├── diagnostic.h └── attributes.h ├── scripts ├── check-SvcParamKeys.sh ├── check-RR-types.sh ├── certificate-hash.c ├── algorithm-hash.c ├── wks-hash.c └── hash.c ├── LICENSE ├── include └── zone │ └── attributes.h ├── m4 └── ax_check_compile_flag.m4 ├── Makefile.in ├── CONTRIBUTING.md ├── CHANGELOG.md ├── README.md ├── .github └── workflows │ ├── coverity-scan.yml │ └── build-test.yml └── configure.ac /simdzoneConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [test_requires] 2 | cmocka/1.1.7 3 | 4 | [generators] 5 | CMakeDeps 6 | 7 | [options] 8 | cmocka*:shared=False 9 | -------------------------------------------------------------------------------- /doc/manual/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.1.2 2 | sphinx-version-warning==1.1.2 3 | sphinx-tabs==3.4.4 4 | sphinx-copybutton==0.5.2 5 | sphinx-notfound-page 6 | sphinx_rtd_theme==2.0 7 | toml 8 | breathe==4.35.0 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | /autom4te.cache 4 | /build 5 | /config.guess 6 | /config.h 7 | /config.h.in 8 | /config.h.in~ 9 | /config.log 10 | /config.status 11 | /config.sub 12 | /configure 13 | /configure~ 14 | /libzone.a 15 | /make.dep 16 | /.depend 17 | /Makefile 18 | /include/zone/export.h 19 | -------------------------------------------------------------------------------- /cmake/westmere.test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * westmere.test.c -- test if -march=westmere works 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | (void)argv; 15 | uint64_t popcnt = _mm_popcnt_u64((uint64_t)argc); 16 | return popcnt == 11; 17 | } 18 | -------------------------------------------------------------------------------- /tests/tools.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tools.c -- convenience tools for testing 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef TOOLS_H 10 | #define TOOLS_H 11 | 12 | // this is not safe to use in a production environment, but it's good enough 13 | // for tests 14 | char *get_tempnam(const char *dir, const char *prefix); 15 | 16 | #endif // TOOLS_H 17 | -------------------------------------------------------------------------------- /compat/getopt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * getopt.h -- getopt definitions for platform that are missing unistd.h 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef GETOPT_H 10 | #define GETOPT_H 11 | 12 | extern int opterr; 13 | extern int optind; 14 | extern int optopt; 15 | extern char *optarg; 16 | 17 | int getopt(int argc, char **argv, const char *opts); 18 | 19 | #endif /* GETOPT_H */ 20 | -------------------------------------------------------------------------------- /cmake/haswell.test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * haswell.test.c -- test if -march=haswell works 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | (void)argv; 15 | int argc32x8[8] = { argc, 0, 0, 0, 0, 0, 0, 0 }; 16 | __m256i argc256 = _mm256_loadu_si256((__m256i *)argc32x8); 17 | return _mm256_testz_si256(argc256, _mm256_set1_epi8(11)); 18 | } 19 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | apt_packages: 6 | - cmake 7 | - doxygen 8 | tools: 9 | python: "3.12" 10 | jobs: 11 | pre_build: 12 | # Generate doxygen XML output (api documentation) and conf.py 13 | - | 14 | mkdir build 15 | cd build 16 | cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_DOCUMENTATION=on .. 17 | cmake --build . --target doxygen 18 | 19 | sphinx: 20 | configuration: build/manual.cache/conf.py 21 | 22 | # Declare the Python requirements required to build your documentation 23 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 24 | python: 25 | install: 26 | - requirements: doc/manual/requirements.txt 27 | -------------------------------------------------------------------------------- /src/fallback/bench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bench.c -- benchmark function(s) for fallback (non-simd) implementation 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "generic/parser.h" 13 | #include "fallback/scanner.h" 14 | 15 | diagnostic_push() 16 | clang_diagnostic_ignored(missing-prototypes) 17 | 18 | int32_t zone_bench_fallback_lex(parser_t *parser, size_t *tokens) 19 | { 20 | token_t token; 21 | 22 | (*tokens) = 0; 23 | take(parser, &token); 24 | while (token.code > 0) { 25 | (*tokens)++; 26 | take(parser, &token); 27 | } 28 | 29 | return token.code ? -1 : 0; 30 | } 31 | 32 | diagnostic_pop() 33 | -------------------------------------------------------------------------------- /doc/manual/index.rst: -------------------------------------------------------------------------------- 1 | .. _documentation_top: 2 | 3 | |project| by |author| 4 | ===================== 5 | 6 | Fast and standards compliant DNS presentation format parser. 7 | 8 | DNS resource records (RRs) can be expressed in text form using the 9 | presentation format. The format is most frequently used to define a zone in 10 | master files, more commonly known as zone files, and is best considered a 11 | concise tabular serialization format with provisions for convenient editing. 12 | 13 | The format is originally defined in :RFC:`1035#section-5` and 14 | :RFC:`1034#section-3.6.1`, but as the DNS is intentionally extensible, the 15 | format has been extended over time. 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | building_sources 21 | getting_started 22 | api_reference 23 | format 24 | design_notes 25 | -------------------------------------------------------------------------------- /src/haswell/bench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bench.c -- AVX2 compilation target for benchmark function(s) 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "haswell/simd.h" 13 | #include "haswell/bits.h" 14 | #include "generic/parser.h" 15 | #include "generic/scanner.h" 16 | 17 | diagnostic_push() 18 | clang_diagnostic_ignored(missing-prototypes) 19 | 20 | int32_t zone_bench_haswell_lex(zone_parser_t *parser, size_t *tokens) 21 | { 22 | token_t token; 23 | 24 | (*tokens) = 0; 25 | take(parser, &token); 26 | while (token.code > 0) { 27 | (*tokens)++; 28 | take(parser, &token); 29 | } 30 | 31 | return token.code ? -1 : 0; 32 | } 33 | 34 | diagnostic_pop() 35 | -------------------------------------------------------------------------------- /src/westmere/bench.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bench.c -- SSE4.2 compilation target for benchmark function(s) 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "westmere/simd.h" 13 | #include "westmere/bits.h" 14 | #include "generic/parser.h" 15 | #include "generic/scanner.h" 16 | 17 | diagnostic_push() 18 | clang_diagnostic_ignored(missing-prototypes) 19 | 20 | int32_t zone_bench_westmere_lex(zone_parser_t *parser, size_t *tokens) 21 | { 22 | token_t token; 23 | 24 | (*tokens) = 0; 25 | take(parser, &token); 26 | while (token.code > 0) { 27 | (*tokens)++; 28 | take(parser, &token); 29 | } 30 | 31 | return token.code ? -1 : 0; 32 | } 33 | 34 | diagnostic_pop() 35 | -------------------------------------------------------------------------------- /doc/manual/api_reference.rst: -------------------------------------------------------------------------------- 1 | API reference 2 | ============= 3 | 4 | Return codes 5 | ------------ 6 | 7 | .. doxygengroup:: return_codes 8 | :project: doxygen 9 | :members: 10 | 11 | Parse functions 12 | --------------- 13 | 14 | .. doxygenfunction:: zone_parse 15 | :project: doxygen 16 | 17 | .. doxygenfunction:: zone_parse_string 18 | :project: doxygen 19 | 20 | Log priorities 21 | -------------- 22 | 23 | .. doxygengroup:: log_priorities 24 | :project: doxygen 25 | 26 | Log functions 27 | ------------- 28 | 29 | .. doxygenfunction:: zone_log 30 | :project: doxygen 31 | 32 | For convenience |project| defines a number of macros. 33 | 34 | .. doxygendefine:: zone_error 35 | :project: doxygen 36 | 37 | .. doxygendefine:: zone_warning 38 | :project: doxygen 39 | 40 | .. doxygendefine:: zone_info 41 | :project: doxygen 42 | -------------------------------------------------------------------------------- /tests/fallback/bits.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bits.c -- test bit manipulation instructions 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "attributes.h" 17 | #include "fallback/bits.h" 18 | 19 | void test_fallback_trailing_zeroes(void **state) 20 | { 21 | (void)state; 22 | fprintf(stderr, "test_fallback_trailing_zeroes\n"); 23 | for (uint64_t shift = 0; shift < 63; shift++) { 24 | uint64_t bit = 1llu << shift; 25 | uint64_t tz = trailing_zeroes(bit); 26 | assert_int_equal(tz, shift); 27 | } 28 | } 29 | 30 | void test_fallback_leading_zeroes(void **state) 31 | { 32 | (void)state; 33 | fprintf(stderr, "test_fallback_leading_zeroes\n"); 34 | for (uint64_t shift = 0; shift < 63; shift++) { 35 | const uint64_t bit = 1llu << shift; 36 | uint64_t lz = leading_zeroes(bit); 37 | assert_int_equal(lz, 63 - shift); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h.in -- configuration header template 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef CONFIG_H 10 | #define CONFIG_H 11 | 12 | /* Define to 1 if you have the declaration of `bswap16', and to 0 if you 13 | don't. */ 14 | #cmakedefine01 HAVE_DECL_BSWAP16 15 | 16 | /* Define to 1 if you have the declaration of `bswap32', and to 0 if you 17 | don't. */ 18 | #cmakedefine01 HAVE_DECL_BSWAP32 19 | 20 | /* Define to 1 if you have the declaration of `bswap64', and to 0 if you 21 | don't. */ 22 | #cmakedefine01 HAVE_DECL_BSWAP64 23 | 24 | /* Define to 1 if you have the header file. */ 25 | #cmakedefine HAVE_ENDIAN_H 1 26 | 27 | /* Define to 1 if you have the `getopt' function. */ 28 | #cmakedefine HAVE_GETOPT 1 29 | 30 | /* Wether or not to compile support for AVX2 */ 31 | #cmakedefine HAVE_HASWELL 1 32 | 33 | /* Wether or not to compile support for SSE4.2 */ 34 | #cmakedefine HAVE_WESTMERE 1 35 | 36 | /* Defines _XOPEN_SOURCE and _POSIX_C_SOURCE implicitly in features.h */ 37 | #ifndef _DEFAULT_SOURCE 38 | # define _DEFAULT_SOURCE 1 39 | #endif 40 | 41 | #endif // CONFIG_H 42 | -------------------------------------------------------------------------------- /doc/manual/links.rst: -------------------------------------------------------------------------------- 1 | .. |url::git| raw:: html 2 | 3 | Git 4 | 5 | .. |url::cmake| raw:: html 6 | 7 | CMake 8 | 9 | .. |url::conan| raw:: html 10 | 11 | Conan 12 | 13 | .. |url::cmocka| raw:: html 14 | 15 | cmocka 16 | 17 | .. |url::doxygen| raw:: html 18 | 19 | Doxygen 20 | 21 | .. |url::sphinx| raw:: html 22 | 23 | Sphinx 24 | 25 | .. |url::chocolatey| raw:: html 26 | 27 | Chocolatey 28 | 29 | .. |url::simdjson| raw:: html 30 | 31 | simdjson 32 | 33 | .. |url::dns-svcb| raw:: html 34 | 35 | DNS Service Bindings (SVCB) 36 | 37 | .. |url::tls-extensiontype-values| raw:: html 38 | 39 | TLS Application-Layer Protocol Negotiation (ALPN) Protocol IDs -------------------------------------------------------------------------------- /src/fallback/name.h: -------------------------------------------------------------------------------- 1 | /* 2 | * name.h -- domain name parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NAME_H 10 | #define NAME_H 11 | 12 | nonnull_all 13 | static really_inline int32_t scan_name( 14 | const char *data, 15 | size_t length, 16 | uint8_t octets[255 + ZONE_BLOCK_SIZE], 17 | size_t *lengthp) 18 | { 19 | uint8_t *l = octets, *w = octets + 1; 20 | const uint8_t *we = octets + 255; 21 | const char *t = data, *te = t + length; 22 | 23 | l[0] = 0; 24 | 25 | if (*t == '.') 26 | return (*lengthp = length) == 1 ? 0 : -1; 27 | 28 | while ((t < te) & (w < we)) { 29 | *w = (uint8_t)*t; 30 | if (*t == '\\') { 31 | uint32_t n; 32 | if (!(n = unescape(t, w))) 33 | return -1; 34 | w += 1; t += n; 35 | } else if (*t == '.') { 36 | if ((w - 1) - l > 63 || (w - 1) - l == 0) 37 | return -1; 38 | l[0] = (uint8_t)((w - 1) - l); 39 | l = w; 40 | l[0] = 0; 41 | w += 1; t += 1; 42 | } else { 43 | w += 1; t += 1; 44 | } 45 | } 46 | 47 | if ((w - 1) - l > 63) 48 | return -1; 49 | *l = (uint8_t)((w - 1) - l); 50 | 51 | if (t != te || w > we) 52 | return -1; 53 | 54 | *lengthp = (size_t)(w - octets); 55 | return *l != 0; 56 | } 57 | 58 | #endif // NAME_H 59 | -------------------------------------------------------------------------------- /src/fallback/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * parser.c -- compilation target for fallback (DNS) zone parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "generic/endian.h" 13 | #include "fallback/bits.h" 14 | #include "generic/parser.h" 15 | #include "fallback/scanner.h" 16 | #include "generic/number.h" 17 | #include "generic/ttl.h" 18 | #include "generic/time.h" 19 | #include "fallback/text.h" 20 | #include "fallback/name.h" 21 | #include "generic/ip4.h" 22 | #include "generic/ip6.h" 23 | #include "generic/base16.h" 24 | #include "generic/base32.h" 25 | #include "generic/base64.h" 26 | #include "generic/nsec.h" 27 | #include "generic/nxt.h" 28 | #include "generic/caa.h" 29 | #include "generic/ilnp64.h" 30 | #include "generic/eui.h" 31 | #include "generic/nsap.h" 32 | #include "generic/wks.h" 33 | #include "generic/loc.h" 34 | #include "generic/gpos.h" 35 | #include "generic/apl.h" 36 | #include "generic/svcb.h" 37 | #include "generic/cert.h" 38 | #include "generic/atma.h" 39 | #include "generic/algorithm.h" 40 | #include "generic/types.h" 41 | #include "generic/type.h" 42 | #include "generic/format.h" 43 | 44 | diagnostic_push() 45 | clang_diagnostic_ignored(missing-prototypes) 46 | 47 | int32_t zone_fallback_parse(parser_t *parser) 48 | { 49 | return parse(parser); 50 | } 51 | 52 | diagnostic_pop() 53 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(cmocka REQUIRED) 2 | 3 | if(HAVE_WESTMERE) 4 | set(sources ${sources} westmere/bits.c) 5 | set_source_files_properties(westmere/bits.c PROPERTIES COMPILE_FLAGS "-march=westmere") 6 | endif() 7 | if(HAVE_HASWELL) 8 | set(sources ${sources} haswell/bits.c) 9 | set_source_files_properties(haswell/bits.c PROPERTIES COMPILE_FLAGS "-march=haswell") 10 | endif() 11 | 12 | cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c semantics.c eui.c bounds.c bits.c ttl.c) 13 | 14 | set(xbounds ${CMAKE_CURRENT_SOURCE_DIR}/zones/xbounds.zone) 15 | set(xbounds_c "${CMAKE_CURRENT_BINARY_DIR}/xbounds.c") 16 | set(xxd_cmake ${CMAKE_CURRENT_SOURCE_DIR}/xxd.cmake) 17 | 18 | add_custom_command( 19 | OUTPUT "${xbounds_c}" 20 | COMMAND ${CMAKE_COMMAND} 21 | ARGS "-DINPUT_FILE=${xbounds}" "-DOUTPUT_FILE=${xbounds_c}" -P ${xxd_cmake} 22 | DEPENDS "${xbounds}" "${xxd_cmake}") 23 | 24 | add_custom_target(generate_xbounds_c DEPENDS "${xbounds_c}") 25 | 26 | target_link_libraries(zone-tests PRIVATE zone) 27 | target_sources(zone-tests PRIVATE "${xbounds_c}" tools.c fallback/bits.c ${sources}) 28 | add_dependencies(zone-tests generate_xbounds_c) 29 | if(CMAKE_C_COMPILER_ID MATCHES "Clang") 30 | target_compile_options(zone-tests PRIVATE -Wno-missing-prototypes -Wno-deprecated-declarations) 31 | elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") 32 | target_compile_options(zone-tests PRIVATE -Wno-missing-prototypes) 33 | endif() 34 | -------------------------------------------------------------------------------- /src/haswell/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * parser.c -- AVX2 specific compilation target for (DNS) zone file parser 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause. 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "haswell/simd.h" 13 | #include "generic/endian.h" 14 | #include "haswell/bits.h" 15 | #include "generic/parser.h" 16 | #include "generic/scanner.h" 17 | #include "generic/number.h" 18 | #include "generic/ttl.h" 19 | #include "westmere/time.h" 20 | #include "westmere/ip4.h" 21 | #include "generic/ip6.h" 22 | #include "generic/text.h" 23 | #include "generic/name.h" 24 | #include "generic/base16.h" 25 | #include "haswell/base32.h" 26 | #include "generic/base64.h" 27 | #include "generic/nsec.h" 28 | #include "generic/nxt.h" 29 | #include "generic/caa.h" 30 | #include "generic/ilnp64.h" 31 | #include "generic/eui.h" 32 | #include "generic/nsap.h" 33 | #include "generic/wks.h" 34 | #include "generic/loc.h" 35 | #include "generic/gpos.h" 36 | #include "generic/apl.h" 37 | #include "generic/svcb.h" 38 | #include "generic/cert.h" 39 | #include "generic/atma.h" 40 | #include "generic/algorithm.h" 41 | #include "generic/types.h" 42 | #include "westmere/type.h" 43 | #include "generic/format.h" 44 | 45 | diagnostic_push() 46 | clang_diagnostic_ignored(missing-prototypes) 47 | 48 | int32_t zone_haswell_parse(parser_t *parser) 49 | { 50 | return parse(parser); 51 | } 52 | 53 | diagnostic_pop() 54 | -------------------------------------------------------------------------------- /src/westmere/parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | * parser.c -- SSE4.2 specific compilation target for (DNS) zone file parser 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause. 7 | * 8 | */ 9 | #include "zone.h" 10 | #include "attributes.h" 11 | #include "diagnostic.h" 12 | #include "westmere/simd.h" 13 | #include "generic/endian.h" 14 | #include "westmere/bits.h" 15 | #include "generic/parser.h" 16 | #include "generic/scanner.h" 17 | #include "generic/number.h" 18 | #include "generic/ttl.h" 19 | #include "westmere/time.h" 20 | #include "westmere/ip4.h" 21 | #include "generic/ip6.h" 22 | #include "generic/text.h" 23 | #include "generic/name.h" 24 | #include "generic/base16.h" 25 | #include "westmere/base32.h" 26 | #include "generic/base64.h" 27 | #include "generic/nsec.h" 28 | #include "generic/nxt.h" 29 | #include "generic/caa.h" 30 | #include "generic/ilnp64.h" 31 | #include "generic/eui.h" 32 | #include "generic/nsap.h" 33 | #include "generic/wks.h" 34 | #include "generic/loc.h" 35 | #include "generic/gpos.h" 36 | #include "generic/apl.h" 37 | #include "generic/svcb.h" 38 | #include "generic/cert.h" 39 | #include "generic/atma.h" 40 | #include "generic/algorithm.h" 41 | #include "generic/types.h" 42 | #include "westmere/type.h" 43 | #include "generic/format.h" 44 | 45 | diagnostic_push() 46 | clang_diagnostic_ignored(missing-prototypes) 47 | 48 | int32_t zone_westmere_parse(parser_t *parser) 49 | { 50 | return parse(parser); 51 | } 52 | 53 | diagnostic_pop() 54 | -------------------------------------------------------------------------------- /scripts/check-SvcParamKeys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -e ../include/zone.h ]; then 4 | ZONE_H="../include/zone.h" 5 | elif [ -e include/zone.h ]; then 6 | ZONE_H="include/zone.h" 7 | else 8 | >&2 echo "Could not find zone.h" 9 | exit 1 10 | fi 11 | TOIMPLEMENT=0 12 | 13 | TEMPFILE=`mktemp --suffix=.xml` 14 | wget --quiet --output-document $TEMPFILE https://www.iana.org/assignments/dns-svcb/dns-svcb.xml 15 | #TEMPFILE=dns-svcb.xml 16 | RECORDS=`xmlstarlet select --template --match "/_:registry/_:registry[@id='dns-svcparamkeys']/_:record[_:value<65280]" --value-of "_:name" --output "#" --value-of "_:value" --output "#" --value-of "_:xref[last()]/@type" --output "#" --value-of "_:xref[last()]/@data" --nl ${TEMPFILE}` 17 | rm ${TEMPFILE} 18 | for RECORD in ${RECORDS} 19 | do 20 | NAME=`echo ${RECORD} | awk -F# '{ print $1 }'` 21 | VALUE=`echo ${RECORD} | awk -F# '{ print $2 }'` 22 | RECORD_TYPE=`echo ${RECORD} | awk -F# '{ print $3 }'` 23 | RECORD_REF=`echo ${RECORD} | awk -F# '{ print $4 }'` 24 | case "${RECORD_TYPE}" in 25 | text) continue;; 26 | rfc) RECORD_REF="https://www.rfc-editor.org/rfc/${RECORD_REF}.html";; 27 | draft) RECORD_REF="https://datatracker.ietf.org/doc/${RECORD_REF}";; 28 | esac 29 | MATCH_NAME=`echo $NAME | tr a-z- A-Z_` 30 | if ! grep -q "^#define ZONE_SVC_PARAM_KEY_${MATCH_NAME} " ${ZONE_H} ; then 31 | echo "${NAME} ${VALUE} ${RECORD_REF}" 32 | TOIMPLEMENT=`expr ${TOIMPLEMENT} + 1` 33 | fi 34 | done 35 | if [ $TOIMPLEMENT -eq 0 ]; then 36 | echo "All SvcParamKeys implemented" 37 | fi 38 | exit ${TOIMPLEMENT} 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, NLnet Labs. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/generic/ilnp64.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ilnp64.h -- 64-bit Locator (RFC6742 section 2.3) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef ILNP64_H 10 | #define ILNP64_H 11 | 12 | // FIXME: very likely eligable for vectorization (or optimization even), but 13 | // gains are small as the type is not frequently used 14 | nonnull_all 15 | static really_inline int32_t parse_ilnp64( 16 | parser_t *parser, 17 | const type_info_t *type, 18 | const rdata_info_t *field, 19 | rdata_t *rdata, 20 | const token_t *token) 21 | { 22 | uint16_t a[4] = { 0, 0, 0, 0 }; 23 | size_t n = 0; 24 | const char *p = token->data, *g = p; 25 | for (;;) { 26 | const uint8_t c = (uint8_t)*p; 27 | if (c == ':') { 28 | if (n == 3 || p == g || p - g > 4) 29 | break; 30 | g = p += 1; 31 | n += 1; 32 | } else { 33 | uint16_t x; 34 | if (c >= '0' && c <= '9') 35 | x = c - '0'; 36 | else if (c >= 'A' && c <= 'F') 37 | x = c - ('A' - 10); 38 | else if (c >= 'a' && c <= 'f') 39 | x = c - ('a' - 10); 40 | else 41 | break; 42 | a[n] = (uint16_t)(a[n] << 4u) + x; 43 | p += 1; 44 | } 45 | } 46 | 47 | if (n != 3 || p == g || p - g > 4 || classify[(uint8_t)*p] == CONTIGUOUS) 48 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 49 | a[0] = htobe16(a[0]); 50 | a[1] = htobe16(a[1]); 51 | a[2] = htobe16(a[2]); 52 | a[3] = htobe16(a[3]); 53 | memcpy(rdata->octets, a, 8); 54 | rdata->octets += 8; 55 | return 0; 56 | } 57 | 58 | #endif // ILNP64_H 59 | -------------------------------------------------------------------------------- /src/generic/ip4.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ip4.h -- fallback parser for IPv4 addresses 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef IP4_H 10 | #define IP4_H 11 | 12 | nonnull_all 13 | static really_inline int32_t scan_ip4(const char *text, uint8_t *wire) 14 | { 15 | const char *start = text; 16 | uint32_t round = 0; 17 | for (;;) { 18 | uint8_t digits[3], count; 19 | uint32_t octet; 20 | digits[0] = (uint8_t)text[0] - '0'; 21 | digits[1] = (uint8_t)text[1] - '0'; 22 | digits[2] = (uint8_t)text[2] - '0'; 23 | if (digits[0] > 9) 24 | return 0; 25 | else if (digits[1] > 9) 26 | (void)(count = 1), octet = digits[0]; 27 | else if (digits[2] > 9) 28 | (void)(count = 2), octet = digits[0] * 10 + digits[1]; 29 | else 30 | (void)(count = 3), octet = digits[0] * 100 + digits[1] * 10 + digits[2]; 31 | 32 | if (octet > 255 || (count > 1 && !digits[0])) 33 | return 0; 34 | text += count; 35 | wire[round++] = (uint8_t)octet; 36 | if (text[0] != '.' || round == 4) 37 | break; 38 | text += 1; 39 | } 40 | 41 | if (round != 4) 42 | return 0; 43 | return (int32_t)(text - start); 44 | } 45 | 46 | nonnull_all 47 | static really_inline int32_t parse_ip4( 48 | parser_t *parser, 49 | const type_info_t *type, 50 | const rdata_info_t *item, 51 | rdata_t *rdata, 52 | const token_t *token) 53 | { 54 | if ((size_t)scan_ip4(token->data, rdata->octets) != token->length) 55 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(item), NAME(type)); 56 | rdata->octets += 4; 57 | return 0; 58 | } 59 | 60 | #endif // IP4_H 61 | -------------------------------------------------------------------------------- /include/zone/attributes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * attributes.h -- compiler attribute abstractions 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef ZONE_ATTRIBUTES_H 10 | #define ZONE_ATTRIBUTES_H 11 | 12 | #if defined __GNUC__ 13 | # define zone_has_gnuc(major, minor) \ 14 | ((__GNUC__ > major) || (__GNUC__ == major && __GNUC_MINOR__ >= minor)) 15 | #else 16 | # define zone_has_gnuc(major, minor) (0) 17 | #endif 18 | 19 | #if defined __has_attribute 20 | # define zone_has_attribute(params) __has_attribute(params) 21 | #else 22 | # define zone_has_attribute(params) (0) 23 | #endif 24 | 25 | #if zone_has_attribute(nonnull) 26 | # define zone_nonnull(params) __attribute__((__nonnull__ params)) 27 | # define zone_nonnull_all __attribute__((__nonnull__)) 28 | #else 29 | # define zone_nonnull(params) 30 | # define zone_nonnull_all 31 | #endif 32 | 33 | #if zone_has_attribute(format) || zone_has_gnuc(2, 4) 34 | # define zone_format(params) __attribute__((__format__ params)) 35 | # if __MINGW32__ 36 | # if __MINGW_PRINTF_FORMAT 37 | # define zone_format_printf(string_index, first_to_check) \ 38 | zone_format((__MINGW_PRINTF_FORMAT, string_index, first_to_check)) 39 | # else 40 | # define zone_format_printf(string_index, first_to_check) \ 41 | zone_format((gnu_printf, string_index, first_to_check)) 42 | # endif 43 | # else 44 | # define zone_format_printf(string_index, first_to_check) \ 45 | zone_format((printf, string_index, first_to_check)) 46 | # endif 47 | #else 48 | # define zone_format(params) 49 | # define zone_format_printf(string_index, first_to_check) 50 | #endif 51 | 52 | #endif // ZONE_ATTRIBUTES_H 53 | -------------------------------------------------------------------------------- /src/generic/nxt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * nxt.h - NXT (RFC2535) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NXT_H 10 | #define NXT_H 11 | 12 | nonnull_all 13 | static really_inline int32_t scan_type( 14 | const char *data, 15 | size_t length, 16 | uint16_t *code, 17 | const mnemonic_t **mnemonic); 18 | 19 | nonnull_all 20 | static really_inline int32_t parse_nxt( 21 | parser_t *parser, 22 | const type_info_t *type, 23 | const rdata_info_t *field, 24 | rdata_t *rdata, 25 | token_t *token) 26 | { 27 | uint16_t code; 28 | const mnemonic_t *mnemonic; 29 | 30 | if (is_contiguous(token)) { 31 | if (scan_type(token->data, token->length, &code, &mnemonic) != 1) 32 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 33 | uint8_t bit = (uint8_t)(code % 8); 34 | uint8_t block = (uint8_t)(code / 8), highest_block = block; 35 | 36 | memset(rdata->octets, 0, block + 1); 37 | rdata->octets[block] = (uint8_t)(1 << (7 - bit)); 38 | 39 | take(parser, token); 40 | while (is_contiguous(token)) { 41 | if (scan_type(token->data, token->length, &code, &mnemonic) != 1) 42 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 43 | bit = (uint8_t)(code % 8); 44 | block = (uint8_t)(code / 8); 45 | if (block > highest_block) { 46 | memset(&rdata->octets[highest_block+1], 0, block - highest_block); 47 | highest_block = block; 48 | } 49 | rdata->octets[block] |= 1 << (7 - bit); 50 | take(parser, token); 51 | } 52 | 53 | rdata->octets += highest_block + 1; 54 | } 55 | 56 | return have_delimiter(parser, type, token); 57 | } 58 | 59 | #endif // NXT_H 60 | -------------------------------------------------------------------------------- /scripts/check-RR-types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -e ../include/zone.h ]; then 4 | ZONE_H="../include/zone.h" 5 | elif [ -e include/zone.h ]; then 6 | ZONE_H="include/zone.h" 7 | else 8 | >&2 echo "Could not find zone.h" 9 | exit 1 10 | fi 11 | TOIMPLEMENT=0 12 | 13 | TEMPFILE=`mktemp --suffix=.xml` 14 | wget --quiet --output-document $TEMPFILE https://www.iana.org/assignments/dns-parameters/dns-parameters.xml 15 | #TEMPFILE=dns-parameters.xml 16 | RECORDS=`xmlstarlet select --template --match "/_:registry/_:registry[@id='dns-parameters-4']/_:record[((_:value>0 and _:value!=41 and _:value<128) or (_:value>255 and _:value<65535)) and _:type!='Unassigned']" --value-of "_:type" --output "#" --value-of "_:value" --output "#" --value-of "_:xref[last()]/@type" --output "#" --value-of "_:xref[last()]/@data" --output "#" --value-of "_:file[@type='template']" --nl ${TEMPFILE}` 17 | rm ${TEMPFILE} 18 | for RECORD in ${RECORDS} 19 | do 20 | TYPE=`echo ${RECORD} | awk -F# '{ print $1 }'|sed 's/-/_/g'` 21 | VALUE=`echo ${RECORD} | awk -F# '{ print $2 }'` 22 | RECORD_TYPE=`echo ${RECORD} | awk -F# '{ print $3 }'` 23 | RECORD_REF=`echo ${RECORD} | awk -F# '{ print $4 }'` 24 | TEMPLATE=`echo ${RECORD} | awk -F# '{ print $5 }'` 25 | case "${RECORD_TYPE}" in 26 | text) continue;; 27 | rfc) RECORD_REF="https://www.rfc-editor.org/rfc/${RECORD_REF}.html";; 28 | draft) RECORD_REF="https://datatracker.ietf.org/doc/${RECORD_REF}";; 29 | person) RECORD_REF="https://www.iana.org/assignments/dns-parameters/${TEMPLATE}";; 30 | esac 31 | if ! grep -q "^#define ZONE_TYPE_${TYPE} " ${ZONE_H} ; then 32 | echo "${TYPE} ${VALUE} ${RECORD_REF}" 33 | TOIMPLEMENT=`expr ${TOIMPLEMENT} + 1` 34 | fi 35 | done 36 | if [ $TOIMPLEMENT -eq 0 ]; then 37 | echo "All RR types implemented" 38 | fi 39 | exit ${TOIMPLEMENT} 40 | 41 | -------------------------------------------------------------------------------- /src/diagnostic.h: -------------------------------------------------------------------------------- 1 | /* 2 | * diagnostic.h -- compiler diagnostic abstractions 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef DIAGNOSTIC_H 10 | #define DIAGNOSTIC_H 11 | 12 | #if _MSC_VER 13 | # define diagnostic_push() \ 14 | __pragma(warning(push)) 15 | # define msvc_diagnostic_ignored(warning_specifier) \ 16 | __pragma(warning(disable:warning_specifier)) 17 | # define diagnostic_pop() \ 18 | __pragma(warning(pop)) 19 | // Support for selectively enabling and disabling warnings via 20 | // #pragma GCC diagnostic was added in GCC 4.6 21 | // (https://gcc.gnu.org/gcc-4.6/changes.html). 22 | #elif (defined __clang__) \ 23 | || (defined __GNUC__ && (((__GNUC__ * 100) + __GNUC_MINOR__) >= 406)) 24 | # define stringify(x) #x 25 | # define paste(flag, warning) stringify(flag ## warning) 26 | # define pragma(x) _Pragma(#x) 27 | # define diagnostic_ignored(warning) pragma(warning) 28 | 29 | # define diagnostic_push() _Pragma("GCC diagnostic push") 30 | # define diagnostic_pop() _Pragma("GCC diagnostic pop") 31 | # if __clang__ 32 | # define clang_diagnostic_ignored(warning) \ 33 | diagnostic_ignored(GCC diagnostic ignored paste(-W,warning)) 34 | # else 35 | # define gcc_diagnostic_ignored(warning) \ 36 | diagnostic_ignored(GCC diagnostic ignored paste(-W,warning)) 37 | # endif 38 | #endif 39 | 40 | #if !defined diagnostic_push 41 | # define diagnostic_push() 42 | # define diagnostic_pop() 43 | #endif 44 | 45 | #if !defined gcc_diagnostic_ignored 46 | # define gcc_diagnostic_ignored(warning) 47 | #endif 48 | 49 | #if !defined clang_diagnostic_ignored 50 | # define clang_diagnostic_ignored(warning) 51 | #endif 52 | 53 | #if !defined msvc_diagnostic_ignored 54 | # define msvc_diagnostic_ignored(warning) 55 | #endif 56 | 57 | #endif // DIAGNOSTIC_H 58 | -------------------------------------------------------------------------------- /scripts/certificate-hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * certificate-hash.c -- Calculate perfect hash for certificate algorithms 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct tuple tuple_t; 15 | struct tuple { 16 | char name[8]; 17 | uint8_t code; 18 | }; 19 | 20 | // https://www.iana.org/assignments/cert-rr-types/cert-rr-types.xhtml 21 | static const tuple_t algorithms[] = { 22 | { "PKIX", 1 }, 23 | { "SPKI", 2 }, 24 | { "PGP", 3 }, 25 | { "IPKIX", 4 }, 26 | { "ISPKI", 5 }, 27 | { "IPGP", 6 }, 28 | { "ACPKIX", 7 }, 29 | { "IACPKIX", 8 }, 30 | { "OID", 254 }, 31 | { "URI", 253 } 32 | }; 33 | 34 | const uint64_t original_magic = 98112llu; 35 | 36 | static uint8_t hash(uint64_t magic, uint64_t value) 37 | { 38 | uint32_t value32 = ((value >> 32) ^ value); 39 | return (value32 * magic) >> 32; 40 | } 41 | 42 | int main(int argc, char *argv[]) 43 | { 44 | const size_t n = sizeof(algorithms)/sizeof(algorithms[0]); 45 | for (uint64_t magic = original_magic; magic < UINT64_MAX; magic++) { 46 | size_t i; 47 | uint16_t keys[256] = { 0 }; 48 | for (i=0; i < n; i++) { 49 | uint64_t value; 50 | memcpy(&value, algorithms[i].name, 8); 51 | 52 | uint8_t key = hash(magic, value); 53 | if (keys[key & 0xf]) 54 | break; 55 | keys[key & 0xf] = 1; 56 | } 57 | 58 | if (i == n) { 59 | printf("i: %zu, magic: %" PRIu64 "\n", i, magic); 60 | for (i=0; i < n; i++) { 61 | uint64_t value; 62 | memcpy(&value, algorithms[i].name, 8); 63 | uint8_t key = hash(magic, value); 64 | printf("%s: %" PRIu8 " (%" PRIu16 ")\n", algorithms[i].name, key & 0xf, algorithms[i].code); 65 | } 66 | return 0; 67 | } 68 | } 69 | 70 | printf("no magic value\n"); 71 | return 1; 72 | } 73 | -------------------------------------------------------------------------------- /src/westmere/bits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bits.h -- Westmere specific implementation of bit manipulation instructions 3 | * 4 | * Copyright (c) 2018-2022 The simdjson authors 5 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 6 | * 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | */ 9 | #ifndef BITS_H 10 | #define BITS_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { 17 | #if has_builtin(__builtin_uaddll_overflow) 18 | return __builtin_uaddll_overflow(value1, value2, (unsigned long long *)result); 19 | #else 20 | *result = value1 + value2; 21 | return *result < value1; 22 | #endif 23 | } 24 | 25 | static inline uint64_t count_ones(uint64_t input_num) { 26 | return (uint64_t)_mm_popcnt_u64(input_num); 27 | } 28 | 29 | no_sanitize_undefined 30 | static inline uint64_t trailing_zeroes(uint64_t mask) { 31 | #if has_builtin(__builtin_ctzll) 32 | return (uint64_t)__builtin_ctzll(mask); 33 | #else 34 | uint64_t result; 35 | asm("bsfq %[mask], %[result]" 36 | : [result] "=r" (result) 37 | : [mask] "mr" (mask)); 38 | return result; 39 | #endif 40 | } 41 | 42 | // result might be undefined when input_num is zero 43 | static inline uint64_t clear_lowest_bit(uint64_t input_num) { 44 | return input_num & (input_num-1); 45 | } 46 | 47 | static inline uint64_t leading_zeroes(uint64_t mask) { 48 | #if has_builtin(__builtin_clzll) 49 | return (uint64_t)__builtin_clzll(mask); 50 | #else 51 | uint64_t result; 52 | asm("bsrq %[mask], %[result]" : 53 | [result] "=r" (result) : 54 | [mask] "mr" (mask)); 55 | return 63 ^ (int)result; 56 | #endif 57 | } 58 | 59 | static inline uint64_t prefix_xor(const uint64_t bitmask) { 60 | __m128i all_ones = _mm_set1_epi8('\xFF'); 61 | __m128i result = _mm_clmulepi64_si128(_mm_set_epi64x(0ULL, (long long)bitmask), all_ones, 0); 62 | return (uint64_t)_mm_cvtsi128_si64(result); 63 | } 64 | 65 | #endif // BITS_H 66 | -------------------------------------------------------------------------------- /doc/manual/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | 4 | # -- Project information ----------------------------------------------------- 5 | 6 | project = '@PROJECT_NAME@' 7 | year = datetime.datetime.now().year 8 | copyright = f'2022-{year}, NLnet Labs' 9 | author = 'NLnet Labs' 10 | 11 | version = '@PROJECT_VERSION@' 12 | release = '@PROJECT_VERSION@' 13 | 14 | # -- General configuration --------------------------------------------------- 15 | 16 | extensions = [ 17 | 'breathe', 18 | 'sphinx_rtd_theme', 19 | 'sphinx.ext.todo', 20 | 'sphinx.ext.ifconfig', 21 | 'sphinx_tabs.tabs', 22 | 'sphinx_copybutton', 23 | 'sphinx.ext.intersphinx', 24 | 'sphinx.ext.autosectionlabel', 25 | 'notfound.extension' 26 | ] 27 | 28 | needs_sphinx = '4.0' 29 | templates_path = ['_templates'] 30 | exclude_patterns = ['_build', 'Thunbs.db', '.DS_Store'] 31 | 32 | language = 'en' 33 | 34 | # -- Options for HTML output ------------------------------------------------- 35 | 36 | html_title = f'{project}, {version}' 37 | html_theme = 'sphinx_rtd_theme' 38 | #html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 39 | html_static_path = ['_static'] 40 | html_theme_options = { 41 | 'logo_only': False, # No logo (yet) 42 | 'display_version': True 43 | } 44 | 45 | primary_domain = 'c' 46 | highlight_language = 'c' 47 | 48 | 49 | # -- Options for Breathe ----------------------------------------------------- 50 | 51 | breathe_domain_by_extension = { 'h': 'c', 'c': 'c' } 52 | breathe_show_define_initializer = True 53 | breathe_show_include = True 54 | breathe_projects = { 'doxygen': 'doxygen/xml' } 55 | breathe_default_project = 'doxygen' 56 | 57 | 58 | # -- Export variables to be used in RST -------------------------------------- 59 | 60 | rst_epilog = f''' 61 | .. |project| replace:: {project} 62 | .. |author| replace:: {author} 63 | ''' 64 | 65 | # Specify custom source directory when building for Read the Docs 66 | def setup(app): 67 | if 'READTHEDOCS' in os.environ: 68 | app.srcdir = '@PROJECT_SOURCE_DIR@' + '/doc/manual' 69 | -------------------------------------------------------------------------------- /src/fallback/text.h: -------------------------------------------------------------------------------- 1 | /* 2 | * text.h -- fallback string parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef TEXT_H 10 | #define TEXT_H 11 | 12 | nonnull_all 13 | static really_inline uint32_t unescape(const char *text, uint8_t *wire) 14 | { 15 | uint8_t d[3]; 16 | 17 | if ((d[0] = (uint8_t)text[1] - '0') > 9) { 18 | *wire = (uint8_t)text[1]; 19 | return 2u; 20 | } else { 21 | d[1] = (uint8_t)text[2] - '0'; 22 | d[2] = (uint8_t)text[3] - '0'; 23 | uint32_t o = d[0] * 100 + d[1] * 10 + d[2]; 24 | *wire = (uint8_t)o; 25 | return (o > 255 || d[1] > 9 || d[2] > 9) ? 0 : 4u; 26 | } 27 | } 28 | 29 | nonnull_all 30 | static really_inline int32_t scan_string( 31 | const char *data, 32 | size_t length, 33 | uint8_t *octets, 34 | const uint8_t *limit) 35 | { 36 | const char *text = data, *text_limit = data + length; 37 | uint8_t *wire = octets; 38 | 39 | if (likely((uintptr_t)limit - (uintptr_t)wire >= length)) { 40 | while (text < text_limit) { 41 | *wire = (uint8_t)*text; 42 | if (likely(*text != '\\')) { 43 | text += 1; 44 | wire += 1; 45 | } else { 46 | const uint32_t octet = unescape(text, wire); 47 | if (!octet) 48 | return -1; 49 | text += octet; 50 | wire += 1; 51 | } 52 | } 53 | 54 | if (text != text_limit) 55 | return -1; 56 | return (int32_t)(wire - octets); 57 | } else { 58 | while (text < text_limit && wire < limit) { 59 | *wire = (uint8_t)*text; 60 | if (likely(*text != '\\')) { 61 | text += 1; 62 | wire += 1; 63 | } else { 64 | const uint32_t octet = unescape(text, wire); 65 | if (!octet) 66 | return -1; 67 | text += octet; 68 | wire += 1; 69 | } 70 | } 71 | 72 | if (text != text_limit || wire > limit) 73 | return -1; 74 | return (int32_t)(wire - octets); 75 | } 76 | } 77 | 78 | #endif // TEXT_H 79 | -------------------------------------------------------------------------------- /src/haswell/bits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bits.h -- Haswell specific implementation of bit manipulation instructions 3 | * 4 | * Copyright (c) 2018-2023 The simdjson authors 5 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 6 | * 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | */ 9 | #ifndef BITS_H 10 | #define BITS_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static inline bool add_overflow(uint64_t value1, uint64_t value2, uint64_t *result) { 17 | #if has_builtin(__builtin_uaddll_overflow) 18 | return __builtin_uaddll_overflow(value1, value2, (unsigned long long *)result); 19 | #else 20 | *result = value1 + value2; 21 | return *result < value1; 22 | #endif 23 | } 24 | 25 | static inline uint64_t count_ones(uint64_t bits) { 26 | return (uint64_t)_mm_popcnt_u64(bits); 27 | } 28 | 29 | no_sanitize_undefined 30 | static inline uint64_t trailing_zeroes(uint64_t bits) { 31 | return (uint64_t)_tzcnt_u64(bits); 32 | } 33 | 34 | // result might be undefined when bits is zero 35 | static inline uint64_t clear_lowest_bit(uint64_t bits) { 36 | return bits & (bits - 1); 37 | } 38 | 39 | static inline uint64_t leading_zeroes(uint64_t bits) { 40 | return (uint64_t)_lzcnt_u64(bits); 41 | } 42 | 43 | static inline uint64_t prefix_xor(const uint64_t bitmask) { 44 | __m128i all_ones = _mm_set1_epi8('\xFF'); 45 | __m128i mask = _mm_set_epi64x(0ULL, (long long)bitmask); 46 | #if defined __SUNPRO_C 47 | // Oracle Developer Studio has issues generating vpclmulqdq 48 | // Oracle Solaris and Intel assembler use the opposite order for source and 49 | // destination operands. See x86 Assemble Language Reference Manual. 50 | __asm volatile ("vpclmulqdq $0,%[all_ones],%[mask],%[mask]" 51 | : [mask] "+x" (mask) 52 | : [all_ones] "x" (all_ones)); 53 | #else 54 | // There should be no such thing with a processor supporting avx2 55 | // but not clmul. 56 | mask = _mm_clmulepi64_si128(mask, all_ones, 0); 57 | #endif 58 | return (uint64_t)_mm_cvtsi128_si64(mask); 59 | } 60 | 61 | #endif // BITS_H 62 | -------------------------------------------------------------------------------- /src/generic/nsap.h: -------------------------------------------------------------------------------- 1 | /* 2 | * nsap.h -- NSAP (RFC1706) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NSAP_H 10 | #define NSAP_H 11 | 12 | // https://datatracker.ietf.org/doc/html/rfc1706 (historic) 13 | 14 | nonnull_all 15 | static really_inline int32_t parse_nsap( 16 | parser_t *parser, 17 | const type_info_t *type, 18 | const rdata_info_t *field, 19 | rdata_t *rdata, 20 | const token_t *token) 21 | { 22 | const uint8_t *data = (const uint8_t *)token->data; 23 | 24 | // RFC1706 section 7 25 | // NSAP format is "0x" (i.e., a zero followed by an 'x' character) followed 26 | // by a variable length string of hex characters (0 to 9, a to f). The hex 27 | // string is case-insensitive. "."s (i.e., periods) may be inserted in the 28 | // hex string anywhere after "0x" for readability. The "."s have no 29 | // significance other than for readability and are not propagated in the 30 | // protocol (e.g., queries or zone transfers). 31 | if (unlikely(data[0] != '0' || !(data[1] == 'X' || data[1] == 'x'))) 32 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 33 | 34 | data += 2; 35 | 36 | while (rdata->octets < rdata->limit) { 37 | uint32_t d0 = base16_table_dec_32bit_d0[data[0]]; 38 | uint32_t d1 = base16_table_dec_32bit_d1[data[1]]; 39 | if ((d0 | d1) > 0xff) { 40 | while (*data == '.') data++; 41 | d0 = base16_table_dec_32bit_d0[data[0]]; 42 | if (d0 > 0xff) 43 | break; 44 | data += 1; 45 | while (*data == '.') data++; 46 | d1 = base16_table_dec_32bit_d1[data[0]]; 47 | if (d1 > 0xff) 48 | goto bad_sequence; 49 | data += 1; 50 | } else { 51 | data += 2; 52 | } 53 | *rdata->octets++ = (uint8_t)(d0 | d1); 54 | } 55 | 56 | if (rdata->octets <= rdata->limit && data == (uint8_t *)token->data + token->length) 57 | return 0; 58 | 59 | bad_sequence: 60 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 61 | } 62 | 63 | #endif // NSAP_H 64 | -------------------------------------------------------------------------------- /src/generic/nsec.h: -------------------------------------------------------------------------------- 1 | /* 2 | * nsec.h -- NSEC (RFC4043) parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NSEC_H 10 | #define NSEC_H 11 | 12 | nonnull_all 13 | static really_inline int32_t scan_type( 14 | const char *, size_t, uint16_t *, const mnemonic_t **); 15 | 16 | typedef uint8_t nsec_t[32 /* 256 / 8 */ + 2]; 17 | 18 | nonnull_all 19 | static really_inline int32_t parse_nsec( 20 | parser_t *parser, 21 | const type_info_t *type, 22 | const rdata_info_t *field, 23 | rdata_t *rdata, 24 | token_t *token) 25 | { 26 | if (likely(is_contiguous(token))) { 27 | nsec_t *bitmap = (void *)rdata->octets; 28 | assert(rdata->octets <= rdata->limit); 29 | assert((size_t)(rdata->limit - rdata->octets) >= 256 * sizeof(nsec_t)); 30 | 31 | uint32_t highest_window = 0; 32 | uint32_t windows[256] = { 0 }; 33 | 34 | do { 35 | uint16_t code; 36 | const mnemonic_t *mnemonic; 37 | 38 | if (scan_type(token->data, token->length, &code, &mnemonic) != 1) 39 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 40 | 41 | const uint8_t bit = (uint8_t)(code % 256); 42 | const uint8_t window = code / 256; 43 | const uint8_t block = bit / 8; 44 | 45 | if (!windows[window]) 46 | memset(bitmap[window], 0, sizeof(bitmap[window])); 47 | if (window > highest_window) 48 | highest_window = window; 49 | windows[window] |= 1 << block; 50 | bitmap[window][2 + block] |= (1 << (7 - bit % 8)); 51 | take(parser, token); 52 | } while (is_contiguous(token)); 53 | 54 | for (uint32_t window = 0; window <= highest_window; window++) { 55 | if (!windows[window]) 56 | continue; 57 | const uint8_t blocks = (uint8_t)(64 - leading_zeroes(windows[window])); 58 | memmove(rdata->octets, &bitmap[window], 2 + blocks); 59 | rdata->octets[0] = (uint8_t)window; 60 | rdata->octets[1] = blocks; 61 | rdata->octets += 2 + blocks; 62 | } 63 | } 64 | 65 | return have_delimiter(parser, type, token); 66 | } 67 | 68 | #endif // NSEC_H 69 | -------------------------------------------------------------------------------- /scripts/algorithm-hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hash.c -- Calculate perfect hash for DNSSEC algorithms 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct tuple tuple_t; 15 | struct tuple { 16 | char name[24]; 17 | uint8_t code; 18 | }; 19 | 20 | // // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml 21 | static const tuple_t algorithms[] = { 22 | { "RSAMD5", 1 }, 23 | { "DH", 2 }, 24 | { "DSA", 3 }, 25 | { "ECC", 4 }, 26 | { "RSASHA1", 5 }, 27 | { "DSA-NSEC-SHA1", 6 }, 28 | { "RSASHA1-NSEC3-SHA1", 7 }, 29 | { "RSASHA256", 8 }, 30 | { "RSASHA512", 10 }, 31 | { "ECC-GOST", 12 }, 32 | { "ECDSAP256SHA256", 13 }, 33 | { "ECDSAP384SHA384", 14 }, 34 | { "INDIRECT", 252 }, 35 | { "PRIVATEDNS", 253 }, 36 | { "PRIVATEOID", 254 } 37 | }; 38 | 39 | const uint64_t original_magic = 29874llu; 40 | 41 | static uint8_t hash(uint64_t magic, uint64_t value) 42 | { 43 | uint32_t value32 = ((value >> 32) ^ value); 44 | return (value32 * magic) >> 32; 45 | } 46 | 47 | int main(int argc, char *argv[]) 48 | { 49 | const size_t n = sizeof(algorithms)/sizeof(algorithms[0]); 50 | for (uint64_t magic = original_magic; magic < UINT64_MAX; magic++) { 51 | size_t i; 52 | uint16_t keys[256] = { 0 }; 53 | for (i=0; i < n; i++) { 54 | uint64_t value; 55 | memcpy(&value, algorithms[i].name, 8); 56 | 57 | uint8_t key = hash(magic, value); 58 | if (keys[key & 0xf]) 59 | break; 60 | keys[key & 0xf] = 1; 61 | } 62 | 63 | if (i == n) { 64 | printf("i: %zu, magic: %" PRIu64 "\n", i, magic); 65 | for (i=0; i < n; i++) { 66 | uint64_t value; 67 | memcpy(&value, algorithms[i].name, 8); 68 | uint8_t key = hash(magic, value); 69 | printf("%s: %" PRIu8 " (%" PRIu16 ")\n", algorithms[i].name, key & 0xf, algorithms[i].code); 70 | } 71 | //print_table(magic); 72 | return 0; 73 | } 74 | } 75 | 76 | printf("no magic value\n"); 77 | return 1; 78 | } 79 | -------------------------------------------------------------------------------- /tests/haswell/bits.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bits-haswell.c -- test Haswell specific bit manipulation instructions 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "attributes.h" 16 | #include "haswell/bits.h" 17 | 18 | void test_haswell_trailing_zeroes(void **state) 19 | { 20 | (void)state; 21 | fprintf(stderr, "test_haswell_trailing_zeroes\n"); 22 | for (uint64_t shift = 0; shift < 63; shift++) { 23 | uint64_t bit = 1llu << shift; 24 | uint64_t tz = trailing_zeroes(bit); 25 | assert_int_equal(tz, shift); 26 | } 27 | } 28 | 29 | void test_haswell_leading_zeroes(void **state) 30 | { 31 | (void)state; 32 | fprintf(stderr, "test_haswell_leading_zeroes\n"); 33 | for (uint64_t shift = 0; shift < 63; shift++) { 34 | const uint64_t bit = 1llu << shift; 35 | uint64_t lz = leading_zeroes(bit); 36 | assert_int_equal(lz, 63 - shift); 37 | } 38 | } 39 | 40 | void test_haswell_prefix_xor(void **state) 41 | { 42 | (void)state; 43 | fprintf(stderr, "test_haswell_prefix_xor\n"); 44 | // "0001 0001 0000 0101 0000 0110 0000 0000" 45 | uint64_t mask = 46 | (1llu << 28) | (1llu << 24) | 47 | (1llu << 18) | (1llu << 16) | 48 | (1llu << 10) | (1llu << 9); 49 | // "0000 1111 0000 0011 0000 0010 0000 0000" 50 | uint64_t prefix_mask = 51 | (1llu << 27) | (1llu << 26) | (1llu << 25) | (1llu << 24) | 52 | (1llu << 17) | (1llu << 16) | 53 | (1llu << 9); 54 | 55 | assert_int_equal(prefix_xor(mask), prefix_mask); 56 | } 57 | 58 | void test_haswell_add_overflow(void **state) 59 | { 60 | (void)state; 61 | fprintf(stderr, "test_haswell_add_overflow\n"); 62 | uint64_t all_ones = UINT64_MAX; 63 | uint64_t result = 0; 64 | uint64_t overflow = add_overflow(all_ones, 2llu, &result); 65 | assert_int_equal(result, 1llu); 66 | assert_true(overflow); 67 | overflow = add_overflow(all_ones, 1llu, &result); 68 | assert_int_equal(result, 0llu); 69 | assert_true(overflow); 70 | overflow = add_overflow(all_ones, 0llu, &result); 71 | assert_int_equal(result, all_ones); 72 | assert_false(overflow); 73 | } 74 | -------------------------------------------------------------------------------- /tests/westmere/bits.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bits-westmere.c -- test Westmere specific bit manipulation instructions 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "attributes.h" 16 | #include "westmere/bits.h" 17 | 18 | void test_westmere_trailing_zeroes(void **state) 19 | { 20 | fprintf(stderr, "test_westmere_trailing_zeroes\n"); 21 | (void)state; 22 | for (uint64_t shift = 0; shift < 63; shift++) { 23 | uint64_t bit = 1llu << shift; 24 | uint64_t tz = trailing_zeroes(bit); 25 | assert_int_equal(tz, shift); 26 | } 27 | } 28 | 29 | void test_westmere_leading_zeroes(void **state) 30 | { 31 | (void)state; 32 | fprintf(stderr, "test_westmere_leading_zeroes\n"); 33 | for (uint64_t shift = 0; shift < 63; shift++) { 34 | const uint64_t bit = 1llu << shift; 35 | uint64_t lz = leading_zeroes(bit); 36 | assert_int_equal(lz, 63 - shift); 37 | } 38 | } 39 | 40 | void test_westmere_prefix_xor(void **state) 41 | { 42 | (void)state; 43 | fprintf(stderr, "test_westmere_prefix_xor\n"); 44 | // "0001 0001 0000 0101 0000 0110 0000 0000" 45 | uint64_t mask = 46 | (1llu << 28) | (1llu << 24) | 47 | (1llu << 18) | (1llu << 16) | 48 | (1llu << 10) | (1llu << 9); 49 | // "0000 1111 0000 0011 0000 0010 0000 0000" 50 | uint64_t prefix_mask = 51 | (1llu << 27) | (1llu << 26) | (1llu << 25) | (1llu << 24) | 52 | (1llu << 17) | (1llu << 16) | 53 | (1llu << 9); 54 | 55 | assert_int_equal(prefix_xor(mask), prefix_mask); 56 | } 57 | 58 | void test_westmere_add_overflow(void **state) 59 | { 60 | (void)state; 61 | fprintf(stderr, "test_westmere_add_overflow\n"); 62 | uint64_t all_ones = UINT64_MAX; 63 | uint64_t result = 0; 64 | uint64_t overflow = add_overflow(all_ones, 2llu, &result); 65 | assert_int_equal(result, 1llu); 66 | assert_true(overflow); 67 | overflow = add_overflow(all_ones, 1llu, &result); 68 | assert_int_equal(result, 0llu); 69 | assert_true(overflow); 70 | overflow = add_overflow(all_ones, 0llu, &result); 71 | assert_int_equal(result, all_ones); 72 | assert_false(overflow); 73 | } 74 | -------------------------------------------------------------------------------- /tests/tools.c: -------------------------------------------------------------------------------- 1 | /* 2 | * tools.c -- convenience tools for testing 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #if _WIN32 14 | #include 15 | #include 16 | #include 17 | 18 | #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) 19 | # define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) 20 | #endif 21 | #else 22 | #include 23 | #include 24 | #endif 25 | 26 | #include "diagnostic.h" 27 | 28 | static bool is_dir(const char *dir) 29 | { 30 | struct stat sb; 31 | if (stat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) 32 | return true; 33 | return false; 34 | } 35 | 36 | static const char *get_tmpdir(const char *dir) 37 | { 38 | const char *tmpdir = NULL; 39 | 40 | #if _WIN32 41 | diagnostic_push() 42 | msvc_diagnostic_ignored(4996) 43 | tmpdir = getenv("TMP"); 44 | diagnostic_pop() 45 | #else 46 | tmpdir = getenv("TMPDIR"); 47 | #endif 48 | if (tmpdir && is_dir(tmpdir)) 49 | return tmpdir; 50 | if (dir && is_dir(tmpdir)) 51 | return dir; 52 | #if defined(P_tmpdir) 53 | if (is_dir(P_tmpdir)) 54 | return P_tmpdir; 55 | #elif !_WIN32 56 | if (is_dir("/tmp")) 57 | return "/tmp"; 58 | #endif 59 | return NULL; 60 | } 61 | 62 | char *get_tempnam(const char *dir, const char *pfx) 63 | { 64 | const char *tmpdir = get_tmpdir(dir); 65 | if (!tmpdir) 66 | return NULL; 67 | 68 | static unsigned int count = 0; 69 | unsigned int pid; 70 | 71 | diagnostic_push() 72 | msvc_diagnostic_ignored(4996) 73 | pid = (unsigned int)getpid(); 74 | diagnostic_pop() 75 | 76 | srand(pid + count++); 77 | 78 | for (unsigned int i = 0; i < 1000; i++) { 79 | char tmp[16]; 80 | int rnd = rand(); 81 | int len = snprintf(tmp, sizeof(tmp), "%s/%s.%d", tmpdir, pfx, rnd); 82 | assert(len >= 0); 83 | char *tmpfile = malloc((unsigned int)len + 1); 84 | if (!tmpfile) 85 | return NULL; 86 | (void)snprintf(tmpfile, (unsigned int)len + 1, "%s/%s.%d", tmpdir, pfx, rnd); 87 | struct stat sb; 88 | if (stat(tmpfile, &sb) == -1) 89 | return tmpfile; 90 | free(tmpfile); 91 | } 92 | 93 | return NULL; 94 | } 95 | -------------------------------------------------------------------------------- /m4/ax_check_compile_flag.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # Check whether the given FLAG works with the current language's compiler 12 | # or gives an error. (Warnings, however, are ignored) 13 | # 14 | # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on 15 | # success/failure. 16 | # 17 | # If EXTRA-FLAGS is defined, it is added to the current language's default 18 | # flags (e.g. CFLAGS) when the check is done. The check is thus made with 19 | # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to 20 | # force the compiler to issue an error when a bad flag is given. 21 | # 22 | # INPUT gives an alternative input source to AC_COMPILE_IFELSE. 23 | # 24 | # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this 25 | # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. 26 | # 27 | # LICENSE 28 | # 29 | # Copyright (c) 2008 Guido U. Draheim 30 | # Copyright (c) 2011 Maarten Bosmans 31 | # 32 | # Copying and distribution of this file, with or without modification, are 33 | # permitted in any medium without royalty provided the copyright notice 34 | # and this notice are preserved. This file is offered as-is, without any 35 | # warranty. 36 | 37 | #serial 6 38 | 39 | AC_DEFUN([AX_CHECK_COMPILE_FLAG], 40 | [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF 41 | AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl 42 | AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ 43 | ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS 44 | _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" 45 | AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], 46 | [AS_VAR_SET(CACHEVAR,[yes])], 47 | [AS_VAR_SET(CACHEVAR,[no])]) 48 | _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) 49 | AS_VAR_IF(CACHEVAR,yes, 50 | [m4_default([$2], :)], 51 | [m4_default([$3], :)]) 52 | AS_VAR_POPDEF([CACHEVAR])dnl 53 | ])dnl AX_CHECK_COMPILE_FLAGS 54 | -------------------------------------------------------------------------------- /tests/xxd.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # xxd.cmake -- create hex dump in c include file style 3 | # 4 | # Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | # 8 | cmake_minimum_required(VERSION 3.10) 9 | 10 | # equivalent of "xxd -i input.file > output.file" 11 | 12 | if(NOT INPUT_FILE) 13 | message(FATAL_ERROR "No input file specified") 14 | endif() 15 | 16 | if(NOT OUTPUT_FILE) 17 | message(FATAL_ERROR "No output file specified") 18 | endif() 19 | 20 | # normalize paths 21 | file(TO_CMAKE_PATH "${INPUT_FILE}" input_path) 22 | file(TO_CMAKE_PATH "${OUTPUT_FILE}" output_path) 23 | 24 | get_filename_component(output_directory "${output_path}" DIRECTORY) 25 | get_filename_component(input_file "${input_path}" NAME) 26 | 27 | # make sure input file exists 28 | if(NOT EXISTS "${input_path}") 29 | message(FATAL_ERROR "Input file does not exist") 30 | endif() 31 | # make sure output directory exists 32 | if(output_directory AND NOT EXISTS "${output_directory}") 33 | message(FATAL_ERROR "Output directory (${output_directory}) does not exist") 34 | endif() 35 | 36 | # read contents in hexadecimal representation 37 | file(READ "${input_path}" input HEX) 38 | 39 | string(LENGTH "${input}" length) 40 | 41 | # file contents should be: 42 | # 43 | # unsigned char file_name[] = { 44 | # 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | # ... 46 | # }; 47 | # unsigned int file_name_len = xxx; 48 | 49 | # write to output 50 | string(APPEND output "// generated by xxd.cmake, do not edit\n") 51 | string(MAKE_C_IDENTIFIER "${input_file}" array_name) 52 | string(APPEND output "unsigned char ${array_name}[] = {\n") 53 | 54 | set(xx "[0123456789abcdef]") # a-f are guaranteed to be in lower case 55 | set(xx2 "${xx}${xx}") 56 | set(xx20 "${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}") 57 | string(REGEX REPLACE "(${xx20})" "\\1;" lines "${input}") 58 | string(REGEX REPLACE "(${xx2})" "0x\\1, " lines "${lines}") 59 | string(REGEX REPLACE ", $" "" lines "${lines}") 60 | 61 | foreach(line ${lines}) 62 | string(APPEND output " ${line}\n") 63 | endforeach() 64 | 65 | string(APPEND output "};\n") 66 | 67 | math(EXPR array_length "${length} / 2") 68 | 69 | string(APPEND output "unsigned int ${array_name}_len = ${array_length};\n") 70 | 71 | file(WRITE "${output_path}" "${output}") 72 | -------------------------------------------------------------------------------- /src/generic/eui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * eui.h -- EUI-48 and EUI-64 (RFC7043) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef EUI_H 10 | #define EUI_H 11 | 12 | nonnull((1,2)) 13 | static really_inline bool 14 | eui_base16_dec_loop_generic_32_inner(const uint8_t *s, uint8_t *o, bool last) 15 | { 16 | const uint32_t val1 = base16_table_dec_32bit_d0[s[0]] 17 | | base16_table_dec_32bit_d1[s[1]]; 18 | const uint32_t val2 = base16_table_dec_32bit_d0[s[3]] 19 | | base16_table_dec_32bit_d1[s[4]]; 20 | 21 | if (val1 > 0xff || val2 > 0xff || s[2] != '-' || (!last && s[5] != '-')) 22 | return false; 23 | 24 | o[0] = (uint8_t)val1; 25 | o[1] = (uint8_t)val2; 26 | 27 | return true; 28 | } 29 | 30 | // RFC7043 section 3.2, require xx-xx-xx-xx-xx-xx 31 | nonnull_all 32 | static really_inline int32_t parse_eui48( 33 | parser_t *parser, 34 | const type_info_t *type, 35 | const rdata_info_t *field, 36 | rdata_t *rdata, 37 | const token_t *token) 38 | { 39 | const uint8_t *input = (const uint8_t *)token->data; 40 | if (token->length == 17 && 41 | eui_base16_dec_loop_generic_32_inner(input, rdata->octets, false) && 42 | eui_base16_dec_loop_generic_32_inner(input+6, rdata->octets+2, false) && 43 | eui_base16_dec_loop_generic_32_inner(input+12, rdata->octets+4, true)) 44 | return (void)(rdata->octets += 6), 0; 45 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 46 | } 47 | 48 | // RFC7043 section 4.2, require xx-xx-xx-xx-xx-xx-xx-xx 49 | nonnull_all 50 | static really_inline int32_t parse_eui64( 51 | parser_t *parser, 52 | const type_info_t *type, 53 | const rdata_info_t *field, 54 | rdata_t *rdata, 55 | const token_t *token) 56 | { 57 | const uint8_t *input = (const uint8_t *)token->data; 58 | if (token->length == 23 && 59 | eui_base16_dec_loop_generic_32_inner(input, rdata->octets, false) && 60 | eui_base16_dec_loop_generic_32_inner(input+6, rdata->octets+2, false) && 61 | eui_base16_dec_loop_generic_32_inner(input+12, rdata->octets+4, false) && 62 | eui_base16_dec_loop_generic_32_inner(input+18, rdata->octets+6, true)) 63 | return (void)(rdata->octets += 8), 0; 64 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 65 | } 66 | 67 | #endif // EUI_H 68 | -------------------------------------------------------------------------------- /src/generic/apl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * apl.h -- Address Prefix Lists (RFC3123) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef APL_H 10 | #define APL_H 11 | 12 | static really_inline int32_t scan_apl( 13 | const char *text, size_t length, uint8_t *octets, size_t size) 14 | { 15 | uint8_t negate = text[0] == '!'; 16 | uint8_t digits[3]; 17 | int32_t count; 18 | uint32_t prefix; 19 | const uint8_t af_inet[2] = { 0x00, 0x01 }, af_inet6[2] = { 0x00, 0x02 }; 20 | 21 | // address family is immediately followed by a colon ":" 22 | if (text[negate + 1] != ':') 23 | return -1; 24 | 25 | switch (text[negate]) { 26 | case '1': 27 | if (size < 8) 28 | return -1; 29 | memcpy(octets, af_inet, sizeof(af_inet)); 30 | if (!(count = scan_ip4(&text[negate+2], &octets[4]))) 31 | return -1; 32 | count += negate + 2; 33 | digits[0] = (uint8_t)text[count+1] - '0'; 34 | digits[1] = (uint8_t)text[count+2] - '0'; 35 | if (text[count] != '/' || digits[0] > 9) 36 | return -1; 37 | if (digits[1] > 9) 38 | (void)(count += 2), prefix = digits[0]; 39 | else 40 | (void)(count += 3), prefix = digits[0] * 10 + digits[1]; 41 | if (prefix > 32 || (size_t)count != length) 42 | return -1; 43 | octets[2] = (uint8_t)prefix; 44 | octets[3] = (uint8_t)((negate << 7) | 4); 45 | return 8; 46 | case '2': 47 | if (size < 20) 48 | return -1; 49 | memcpy(octets, af_inet6, sizeof(af_inet6)); 50 | if (!(count = scan_ip6(&text[negate+2], &octets[4]))) 51 | return -1; 52 | count += negate + 2; 53 | digits[0] = (uint8_t)text[count+1] - '0'; 54 | digits[1] = (uint8_t)text[count+2] - '0'; 55 | digits[2] = (uint8_t)text[count+3] - '0'; 56 | if (text[count] != '/' || digits[0] > 9) 57 | return -1; 58 | if (digits[1] > 9) 59 | (void)(count += 2), prefix = digits[0]; 60 | else if (digits[2] > 9) 61 | (void)(count += 3), prefix = digits[0] * 10 + digits[1]; 62 | else 63 | (void)(count += 4), prefix = digits[0] * 100 + digits[1] * 10 + digits[0]; 64 | if (prefix > 128 || (size_t)count != length) 65 | return -1; 66 | octets[2] = (uint8_t)prefix; 67 | octets[3] = (uint8_t)((negate << 7) | 16); 68 | return 20; 69 | default: 70 | return -1; 71 | } 72 | } 73 | 74 | #endif // APL_H 75 | -------------------------------------------------------------------------------- /src/fallback/bits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bits.h -- bit manipulation instructions 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef BITS_H 10 | #define BITS_H 11 | 12 | #if _MSC_VER 13 | #include 14 | 15 | static really_inline uint64_t trailing_zeroes(uint64_t mask) 16 | { 17 | unsigned long index; 18 | if (_BitScanForward64(&index, mask)) 19 | return index; 20 | else 21 | return 64; 22 | } 23 | 24 | static really_inline uint64_t leading_zeroes(uint64_t mask) 25 | { 26 | unsigned long index; 27 | if (_BitScanReverse64(&index, mask)) 28 | return 63 - index; 29 | else 30 | return 64; 31 | } 32 | 33 | #else 34 | 35 | static really_inline uint64_t trailing_zeroes(uint64_t mask) 36 | { 37 | #if has_builtin(__builtin_ctzll) 38 | return (uint64_t)__builtin_ctzll(mask); 39 | #else 40 | // Code by Kim Walish from https://www.chessprogramming.org/BitScan. 41 | // Distributed under CC BY-SA 3.0. 42 | static const uint64_t magic = 0x03f79d71b4cb0a89ull; 43 | const int magictable[64] = { 44 | 0, 47, 1, 56, 48, 27, 2, 60, 45 | 57, 49, 41, 37, 28, 16, 3, 61, 46 | 54, 58, 35, 52, 50, 42, 21, 44, 47 | 38, 32, 29, 23, 17, 11, 4, 62, 48 | 46, 55, 26, 59, 40, 36, 15, 53, 49 | 34, 51, 20, 43, 31, 22, 10, 45, 50 | 25, 39, 14, 33, 19, 30, 9, 24, 51 | 13, 18, 8, 12, 7, 6, 5, 63 52 | }; 53 | 54 | return magictable[((mask ^ (mask - 1)) * magic) >> 58]; 55 | #endif 56 | } 57 | 58 | static really_inline uint64_t leading_zeroes(uint64_t mask) 59 | { 60 | #if has_builtin(__builtin_clzll) 61 | return (uint64_t)__builtin_clzll(mask); 62 | #else 63 | // Code by Kim Walish from https://www.chessprogramming.org/BitScan. 64 | // Distributed under CC BY-SA 3.0. 65 | static const uint64_t magic = 0x03f79d71b4cb0a89ull; 66 | const int magictable[64] = { 67 | 63, 16, 62, 7, 15, 36, 61, 3, 68 | 6, 14, 22, 26, 35, 47, 60, 2, 69 | 9, 5, 28, 11, 13, 21, 42, 19, 70 | 25, 31, 34, 40, 46, 52, 59, 1, 71 | 17, 8, 37, 4, 23, 27, 48, 10, 72 | 29, 12, 43, 20, 32, 41, 53, 18, 73 | 38, 24, 49, 30, 44, 33, 54, 39, 74 | 50, 45, 55, 51, 56, 57, 58, 0 75 | }; 76 | 77 | mask |= mask >> 1; 78 | mask |= mask >> 2; 79 | mask |= mask >> 4; 80 | mask |= mask >> 8; 81 | mask |= mask >> 16; 82 | mask |= mask >> 32; 83 | 84 | return magictable[(mask * magic) >> 58]; 85 | #endif 86 | } 87 | #endif // _MSC_VER 88 | #endif // BITS_H 89 | -------------------------------------------------------------------------------- /src/attributes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * attributes.h -- internal compiler attribute abstractions 3 | * 4 | * Copyright (c) 2023-2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include "zone/attributes.h" 10 | 11 | #ifndef ATTRIBUTES_H 12 | #define ATTRIBUTES_H 13 | 14 | #define nonnull(params) zone_nonnull(params) 15 | #define nonnull_all zone_nonnull_all 16 | 17 | #if _MSC_VER 18 | # define really_inline __forceinline 19 | # define never_inline __declspec(noinline) 20 | # define warn_unused_result 21 | # define no_sanitize_undefined 22 | 23 | # define likely(params) (params) 24 | # define unlikely(params) (params) 25 | 26 | #else // _MSC_VER 27 | #if defined __has_builtin 28 | # define has_builtin(params) __has_builtin(params) 29 | #else 30 | # define has_builtin(params) (0) 31 | #endif 32 | 33 | # if (zone_has_attribute(always_inline) || zone_has_gnuc(3, 1)) && ! defined __NO_INLINE__ 34 | // Compilation using GCC 4.2.1 without optimizations fails. 35 | // sorry, unimplemented: inlining failed in call to ... 36 | // GCC 4.1.2 and GCC 4.30 compile forward declared functions annotated 37 | // with __attribute__((always_inline)) without problems. Test if 38 | // __NO_INLINE__ is defined and define macro accordingly. 39 | # define really_inline inline __attribute__((always_inline)) 40 | # else 41 | # define really_inline inline 42 | # endif 43 | 44 | # if zone_has_attribute(noinline) || zone_has_gnuc(2, 96) 45 | # define never_inline __attribute__((noinline)) 46 | # else 47 | # define never_inline 48 | # endif 49 | 50 | # if zone_has_attribute(warn_unused_result) 51 | # define warn_unused_result __attribute__((warn_unused_result)) 52 | # else 53 | # define warn_unused_result 54 | # endif 55 | 56 | # if zone_has_attribute(no_sanitize) 57 | // GCC 8.1 added the no_sanitize function attribute. 58 | # define no_sanitize_undefined __attribute__((no_sanitize("undefined"))) 59 | # elif zone_has_attribute(no_sanitize_undefined) 60 | // GCC 4.9.0 added the UndefinedBehaviorSanitizer (ubsan) and the 61 | // no_sanitize_undefined function attribute. 62 | # define no_sanitize_undefined 63 | # else 64 | # define no_sanitize_undefined 65 | # endif 66 | 67 | # if has_builtin(__builtin_expect) 68 | # define likely(params) __builtin_expect(!!(params), 1) 69 | # define unlikely(params) __builtin_expect(!!(params), 0) 70 | # else 71 | # define likely(params) (params) 72 | # define unlikely(params) (params) 73 | # endif 74 | #endif 75 | 76 | #endif // ATTRIBUTES_H 77 | -------------------------------------------------------------------------------- /compat/getopt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "getopt.h" 4 | 5 | /* 6 | * Here's something you've all been waiting for: the AT&T public domain 7 | * source for getopt(3). It is the code which was given out at the 1985 8 | * UNIFORUM conference in Dallas. I obtained it by electronic mail 9 | * directly from AT&T. The people there assure me that it is indeed 10 | * in the public domain. 11 | * 12 | * There is no manual page. That is because the one they gave out at 13 | * UNIFORUM was slightly different from the current System V Release 2 14 | * manual page. The difference apparently involved a note about the 15 | * famous rules 5 and 6, recommending using white space between an option 16 | * and its first argument, and not grouping options that have arguments. 17 | * Getopt itself is currently lenient about both of these things White 18 | * space is allowed, but not mandatory, and the last option in a group can 19 | * have an argument. That particular version of the man page evidently 20 | * has no official existence, and my source at AT&T did not send a copy. 21 | * The current SVR2 man page reflects the actual behavior of this getopt. 22 | * However, I am not about to post a copy of anything licensed by AT&T. 23 | */ 24 | 25 | #define ERR(szz,czz) if(opterr){fprintf(stderr,"%s%s%c\n",argv[0],szz,czz);} 26 | 27 | int opterr = 1; 28 | int optind = 1; 29 | int optopt; 30 | char *optarg; 31 | 32 | int 33 | getopt( 34 | int argc, 35 | char **argv, 36 | const char *opts) 37 | { 38 | static int sp = 1; 39 | register int c; 40 | register char *cp; 41 | 42 | if (sp == 1) { 43 | if (optind >= argc || 44 | argv[optind][0] != '-' || argv[optind][1] == '\0') 45 | return (EOF); 46 | else if (strcmp(argv[optind], "--") == 0) { 47 | optind++; 48 | return (EOF); 49 | } 50 | } 51 | optopt = c = argv[optind][sp]; 52 | if (c == ':' || (cp = strchr(opts, c)) == NULL) { 53 | ERR(": illegal option -- ", c); 54 | if (argv[optind][++sp] == '\0') { 55 | optind++; 56 | sp = 1; 57 | } 58 | return ('?'); 59 | } 60 | if (*++cp == ':') { 61 | if (argv[optind][sp + 1] != '\0') 62 | optarg = &argv[optind++][sp + 1]; 63 | else if (++optind >= argc) { 64 | ERR(": option requires an argument -- ", c); 65 | sp = 1; 66 | return ('?'); 67 | } else 68 | optarg = argv[optind++]; 69 | sp = 1; 70 | } else { 71 | if (argv[optind][++sp] == '\0') { 72 | sp = 1; 73 | optind++; 74 | } 75 | optarg = NULL; 76 | } 77 | return (c); 78 | } 79 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile.in -- one file to make them all 3 | # 4 | # Copyright (c) 2022-2024, NLnet Labs. All rights reserved. 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | # 8 | WESTMERE = @HAVE_WESTMERE@ 9 | HASWELL = @HAVE_HASWELL@ 10 | 11 | CC = @CC@ 12 | CPPFLAGS = @CPPFLAGS@ -Iinclude -I$(SOURCE)/include -I$(SOURCE)/src -I. 13 | CFLAGS = @CFLAGS@ 14 | DEPFLAGS = @DEPFLAGS@ 15 | VPATH = @srcdir@ 16 | 17 | SOURCE = @srcdir@ 18 | 19 | SOURCES = src/zone.c src/fallback/parser.c 20 | OBJECTS = $(SOURCES:.c=.o) 21 | 22 | WESTMERE_SOURCES = src/westmere/parser.c 23 | WESTMERE_OBJECTS = $(WESTMERE_SOURCES:.c=.o) 24 | 25 | HASWELL_SOURCES = src/haswell/parser.c 26 | HASWELL_OBJECTS = $(HASWELL_SOURCES:.c=.o) 27 | 28 | NO_OBJECTS = 29 | 30 | DEPENDS = $(SOURCES:.c=.d) $(WESTMERE_SOURCES:.c=.d) $(HASWELL_SOURCES:.c=.d) 31 | 32 | # The export header automatically defines visibility macros. These macros are 33 | # required for standalone builds on Windows. I.e., exported functions must be 34 | # declared with __declspec(dllexport) for dynamic link libraries (.dll) and 35 | # __declspec(dllimport) for statically linked libraries (.lib). Define dummy 36 | # macros for compatibility. 37 | EXPORT_HEADER = include/zone/export.h 38 | 39 | .PHONY: all clean 40 | 41 | all: libzone.a 42 | 43 | clean: 44 | @rm -f .depend 45 | @rm -f libzone.a $(OBJECTS) $(EXPORT_HEADER) 46 | @rm -f $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) 47 | 48 | distclean: clean 49 | @rm -f Makefile config.h config.log config.status 50 | 51 | realclean: distclean 52 | @rm -rf autom4te* 53 | 54 | maintainer-clean: realclean 55 | 56 | devclean: realclean 57 | @rm -rf config.h.in configure 58 | 59 | libzone.a: $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) Makefile 60 | $(AR) rcs libzone.a $(OBJECTS) $($(WESTMERE)_OBJECTS) $($(HASWELL)_OBJECTS) 61 | 62 | $(EXPORT_HEADER): 63 | @mkdir -p include/zone 64 | @echo "#define ZONE_EXPORT" > $(EXPORT_HEADER) 65 | 66 | $(WESTMERE_OBJECTS): $(EXPORT_HEADER) .depend Makefile 67 | @mkdir -p src/westmere 68 | $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=westmere -o $@ -c $(SOURCE)/$(@:.o=.c) 69 | 70 | $(HASWELL_OBJECTS): $(EXPORT_HEADER) .depend Makefile 71 | @mkdir -p src/haswell 72 | $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -march=haswell -o $@ -c $(SOURCE)/$(@:.o=.c) 73 | 74 | $(OBJECTS): $(EXPORT_HEADER) .depend Makefile 75 | @mkdir -p src/fallback 76 | $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) -o $@ -c $(SOURCE)/$(@:.o=.c) 77 | @touch $@ 78 | 79 | .depend: 80 | @cat /dev/null > $@ 81 | @for x in $(DEPENDS:.d=); do echo "$${x}.o: $(SOURCE)/$${x}.c $${x}.d" >> $@; done 82 | 83 | -include .depend 84 | $(DEPENDS): 85 | -include $(DEPENDS) 86 | -------------------------------------------------------------------------------- /src/generic/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * time.h -- timestamp parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef TIME_H 10 | #define TIME_H 11 | 12 | /* number of days per month (except for February in leap years) */ 13 | static const uint8_t days_in_month[13] = { 14 | 0 /* no --month */, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 15 | 16 | static const uint16_t days_to_month[13] = { 17 | 0 /* no --month */, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; 18 | 19 | static uint64_t is_leap_year(uint64_t year) 20 | { 21 | return (year % 4 == 0) & ((year % 100 != 0) | (year % 400 == 0)); 22 | } 23 | 24 | static uint64_t leap_days(uint64_t y1, uint64_t y2) 25 | { 26 | --y1; 27 | --y2; 28 | return (y2/4 - y1/4) - (y2/100 - y1/100) + (y2/400 - y1/400); 29 | } 30 | 31 | nonnull_all 32 | static really_inline int32_t parse_time( 33 | parser_t *parser, 34 | const type_info_t *type, 35 | const rdata_info_t *field, 36 | rdata_t *rdata, 37 | const token_t *token) 38 | { 39 | if (token->length != 14) 40 | return parse_int32(parser, type, field, rdata, token); 41 | 42 | uint64_t d[14]; // YYYYmmddHHMMSS 43 | const char *p = token->data; 44 | for (int i = 0; i < 14; i++) { 45 | d[i] = (uint8_t)p[i] - '0'; 46 | if (d[i] > 9) 47 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 48 | } 49 | 50 | // code adapted from Python 2.4.1 sources (Lib/calendar.py) 51 | const uint64_t year = (d[0] * 1000) + (d[1] * 100) + (d[2] * 10) + d[3]; 52 | const uint64_t mon = (d[4] * 10) + d[5]; 53 | const uint64_t mday = (d[6] * 10) + d[7]; 54 | const uint64_t hour = (d[8] * 10) + d[9]; 55 | const uint64_t min = (d[10] * 10) + d[11]; 56 | const uint64_t sec = (d[12] * 10) + d[13]; 57 | 58 | if (year < 1970) 59 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 60 | 61 | uint64_t leap_year = is_leap_year(year); 62 | uint64_t days = 365 * (year - 1970) + leap_days(1970, year); 63 | 64 | if (!mon || mon > 12) 65 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 66 | if (!mday || mday > days_in_month[mon] + (leap_year & (mon == 2))) 67 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 68 | if (hour > 23 || min > 59 || sec > 59) 69 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 70 | 71 | days += days_to_month[mon]; 72 | days += (mon > 2) & leap_year; 73 | days += mday - 1; 74 | 75 | const uint64_t hours = days * 24 + hour; 76 | const uint64_t minutes = hours * 60 + min; 77 | const uint64_t seconds = minutes * 60 + sec; 78 | 79 | uint32_t time = htobe32((uint32_t)seconds); 80 | memcpy(rdata->octets, &time, sizeof(time)); 81 | rdata->octets += 4; 82 | return 0; 83 | } 84 | 85 | #endif // TIME_H 86 | -------------------------------------------------------------------------------- /src/generic/text.h: -------------------------------------------------------------------------------- 1 | /* 2 | * text.h -- string parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef TEXT_H 10 | #define TEXT_H 11 | 12 | nonnull_all 13 | static really_inline uint32_t unescape(const char *text, uint8_t *wire) 14 | { 15 | uint8_t d[3]; 16 | uint32_t o; 17 | 18 | if ((d[0] = (uint8_t)text[1] - '0') > 9) { 19 | o = (uint8_t)text[1]; 20 | *wire = (uint8_t)o; 21 | return 2u; 22 | } else { 23 | d[1] = (uint8_t)text[2] - '0'; 24 | d[2] = (uint8_t)text[3] - '0'; 25 | o = d[0] * 100 + d[1] * 10 + d[2]; 26 | *wire = (uint8_t)o; 27 | return (o > 255 || d[1] > 9 || d[2] > 9) ? 0 : 4u; 28 | } 29 | } 30 | 31 | typedef struct string_block string_block_t; 32 | struct string_block { 33 | uint64_t backslashes; 34 | }; 35 | 36 | nonnull_all 37 | static really_inline void copy_string_block( 38 | string_block_t *block, const char *text, uint8_t *wire) 39 | { 40 | simd_8x32_t input; 41 | simd_loadu_8x32(&input, text); 42 | simd_storeu_8x32(wire, &input); 43 | block->backslashes = simd_find_8x32(&input, '\\'); 44 | } 45 | 46 | nonnull_all 47 | static really_inline int32_t scan_string( 48 | const char *data, 49 | size_t length, 50 | uint8_t *octets, 51 | const uint8_t *limit) 52 | { 53 | const char *text = data; 54 | uint8_t *wire = octets; 55 | string_block_t block; 56 | 57 | copy_string_block(&block, text, octets); 58 | 59 | uint64_t count = 32; 60 | if (length < 32) 61 | count = length; 62 | uint64_t mask = (1llu << count) - 1u; 63 | 64 | // check for escape sequences 65 | if (unlikely(block.backslashes & mask)) 66 | goto escaped; 67 | 68 | if (length < 32) 69 | return (int32_t)count; 70 | 71 | text += count; 72 | wire += count; 73 | length -= count; 74 | 75 | do { 76 | copy_string_block(&block, text, wire); 77 | count = 32; 78 | if (length < 32) 79 | count = length; 80 | mask = (1llu << count) - 1u; 81 | 82 | // check for escape sequences 83 | if (unlikely(block.backslashes & mask)) { 84 | escaped: 85 | block.backslashes &= -block.backslashes; 86 | mask = block.backslashes - 1; 87 | count = count_ones(mask); 88 | const uint32_t octet = unescape(text+count, wire+count); 89 | if (!octet) 90 | return -1; 91 | text += count + octet; 92 | wire += count + 1; 93 | length -= count + octet; 94 | } else { 95 | text += count; 96 | wire += count; 97 | length -= count; 98 | } 99 | } while (length && wire < limit); 100 | 101 | if (length || (wire > limit)) 102 | return -1; 103 | assert(!length); 104 | return (int32_t)(wire - octets); 105 | } 106 | 107 | #endif // TEXT_H 108 | -------------------------------------------------------------------------------- /tests/base32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * base32.c -- test base32 support 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "zone.h" 15 | #include "attributes.h" 16 | #include "generic/endian.h" 17 | 18 | static int32_t add_rr( 19 | zone_parser_t *parser, 20 | const zone_name_t *owner, 21 | uint16_t type, 22 | uint16_t class, 23 | uint32_t ttl, 24 | uint16_t rdlength, 25 | const uint8_t *rdata, 26 | void *user_data) 27 | { 28 | (void)parser; 29 | (void)owner; 30 | (void)type; 31 | (void)class; 32 | (void)ttl; 33 | (void)rdlength; 34 | (void)rdata; 35 | (void)user_data; 36 | return ZONE_SUCCESS; 37 | } 38 | 39 | static const uint8_t foobar[] = 40 | { 6, 'f', 'o', 'o', 'b', 'a', 'r' }; 41 | 42 | static uint8_t origin[] = 43 | { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; 44 | 45 | /*!cmocka */ 46 | void base32_syntax(void **state) 47 | { 48 | static const struct { 49 | int32_t result; 50 | const char *base32; 51 | const uint8_t *octets; 52 | const size_t length; 53 | } tests[] = { 54 | // FIXME: add tests to ensure padding is not allowed 55 | // bad character in contiguous set 56 | { ZONE_SYNTAX_ERROR, "2t7b4g4vsa5zmi47k61mv5bv1a22bojr", NULL, 0 }, 57 | // ^ (not in base32 alphabet) 58 | // bad character after contiguous set 59 | { ZONE_SYNTAX_ERROR, "2t7b4g4vsa5smi47k61mv5bv1a22bojz", NULL, 0 }, 60 | // (not in base32 alphabet) ^ 61 | // upper case 62 | { ZONE_SUCCESS, "CPNMUOJ1E8", foobar, sizeof(foobar) }, 63 | // lower case 64 | { ZONE_SUCCESS, "cpnmuoj1e8", foobar, sizeof(foobar) }, 65 | }; 66 | 67 | (void)state; 68 | 69 | for (size_t i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 70 | char rr[256]; 71 | const char rrfmt[] = "foo. NSEC3 1 1 12 aabbccdd ( %s A NS )"; 72 | zone_parser_t parser; 73 | zone_name_buffer_t name; 74 | zone_rdata_buffer_t rdata; 75 | zone_buffers_t buffers = { 1, &name, &rdata }; 76 | zone_options_t options; 77 | int32_t result; 78 | 79 | memset(rr, 0, sizeof(rr)); 80 | (void)snprintf(rr, sizeof(rr), rrfmt, tests[i].base32); 81 | 82 | fprintf(stderr, "INPUT: '%s'\n", rr); 83 | 84 | memset(&options, 0, sizeof(options)); 85 | options.accept.callback = add_rr; 86 | options.origin.octets = origin; 87 | options.origin.length = sizeof(origin); 88 | options.default_ttl = 3600; 89 | options.default_class = ZONE_CLASS_IN; 90 | 91 | result = zone_parse_string(&parser, &options, &buffers, rr, strlen(rr), NULL); 92 | assert_int_equal(result, tests[i].result); 93 | if (tests[i].result == ZONE_SUCCESS) 94 | assert_memory_equal(rdata.octets+9, tests[i].octets, tests[i].length); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /scripts/wks-hash.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct tuple tuple_t; 8 | struct tuple { 9 | char name[16]; 10 | uint16_t code; 11 | }; 12 | 13 | static const tuple_t services[] = { 14 | { "tcpmux", 1 }, 15 | { "echo", 7 }, 16 | { "ftp-data", 20 }, 17 | { "ftp", 21 }, 18 | { "ssh", 22 }, 19 | { "telnet", 23 }, 20 | { "lmtp", 24 }, 21 | { "smtp", 25 }, 22 | { "nicname", 43 }, 23 | { "domain", 53 }, 24 | { "whoispp", 63 }, 25 | { "http", 80 }, 26 | { "kerberos", 88 }, 27 | { "npp", 92 }, 28 | { "pop3", 110 }, 29 | { "nntp", 119 }, 30 | { "ntp", 123 }, 31 | { "imap", 143 }, 32 | { "snmp", 161 }, 33 | { "snmptrap", 162 }, 34 | { "bgmp", 264 }, 35 | { "ptp-event", 319 }, 36 | { "ptp-general", 320 }, 37 | { "nnsp", 433 }, 38 | { "https", 443 }, 39 | { "submission", 587 }, 40 | // FIXME: submissions cannot be distinguished from submission by hash value 41 | // because the shared prefix is too long. it makes sense to calculate 42 | // the hash over the suffix rather then the prefix or include the 43 | // length 44 | { "submissions", 465 }, 45 | { "nntps", 563 }, 46 | { "ldaps", 636 }, 47 | { "domain-s", 853 }, 48 | { "ftps-data", 989 }, 49 | { "ftps", 990 }, 50 | { "imaps", 993 }, 51 | { "pop3s", 995 }, 52 | { "time", 37 } 53 | }; 54 | 55 | const uint64_t original_magic = 138261570llu; // established after first run 56 | 57 | static uint8_t hash(uint64_t magic, uint64_t value, size_t length) 58 | { 59 | // ensure upper case modifies numbers and dashes unconditionally too, 60 | // but does not intruduce clashes 61 | value &= 0xdfdfdfdfdfdfdfdfllu; 62 | uint32_t value32 = ((value >> 32) ^ value); 63 | return (((value32 * magic) >> 32) + length) & 0x3f; 64 | } 65 | 66 | int main(int argc, char *argv[]) 67 | { 68 | const size_t n = sizeof(services)/sizeof(services[0]); 69 | for (uint64_t magic = original_magic; magic < UINT64_MAX; magic++) { 70 | size_t i; 71 | uint16_t keys[256] = { 0 }; 72 | for (i=0; i < n; i++) { 73 | uint64_t value; 74 | memcpy(&value, services[i].name, 8); 75 | 76 | uint8_t key = hash(magic, value, strlen(services[i].name)); 77 | if (keys[key]) 78 | break; 79 | keys[key] = 1; 80 | } 81 | 82 | if (i == n) { 83 | struct { const char *name; uint16_t port; } table[64] = { 0 }; 84 | printf("services: %zu, magic: %" PRIu64 "\n", i, magic); 85 | for (i=0; i < n; i++) { 86 | uint64_t value; 87 | memcpy(&value, services[i].name, 8); 88 | uint8_t key = hash(magic, value, strlen(services[i].name)); 89 | table[key].name = services[i].name; 90 | table[key].port = services[i].code; 91 | } 92 | for (uint8_t key=0; key < sizeof(table)/sizeof(table[0]); key++) { 93 | if (table[key].port) 94 | printf(" SERVICE(\"%s\", %u),\n", table[key].name, table[key].port); 95 | else 96 | printf(" UNKNOWN_SERVICE(),\n"); 97 | } 98 | return 0; 99 | } 100 | } 101 | 102 | printf("no magic value\n"); 103 | return 1; 104 | } 105 | -------------------------------------------------------------------------------- /src/westmere/base32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * base32.h -- Fast Base32 decoder 3 | * 4 | * Copyright (c) 2023, Daniel Lemire and @aqrit. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef BASE32_H 10 | #define BASE32_H 11 | 12 | #include 13 | #include 14 | 15 | 16 | ////////////////////////// 17 | /// Source: Wojciech Muła, Daniel Lemire, Faster Base64 Encoding and Decoding Using AVX2 Instructions, 18 | /// ACM Transactions on the Web 12 (3), 2018 19 | /// https://arxiv.org/abs/1704.00605 20 | ////////////////////////// 21 | static size_t base32hex_sse(uint8_t *dst, const uint8_t *src) { 22 | static int8_t zero_masks[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, 24 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; 25 | bool valid = true; 26 | const __m128i delta_check = 27 | _mm_setr_epi8(-16, -32, -48, 70, -65, 41, -97, 9, 0, 0, 0, 0, 0, 0, 0, 0); 28 | const __m128i delta_rebase = 29 | _mm_setr_epi8(0, 0, 0, -48, -55, -55, -87, -87, 0, 0, 0, 0, 0, 0, 0, 0); 30 | const uint8_t *srcinit = src; 31 | do { 32 | __m128i v = _mm_loadu_si128((__m128i *)src); 33 | 34 | __m128i hash_key = _mm_and_si128(_mm_srli_epi32(v, 4), _mm_set1_epi8(0x0F)); 35 | __m128i check = _mm_add_epi8(_mm_shuffle_epi8(delta_check, hash_key), v); 36 | v = _mm_add_epi8(v, _mm_shuffle_epi8(delta_rebase, hash_key)); 37 | unsigned int m = (unsigned)_mm_movemask_epi8(check); 38 | 39 | if (m) { 40 | int length = (int)trailing_zeroes(m); 41 | if (length == 0) { 42 | break; 43 | } 44 | src += length; 45 | __m128i zero_mask = 46 | _mm_loadu_si128((__m128i *)(zero_masks + 16 - length)); 47 | v = _mm_andnot_si128(zero_mask, v); 48 | valid = false; 49 | } else { // common case 50 | src += 16; 51 | } 52 | v = _mm_maddubs_epi16(v, _mm_set1_epi32(0x01200120)); 53 | v = _mm_madd_epi16( 54 | v, _mm_set_epi32(0x00010400, 0x00104000, 0x00010400, 0x00104000)); 55 | // ...00000000`0000eeee`efffffgg`ggghhhhh`00000000`aaaaabbb`bbcccccd`dddd0000 56 | v = _mm_or_si128(v, _mm_srli_epi64(v, 48)); 57 | v = _mm_shuffle_epi8( 58 | v, _mm_set_epi8(0, 0, 0, 0, 0, 0, 12, 13, 8, 9, 10, 4, 5, 0, 1, 2)); 59 | 60 | /* decoded 10 bytes... but write 16 cause why not? */ 61 | _mm_storeu_si128((__m128i *)dst, v); 62 | dst += 10; 63 | } while (valid); 64 | 65 | return (size_t)(src - srcinit); 66 | } 67 | 68 | nonnull_all 69 | static really_inline int32_t parse_base32( 70 | parser_t *parser, 71 | const type_info_t *type, 72 | const rdata_info_t *field, 73 | rdata_t *rdata, 74 | const token_t *token) 75 | { 76 | size_t length = (token->length * 5) / 8; 77 | if (length > 255 || (uintptr_t)rdata->limit - (uintptr_t)rdata->octets < (length + 1)) 78 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 79 | 80 | size_t decoded = base32hex_sse(rdata->octets+1, (const uint8_t*)token->data); 81 | if (decoded != token->length) 82 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 83 | *rdata->octets = (uint8_t)length; 84 | rdata->octets += 1 + length; 85 | return 0; 86 | } 87 | 88 | #endif // BASE32_H 89 | -------------------------------------------------------------------------------- /tests/ip4.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ip4.c -- test IPv4 support 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "zone.h" 15 | 16 | static int32_t add_rr( 17 | zone_parser_t *parser, 18 | const zone_name_t *owner, 19 | uint16_t type, 20 | uint16_t class, 21 | uint32_t ttl, 22 | uint16_t rdlength, 23 | const uint8_t *rdata, 24 | void *user_data) 25 | { 26 | (void)parser; 27 | (void)owner; 28 | (void)type; 29 | (void)class; 30 | (void)ttl; 31 | (void)rdlength; 32 | (void)rdata; 33 | (void)user_data; 34 | return ZONE_SUCCESS; 35 | } 36 | 37 | static const uint8_t address_192_0_2_1[] = { 192, 0, 2, 1 }; 38 | 39 | static uint8_t origin[] = 40 | { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; 41 | 42 | /*!cmocka */ 43 | void ipv4_syntax(void **state) 44 | { 45 | static const struct { 46 | int32_t result; 47 | const char *address; 48 | const uint8_t *octets; 49 | } tests[] = { 50 | // bad number of digits in octet 51 | { ZONE_SYNTAX_ERROR, "1111.1.1.1", NULL }, 52 | { ZONE_SYNTAX_ERROR, "1.1111.1.1", NULL }, 53 | { ZONE_SYNTAX_ERROR, "1.1.1111.1", NULL }, 54 | { ZONE_SYNTAX_ERROR, "1.1.1.1111", NULL }, 55 | // bad number of octets 56 | { ZONE_SYNTAX_ERROR, "1.1.1.1.1", NULL }, 57 | { ZONE_SYNTAX_ERROR, "1.1.1", NULL }, 58 | // bad number of dots 59 | { ZONE_SYNTAX_ERROR, ".1.1.1.1", NULL }, 60 | { ZONE_SYNTAX_ERROR, "..1.1.1.1", NULL }, 61 | { ZONE_SYNTAX_ERROR, "1..1.1.1", NULL }, 62 | { ZONE_SYNTAX_ERROR, "1.1..1.1", NULL }, 63 | { ZONE_SYNTAX_ERROR, "1.1.1..1", NULL }, 64 | { ZONE_SYNTAX_ERROR, "1.1.1.1.", NULL }, 65 | { ZONE_SYNTAX_ERROR, "1.1.1.1..", NULL }, 66 | // bad number of octets, right number of dots 67 | { ZONE_SYNTAX_ERROR, "1.1.1.", NULL }, 68 | { ZONE_SYNTAX_ERROR, ".1.1.1", NULL }, 69 | // bad octets 70 | { ZONE_SYNTAX_ERROR, "1.1.1.256", NULL }, 71 | { ZONE_SYNTAX_ERROR, "1.1.256.1", NULL }, 72 | { ZONE_SYNTAX_ERROR, "1.256.1.1", NULL }, 73 | { ZONE_SYNTAX_ERROR, "256.1.1.1", NULL }, 74 | // leading zeroes 75 | { ZONE_SYNTAX_ERROR, "192.00.2.1", NULL }, 76 | { ZONE_SYNTAX_ERROR, "192.0.02.1", NULL }, 77 | { ZONE_SYNTAX_ERROR, "192.0.2.01", NULL }, 78 | { ZONE_SUCCESS, "192.0.2.1", address_192_0_2_1 } 79 | }; 80 | 81 | (void)state; 82 | 83 | for (size_t i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 84 | char rr[128]; 85 | zone_parser_t parser; 86 | zone_name_buffer_t name; 87 | zone_rdata_buffer_t rdata; 88 | zone_buffers_t buffers = { 1, &name, &rdata }; 89 | zone_options_t options; 90 | int32_t result; 91 | 92 | memset(rr, 0, sizeof(rr)); 93 | (void)snprintf(rr, sizeof(rr), "foo. A %s", tests[i].address); 94 | 95 | fprintf(stderr, "INPUT: %s\n", rr); 96 | 97 | memset(&options, 0, sizeof(options)); 98 | options.accept.callback = add_rr; 99 | options.origin.octets = origin; 100 | options.origin.length = sizeof(origin); 101 | options.default_ttl = 3600; 102 | options.default_class = ZONE_CLASS_IN; 103 | 104 | result = zone_parse_string(&parser, &options, &buffers, rr, strlen(rr), NULL); 105 | assert_int_equal(result, tests[i].result); 106 | if (tests[i].octets) 107 | assert_memory_equal(rdata.octets, tests[i].octets, 4); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to simdzone 2 | 3 | The simdzone library is open source and made available under the permissive 4 | 3-clause BSD license. 5 | 6 | Contributions are very welcome! 7 | 8 | > The original specification in [RFC1035][1] is rather ambiguous and does not 9 | > cover additions from later RFCs. See [SYNTAX.md](SYNTAX.md) for a quick 10 | > summary of the format and interpretation in simdzone. 11 | 12 | [1]: https://datatracker.ietf.org/doc/html/rfc1035#section-5 13 | 14 | ## Reference data 15 | 16 | 1. [Zone Data for .se and .nu][2] can be obtained via a DNS zone transfer. 17 | 18 | 2. The [Centralized Zone Data Service (CZDS)][3] provides access to zone data 19 | for participating gTLDs. 20 | 21 | > Downloading zone data via the browser can be problematic. The 22 | > [The CZDS API client in Java][4] can be used as a workaround. 23 | 24 | 3. The *Hint and Zone Files* can be obtained from Internet Assigned Numbers 25 | Authority (IANA) [Root Zone Management][5] page. 26 | 27 | [2]: https://internetstiftelsen.se/en/zone-data/ 28 | [3]: https://czds.icann.org/ 29 | [4]: https://github.com/icann/czds-api-client-java/ 30 | [5]: https://www.iana.org/domains/root 31 | 32 | ## Source layout 33 | 34 | `include` contains only headers required for consumption of the library. 35 | 36 | `src` contains the implementation and internal headers. 37 | 38 | The layout of `src` is (of course) inspired by the layout in simdjson. The 39 | structure is intentionally simple and without (too much) hierarchy, but as 40 | simdzone has very architecture specific code to maximize performance, there 41 | are some caveats. 42 | 43 | Processors may support multiple instruction sets. e.g. x86\_64 may support 44 | SSE4.2, AVX2 and AVX-512 instruction sets depending on the processor family. 45 | The preferred implementation is automatically selected at runtime. As a result, 46 | code may need to be compiled more than once. To improve code reuse, shared 47 | logic resides in headers rather than source files and is declared static to 48 | avoid name clashes. Bits and pieces are then mixed and matched in a 49 | `src//parser.c` compilation target to allow for multiple implementations 50 | to co-exist. 51 | 52 | Sources and headers common to all architectures that do not implement parsing 53 | for a specific data-type reside directly under `src`. Code specific to an 54 | architecture resides in a directory under `src`, e.g. `haswell` or `fallback`. 55 | `src/generic` contains scanner and parser code common to all implementations, 56 | but leans towards code shared by SIMD implementations. 57 | 58 | For example, SIMD-optimized scanner code resides in `src/generic/scanner.h`, 59 | abstractions for intrinsics reside in e.g. `src/haswell/simd.h` and `lex(...)`, 60 | which is used by all implementations, is implemented in `src/lexer.h`. 61 | A fallback scanner is implemented in `src/fallback/scanner.h`. 62 | 63 | A SIMD-optimized type parser is implemented in `src/generic/type.h`, a fallback 64 | type parser is implemented in `src/fallback/type.h`. Future versions are 65 | expected to add more optimized parsers for specific data types, even parsers 66 | that are tied to a specific instruction set. The layout accommodates these 67 | scenarios. e.g. an AVX2 optimized parser may reside in `src/haswell/.h`, 68 | an SSE4.2 optimized parser may reside in `src/westmere/.h`, etc. 69 | 70 | ## Symbol visibility 71 | 72 | All exported symbols, identifiers, etc must be prefixed with `zone_`, or 73 | `ZONE_` for macros. Non-exported symbols are generally not prefixed. e.g. 74 | `lex(...)` and `scan(...)` are declared static and as such are not required to 75 | be prefixed. 76 | -------------------------------------------------------------------------------- /src/generic/caa.h: -------------------------------------------------------------------------------- 1 | /* 2 | * caa.h -- CAA (RFC8659) parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef CAA_H 10 | #define CAA_H 11 | 12 | static const uint8_t bad_caa_chars[256] = { 13 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x00 - 0x07 14 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x08 - 0x0f 15 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x10 - 0x17 16 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x18 - 0x10f 17 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x20 - 0x27 18 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x28 - 0x2f 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 - 0x37 20 | 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x38 - 0x3f 21 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x40 - 0x47 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x48 - 0x4f 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 - 0x57 24 | 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x58 - 0x5f 25 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 - 0x67 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 - 0x6f 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 - 0x77 28 | 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x78 - 0x7f 29 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x80 - 0x87 30 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x88 - 0x8f 31 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x90 - 0x97 32 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0x98 - 0x9f 33 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xa0 - 0xa7 34 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xa8 - 0xaf 35 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xb0 - 0xb7 36 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xb8 - 0xbf 37 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xc0 - 0xc7 38 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xc8 - 0xcf 39 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xd0 - 0xd7 40 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xd8 - 0xdf 41 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xe0 - 0xe7 42 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xe0 - 0xe7 43 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xf8 - 0xff 44 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xf8 - 0xff 45 | }; 46 | 47 | nonnull_all 48 | static really_inline int32_t parse_caa_tag( 49 | parser_t *parser, 50 | const type_info_t *type, 51 | const rdata_info_t *field, 52 | rdata_t *rdata, 53 | const token_t *token) 54 | { 55 | // RFC8659 section 4.1 56 | // https://datatracker.ietf.org/doc/html/rfc8659 57 | // 58 | // Certification Authority Restriction Properties registered by IANA 59 | // https://www.iana.org/assignments/pkix-parameters/pkix-parameters.xhtml 60 | 61 | if (token->length > 255) 62 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 63 | *rdata->octets++ = (uint8_t)token->length; 64 | 65 | uint32_t bad_chars = 0; 66 | for (size_t count=0; count < token->length; count++) { 67 | const uint8_t octet = (uint8_t)token->data[count]; 68 | *rdata->octets++ = octet; 69 | bad_chars |= bad_caa_chars[octet]; 70 | } 71 | 72 | // Tags MAY contain ASCII characters "a" through "z", "A" through "Z", 73 | // and the numbers 0 through 9. Tags MUST NOT contain any other 74 | // characters. Matching of tags is case insensitive. 75 | if (bad_chars) 76 | SEMANTIC_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 77 | 78 | return 0; 79 | } 80 | 81 | #endif // CAA_H 82 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to simdzone will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.2.5] - 2025-??-?? 9 | 10 | ### Added 11 | 12 | ### Fixed 13 | 14 | ## [0.2.4] - 2025-12-04 15 | 16 | ### Added 17 | 18 | ### Fixed 19 | 20 | - Correct lengths for GOST R 34.10-2012 and SM3 delegation signer (DS) digest 21 | algorithms 22 | - Require the AMTRELAY relay field to be `.` for the no gateway relay type as 23 | specified by RFC 8777 (#257) 24 | 25 | ## [0.2.3] - 2025-09-03 26 | 27 | ### Added 28 | 29 | - check_pie: match nsd support (#253). 30 | 31 | ### Fixed 32 | 33 | - Fix tests to initialize padding (#252). 34 | - Fix for #253, add acx_nlnetlabs.m4 in the repo and allow CFLAGS passed to 35 | configure to set the flags. 36 | 37 | ## [0.2.2] - 2025-04-24 38 | 39 | ### Added 40 | 41 | - Support for EID, NIMLOC, SINK, TALINK, DSYNC, DOA, AMTRELAY and IPN RR types. 42 | 43 | ### Fixed 44 | 45 | - Empty base16 and base64 in CDS and CDNSKEY can be represented with a '0'. 46 | As specified in Section 4 of RFC 8078. 47 | - Initialise padding after the file buffer (#249). 48 | - Fix type NSAP-PTR (#250). 49 | - Fix LOC poweroften lookup (#251). 50 | 51 | ## [0.2.1] - 2025-01-17 52 | 53 | ### Fixed 54 | 55 | - Cleanup westmere and haswell object files (#244) Thanks @fobser 56 | - Out of tree builds (NLnetLabs/nsd#415) 57 | - Fix function declarations for fallback detection routine in isadetection.h. 58 | 59 | ## [0.2.0] - 2024-12-12 60 | 61 | ### Added 62 | 63 | - Add semantic checks for DS and ZONEMD digests (NLnetLabs/nsd#205). 64 | - Support registering a callback for $INCLUDE entries (NLnetLabs/nsd#229). 65 | - Add tls-supported-groups SvcParam support. 66 | - Check iana registries for unimplemented (new) RR types and SvcParamKeys. 67 | - Add support for NINFO, RKEY, RESINFO, WALLET, CLA and TA RR types. 68 | 69 | ### Fixed 70 | 71 | - Prepend -march to CFLAGS to fix architecture detection (NLnetLabs/nsd#372). 72 | - Fix propagation of implicit TTLs (NLnetLabs/nsd#375). 73 | - Fix detection of Westmere architecture by checking for CLMUL too. 74 | - Fix compilation on NetBSD (#233). 75 | - Fix reading specialized symbolic links (NLnetLabs/nsd#380). 76 | 77 | ## [0.1.1] - 2024-07-19 78 | 79 | ### Added 80 | 81 | - Test to verify configure.ac and Makefile.in are correct. 82 | - Add support for reading from stdin if filename is "-". 83 | - Add support for building with Oracle Developer Studio 12.6. 84 | - Add support for "time" service for Well-Know Services (WKS) RR. 85 | 86 | ### Fixed 87 | 88 | - Fix makefile dependencies. 89 | - Fix makefile to use source directory for build dependencies. 90 | - Fix changelog to reflect v0.1.0 release. 91 | - Update makefile to not use target-specific variables. 92 | - Fix makefile clean targets. 93 | - Fix state keeping in fallback scanner for contiguous and quoted. 94 | - Fix bug in name scanner. 95 | - Fix type mnemonic parsing in fallback parser. 96 | - Fix endian.h to include machine/endian.h on OpenBSD releases before 5.6. 97 | - Fix use after free on buffer resize. 98 | - Fix parsing of numeric protocols in WKS RRs. 99 | - Make devclean target depend on realclean target. 100 | - Fix detection of AVX2 support by checking generic AVX support by the 101 | processor and operating system (#222). 102 | 103 | ### Changed 104 | 105 | - Make relative includes relative to current working directory. 106 | - Split Autoconf and CMake compiler tests for supported SIMD instructions. 107 | 108 | ## [0.1.0] - 2024-04-16 109 | 110 | ### Added 111 | 112 | - Initial release. 113 | -------------------------------------------------------------------------------- /src/haswell/base32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * base32.h -- Fast Base32 decoder 3 | * 4 | * Copyright (c) 2023, Daniel Lemire and @aqrit. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef BASE32_H 10 | #define BASE32_H 11 | 12 | #include 13 | 14 | ////////////////////////// 15 | /// Source: Wojciech Muła, Daniel Lemire, Faster Base64 Encoding and Decoding Using AVX2 Instructions, 16 | /// ACM Transactions on the Web 12 (3), 2018 17 | /// https://arxiv.org/abs/1704.00605 18 | ////////////////////////// 19 | 20 | static size_t base32hex_avx(uint8_t *dst, const uint8_t *src) { 21 | static int8_t zero_masks256[64] = { 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 25 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; 26 | bool valid = true; 27 | const __m256i delta_check = _mm256_setr_epi8( 28 | -16, -32, -48, 70, -65, 41, -97, 9, 0, 0, 0, 0, 0, 0, 0, 0, -16, -32, -48, 29 | 70, -65, 41, -97, 9, 0, 0, 0, 0, 0, 0, 0, 0); 30 | const __m256i delta_rebase = _mm256_setr_epi8( 31 | 0, 0, 0, -48, -55, -55, -87, -87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -48, 32 | -55, -55, -87, -87, 0, 0, 0, 0, 0, 0, 0, 0); 33 | const uint8_t *srcinit = src; 34 | do { 35 | __m256i v = _mm256_loadu_si256((__m256i *)src); 36 | 37 | __m256i hash_key = 38 | _mm256_and_si256(_mm256_srli_epi32(v, 4), _mm256_set1_epi8(0x0F)); 39 | __m256i check = 40 | _mm256_add_epi8(_mm256_shuffle_epi8(delta_check, hash_key), v); 41 | v = _mm256_add_epi8(v, _mm256_shuffle_epi8(delta_rebase, hash_key)); 42 | unsigned int m = (unsigned)_mm256_movemask_epi8(check); 43 | 44 | if (m) { 45 | int length = (int)trailing_zeroes(m); 46 | if (length == 0) { 47 | break; 48 | } 49 | src += length; 50 | __m256i zero_mask = 51 | _mm256_loadu_si256((__m256i *)(zero_masks256 + 32 - length)); 52 | v = _mm256_andnot_si256(zero_mask, v); 53 | valid = false; 54 | } else { // common case 55 | src += 32; 56 | } 57 | v = _mm256_maddubs_epi16(v, _mm256_set1_epi32(0x01200120)); 58 | v = _mm256_madd_epi16( 59 | v, _mm256_set_epi32(0x00010400, 0x00104000, 0x00010400, 0x00104000, 60 | 0x00010400, 0x00104000, 0x00010400, 0x00104000)); 61 | // ...00000000`0000eeee`efffffgg`ggghhhhh`00000000`aaaaabbb`bbcccccd`dddd0000 62 | v = _mm256_or_si256(v, _mm256_srli_epi64(v, 48)); 63 | v = _mm256_shuffle_epi8( 64 | v, _mm256_set_epi8(0, 0, 0, 0, 0, 0, 12, 13, 8, 9, 10, 4, 5, 0, 1, 2, 0, 65 | 0, 0, 0, 0, 0, 12, 13, 8, 9, 10, 4, 5, 0, 1, 2)); 66 | // 5. store bytes 67 | _mm_storeu_si128((__m128i *)dst, _mm256_castsi256_si128(v)); 68 | dst += 10; 69 | _mm_storeu_si128((__m128i *)dst, _mm256_extractf128_si256(v, 1)); 70 | dst += 10; 71 | 72 | } while (valid); 73 | 74 | return (size_t)(src - srcinit); 75 | } 76 | 77 | nonnull_all 78 | static really_inline int32_t parse_base32( 79 | parser_t *parser, 80 | const type_info_t *type, 81 | const rdata_info_t *field, 82 | rdata_t *rdata, 83 | const token_t *token) 84 | { 85 | size_t length = (token->length * 5) / 8; 86 | if (length > 255 || (uintptr_t)rdata->limit - (uintptr_t)rdata->octets < (length + 1)) 87 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 88 | 89 | size_t decoded = base32hex_avx(rdata->octets+1, (const uint8_t*)token->data); 90 | if (decoded != token->length) 91 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 92 | *rdata->octets = (uint8_t)length; 93 | rdata->octets += 1 + length; 94 | return 0; 95 | } 96 | 97 | #endif // BASE32_H 98 | -------------------------------------------------------------------------------- /src/generic/cert.h: -------------------------------------------------------------------------------- 1 | /* 2 | * cert.h 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef CERT_H 10 | #define CERT_H 11 | 12 | // https://www.iana.org/assignments/cert-rr-types/cert-rr-types.xhtml 13 | 14 | typedef struct certificate_type certificate_type_t; 15 | struct certificate_type { 16 | struct { 17 | char name[8]; 18 | size_t length; 19 | } key; 20 | uint16_t value; 21 | }; 22 | 23 | #define BAD_CERTIFICATE_TYPE(value) \ 24 | { { "", 0 }, 0 } 25 | #define CERTIFICATE_TYPE(name, value) \ 26 | { { name, sizeof(name) - 1 }, value } 27 | 28 | static const certificate_type_t certificate_types[] = { 29 | BAD_CERTIFICATE_TYPE(0), 30 | CERTIFICATE_TYPE("PKIX", 1), 31 | CERTIFICATE_TYPE("SPKI", 2), 32 | CERTIFICATE_TYPE("PGP", 3), 33 | CERTIFICATE_TYPE("IPKIX", 4), 34 | CERTIFICATE_TYPE("ISPKI", 5), 35 | CERTIFICATE_TYPE("IPGP", 6), 36 | CERTIFICATE_TYPE("ACPKIX", 7), 37 | CERTIFICATE_TYPE("IACPKIX", 8), 38 | CERTIFICATE_TYPE("URI", 253), 39 | CERTIFICATE_TYPE("OID", 254), 40 | }; 41 | 42 | static const certificate_type_t *certificate_type_map[16] = { 43 | &certificate_types[5], // ISPKI (0) 44 | &certificate_types[0], 45 | &certificate_types[0], 46 | &certificate_types[0], 47 | &certificate_types[0], 48 | &certificate_types[0], 49 | &certificate_types[10], // OID (6) 50 | &certificate_types[0], 51 | &certificate_types[3], // PGP (8) 52 | &certificate_types[4], // IPKIX (9) 53 | &certificate_types[2], // SPKI (10) 54 | &certificate_types[1], // PKIX (11) 55 | &certificate_types[8], // IACPKIX (12) 56 | &certificate_types[9], // URI (13) 57 | &certificate_types[6], // IPGP (14) 58 | &certificate_types[7] // ACPKIX (15) 59 | }; 60 | 61 | // magic value generated using certificate-hash.c 62 | static uint8_t certificate_hash(uint64_t value) 63 | { 64 | value = le64toh(value); 65 | uint32_t value32 = (uint32_t)((value >> 32) ^ value); 66 | return (uint8_t)((value32 * 98112ull) >> 32) & 0xf; 67 | } 68 | 69 | nonnull_all 70 | static really_inline int32_t scan_certificate_type( 71 | const char *data, size_t length, uint16_t *type) 72 | { 73 | static const int8_t zero_masks[48] = { 74 | -1, -1, -1, -1, -1, -1, -1, -1, 75 | -1, -1, -1, -1, -1, -1, -1, -1, 76 | -1, -1, -1, -1, -1, -1, -1, -1, 77 | -1, -1, -1, -1, -1, -1, -1, -1, 78 | 0, 0, 0, 0, 0, 0, 0, 0, 79 | 0, 0, 0, 0, 0, 0, 0, 0 80 | }; 81 | 82 | if ((uint8_t)*data - '0' > 9) { 83 | uint64_t input; 84 | memcpy(&input, data, 8); 85 | static const uint64_t letter_mask = 0x4040404040404040llu; 86 | // convert to upper case 87 | input &= ~((input & letter_mask) >> 1); 88 | // zero out non-relevant bytes 89 | uint64_t zero_mask; 90 | memcpy(&zero_mask, &zero_masks[32 - (length & 0xf)], 8); 91 | input &= zero_mask; 92 | const uint8_t index = certificate_hash(input); 93 | assert(index < 16); 94 | const certificate_type_t *certificate_type = certificate_type_map[index]; 95 | uint64_t name; 96 | memcpy(&name, certificate_type->key.name, 8); 97 | *type = certificate_type->value; 98 | return (input == name) & 99 | (length == certificate_type->key.length) & 100 | (*type != 0); 101 | } 102 | 103 | return scan_int16(data, length, type); 104 | } 105 | 106 | nonnull_all 107 | static really_inline int32_t parse_certificate_type( 108 | parser_t *parser, 109 | const type_info_t *type, 110 | const rdata_info_t *field, 111 | rdata_t *rdata, 112 | const token_t *token) 113 | { 114 | uint16_t cert; 115 | if (!scan_certificate_type(token->data, token->length, &cert)) 116 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 117 | cert = htobe16(cert); 118 | memcpy(rdata->octets, &cert, 2); 119 | rdata->octets += 2; 120 | return 0; 121 | } 122 | 123 | #endif // CERT_H 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://github.com/NLnetLabs/simdzone/actions/workflows/build-test.yml/badge.svg) 2 | [![Coverity Status](https://scan.coverity.com/projects/27509/badge.svg)](https://scan.coverity.com/projects/nlnetlabs-simdzone) 3 | [![Mastodon Follow](https://img.shields.io/mastodon/follow/109262826617293067?domain=https%3A%2F%2Ffosstodon.org&style=social)](https://fosstodon.org/@nlnetlabs) 4 | 5 | # simdzone: Parsing zone files really fast 6 | 7 | Fast and standards compliant DNS presentation format parser. 8 | 9 | DNS resource records (RRs) can be expressed in text form using the 10 | presentation format. The format is most frequently used to define a zone in 11 | master files, more commonly known as zone files, and is best considered a 12 | tabular serialization format with provisions for convenient editing. 13 | 14 | The format is originally defined in [RFC1035 section 5][rfc1035-section-5] and 15 | [RFC1034 section 3.6.1][rfc1034-section-3-6-1], but as the DNS is 16 | intentionally extensible, the format has been extended over time too. 17 | 18 | This project provides a lightning fast presentation format deserializer (and 19 | serializer eventually) for other projects to leverage. Learn more about 20 | simdzone by reading the [documentation](https://simdzone.docs.nlnetlabs.nl/). 21 | 22 | ## Research paper 23 | 24 | * Jeroen Koekkoek and Daniel Lemire, [Parsing Millions of DNS Records per Second](https://arxiv.org/abs/2411.12035), Software: Practice and Experience (to appear) 25 | 26 | 27 | 28 | 29 | ## Motivation 30 | Zone files can become quite large (.com ~24G, .se ~1.3G) and the parser in 31 | NSD left something to be desired. simdjson demonstrates that applying SIMD 32 | instructions for parsing structured text can significantly boost performance. 33 | simdzone, whose name is a play on [simdjson][simdjson], aims to achieve a 34 | similar performance boost for parsing zone data. 35 | 36 | > Currently SSE4.2 and AVX2 are supported, a fallback is used otherwise. 37 | 38 | > simdzone copies some code from the [simdjson][simdjson] project, with 39 | > permission to use and distribute it under the terms of 40 | > [The 3-Clause BSD License][bsd-3-clause]. 41 | 42 | [rfc1035-section-5]: https://datatracker.ietf.org/doc/html/rfc1035#section-5 43 | [rfc1034-section-3-6-1]: https://datatracker.ietf.org/doc/html/rfc1034#section-3.6.1 44 | [nsd]: https://nlnetlabs.nl/projects/nsd/about/ 45 | [simdjson]: https://github.com/simdjson/simdjson 46 | [bsd-3-clause]: https://opensource.org/license/bsd-3-clause/ 47 | 48 | ## Results 49 | Running `zone-bench` on my system (Intel Core i7-1065G7) against an older 50 | `.com` zone file of 12482791271 bytes under Linux (Fedora 39). 51 | 52 | clang version 17.0.6, release mode: 53 | ``` 54 | $ time ./zone-bench parse ../../zones/com.zone 55 | Selected target haswell 56 | Parsed 341535548 records 57 | 58 | real 0m13.533s 59 | user 0m12.355s 60 | sys 0m1.160s 61 | ``` 62 | 63 | There are bound to be bugs and quite possibly smarter ways of implementing 64 | some operations, but the results are promising. 65 | 66 | ## Compiling 67 | Make sure the following tools are installed: 68 | * C toolchain (the set of tools to compile C code) 69 | * [cmocka](https://cmocka.org/) (if configured with `-DBUILD_TESTING=on`) 70 | * [Doxygen](https://www.doxygen.nl/) (if configured with `-DBUILD_DOCUMENTATION=on`) 71 | * [Sphinx](https://www.sphinx-doc.org/en/master/) (if configured with `-DBUILD_DOCUMENTATION=on`) 72 | 73 | To compile in release mode: 74 | ``` 75 | $ cd zone-parser 76 | $ mkdir build 77 | $ cd build 78 | $ cmake -DCMAKE_BUILD_TYPE=Release .. 79 | $ cmake --build . 80 | ``` 81 | 82 | To compile in debug mode with testing: 83 | ``` 84 | $ cd zone-parser 85 | $ mkdir build 86 | $ cd build 87 | $ cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=on .. 88 | $ cmake --build . 89 | ``` 90 | 91 | ## Contributing 92 | Contributions in any way, shape or form are very welcome! Please see 93 | [CONTRIBUTING.md](CONTRIBUTING.md) to find out how you can help. 94 | 95 | Design decisions and notes on the [FORMAT](FORMAT.md). 96 | -------------------------------------------------------------------------------- /tests/time.c: -------------------------------------------------------------------------------- 1 | /* 2 | * time.c -- test RRSIG time stamp support 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "zone.h" 16 | #include "attributes.h" 17 | #include "generic/endian.h" 18 | 19 | static int32_t add_rr( 20 | zone_parser_t *parser, 21 | const zone_name_t *owner, 22 | uint16_t type, 23 | uint16_t class, 24 | uint32_t ttl, 25 | uint16_t rdlength, 26 | const uint8_t *rdata, 27 | void *user_data) 28 | { 29 | (void)parser; 30 | (void)owner; 31 | (void)type; 32 | (void)class; 33 | (void)ttl; 34 | (void)rdlength; 35 | (void)rdata; 36 | (void)user_data; 37 | return 0; 38 | } 39 | 40 | static uint8_t origin[] = 41 | { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; 42 | 43 | /*!cmocka */ 44 | void time_stamp_syntax(void **state) 45 | { 46 | static const struct { 47 | const char *timestamp; 48 | uint32_t seconds; 49 | int32_t result; 50 | } tests[] = { 51 | // Time specified in seconds since epoch 52 | { "4294967295", 4294967295, ZONE_SUCCESS }, 53 | // one second over maximum value 54 | { "4294967296", 0, ZONE_SYNTAX_ERROR }, 55 | // starts with zero 56 | { "01", 1, ZONE_SUCCESS }, 57 | // Time specified as YYYYMMDDHHmmSS 58 | // bad number of digits 59 | { "202301010101", 0, ZONE_SYNTAX_ERROR }, 60 | { "202301010101010", 0, ZONE_SYNTAX_ERROR }, 61 | // year before 1970 62 | { "19690101010101", 0, ZONE_SYNTAX_ERROR }, 63 | // year after 2106 64 | { "21070101010101", 28319565, ZONE_SUCCESS }, 65 | // month 0 66 | { "20230001010101", 0, ZONE_SYNTAX_ERROR }, 67 | // month 13 68 | { "20231301010101", 0, ZONE_SYNTAX_ERROR }, 69 | // february 29 non-leap year 70 | { "20230229010101", 0, ZONE_SYNTAX_ERROR }, 71 | // february 29 leap year 72 | { "20240229010101", 1709168461, ZONE_SUCCESS }, 73 | // hour 24 74 | { "20230101240101", 0, ZONE_SYNTAX_ERROR }, 75 | // minute 60 76 | { "20230101016001", 0, ZONE_SYNTAX_ERROR }, 77 | // correct time stamp 78 | { "20230704160000", 1688486400, ZONE_SUCCESS } 79 | }; 80 | 81 | (void)state; 82 | 83 | for (size_t i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 84 | zone_parser_t parser; 85 | zone_name_buffer_t name; 86 | zone_rdata_buffer_t rdata; 87 | zone_buffers_t buffers = { 1, &name, &rdata }; 88 | zone_options_t options; 89 | int32_t result; 90 | 91 | #define FORMAT "host.example.com. 86400 IN RRSIG A 5 3 86400 %s (\n" \ 92 | " 20030220173103 2642 example.com.\n" \ 93 | " oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTr\n" \ 94 | " PYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o\n" \ 95 | " B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3t\n" \ 96 | " GNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG\n" \ 97 | " J5D6fwFm8nN+6pBzeDQfsS3Ap3o= )" 98 | 99 | size_t size = strlen(FORMAT) + strlen(tests[i].timestamp) + ZONE_BLOCK_SIZE + 1; 100 | char *rr = calloc((size_t)size + 1, 1); 101 | (void)snprintf(rr, size, FORMAT, tests[i].timestamp); 102 | 103 | fprintf(stderr, "INPUT: %s\n", rr); 104 | memset(&options, 0, sizeof(options)); 105 | options.accept.callback = add_rr; 106 | options.origin.octets = origin; 107 | options.origin.length = sizeof(origin); 108 | options.default_ttl = 3600; 109 | options.default_class = ZONE_CLASS_IN; 110 | 111 | result = zone_parse_string(&parser, &options, &buffers, rr, strlen(rr), NULL); 112 | free(rr); 113 | assert_int_equal(result, tests[i].result); 114 | if (tests[i].result != ZONE_SUCCESS) 115 | continue; 116 | uint32_t seconds; 117 | memcpy(&seconds, rdata.octets+8, sizeof(seconds)); 118 | seconds = be32toh(seconds); 119 | assert_int_equal(seconds, tests[i].seconds); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /cmake/Modules/Findcmocka/runner.c.in: -------------------------------------------------------------------------------- 1 | /* 2 | * runner.c.in -- boilerplate test runner for cmocka CMake module 3 | * 4 | * Copyright (c) 2020-2023, Jeroen Koekkoek 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | static struct { 18 | int print_help; 19 | const char *group_pattern; 20 | const char *test_pattern; 21 | } options = { 0, "*", "*" }; 22 | 23 | struct group { 24 | const char *name; 25 | const struct CMUnitTest *tests; 26 | CMFixtureFunction setup; 27 | CMFixtureFunction teardown; 28 | }; 29 | 30 | /* 31 | * Copied from Eclipse Cyclone DDS (src/cmake/Modules/CUnit/src/main.c.in) 32 | * under the terms of the BSD-3-Clause license on January 19th 2020. 33 | * 34 | * Copyright (c) 2006 to 2018, ADLINK Technology Limited and others 35 | */ 36 | static int patmatch(const char *pat, const char *str) 37 | { 38 | while (*pat) { 39 | if (*pat == '?') { 40 | /* any character will do */ 41 | if (*str++ == 0) { 42 | return 0; 43 | } 44 | pat++; 45 | } else if (*pat == '*') { 46 | /* collapse a sequence of wildcards, requiring as many 47 | characters in str as there are ?s in the sequence */ 48 | while (*pat == '*' || *pat == '?') { 49 | if (*pat == '?' && *str++ == 0) { 50 | return 0; 51 | } 52 | pat++; 53 | } 54 | /* try matching on all positions where str matches pat */ 55 | while (*str) { 56 | if (*str == *pat && patmatch(pat+1, str+1)) { 57 | return 1; 58 | } 59 | str++; 60 | } 61 | return *pat == 0; 62 | } else { 63 | /* only an exact match */ 64 | if (*str++ != *pat++) { 65 | return 0; 66 | } 67 | } 68 | } 69 | 70 | return *str == 0; 71 | } 72 | 73 | static void usage(const char *prog) 74 | { 75 | fprintf(stderr, "Usage: %s OPTIONS\n", prog); 76 | fprintf(stderr, "Try '%s -h' for more information\n", prog); 77 | } 78 | 79 | static void help(const char *prog) 80 | { 81 | printf("Usage: %s [OPTIONS]\n", prog); 82 | printf("\n"); 83 | printf("Options:\n"); 84 | printf(" -h Show this help message and exit\n"); 85 | printf(" -g PATTERN Run only tests in suites matching pattern\n"); 86 | printf(" -t PATTERN Run only tests matching pattern\n"); 87 | printf("\n"); 88 | } 89 | 90 | static int parse_options(int argc, char *argv[]) 91 | { 92 | int err = 0; 93 | 94 | for (int i = 1; err == 0 && i < argc; i++) { 95 | switch ((argv[i][0] == '-') ? argv[i][1] : 0) { 96 | case 'h': 97 | options.print_help = 1; 98 | break; 99 | case 'g': 100 | if ((i+1) < argc) { 101 | options.group_pattern = argv[++i]; 102 | break; 103 | } 104 | /* fall through */ 105 | case 't': 106 | if ((i+1) < argc) { 107 | options.test_pattern = argv[++i]; 108 | break; 109 | } 110 | /* fall through */ 111 | default: 112 | err = 1; 113 | break; 114 | } 115 | } 116 | 117 | return err; 118 | } 119 | 120 | @cmocka_functions@ 121 | 122 | @cmocka_test_groups@ 123 | 124 | #define exec_tests(group, setup, teardown) \ 125 | do { \ 126 | if (patmatch(options.group_pattern, #group)) { \ 127 | cnt += cmocka_run_group_tests(group, setup, teardown); \ 128 | } \ 129 | } while(0) 130 | 131 | int main(int argc, char *argv[]) 132 | { 133 | int cnt = 0; /* number of failed tests */ 134 | char *prog = argv[0]; 135 | 136 | for (char *sep = argv[0]; *sep; sep++) { 137 | if (*sep == '/' || *sep == '\\') { 138 | prog = sep + 1; 139 | } 140 | } 141 | 142 | if (parse_options(argc, argv) != 0) { 143 | usage(prog); 144 | return 1; 145 | } else if (options.print_help) { 146 | help(prog); 147 | return 0; 148 | } 149 | 150 | cmocka_set_test_filter(options.test_pattern); 151 | 152 | @cmocka_run_tests@ 153 | 154 | return (cnt != 0); 155 | } 156 | -------------------------------------------------------------------------------- /src/haswell/simd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * haswell.h -- SIMD abstractions targeting AVX2 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #ifndef SIMD_H 9 | #define SIMD_H 10 | 11 | #include 12 | #include 13 | 14 | #define SIMD_8X_SIZE (32) 15 | 16 | typedef uint8_t simd_table_t[SIMD_8X_SIZE]; 17 | 18 | #define SIMD_TABLE(v00, v01, v02, v03, v04, v05, v06, v07, \ 19 | v08, v09, v0a, v0b, v0c, v0d, v0e, v0f) \ 20 | { \ 21 | v00, v01, v02, v03, v04, v05, v06, v07, \ 22 | v08, v09, v0a, v0b, v0c, v0d, v0e, v0f, \ 23 | v00, v01, v02, v03, v04, v05, v06, v07, \ 24 | v08, v09, v0a, v0b, v0c, v0d, v0e, v0f \ 25 | } 26 | 27 | typedef struct { __m256i chunks[1]; } simd_8x_t; 28 | 29 | typedef struct { __m128i chunks[1]; } simd_8x16_t; 30 | 31 | typedef simd_8x_t simd_8x32_t; 32 | 33 | typedef struct { __m256i chunks[2]; } simd_8x64_t; 34 | 35 | 36 | nonnull_all 37 | static really_inline void simd_loadu_8x(simd_8x_t *simd, const void *address) 38 | { 39 | simd->chunks[0] = _mm256_loadu_si256((const __m256i *)(address)); 40 | } 41 | 42 | nonnull_all 43 | static really_inline void simd_storeu_8x(void *address, simd_8x_t *simd) 44 | { 45 | _mm256_storeu_si256((__m256i *)address, simd->chunks[0]); 46 | } 47 | 48 | nonnull_all 49 | static really_inline uint64_t simd_find_8x(const simd_8x_t *simd, char key) 50 | { 51 | const __m256i k = _mm256_set1_epi8(key); 52 | const __m256i r = _mm256_cmpeq_epi8(simd->chunks[0], k); 53 | return (uint32_t)_mm256_movemask_epi8(r); 54 | } 55 | 56 | nonnull_all 57 | static really_inline uint64_t simd_find_any_8x( 58 | const simd_8x_t *simd, const simd_table_t table) 59 | { 60 | const __m256i t = _mm256_loadu_si256((const __m256i *)table); 61 | const __m256i r = _mm256_cmpeq_epi8( 62 | _mm256_shuffle_epi8(t, simd->chunks[0]), simd->chunks[0]); 63 | return (uint32_t)_mm256_movemask_epi8(r); 64 | } 65 | 66 | nonnull_all 67 | static really_inline void simd_loadu_8x16(simd_8x16_t *simd, const uint8_t *address) 68 | { 69 | simd->chunks[0] = _mm_loadu_si128((const __m128i *)address); 70 | } 71 | 72 | nonnull_all 73 | static really_inline uint64_t simd_find_8x16(const simd_8x16_t *simd, char key) 74 | { 75 | const __m128i k = _mm_set1_epi8(key); 76 | const __m128i r = _mm_cmpeq_epi8(simd->chunks[0], k); 77 | const uint64_t m = (uint16_t)_mm_movemask_epi8(r); 78 | return m; 79 | } 80 | 81 | #define simd_loadu_8x32(simd, address) simd_loadu_8x(simd, address) 82 | #define simd_storeu_8x32(address, simd) simd_storeu_8x(address, simd) 83 | #define simd_find_8x32(simd, key) simd_find_8x(simd, key) 84 | 85 | nonnull_all 86 | static really_inline void simd_loadu_8x64(simd_8x64_t *simd, const uint8_t *address) 87 | { 88 | simd->chunks[0] = _mm256_loadu_si256((const __m256i *)(address)); 89 | simd->chunks[1] = _mm256_loadu_si256((const __m256i *)(address+32)); 90 | } 91 | 92 | nonnull_all 93 | static really_inline uint64_t simd_find_8x64(const simd_8x64_t *simd, char key) 94 | { 95 | const __m256i k = _mm256_set1_epi8(key); 96 | 97 | const __m256i r0 = _mm256_cmpeq_epi8(simd->chunks[0], k); 98 | const __m256i r1 = _mm256_cmpeq_epi8(simd->chunks[1], k); 99 | 100 | const uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); 101 | const uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); 102 | 103 | return m0 | (m1 << 32); 104 | } 105 | 106 | nonnull_all 107 | static really_inline uint64_t simd_find_any_8x64( 108 | const simd_8x64_t *simd, const simd_table_t table) 109 | { 110 | const __m256i t = _mm256_loadu_si256((const __m256i *)table); 111 | 112 | const __m256i r0 = _mm256_cmpeq_epi8( 113 | _mm256_shuffle_epi8(t, simd->chunks[0]), simd->chunks[0]); 114 | const __m256i r1 = _mm256_cmpeq_epi8( 115 | _mm256_shuffle_epi8(t, simd->chunks[1]), simd->chunks[1]); 116 | 117 | const uint64_t m0 = (uint32_t)_mm256_movemask_epi8(r0); 118 | const uint64_t m1 = (uint32_t)_mm256_movemask_epi8(r1); 119 | 120 | return m0 | (m1 << 32); 121 | } 122 | 123 | #endif // SIMD_H 124 | -------------------------------------------------------------------------------- /tests/bits.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bits.c -- test bit manipulation instructions 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "config.h" 18 | #include "attributes.h" 19 | #include "isadetection.h" 20 | 21 | #if _MSC_VER 22 | # define strcasecmp(s1, s2) _stricmp(s1, s2) 23 | # define strncasecmp(s1, s2, n) _strnicmp(s1, s2, n) 24 | #else 25 | #include 26 | #endif 27 | 28 | #include "diagnostic.h" 29 | 30 | struct kernel { 31 | const char *name; 32 | uint32_t instruction_set; 33 | void (*test_trailing_zeroes)(void **state); 34 | void (*test_leading_zeroes)(void **state); 35 | void (*test_prefix_xor)(void **state); 36 | void (*test_add_overflow)(void **state); 37 | }; 38 | 39 | #if HAVE_HASWELL 40 | extern void test_haswell_trailing_zeroes(void **); 41 | extern void test_haswell_leading_zeroes(void **); 42 | extern void test_haswell_prefix_xor(void **); 43 | extern void test_haswell_add_overflow(void **); 44 | #endif 45 | 46 | #if HAVE_WESTMERE 47 | extern void test_westmere_trailing_zeroes(void **); 48 | extern void test_westmere_leading_zeroes(void **); 49 | extern void test_westmere_prefix_xor(void **); 50 | extern void test_westmere_add_overflow(void **); 51 | #endif 52 | 53 | extern void test_fallback_trailing_zeroes(void **); 54 | extern void test_fallback_leading_zeroes(void **); 55 | 56 | static const struct kernel kernels[] = { 57 | #if HAVE_HASWELL 58 | { "haswell", AVX2, &test_haswell_trailing_zeroes, 59 | &test_haswell_leading_zeroes, 60 | &test_haswell_prefix_xor, 61 | &test_haswell_add_overflow }, 62 | #endif 63 | #if HAVE_WESTMERE 64 | { "westmere", SSE42|PCLMULQDQ, &test_westmere_trailing_zeroes, 65 | &test_westmere_leading_zeroes, 66 | &test_westmere_prefix_xor, 67 | &test_westmere_add_overflow }, 68 | #endif 69 | { "fallback", DEFAULT, &test_fallback_trailing_zeroes, 70 | &test_fallback_leading_zeroes, 71 | 0, 0 } 72 | }; 73 | 74 | static inline const struct kernel * 75 | select_kernel(void) 76 | { 77 | const char *preferred; 78 | const uint32_t supported = detect_supported_architectures(); 79 | const size_t length = sizeof(kernels)/sizeof(kernels[0]); 80 | size_t count = 0; 81 | 82 | diagnostic_push() 83 | msvc_diagnostic_ignored(4996) 84 | preferred = getenv("ZONE_KERNEL"); 85 | diagnostic_pop() 86 | 87 | if (preferred) { 88 | for (; count < length; count++) 89 | if (strcasecmp(preferred, kernels[count].name) == 0) 90 | break; 91 | if (count == length) 92 | count = 0; 93 | } 94 | 95 | for (; count < length; count++) 96 | if ((kernels[count].instruction_set & supported) == (kernels[count].instruction_set)) 97 | return &kernels[count]; 98 | 99 | return &kernels[length - 1]; 100 | } 101 | 102 | /*!cmocka */ 103 | void test_trailing_zeroes(void **state) 104 | { 105 | const struct kernel *kernel = select_kernel(); 106 | assert(kernel); 107 | kernel->test_trailing_zeroes(state); 108 | } 109 | 110 | /*!cmocka */ 111 | void test_leading_zeroes(void **state) 112 | { 113 | const struct kernel *kernel = select_kernel(); 114 | assert(kernel); 115 | kernel->test_leading_zeroes(state); 116 | } 117 | 118 | /*!cmocka */ 119 | void test_prefix_xor(void **state) 120 | { 121 | const struct kernel *kernel = select_kernel(); 122 | assert(kernel); 123 | if (kernel->test_prefix_xor) 124 | kernel->test_prefix_xor(state); 125 | else 126 | assert_true(1); 127 | } 128 | 129 | /*!cmocka */ 130 | void test_add_overflow(void **state) 131 | { 132 | const struct kernel *kernel = select_kernel(); 133 | assert(kernel); 134 | if (kernel->test_add_overflow) 135 | kernel->test_add_overflow(state); 136 | else 137 | assert_true(1); 138 | } 139 | -------------------------------------------------------------------------------- /src/generic/ttl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ttl.h -- Time to Live (TTL) parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef TTL_H 10 | #define TTL_H 11 | 12 | // [sS] = 1, [mM] = 60, [hH] = 60*60, [dD] = 24*60*60, [wW] = 7*24*60*60 13 | static const uint32_t ttl_units[256] = { 14 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0f 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1f 16 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 - 0x2f 17 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3f 18 | 0, 0, 0, 0, 86400, 0, 0, 0, 3600, 0, 0, 0, 0, 60, 0, 0, // 0x40 - 0x4f 19 | 0, 0, 0, 1, 0, 0, 0, 604800, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0xf5 20 | 0, 0, 0, 0, 86400, 0, 0, 0, 3600, 0, 0, 0, 0, 60, 0, 0, // 0x60 - 0x6f 21 | 0, 0, 0, 1, 0, 0, 0, 604800, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x7f 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0x8f 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0 - 0xcf 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0 - 0xdf 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0 - 0xef 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xf0 - 0xff 30 | }; 31 | 32 | nonnull_all 33 | static really_inline int32_t scan_ttl( 34 | const char *data, size_t length, bool allow_units, uint32_t *ttl) 35 | { 36 | if (scan_int32(data, length, ttl)) 37 | return 1; 38 | if (!allow_units) 39 | return 0; 40 | 41 | uint64_t sum = 0, number = (uint8_t)data[0] - '0'; 42 | // ttls must start with a number. e.g. 1h not h1 43 | if (number > 9) 44 | return 0; 45 | 46 | uint64_t unit = 0, last_unit = 0; 47 | enum { NUMBER, UNIT } state = NUMBER; 48 | 49 | for (size_t count = 1; count < length; count++) { 50 | const uint64_t digit = (uint8_t)data[count] - '0'; 51 | 52 | if (state == NUMBER) { 53 | if (digit < 10) { 54 | number = number * 10 + digit; 55 | if (number > UINT32_MAX) 56 | return 0; 57 | } else if (!(unit = ttl_units[ (uint8_t)data[count] ])) { 58 | return 0; 59 | // units must not be repeated e.g. 1m1m 60 | } else if (unit == last_unit) { 61 | return 0; 62 | // greater units must precede smaller units. e.g. 1m1s, not 1s1m 63 | } else if (unit < last_unit) { 64 | return 0; 65 | } else { 66 | if (UINT32_MAX / unit < number) 67 | return 0; 68 | number *= unit; 69 | if (UINT32_MAX - sum < number) 70 | return 0; 71 | last_unit = unit; 72 | sum += number; 73 | number = 0; 74 | state = UNIT; 75 | } 76 | } else if (state == UNIT) { 77 | // units must be followed by a number. e.g. 1h30m, not 1hh 78 | if (digit > 9) 79 | return 0; 80 | // units must not be followed by a number if smallest unit, 81 | // i.e. seconds, was previously specified 82 | if (last_unit == 1) 83 | return 0; 84 | number = digit; 85 | state = NUMBER; 86 | } 87 | } 88 | 89 | if (UINT32_MAX - sum < number) 90 | return 0; 91 | 92 | sum += number; 93 | *ttl = (uint32_t)sum; 94 | return 1; 95 | } 96 | 97 | nonnull_all 98 | static really_inline int32_t parse_ttl( 99 | parser_t *parser, 100 | const type_info_t *type, 101 | const rdata_info_t *field, 102 | rdata_t *rdata, 103 | const token_t *token) 104 | { 105 | uint32_t ttl; 106 | if (!scan_ttl(token->data, token->length, parser->options.pretty_ttls, &ttl)) 107 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 108 | // FIXME: comment RFC2308 msb 109 | if (ttl & (1u << 31)) 110 | SEMANTIC_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 111 | ttl = htobe32(ttl); 112 | memcpy(rdata->octets, &ttl, sizeof(ttl)); 113 | rdata->octets += 4; 114 | return 0; 115 | } 116 | 117 | #endif // TTL_H 118 | -------------------------------------------------------------------------------- /.github/workflows/coverity-scan.yml: -------------------------------------------------------------------------------- 1 | # 2 | # coverity-scan.yml -- GitHub Actions workflow for Coverity Scan analysis 3 | # 4 | # Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | # 8 | # 9 | name: coverity-scan 10 | 11 | on: 12 | schedule: 13 | - cron: "0 12 * * *" 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | include: 21 | - os: ubuntu-22.04 22 | cc: gcc 23 | build_type: Debug 24 | build_tool_options: -j 4 25 | steps: 26 | - uses: actions/checkout@v3 27 | - id: setup_coverity 28 | shell: bash 29 | env: 30 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 31 | run: | 32 | set -e -x 33 | headers=$(basename $(mktemp "$(pwd)/cov.XXXXXXXX")) 34 | code=$(curl -X HEAD -s -S -F project="${GITHUB_REPOSITORY}" \ 35 | -F token="${token}" \ 36 | -o /dev/null -D ${headers} -w '%{http_code}' \ 37 | 'https://scan.coverity.com/download/cxx/linux64') 38 | [ "${code}" != "200" ] && echo "cURL exited with ${code}" 1>&2 && exit 1 39 | file=$(sed -n -E 's/.*filename="([^"]+)".*/\1/p' ${headers}) 40 | echo "cov_archive=${file}" >> $GITHUB_OUTPUT 41 | echo "$(pwd)/cov-analysis/bin" >> $GITHUB_PATH 42 | rm -f ${headers} 43 | - id: cache_coverity 44 | uses: actions/cache/restore@v3 45 | with: 46 | key: coverity | 1 | "$(cov_archive)" 47 | path: cov-analysis 48 | - id: install_coverity 49 | if: steps.cache_coverity.outputs.cache-hit != 'true' 50 | shell: bash 51 | env: 52 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 53 | run: | 54 | set -e -x 55 | headers=$(basename $(mktemp "$(pwd)/cov.XXXXXXXX")) 56 | code=$(curl -s -S -F project="${GITHUB_REPOSITORY}" \ 57 | -F token="${token}" \ 58 | -O -J -D ${headers} -w '%{http_code}' \ 59 | 'https://scan.coverity.com/download/cxx/linux64') 60 | [ "${code}" != "200" ] && echo "cURL exited with ${code}" 1>&2 && exit 1 61 | file=$(sed -n -E 's/^.*filename="([^"]+)".*$/\1/p' ${headers}) 62 | tar -xzf ${file} -C . 63 | dir=$(find . -type d -name "cov-analysis*" | head -1) 64 | mv "${dir}" "cov-analysis" 65 | rm -f ${headers} "${file}" 66 | - id: build_simdzone 67 | shell: bash 68 | env: 69 | CC: ${{ matrix.cc }} 70 | GENERATOR: ${{ matrix.generator }} 71 | BUILD_TYPE: ${{ matrix.build_type }} 72 | BUILD_TOOL_OPTIONS: ${{ matrix.build_tool_options }} 73 | WARNINGS_AS_ERRORS: ${{ matrix.warnings_as_errors }} 74 | run: | 75 | set -e -x 76 | mkdir build 77 | cd build 78 | cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE:-RelWithDebInfo} \ 79 | -DCMAKE_COMPILE_WARNING_AS_ERROR=${WARNINGS_AS_ERRORS:-on} \ 80 | ${GENERATOR:+-G} ${GENERATOR:+"${GENERATOR}"} .. 81 | cov-build --dir ../cov-int \ 82 | cmake --build . --config ${BUILD_TYPE:-RelWithDebInfo} -- ${BUILD_TOOL_OPTIONS} 83 | - id: submit_to_coverity_scan 84 | shell: bash 85 | env: 86 | email: ${{ secrets.COVERITY_SCAN_EMAIL }} 87 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 88 | run: | 89 | set -e -x 90 | tar -czf analysis-results.tgz cov-int 91 | code=$(curl -s -S -F project="${GITHUB_REPOSITORY}" \ 92 | -F token="${token}" \ 93 | -F file=@analysis-results.tgz \ 94 | -F version=$(git rev-parse --short HEAD) \ 95 | -F description="GitHub Actions build" \ 96 | -F email="${email:=spam@nlnetlabs.nl}" \ 97 | -w '%{http_code}' \ 98 | "https://scan.coverity.com/builds") 99 | [[ "${code}" =~ "success" ]] || (echo "cURL exited with ${code}" 1>&2 && exit 1) 100 | rm -f analysis-results.tgz 101 | -------------------------------------------------------------------------------- /src/generic/number.h: -------------------------------------------------------------------------------- 1 | /* 2 | * number.h -- integer parsing routines 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NUMBER_H 10 | #define NUMBER_H 11 | 12 | nonnull((1,3)) 13 | static really_inline int32_t scan_int8( 14 | const char *data, size_t length, uint8_t *number) 15 | { 16 | uint32_t sum = (uint8_t)data[0] - '0'; 17 | 18 | if (sum > 9 || !length || length > 3) 19 | return 0; 20 | 21 | for (size_t count=1; count < length; count++) { 22 | const uint8_t digit = (uint8_t)data[count] - '0'; 23 | sum = sum * 10 + digit; 24 | if (digit > 9) 25 | return 0; 26 | } 27 | 28 | *number = (uint8_t)sum; 29 | return sum <= 255u; 30 | } 31 | 32 | nonnull((1,3)) 33 | static really_inline int32_t scan_int16( 34 | const char *data, size_t length, uint16_t *number) 35 | { 36 | uint32_t sum = (uint8_t)data[0] - '0'; 37 | 38 | if (sum > 9 || !length || length > 5) 39 | return 0; 40 | 41 | for (size_t count=1; count < length; count++) { 42 | const uint8_t digit = (uint8_t)data[count] - '0'; 43 | sum = sum * 10 + digit; 44 | if (digit > 9) 45 | return 0; 46 | } 47 | 48 | *number = (uint16_t)sum; 49 | return sum <= 65535u; 50 | } 51 | 52 | nonnull((1,3)) 53 | static really_inline int32_t scan_int32( 54 | const char *data, size_t length, uint32_t *number) 55 | { 56 | uint64_t sum = (uint8_t)data[0] - '0'; 57 | 58 | if (sum > 9 || !length || length > 10) 59 | return 0; 60 | 61 | for (size_t count=1; count < length; count++) { 62 | const uint8_t digit = (uint8_t)data[count] - '0'; 63 | sum = sum * 10 + digit; 64 | if (digit > 9) 65 | return 0; 66 | } 67 | 68 | *number = (uint32_t)sum; 69 | return sum <= 4294967295u; 70 | } 71 | 72 | nonnull((1,3)) 73 | static really_inline int32_t scan_int64( 74 | const char *data, size_t length, uint64_t *number) 75 | { 76 | uint64_t sum = (uint8_t)data[0] - '0'; 77 | 78 | if (sum > 9 || !length || length > 20) 79 | return 0; 80 | 81 | for (size_t count=1; count < length; count++) { 82 | const uint8_t digit = (uint8_t)data[count] - '0'; 83 | sum = sum * 10 + digit; 84 | if (digit > 9) 85 | return 0; 86 | } 87 | 88 | *number = sum; 89 | return 1; /* TODO: detect overflow */ 90 | } 91 | 92 | nonnull_all 93 | static really_inline int32_t parse_int8( 94 | parser_t *parser, 95 | const type_info_t *type, 96 | const rdata_info_t *field, 97 | rdata_t *rdata, 98 | const token_t *token) 99 | { 100 | uint8_t number; 101 | if (!scan_int8(token->data, token->length, &number)) 102 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 103 | *rdata->octets++ = number; 104 | return 0; 105 | } 106 | 107 | nonnull_all 108 | static really_inline int32_t parse_int16( 109 | parser_t *parser, 110 | const type_info_t *type, 111 | const rdata_info_t *field, 112 | rdata_t *rdata, 113 | const token_t *token) 114 | { 115 | uint16_t number; 116 | if (!scan_int16(token->data, token->length, &number)) 117 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 118 | number = htobe16(number); 119 | memcpy(rdata->octets, &number, 2); 120 | rdata->octets += 2; 121 | return 0; 122 | } 123 | 124 | nonnull_all 125 | static really_inline int32_t parse_int32( 126 | parser_t *parser, 127 | const type_info_t *type, 128 | const rdata_info_t *field, 129 | rdata_t *rdata, 130 | const token_t *token) 131 | { 132 | uint32_t number; 133 | if (!scan_int32(token->data, token->length, &number)) 134 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 135 | number = htobe32(number); 136 | memcpy(rdata->octets, &number, 4); 137 | rdata->octets += 4; 138 | return 0; 139 | } 140 | 141 | nonnull_all 142 | static really_inline int32_t parse_int64( 143 | parser_t *parser, 144 | const type_info_t *type, 145 | const rdata_info_t *field, 146 | rdata_t *rdata, 147 | const token_t *token) 148 | { 149 | uint64_t number; 150 | if (!scan_int64(token->data, token->length, &number)) 151 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 152 | number = htobe64(number); 153 | memcpy(rdata->octets, &number, 8); 154 | rdata->octets += 8; 155 | return 0; 156 | } 157 | 158 | #endif // NUMBER_H 159 | -------------------------------------------------------------------------------- /src/generic/base32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * base32.h -- Base32 decoder 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef BASE32_H 10 | #define BASE32_H 11 | 12 | static const uint8_t b32rmap[256] = { 13 | 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0 - 7 */ 14 | 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, /* 8 - 15 */ 15 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 - 23 */ 16 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24 - 31 */ 17 | 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 - 39 */ 18 | 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, /* 40 - 47 */ 19 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 48 - 55 */ 20 | 0x08, 0x09, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, /* 56 - 63 */ 21 | 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, /* 64 - 71 */ 22 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, /* 72 - 79 */ 23 | 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, /* 80 - 87 */ 24 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88 - 95 */ 25 | 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, /* 96 - 103 */ 26 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, /* 104 - 111 */ 27 | 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, /* 112 - 119 */ 28 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120 - 127 */ 29 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128 - 135 */ 30 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 136 - 143 */ 31 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 144 - 151 */ 32 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 152 - 159 */ 33 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 160 - 167 */ 34 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 168 - 175 */ 35 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 176 - 183 */ 36 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 184 - 191 */ 37 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 192 - 199 */ 38 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 200 - 207 */ 39 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 208 - 215 */ 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 216 - 223 */ 41 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 224 - 231 */ 42 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 232 - 239 */ 43 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 240 - 247 */ 44 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 248 - 255 */ 45 | }; 46 | 47 | static const uint8_t b32rmap_special = 0xf0; 48 | 49 | nonnull_all 50 | static really_inline int32_t parse_base32( 51 | parser_t *parser, 52 | const type_info_t *type, 53 | const rdata_info_t *field, 54 | rdata_t *rdata, 55 | const token_t *token) 56 | { 57 | uint32_t state = 0; 58 | 59 | size_t length = (token->length * 5) / 8; 60 | if (length > 255 || (uintptr_t)rdata->limit - (uintptr_t)rdata->octets < (length + 1)) 61 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 62 | 63 | *rdata->octets++ = (uint8_t)length; 64 | 65 | const char *p = token->data; 66 | for (;; p++) { 67 | const uint8_t ofs = b32rmap[(uint8_t)*p]; 68 | 69 | if (ofs >= b32rmap_special) 70 | break; 71 | 72 | switch (state) { 73 | case 0: 74 | *rdata->octets = (uint8_t)(ofs << 3); 75 | state = 1; 76 | break; 77 | case 1: 78 | *rdata->octets++ |= (uint8_t)(ofs >> 2); 79 | *rdata->octets = (uint8_t)(ofs << 6); 80 | state = 2; 81 | break; 82 | case 2: 83 | *rdata->octets |= (uint8_t)(ofs << 1); 84 | state = 3; 85 | break; 86 | case 3: 87 | *rdata->octets++ |= (uint8_t)(ofs >> 4); 88 | *rdata->octets = (uint8_t)(ofs << 4); 89 | state = 4; 90 | break; 91 | case 4: 92 | *rdata->octets++ |= (uint8_t)(ofs >> 1); 93 | *rdata->octets = (uint8_t)(ofs << 7); 94 | state = 5; 95 | break; 96 | case 5: 97 | *rdata->octets |= (uint8_t)(ofs << 2); 98 | state = 6; 99 | break; 100 | case 6: 101 | *rdata->octets++ |= (uint8_t)(ofs >> 3); 102 | *rdata->octets = (uint8_t)(ofs << 5); 103 | state = 7; 104 | break; 105 | case 7: 106 | *rdata->octets++ |= ofs; 107 | state = 0; 108 | break; 109 | } 110 | } 111 | 112 | if (p != token->data + token->length) 113 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 114 | return 0; 115 | } 116 | 117 | #endif // BASE32_H 118 | -------------------------------------------------------------------------------- /src/generic/name.h: -------------------------------------------------------------------------------- 1 | /* 2 | * name.h -- domain name parser 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef NAME_H 10 | #define NAME_H 11 | 12 | typedef struct name_block name_block_t; 13 | struct name_block { 14 | uint64_t backslashes; 15 | uint64_t dots; 16 | }; 17 | 18 | nonnull_all 19 | static really_inline void copy_name_block( 20 | name_block_t *block, const char *text, uint8_t *wire) 21 | { 22 | simd_8x32_t input; 23 | simd_loadu_8x32(&input, text); 24 | simd_storeu_8x32(wire, &input); 25 | block->backslashes = simd_find_8x32(&input, '\\'); 26 | block->dots = simd_find_8x32(&input, '.'); 27 | } 28 | 29 | nonnull_all 30 | static really_inline int32_t scan_name( 31 | const char *data, 32 | size_t tlength, 33 | uint8_t octets[255 + ZONE_BLOCK_SIZE], 34 | size_t *lengthp) 35 | { 36 | uint64_t label = 0; 37 | const char *text = data; 38 | uint8_t *wire = octets + 1; 39 | name_block_t block; 40 | 41 | octets[0] = 0; 42 | 43 | // real world domain names quickly exceed 16 octets (www.example.com is 44 | // encoded as 3www7example3com0, or 18 octets), but rarely exceed 32 45 | // octets. encode in 32-byte blocks. 46 | copy_name_block(&block, text, wire); 47 | 48 | uint64_t count = 32, length = 0, base = 0, left = tlength; 49 | uint64_t carry = 0; 50 | if (tlength < 32) 51 | count = tlength; 52 | uint64_t mask = (1llu << count) - 1u; 53 | 54 | // check for escape sequences 55 | if (unlikely(block.backslashes & mask)) 56 | goto escaped; 57 | 58 | // check for root, i.e. "." 59 | if (unlikely(block.dots & 1llu)) 60 | return ((*lengthp = tlength) == 1 ? 0 : -1); 61 | 62 | length = count; 63 | block.dots &= mask; 64 | carry = (block.dots >> (length - 1)); 65 | 66 | // check for null labels, i.e. ".." 67 | if (unlikely(block.dots & (block.dots >> 1))) 68 | return -1; 69 | 70 | if (likely(block.dots)) { 71 | count = trailing_zeroes(block.dots); 72 | block.dots = clear_lowest_bit(block.dots); 73 | octets[label] = (uint8_t)count; 74 | label = count + 1; 75 | while (block.dots) { 76 | count = trailing_zeroes(block.dots); 77 | block.dots = clear_lowest_bit(block.dots); 78 | octets[label] = (uint8_t)(count - label); 79 | label = count + 1; 80 | } 81 | } 82 | 83 | octets[label] = (uint8_t)(length - label); 84 | 85 | if (tlength <= 32) 86 | return (void)(*lengthp = length + 1), carry == 0; 87 | 88 | text += length; 89 | wire += length; 90 | left -= length; 91 | 92 | do { 93 | copy_name_block(&block, text, wire); 94 | count = 32; 95 | if (left < 32) 96 | count = left; 97 | mask = (1llu << count) - 1u; 98 | base = length; 99 | 100 | // check for escape sequences 101 | if (unlikely(block.backslashes & mask)) { 102 | escaped: 103 | block.backslashes &= -block.backslashes; 104 | mask = block.backslashes - 1; 105 | block.dots &= mask; 106 | count = count_ones(mask); 107 | const uint32_t octet = unescape(text+count, wire+count); 108 | if (!octet) 109 | return -1; 110 | text += count + octet; 111 | wire += count + 1; 112 | length += count + 1; 113 | left -= count + octet; 114 | count += 1; // for correct carry 115 | } else { 116 | block.dots &= mask; 117 | text += count; 118 | wire += count; 119 | length += count; 120 | left -= count; 121 | } 122 | 123 | // check for null labels, i.e. ".." 124 | if (unlikely(block.dots & ((block.dots >> 1) | carry))) 125 | return -1; 126 | carry = block.dots >> (count - 1); 127 | 128 | if (likely(block.dots)) { 129 | count = trailing_zeroes(block.dots) + base; 130 | block.dots = clear_lowest_bit(block.dots); 131 | octets[label] = (uint8_t)(count - label); 132 | // check if label exceeds 63 octets 133 | if (unlikely(count - label > 63)) 134 | return -1; 135 | label = count + 1; 136 | while (block.dots) { 137 | count = trailing_zeroes(block.dots) + base; 138 | block.dots = clear_lowest_bit(block.dots); 139 | octets[label] = (uint8_t)(count - label); 140 | label = count + 1; 141 | } 142 | } else { 143 | // check if label exceeds 63 octets 144 | if (length - label > 63) 145 | return -1; 146 | } 147 | 148 | octets[label] = (uint8_t)(length - label); 149 | } while (left && length < 255); 150 | 151 | if (length >= 255) 152 | return -1; 153 | 154 | *lengthp = length + 1; 155 | return carry == 0; 156 | } 157 | 158 | #endif // NAME_H 159 | -------------------------------------------------------------------------------- /tests/eui.c: -------------------------------------------------------------------------------- 1 | /* 2 | * eui.c -- test EUI support 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "zone.h" 15 | 16 | struct eui_test { 17 | int32_t code; 18 | uint16_t type; 19 | const char *text; 20 | const uint8_t *rdata; 21 | }; 22 | 23 | static int32_t accept_eui48_and_eui64( 24 | zone_parser_t *parser, 25 | const zone_name_t *owner, 26 | uint16_t type, 27 | uint16_t class, 28 | uint32_t ttl, 29 | uint16_t rdlength, 30 | const uint8_t *rdata, 31 | void *user_data) 32 | { 33 | (void)parser; 34 | (void)owner; 35 | (void)type; 36 | (void)class; 37 | (void)ttl; 38 | (void)rdlength; 39 | (void)rdata; 40 | 41 | const struct eui_test *test = (void *)user_data; 42 | 43 | if (test->code != 0) 44 | return ZONE_SYNTAX_ERROR; 45 | if (test->type == ZONE_TYPE_EUI48 && rdlength != 6) 46 | return ZONE_SYNTAX_ERROR; 47 | if (test->type == ZONE_TYPE_EUI64 && rdlength != 8) 48 | return ZONE_SYNTAX_ERROR; 49 | if (memcmp(rdata, test->rdata, rdlength) != 0) 50 | return ZONE_SYNTAX_ERROR; 51 | return 0; 52 | } 53 | 54 | #define PAD(literal) \ 55 | literal \ 56 | "\0\0\0\0\0\0\0\0" /* 0 - 7 */ \ 57 | "\0\0\0\0\0\0\0\0" /* 8 - 15 */ \ 58 | "\0\0\0\0\0\0\0\0" /* 16 - 23 */ \ 59 | "\0\0\0\0\0\0\0\0" /* 24 - 31 */ \ 60 | "\0\0\0\0\0\0\0\0" /* 32 - 39 */ \ 61 | "\0\0\0\0\0\0\0\0" /* 40 - 47 */ \ 62 | "\0\0\0\0\0\0\0\0" /* 48 - 55 */ \ 63 | "\0\0\0\0\0\0\0\0" /* 56 - 63 */ \ 64 | "" 65 | 66 | /*!cmocka */ 67 | void eui48_and_eui64(void **state) 68 | { 69 | static uint8_t origin[] = 70 | { 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0 }; 71 | static const uint8_t eui48_address[] = 72 | { 0x00, 0x00, 0x5e, 0x00, 0x53, 0x2a }; 73 | static const uint8_t eui64_address[] = 74 | { 0x00, 0x00, 0x5e, 0xef, 0x10, 0x00, 0x00, 0x2a }; 75 | 76 | static const struct eui_test tests[] = { 77 | // EUI48 78 | { 0, ZONE_TYPE_EUI48, PAD("host.example. 86400 IN EUI48 00-00-5e-00-53-2a"), eui48_address }, 79 | // missing rdata 80 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48"), NULL }, 81 | // trailing rdata 82 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 00-00-5e-00-53-2a foobar"), NULL }, 83 | // quoted address 84 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 \"00-00-5e-00-53-2a\""), NULL }, 85 | // bad addresses 86 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 00-00-5e-00-53-2"), NULL }, 87 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 00-00-5e-00-53-2a-"), NULL }, 88 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 00.00.5e.00.53.2a"), NULL }, 89 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 0--00-5e-00-53-2a"), NULL }, 90 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI48, PAD("@ EUI48 foobar"), NULL }, 91 | // EUI64 92 | { 0, ZONE_TYPE_EUI64, PAD("host.example. 86400 IN EUI64 00-00-5e-ef-10-00-00-2a"), eui64_address }, 93 | // missing rdata 94 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64"), NULL }, 95 | // trailing rdata 96 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 00-00-5e-ef-10-00-00-2a foobar"), NULL }, 97 | // quoted address 98 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 \"00-00-5e-ef-10-00-00-2a\""), NULL }, 99 | // bad addresses 100 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 00-00-5e-ef-10-00-00-2"), NULL }, 101 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 00-00-5e-ef-10-00-00-2a-"), NULL }, 102 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 00.00.5e.ef.10.00.00.2a"), NULL }, 103 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 0--00-5e-ef-10-00-00-2a"), NULL }, 104 | { ZONE_SYNTAX_ERROR, ZONE_TYPE_EUI64, PAD("@ EUI64 foobar"), NULL }, 105 | }; 106 | 107 | (void)state; 108 | 109 | for (size_t i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 110 | zone_parser_t parser; 111 | zone_name_buffer_t name; 112 | zone_rdata_buffer_t rdata; 113 | zone_buffers_t buffers = { 1, &name, &rdata }; 114 | zone_options_t options; 115 | int32_t code; 116 | const struct eui_test *test = &tests[i]; 117 | 118 | fprintf(stderr, "INPUT: %s\n", test->text); 119 | 120 | memset(&options, 0, sizeof(options)); 121 | options.accept.callback = accept_eui48_and_eui64; 122 | options.origin.octets = origin; 123 | options.origin.length = sizeof(origin); 124 | options.default_ttl = 3600; 125 | options.default_class = 1; 126 | 127 | code = zone_parse_string(&parser, &options, &buffers, test->text, strlen(test->text), (void*)test); 128 | assert_int_equal(code, test->code); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /doc/manual/building_sources.rst: -------------------------------------------------------------------------------- 1 | .. include:: links.rst 2 | 3 | #################### 4 | Building the sources 5 | #################### 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :hidden: 10 | 11 | |project| supports a wide variety of platforms and architectures by design. 12 | The library performs well on any processor, but a modern processor supporting 13 | single instruction, multiple data (SIMD) is required for best performance. 14 | 15 | Supported instruction sets. 16 | 17 | * AVX2 18 | * SSE4.2 19 | 20 | .. note:: 21 | Support for additional SIMD instruction sets like Arm Neon, AVX-512, etc 22 | will be implemented, but is somewhat hindered by lack of available hardware. 23 | Help with implementing or even testing support for non-x86_64 architectures 24 | is greatly appreciated. 25 | 26 | 27 | Automated builds are in place for Linux, macOS and Windows. Any operating 28 | system specific issues for those platforms are caught early, but |project| 29 | strives to work on many operating systems. 30 | 31 | 32 | Prerequisites 33 | ============= 34 | 35 | .. note:: 36 | |project| started out as a replacement zone parser for NSD and is included 37 | in the NSD source packages and repository. For compatibility with the 38 | build system used by NSD, an autoconf configure script and a Makefile are 39 | included, but for standalone compilation, CMake is required. 40 | 41 | |project| is implemented in C and a C compiler supporting C99 is required to 42 | build the libary. e.g. GCC, Clang or Microsoft Visual Studio (MSVC). The 43 | project purposely has no runtime dependencies. 44 | 45 | Make sure the following software is installed on your system: 46 | 47 | * A C99 compatible C compiler. 48 | * |url::git| version control system. 49 | * |url::cmake|, version 3.10 or later, see :ref:build_options. 50 | * Optionally, |url::cmocka| when building with testing. 51 | * Optionally, |url::doxygen| when building with documentation. 52 | * Optionally, |url::sphinx| when building with documentation. 53 | 54 | 55 | .. note:: 56 | |url::conan| can be used to manage build dependencies. 57 | 58 | 59 | .. tabs:: 60 | 61 | .. group-tab:: Linux 62 | 63 | Install dependencies. 64 | 65 | .. code-block:: console 66 | 67 | dnf install git cmake gcc 68 | apt install git cmake gcc 69 | 70 | .. group-tab:: macOS 71 | 72 | Install XCode from the App Store. 73 | 74 | .. group-tab:: Windows 75 | 76 | Install Microsoft Visual Studio, then install |url::chocolatey|. 77 | 78 | .. code-block:: console 79 | 80 | choco install git 81 | choco install cmake 82 | 83 | 84 | Building 85 | ======== 86 | 87 | Clone |project| from GitHub: 88 | 89 | .. code-block:: console 90 | 91 | git clone https://github.com/NLnetLabs/simdzone.git 92 | cd simdzone 93 | 94 | Build the library using the operating system specific instructions below. The 95 | instructions are tailored to users of the library. Developers may want to 96 | choose a different build type, e.g. ``RelWithDebInfo`` or ``Debug``, and 97 | skip installation. 98 | 99 | .. tabs:: 100 | 101 | .. group-tab:: Linux 102 | 103 | .. code-block:: console 104 | 105 | mkdir build 106 | cd build 107 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX= .. 108 | cmake --build . --parallel 109 | cmake --install . 110 | 111 | .. group-tab:: macOS 112 | 113 | .. code-block:: console 114 | 115 | mkdir build 116 | cd build 117 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX= .. 118 | cmake --build . --parallel 119 | cmake --install . 120 | 121 | .. group-tab:: Windows 122 | 123 | .. code-block:: console 124 | 125 | mkdir build 126 | cd build 127 | cmake -G "" -A -DCMAKE_INSTALL_PREFIX= .. 128 | cmake --build . --parallel --config Release 129 | cmake --install . --config Release 130 | 131 | |project| can be built using for any of the supported platforms. 132 | Omitting ``-G "" -A `` usually selects a 133 | sensible default (that of the host). Different platforms and toolkits 134 | can be selected though. See the manual page ``man cmake-generators`` 135 | or the `cmake generators documentation 136 | `_ 137 | for details. 138 | 139 | Build options 140 | ------------- 141 | 142 | .. list-table:: 143 | 144 | * - ``-DBUILD_TESTING=ON`` 145 | - Build the testing tree. 146 | * - ``-DBUILD_DOCUMENTATION=ON`` 147 | - Build documentation. 148 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # 2 | # configure.ac -- Autoconf script for simdzone 3 | # 4 | # Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | # 6 | # SPDX-License-Identifier: BSD-3-Clause 7 | # 8 | 9 | # This file is intended for inclusion by configure.ac in NSD. Support for any 10 | # platform not supported by NSD here is undesirable. Builds for standalone use 11 | # or development/testing are required to use CMake. 12 | 13 | AC_INIT([simdzone],[0.2.5],[https://github.com/NLnetLabs/simdzone/issues]) 14 | 15 | AC_CONFIG_HEADERS([config.h]) 16 | AC_CONFIG_FILES([Makefile]) 17 | 18 | sinclude(acx_nlnetlabs.m4) 19 | m4_include(m4/ax_check_compile_flag.m4) 20 | 21 | CFLAGS="$CFLAGS" 22 | m4_version_prereq([2.70], [AC_PROG_CC], [AC_PROG_CC_STDC]) 23 | 24 | # allow user to override the -g -O2 flags. 25 | if test "x$CFLAGS" = "x" ; then 26 | ACX_CHECK_COMPILER_FLAG(g, [CFLAGS="$CFLAGS -g"]) 27 | ACX_CHECK_COMPILER_FLAG(O2, [CFLAGS="$CFLAGS -O2"]) 28 | ACX_CHECK_PIE 29 | fi 30 | 31 | AC_CHECK_HEADERS([endian.h sys/endian.h],,, [AC_INCLUDES_DEFAULT]) 32 | AC_CHECK_DECLS([bswap16,bswap32,bswap64], [], [], [ 33 | AC_INCLUDES_DEFAULT 34 | #ifdef HAVE_ENDIAN_H 35 | #include 36 | #endif 37 | #ifdef HAVE_SYS_ENDIAN_H 38 | #include 39 | #endif 40 | ]) 41 | 42 | AC_ARG_ENABLE(westmere, AS_HELP_STRING([--disable-westmere],[Disable Westmere (SSE4.2) kernel])) 43 | case "$enable_westmere" in 44 | no) enable_westmere=no ;; 45 | yes|*) enable_westmere=yes ;; 46 | esac 47 | 48 | AC_ARG_ENABLE(haswell, AS_HELP_STRING([--disable-haswell],[Disable Haswell (AVX2) kernel])) 49 | case "$enable_haswell" in 50 | no) enable_haswell=no ;; 51 | yes|*) enable_haswell=yes ;; 52 | esac 53 | 54 | # GCC and Clang 55 | AX_CHECK_COMPILE_FLAG([-MMD],DEPFLAGS="-MMD -MP") 56 | # Oracle Developer Studio (no -MP) 57 | AX_CHECK_COMPILE_FLAG([-xMMD],DEPFLAGS="-xMMD") 58 | 59 | AC_SUBST([DEPFLAGS]) 60 | 61 | # Figure out the canonical target architecture. 62 | AC_CANONICAL_TARGET 63 | 64 | # Multiple instruction sets may be supported by a specific architecture. 65 | # e.g. x86_64 may (or may not) support any of SSE42, AVX2 and AVX-512. The 66 | # best instruction set is automatically selected at runtime, but the compiler 67 | # may or may not support generating code for an instruction set. 68 | case "$target" in 69 | *amd64*) x86_64=yes ;; 70 | *x86_64*) x86_64=yes ;; 71 | *) x86_64=no ;; 72 | esac 73 | 74 | HAVE_WESTMERE=NO 75 | HAVE_HASWELL=NO 76 | 77 | if test $x86_64 = "yes"; then 78 | AC_CHECK_HEADER(immintrin.h,,,) 79 | AX_CHECK_COMPILE_FLAG([-march=westmere],,,[-Werror]) 80 | AX_CHECK_COMPILE_FLAG([-march=haswell],,,[-Werror]) 81 | 82 | # Check if the arch instruction set support includes the simd instructions. 83 | if test $enable_westmere != "no" -a \ 84 | $ax_cv_check_cflags__Werror__march_westmere = "yes" -a \ 85 | $ac_cv_header_immintrin_h = "yes" ; then 86 | AC_MSG_CHECKING(whether -march=westmere works) 87 | BAKCFLAGS="$CFLAGS" 88 | CFLAGS="-march=westmere $CFLAGS" 89 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([ 90 | AC_INCLUDES_DEFAULT 91 | [ 92 | #include 93 | #include 94 | 95 | int main(int argc, char *argv[]) 96 | { 97 | (void)argv; 98 | uint64_t popcnt = _mm_popcnt_u64((uint64_t)argc); 99 | return popcnt == 11; 100 | } 101 | ]]) 102 | ],[ 103 | AC_DEFINE(HAVE_WESTMERE, 1, [Wether or not to compile support for SSE4.2]) 104 | HAVE_WESTMERE=WESTMERE 105 | AC_MSG_RESULT(yes) 106 | ],[ 107 | AC_MSG_RESULT(no) 108 | ]) 109 | CFLAGS="$BAKCFLAGS" 110 | fi 111 | 112 | if test $enable_haswell != "no" -a \ 113 | $ax_cv_check_cflags__Werror__march_haswell = "yes" -a \ 114 | $ac_cv_header_immintrin_h = "yes" ; then 115 | AC_MSG_CHECKING(whether -march=haswell works) 116 | BAKCFLAGS="$CFLAGS" 117 | CFLAGS="-march=haswell $CFLAGS" 118 | AC_COMPILE_IFELSE([AC_LANG_SOURCE([ 119 | AC_INCLUDES_DEFAULT 120 | [ 121 | #include 122 | #include 123 | 124 | int main(int argc, char *argv[]) 125 | { 126 | (void)argv; 127 | int argc32x8[8] = { argc, 0, 0, 0, 0, 0, 0, 0 }; 128 | __m256i argc256 = _mm256_loadu_si256((__m256i *)argc32x8); 129 | return _mm256_testz_si256(argc256, _mm256_set1_epi8(11)); 130 | } 131 | ]]) 132 | ],[ 133 | AC_DEFINE(HAVE_HASWELL, 1, [Wether or not to compile support for AVX2]) 134 | HAVE_HASWELL=HASWELL 135 | AC_MSG_RESULT(yes) 136 | ],[ 137 | AC_MSG_RESULT(no) 138 | ]) 139 | CFLAGS="$BAKCFLAGS" 140 | fi 141 | fi 142 | 143 | AC_CHECK_FUNCS([realpath],,[AC_MSG_ERROR([realpath is not available])]) 144 | 145 | AC_SUBST([HAVE_ENDIAN_H]) 146 | AC_SUBST([HAVE_WESTMERE]) 147 | AC_SUBST([HAVE_HASWELL]) 148 | 149 | AH_BOTTOM([ 150 | /* Defines _XOPEN_SOURCE and _POSIX_C_SOURCE implicitly in features.h */ 151 | #ifndef _DEFAULT_SOURCE 152 | # define _DEFAULT_SOURCE 1 153 | #endif 154 | ]) 155 | 156 | AC_OUTPUT 157 | -------------------------------------------------------------------------------- /doc/manual/getting_started.rst: -------------------------------------------------------------------------------- 1 | ############### 2 | Getting started 3 | ############### 4 | 5 | |project| is a high performance DNS presentation format parser. |project| 6 | takes care of control entries and deserializes resource record (RR) entries to 7 | wire format. Handling of any special considerations that apply to the data are 8 | delegated to the application to allow for wider applicability and better 9 | performance. 10 | 11 | For example, zone files must contain exactly one SOA RR at the top of the zone 12 | and all RRs must be of the same class (see :RFC:`1035#section-5.2`). The 13 | application is responsible for enforcing these restrictions so that the 14 | parser itself is equally well suited to parse serialized zone transfers, etc. 15 | 16 | The interface is purposely minimalistic and provides two parse functions: 17 | 18 | - ``zone_parse`` to parse files. 19 | - ``zone_parse_string`` to parse in-memory data. 20 | 21 | 22 | To keep track of state, the functions require the application to pass a 23 | ``zone_parser_t``, which is initialized on use. ``zone_options_t`` can be 24 | used to configure callbacks, defaults and behavior. Specifying at least an 25 | accept callback function (invoked for each resource record), origin, default 26 | ttl and default class is required. 27 | 28 | To avoid memory leaks and improve performance the parser takes a set of 29 | pre-allocated buffers. A ``zone_name_buffer_t`` to store the owner and a 30 | ``zone_rdata_buffer_t`` to store RDATA. The ``user_data`` pointer is passed 31 | verbatim to the specified callback function and can be used to communicate 32 | application context. 33 | 34 | 35 | Parsing zones 36 | ------------- 37 | 38 | Let's create a simple zone parser to demonstrate |project| usage. 39 | 40 | First, we define a function to receives RRs as they're parsed. 41 | 42 | .. code-block:: C 43 | :linenos: 44 | :lineno-start: 1 45 | 46 | #include 47 | #include 48 | #include 49 | 50 | #include 51 | 52 | struct zone { 53 | size_t count; 54 | uint16_t class; 55 | }; 56 | 57 | static const int32_t accept_rr( 58 | zone_parser_t *parser, 59 | const zone_name_t *owner, 60 | uint16_t type, 61 | uint16_t class, 62 | uint32_t ttl, 63 | uint16_t rdlength, 64 | const uint8_t *rdata, 65 | void *user_data) 66 | { 67 | struct zone *zone = user_data; 68 | // require first record to be of type SOA 69 | if (!zone->count) { 70 | zone->class = class; 71 | if (type != 6) { 72 | // use log function to automatically print file and line number 73 | zone_log(parser, ZONE_ERROR, "First record is not of type SOA"); 74 | return ZONE_SEMANTIC_ERROR; 75 | } 76 | } else { 77 | // require records not to be of type SOA 78 | if (type == 6) { 79 | zone_log(parser, ZONE_ERROR, "Extra record of type SOA"); 80 | return ZONE_SEMANTIC_ERROR; 81 | // require each record uses the same class 82 | } else if (class != zone->class) { 83 | zone_log(parser, ZONE_ERROR, "Record not in class " PRIu16, zone->class); 84 | return ZONE_SEMANTIC_ERROR; 85 | } 86 | } 87 | 88 | // authoritative servers would now store RR in a database or similar 89 | 90 | zone->count++; 91 | return ZONE_SUCCESS; 92 | } 93 | 94 | ``zone_log`` is a convenience function that can be used to print location 95 | information with the error message. Returning ``ZONE_SEMANTIC_ERROR`` from 96 | the callback signals the parser an error has occurred and processing must be 97 | halted. |project| defines a number of error codes, but any negative number 98 | will halt the parser. The error code is propagated and eventually returned 99 | as the result of ``zone_parse``. 100 | 101 | Next we define a ``main`` function that's called on execution of the program. 102 | 103 | .. code-block:: C 104 | :linenos: 105 | :lineno-start: 1 106 | 107 | int main(int argc, char *argv[]) 108 | { 109 | zone_parser_t parser; 110 | zone_name_buffer_t name; 111 | zone_rdata_buffer_t rdata; 112 | zone_buffers_t buffers = { 1, &name, &rdata }; 113 | zone_options_t options = { 0 }; // must be properly initialized 114 | 115 | if (argc != 3) { 116 | fprintf(stderr, "Usage: %s zone-file origin\n", argv[0]); 117 | exit(EXIT_FAILURE); 118 | } 119 | 120 | options.accept.callback = accept_rr; 121 | options.origin = argv[2]; 122 | options.default_ttl = 3600; 123 | options.default_class = 1; // IN 124 | 125 | int32_t result; 126 | struct zone zone = { 0 }; 127 | 128 | result = zone_parse(&parser, &options, &buffers, argv[1], &zone); 129 | if (result < 0) { 130 | fprintf(stderr, "Could not parse %s\n", argv[1]); 131 | exit(EXIT_FAILURE); 132 | } 133 | 134 | printf("parsed %zu records in %s", zone->count, argv[1]); 135 | 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /src/westmere/simd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * simd.h -- SIMD abstractions targeting SSE4.2 3 | * 4 | * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 | * 6 | * See LICENSE for the license. 7 | * 8 | */ 9 | #ifndef SIMD_H 10 | #define SIMD_H 11 | 12 | #include 13 | #include 14 | 15 | #define SIMD_8X_SIZE (16) 16 | 17 | typedef uint8_t simd_table_t[SIMD_8X_SIZE]; 18 | 19 | #define SIMD_TABLE(v00, v01, v02, v03, v04, v05, v06, v07, \ 20 | v08, v09, v0a, v0b, v0c, v0d, v0e, v0f) \ 21 | { \ 22 | v00, v01, v02, v03, v04, v05, v06, v07, \ 23 | v08, v09, v0a, v0b, v0c, v0d, v0e, v0f \ 24 | } 25 | 26 | typedef struct { __m128i chunks[1]; } simd_8x_t; 27 | 28 | typedef simd_8x_t simd_8x16_t; 29 | 30 | typedef struct { __m128i chunks[2]; } simd_8x32_t; 31 | 32 | typedef struct { __m128i chunks[4]; } simd_8x64_t; 33 | 34 | nonnull_all 35 | static really_inline void simd_loadu_8x(simd_8x_t *simd, const uint8_t *address) 36 | { 37 | simd->chunks[0] = _mm_loadu_si128((const __m128i *)address); 38 | } 39 | 40 | nonnull_all 41 | static really_inline void simd_storeu_8x(uint8_t *address, const simd_8x_t *simd) 42 | { 43 | _mm_storeu_si128((__m128i *)address, simd->chunks[0]); 44 | } 45 | 46 | nonnull_all 47 | static really_inline uint64_t simd_find_8x(const simd_8x_t *simd, char key) 48 | { 49 | const __m128i k = _mm_set1_epi8(key); 50 | const __m128i r = _mm_cmpeq_epi8(simd->chunks[0], k); 51 | return (uint16_t)_mm_movemask_epi8(r); 52 | } 53 | 54 | nonnull_all 55 | static really_inline uint64_t simd_find_any_8x( 56 | const simd_8x_t *simd, const simd_table_t table) 57 | { 58 | const __m128i t = _mm_loadu_si128((const __m128i *)table); 59 | const __m128i r = _mm_cmpeq_epi8( 60 | _mm_shuffle_epi8(t, simd->chunks[0]), simd->chunks[0]); 61 | return (uint16_t)_mm_movemask_epi8(r); 62 | } 63 | 64 | #define simd_loadu_8x16(simd, address) simd_loadu_8x(simd, address) 65 | #define simd_find_8x16(simd, key) simd_find_8x(simd, key) 66 | 67 | nonnull_all 68 | static really_inline void simd_loadu_8x32(simd_8x32_t *simd, const char *address) 69 | { 70 | simd->chunks[0] = _mm_loadu_si128((const __m128i *)(address)); 71 | simd->chunks[1] = _mm_loadu_si128((const __m128i *)(address+16)); 72 | } 73 | 74 | nonnull_all 75 | static really_inline void simd_storeu_8x32(uint8_t *address, const simd_8x32_t *simd) 76 | { 77 | _mm_storeu_si128((__m128i *)(address), simd->chunks[0]); 78 | _mm_storeu_si128((__m128i *)(address+16), simd->chunks[1]); 79 | } 80 | 81 | nonnull_all 82 | static really_inline uint64_t simd_find_8x32(const simd_8x32_t *simd, char key) 83 | { 84 | const __m128i k = _mm_set1_epi8(key); 85 | const __m128i r0 = _mm_cmpeq_epi8(simd->chunks[0], k); 86 | const __m128i r1 = _mm_cmpeq_epi8(simd->chunks[1], k); 87 | const uint32_t m0 = (uint16_t)_mm_movemask_epi8(r0); 88 | const uint32_t m1 = (uint16_t)_mm_movemask_epi8(r1); 89 | return m0 | (m1 << 16); 90 | } 91 | 92 | nonnull_all 93 | static really_inline void simd_loadu_8x64(simd_8x64_t *simd, const uint8_t *address) 94 | { 95 | simd->chunks[0] = _mm_loadu_si128((const __m128i *)(address)); 96 | simd->chunks[1] = _mm_loadu_si128((const __m128i *)(address+16)); 97 | simd->chunks[2] = _mm_loadu_si128((const __m128i *)(address+32)); 98 | simd->chunks[3] = _mm_loadu_si128((const __m128i *)(address+48)); 99 | } 100 | 101 | nonnull_all 102 | static really_inline uint64_t simd_find_8x64(const simd_8x64_t *simd, char key) 103 | { 104 | const __m128i k = _mm_set1_epi8(key); 105 | 106 | const __m128i r0 = _mm_cmpeq_epi8(simd->chunks[0], k); 107 | const __m128i r1 = _mm_cmpeq_epi8(simd->chunks[1], k); 108 | const __m128i r2 = _mm_cmpeq_epi8(simd->chunks[2], k); 109 | const __m128i r3 = _mm_cmpeq_epi8(simd->chunks[3], k); 110 | 111 | const uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); 112 | const uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); 113 | const uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); 114 | const uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); 115 | 116 | return m0 | (m1 << 16) | (m2 << 32) | (m3 << 48); 117 | } 118 | 119 | nonnull_all 120 | static really_inline uint64_t simd_find_any_8x64( 121 | const simd_8x64_t *simd, const simd_table_t table) 122 | { 123 | const __m128i t = _mm_loadu_si128((const __m128i *)table); 124 | 125 | const __m128i r0 = _mm_cmpeq_epi8( 126 | _mm_shuffle_epi8(t, simd->chunks[0]), simd->chunks[0]); 127 | const __m128i r1 = _mm_cmpeq_epi8( 128 | _mm_shuffle_epi8(t, simd->chunks[1]), simd->chunks[1]); 129 | const __m128i r2 = _mm_cmpeq_epi8( 130 | _mm_shuffle_epi8(t, simd->chunks[2]), simd->chunks[2]); 131 | const __m128i r3 = _mm_cmpeq_epi8( 132 | _mm_shuffle_epi8(t, simd->chunks[3]), simd->chunks[3]); 133 | 134 | const uint64_t m0 = (uint16_t)_mm_movemask_epi8(r0); 135 | const uint64_t m1 = (uint16_t)_mm_movemask_epi8(r1); 136 | const uint64_t m2 = (uint16_t)_mm_movemask_epi8(r2); 137 | const uint64_t m3 = (uint16_t)_mm_movemask_epi8(r3); 138 | 139 | return m0 | (m1 << 16) | (m2 << 32) | (m3 << 48); 140 | } 141 | 142 | #endif // SIMD_H 143 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: build-test 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | include: 11 | - os: ubuntu-22.04 12 | cc: gcc 13 | build_type: Debug 14 | build_tool_options: -j 4 15 | analyzer: off 16 | - os: ubuntu-22.04 17 | cc: clang 18 | build_type: Debug 19 | build_tool_options: -j 4 20 | analyzer: off 21 | sanitizer: address,undefined 22 | - os: macos-14 23 | packages: automake 24 | build_type: Debug 25 | build_tool_options: -j 4 26 | analyzer: off 27 | - os: windows-2022 28 | generator: "Visual Studio 17 2022" 29 | build_type: Debug 30 | build_tool_options: "-nologo -verbosity:minimal -maxcpucount:4 -p:CL_MPCount=4" 31 | - os: windows-2022 32 | cc: 'C:/mingw64/bin/gcc.exe' 33 | generator: 'MinGW Makefiles' 34 | build_type: Debug 35 | steps: 36 | - uses: actions/checkout@v4 37 | # Use for SSH access 38 | #- uses: mxschmitt/action-tmate@v3 39 | # with: 40 | # detached: true 41 | - uses: actions/setup-python@v5 42 | with: 43 | python-version: '3.x' # use latest Python 3.x 44 | - name: 'Add user site-packages to PATH' 45 | if: runner.os != 'Windows' 46 | shell: bash 47 | run: | 48 | echo "$(python3 -m site --user-base)/bin" >> $GITHUB_PATH 49 | - name: 'Add user site-packages to PATH' 50 | if: runner.os == 'Windows' 51 | shell: pwsh 52 | run: | 53 | $python_base = python -m site --user-base 54 | Write-Output "$python_base\\bin" >> $GITHUB_PATH 55 | - name: 'Install macOS packages' 56 | if: runner.os == 'macOS' 57 | shell: bash 58 | run: | 59 | brew install ${{matrix.packages}} 60 | - name: 'Install MSYS2 packages' 61 | uses: msys2/setup-msys2@v2 62 | if: runner.os == 'Windows' && matrix.generator == 'MinGW Makefiles' 63 | with: 64 | update: false 65 | release: false 66 | install: make gcc base-devel autotools autoconf-wrapper 67 | - name: 'Workaround for actions/runner-images#9491' 68 | if: runner.os == 'Linux' 69 | run: sudo sysctl vm.mmap_rnd_bits=28 70 | - name: 'Install Conan C/C++ package manager' 71 | id: install_conan 72 | shell: bash 73 | env: 74 | CC: ${{matrix.cc}} 75 | # Set CONAN_BASH_PATH to avoid having to build msys2 for Conan packages. 76 | CONAN_BASH_PATH: "C:\\msys64\\usr\\bin\\bash.exe" 77 | run: | 78 | pip install conan --user --upgrade 79 | conan profile detect 80 | conan_home=$(conan config home) 81 | echo "conan_data=${conan_home}/p" >> $GITHUB_OUTPUT 82 | - name: 'Restore Conan cache' 83 | id: cache_conan 84 | uses: actions/cache@v4 85 | with: 86 | key: conan | 1 | ${{runner.os}} | ${{matrix.cc}} | ${{matrix.build_type}} 87 | path: ${{ steps.install_conan.outputs.conan_data }} 88 | - name: 'Build simdzone' 89 | id: build 90 | shell: bash 91 | env: 92 | CC: ${{matrix.cc}} 93 | ANALYZER: ${{matrix.analyzer}} 94 | SANITIZER: ${{matrix.sanitizer}} 95 | GENERATOR: ${{matrix.generator}} 96 | BUILD_TYPE: ${{matrix.build_type}} 97 | BUILD_TOOL_OPTIONS: ${{matrix.build_tool_options}} 98 | WARNINGS_AS_ERRORS: ${{matrix.warnings_as_errors}} 99 | CONAN_BASH_PATH: "C:\\msys64\\usr\\bin\\bash.exe" 100 | run: | 101 | set -e -x 102 | mkdir build 103 | cd build 104 | conan install -b missing -s build_type=${BUILD_TYPE:-RelWithDebInfo} -of . ../conanfile.txt 105 | cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE:-RelWithDebInfo} \ 106 | -DCMAKE_COMPILE_WARNING_AS_ERROR=${WARNINGS_AS_ERRORS:-on} \ 107 | -DCMAKE_PREFIX_PATH=$(pwd) \ 108 | -DBUILD_TESTING=on \ 109 | -DANALYZER=${ANALYZER:-off} \ 110 | -DSANITIZER=${SANITIZER:-off} \ 111 | ${GENERATOR:+-G} ${GENERATOR:+"${GENERATOR}"} .. 112 | cmake --build . --config ${BUILD_TYPE:-RelWithDebInfo} -- ${BUILD_TOOL_OPTIONS} 113 | - name: 'Run simdzone tests' 114 | id: test 115 | shell: bash 116 | env: 117 | BUILD_TYPE: ${{matrix.build_type}} 118 | run: | 119 | set -e -x 120 | cd build 121 | ctest -j 4 --output-on-failure -T test -C ${BUILD_TYPE:-RelWithDebInfo} 122 | ZONE_KERNEL=fallback ctest -j 4 --output-on-failure -T test -C ${BUILD_TYPE:-RelWithDebInfo} 123 | - name: 'Build simdzone with configure + make' 124 | if: runner.os != 'Windows' 125 | id: test_autoconf 126 | shell: bash 127 | run: | 128 | set -e -x 129 | echo $PATH 130 | autoreconf -i 131 | ./configure 132 | make -j 2 133 | -------------------------------------------------------------------------------- /tests/ttl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ttl.c -- Test $TTL works as advertised 3 | * 4 | * Copyright (c) 2024, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "zone.h" 17 | #include "tools.h" 18 | 19 | struct rr_ttl { 20 | size_t rr; 21 | size_t ttl_count; 22 | uint32_t *ttls; 23 | }; 24 | 25 | static int32_t accept_rr( 26 | zone_parser_t *parser, 27 | const zone_name_t *owner, 28 | uint16_t type, 29 | uint16_t class, 30 | uint32_t ttl, 31 | uint16_t rdlength, 32 | const uint8_t *rdata, 33 | void *user_data) 34 | { 35 | (void)parser; 36 | (void)owner; 37 | (void)type; 38 | (void)class; 39 | (void)rdlength; 40 | (void)rdata; 41 | 42 | struct rr_ttl *rr_ttl = user_data; 43 | 44 | if (rr_ttl->rr >= rr_ttl->ttl_count) 45 | return ZONE_SYNTAX_ERROR; 46 | if (rr_ttl->ttls[rr_ttl->rr++] != ttl) 47 | return ZONE_SYNTAX_ERROR; 48 | return ZONE_SUCCESS; 49 | } 50 | 51 | /*!cmocka */ 52 | void correct_ttl_is_used(void **state) 53 | { 54 | 55 | (void)state; 56 | 57 | struct { 58 | const char *str; 59 | struct rr_ttl ttls; 60 | } tests[] = { 61 | { 62 | "$ORIGIN com.\n" 63 | "example 300 IN SOA ns hostmaster 2024081901 3600 600 86400 3600\n" 64 | "example IN NS ns\n", 65 | { 0, 2, (uint32_t[]){ 300, 300 } } 66 | }, 67 | { 68 | "$ORIGIN com.\n" 69 | "$TTL 350\n" 70 | "example 300 IN SOA ns hostmaster 2024081901 3600 600 86400 3600\n" 71 | "example IN NS ns\n", 72 | { 0, 2, (uint32_t[]){ 300, 350 } } 73 | } 74 | }; 75 | 76 | for (int i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 77 | size_t len = strlen(tests[i].str); 78 | char *str = malloc(len + ZONE_BLOCK_SIZE + 1); 79 | assert_non_null(str); 80 | memcpy(str, tests[i].str, len + 1); 81 | memset(str+len+1, 0, ZONE_BLOCK_SIZE); 82 | 83 | zone_parser_t parser; 84 | zone_name_buffer_t name; 85 | zone_rdata_buffer_t rdata; 86 | zone_buffers_t buffers = { 1, &name, &rdata }; 87 | zone_options_t options; 88 | const uint8_t origin[1] = { 0 }; 89 | int32_t code; 90 | 91 | memset(&options, 0, sizeof(options)); 92 | options.accept.callback = accept_rr; 93 | options.origin.octets = origin; 94 | options.origin.length = sizeof(origin); 95 | options.default_ttl = 3600; 96 | options.default_class = 1; 97 | 98 | code = zone_parse_string(&parser, &options, &buffers, str, (size_t)len, &tests[i].ttls); 99 | free(str); 100 | assert_int_equal(code, ZONE_SUCCESS); 101 | assert_int_equal(tests[i].ttls.rr, tests[i].ttls.ttl_count); 102 | } 103 | } 104 | 105 | /*!cmocka */ 106 | void correct_ttl_is_used_in_include(void **state) 107 | { 108 | (void)state; 109 | 110 | struct { 111 | const char *fmt; 112 | const char *str; 113 | struct rr_ttl ttls; 114 | } tests[] = { 115 | { "$ORIGIN com.\n" 116 | "example 300 IN SOA ns hostmaster 2024081901 3600 600 86400 3600\n" 117 | "$INCLUDE \"%s\"\n" 118 | "example IN A 192.0.2.1\n", 119 | "example 600 IN A 192.0.2.2\n" 120 | "example IN A 192.0.2.3\n", 121 | { 0, 4, (uint32_t[]){ 300, 600, 600, 300 } } 122 | }, 123 | { "$ORIGIN com.\n" 124 | "$TTL 350\n" 125 | "example 300 IN SOA ns hostmaster 2024081901 3600 600 86400 3600\n" 126 | "$INCLUDE \"%s\"\n" 127 | "example IN A 192.0.2.1\n", 128 | "$TTL 650\n" 129 | "example 600 IN A 192.0.2.2\n" 130 | "example IN A 192.0.2.3\n", 131 | { 0, 4, (uint32_t[]){ 300, 600, 650, 350 } } 132 | } 133 | }; 134 | 135 | for (int i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) { 136 | char *inc = get_tempnam(NULL, "zone"); 137 | assert_non_null(inc); 138 | 139 | char buf[32]; 140 | int len = snprintf(buf, sizeof(buf), tests[i].fmt, inc); 141 | assert_false(len < 0); 142 | char *str = malloc((size_t)len + ZONE_BLOCK_SIZE + 1); 143 | assert_non_null(str); 144 | (void)snprintf(str, (size_t)len + 1, tests[i].fmt, inc); 145 | memset(str+len+1, 0, ZONE_BLOCK_SIZE); 146 | 147 | FILE *handle = fopen(inc, "wb"); 148 | assert_non_null(handle); 149 | int count = fputs(tests[i].str, handle); 150 | assert_int_not_equal(count, EOF); 151 | (void)fflush(handle); 152 | (void)fclose(handle); 153 | 154 | zone_parser_t parser; 155 | zone_name_buffer_t name; 156 | zone_rdata_buffer_t rdata; 157 | zone_buffers_t buffers = { 1, &name, &rdata }; 158 | zone_options_t options; 159 | const uint8_t origin[1] = { 0 }; 160 | int32_t code; 161 | 162 | memset(&options, 0, sizeof(options)); 163 | options.accept.callback = accept_rr; 164 | options.origin.octets = origin; 165 | options.origin.length = sizeof(origin); 166 | options.default_ttl = 3600; 167 | options.default_class = 1; 168 | 169 | code = zone_parse_string(&parser, &options, &buffers, str, (size_t)len, &tests[i].ttls); 170 | remove(inc); 171 | free(inc); 172 | free(str); 173 | assert_int_equal(code, ZONE_SUCCESS); 174 | assert_int_equal(tests[i].ttls.rr, tests[i].ttls.ttl_count); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /scripts/hash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hash.c -- Calculate perfect hash for TYPEs and CLASSes 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct tuple tuple_t; 15 | struct tuple { 16 | char name[16]; 17 | uint16_t code; 18 | bool type; 19 | }; 20 | 21 | static const tuple_t types_and_classes[] = { 22 | // classes 23 | { "IN", 1, false }, 24 | { "CS", 2, false }, 25 | { "CH", 3, false }, 26 | { "HS", 4, false }, 27 | // types 28 | { "A", 1, true }, 29 | { "NS", 2, true }, 30 | { "MD", 3, true }, 31 | { "MF", 4, true }, 32 | { "CNAME", 5, true }, 33 | { "SOA", 6, true }, 34 | { "MB", 7, true }, 35 | { "MG", 8, true }, 36 | { "MR", 9, true }, 37 | { "NULL", 10, true }, 38 | { "WKS", 11, true }, 39 | { "PTR", 12, true }, 40 | { "HINFO", 13, true }, 41 | { "MINFO", 14, true }, 42 | { "MX", 15, true }, 43 | { "TXT", 16, true }, 44 | { "RP", 17, true }, 45 | { "AFSDB", 18, true }, 46 | { "X25", 19, true }, 47 | { "ISDN", 20, true }, 48 | { "RT", 21, true }, 49 | { "NSAP", 22, true }, 50 | { "NSAP-PTR", 23, true }, 51 | { "SIG", 24, true }, 52 | { "KEY", 25, true }, 53 | { "PX", 26, true }, 54 | { "GPOS", 27, true }, 55 | { "AAAA", 28, true }, 56 | { "LOC", 29, true }, 57 | { "NXT", 30, true }, 58 | { "EID", 31, true }, 59 | { "NIMLOC", 32, true }, 60 | { "SRV", 33, true }, 61 | { "ATMA", 34, true }, 62 | { "NAPTR", 35, true }, 63 | { "KX", 36, true }, 64 | { "CERT", 37, true }, 65 | { "A6", 38, true }, 66 | { "DNAME", 39, true }, 67 | { "SINK", 40, true }, 68 | { "APL", 42, true }, 69 | { "DS", 43, true }, 70 | { "SSHFP", 44, true }, 71 | { "IPSECKEY", 45, true }, 72 | { "RRSIG", 46, true }, 73 | { "NSEC", 47, true }, 74 | { "DNSKEY", 48, true }, 75 | { "DHCID", 49, true }, 76 | { "NSEC3", 50, true }, 77 | { "NSEC3PARAM", 51, true }, 78 | { "TLSA", 52, true }, 79 | { "SMIMEA", 53, true }, 80 | { "HIP", 55, true }, 81 | { "NINFO", 56, true }, 82 | { "RKEY", 57, true }, 83 | { "TALINK", 58, true }, 84 | { "CDS", 59, true }, 85 | { "CDNSKEY", 60, true }, 86 | { "OPENPGPKEY", 61, true }, 87 | { "CSYNC", 62, true }, 88 | { "ZONEMD", 63, true }, 89 | { "SVCB", 64, true }, 90 | { "HTTPS", 65, true }, 91 | { "DSYNC", 66, true }, 92 | { "SPF", 99, true }, 93 | { "NID", 104, true }, 94 | { "L32", 105, true }, 95 | { "L64", 106, true }, 96 | { "LP", 107, true }, 97 | { "EUI48", 108, true }, 98 | { "EUI64", 109, true }, 99 | { "URI", 256, true }, 100 | { "CAA", 257, true }, 101 | { "AVC", 258, true }, 102 | { "DOA", 259, true }, 103 | { "AMTRELAY", 260, true }, 104 | { "RESINFO", 261, true }, 105 | { "WALLET", 262, true }, 106 | { "CLA", 263, true }, 107 | { "IPN", 264, true }, 108 | { "TA", 32768, true }, 109 | { "DLV", 32769, true } 110 | }; 111 | 112 | const uint64_t original_magic = 3523216699ull; // original hash from hash.cpp 113 | 114 | static uint8_t hash(uint64_t magic, uint64_t value) 115 | { 116 | uint32_t value32 = ((value >> 32) ^ value); 117 | return (value32 * magic) >> 32; 118 | } 119 | 120 | static void print_table(uint64_t magic) 121 | { 122 | struct { uint16_t code; bool type; } keys[256]; 123 | memset(keys, 0, sizeof(keys)); 124 | const size_t n = sizeof(types_and_classes)/sizeof(types_and_classes[0]); 125 | for (size_t i=0; i < n; i++) { 126 | uint64_t value; 127 | memcpy(&value, types_and_classes[i].name, 8); 128 | uint8_t key = hash(magic, value); 129 | keys[key].code = types_and_classes[i].code; 130 | keys[key].type = types_and_classes[i].type; 131 | } 132 | 133 | printf("static const symbol_t *hash_to_symbol[256] = {\n"); 134 | for (size_t i=0; i < 256; ) { 135 | printf(" "); 136 | for (size_t j=i+8; i < j; i++) { 137 | uint16_t code; 138 | switch(keys[i].code) { 139 | case 32768: code = 270; // index of TA in types array in generic/types.h 140 | break; 141 | case 32769: code = 271; // index of DLV in types array in generic/types.h 142 | break; 143 | default : code = keys[i].code; 144 | break; 145 | } 146 | char macro = !code ? 'V' : keys[i].type ? 'T' : 'C'; 147 | char s[10]; 148 | snprintf(s, sizeof(s), " %c(%u)", macro, code); 149 | printf("%7s%s", s, (i < 255 ? "," : "")); 150 | } 151 | printf("\n"); 152 | } 153 | printf("};\n"); 154 | } 155 | 156 | int main(int argc, char *argv[]) 157 | { 158 | const size_t n = sizeof(types_and_classes)/sizeof(types_and_classes[0]); 159 | for (uint64_t magic = original_magic; magic < UINT64_MAX; magic++) { 160 | size_t i; 161 | uint16_t keys[256] = { 0 }; 162 | for (i=0; i < n; i++) { 163 | uint64_t value; 164 | memcpy(&value, types_and_classes[i].name, 8); 165 | 166 | uint8_t key = hash(magic, value); 167 | if (keys[key]) 168 | break; 169 | keys[key] = 1; 170 | } 171 | 172 | if (i == n) { 173 | printf("i: %zu, magic: %" PRIu64 "\n", i, magic); 174 | for (i=0; i < n; i++) { 175 | uint64_t value; 176 | memcpy(&value, types_and_classes[i].name, 8); 177 | uint8_t key = hash(magic, value); 178 | printf("TYPE_%s: %" PRIu8 " (%" PRIu16 ")\n", types_and_classes[i].name, key, types_and_classes[i].code); 179 | } 180 | print_table(magic); 181 | return 0; 182 | } 183 | } 184 | 185 | printf("no magic value\n"); 186 | return 1; 187 | } 188 | -------------------------------------------------------------------------------- /doc/manual/design_notes.rst: -------------------------------------------------------------------------------- 1 | .. include:: links.rst 2 | 3 | .. _simdjson paper: https://arxiv.org/abs/1902.08318 4 | 5 | ############### 6 | Notes on design 7 | ############### 8 | 9 | |project| draws inspiration from the |url::simdjson| project. The simdjson 10 | project demonstrates that throughput can be much improved using single 11 | instruction, multiple data (SIMD) instructions found in modern processors. 12 | 13 | The `simdjson paper`_ describes the technical details in great depth and is 14 | definitely worth reading first. A concise recap to understand where |project| 15 | is different. 16 | 17 | simdjson operates in two stages. simdjson first allocates enough memory to 18 | store the document and the worst-case number of indexes. The first stage, or 19 | indexing stage, operates on 64-byte blocks and uses vectorized classification 20 | to identify structural characters. Relative indexes are stored in a bitmask 21 | and binary logic is used to identify strings and sequences of non-structural 22 | characters. Using more binary logic, the indexes of all structural characters 23 | and the first character of each sequence is retained. Fast population and 24 | trailing zero count instructions found in modern processors are then used to 25 | write out indexes in order to a vector, dubbed "tape" by simdjson. 26 | 27 | The second stage iterates all indexes and parses booleans, integers, 28 | floating-point values, strings and any structurals, constructing the Document 29 | Object Model (DOM) on-the-fly. SIMD instructions are used to avoid branching 30 | as much as possible. 31 | 32 | |project| adheres to most basic principles, but there are some important 33 | modifications due to differences between both formats. 34 | 35 | .. note:: 36 | This document is meant to capture important notes on the inner workings 37 | of |project|, it is currently work-in-progress. 38 | 39 | 40 | Buffered operation 41 | ------------------ 42 | 43 | |project| was created to work with large DNS zones more conveniently. 44 | 45 | - ``.com`` (~161 million domains, ~24GB in size) 46 | - ``.nl`` (~6.3 million domains) 47 | - ``.se`` (~1.4 million domains, ~1.2GB in size) 48 | 49 | Keeping all data in memory for parsing and serving means a minimum of ~50GB of 50 | memory is required for loading ``.com``. Not counting the structures to retain 51 | ordering and hierarchy, which typically require more memory than the data. 52 | As a result, |project| retains the notion of a tape and two separate stages, 53 | but indexing happens on-the-fly as required. 54 | 55 | On-the-fly indexing is also required because the DNS presentation format 56 | supports the ``$INCLUDE`` control directive. If such a control directive is 57 | encountered, the parser is expected to parse the data in the specified file 58 | before parsing the remaining contents in the current file. Tape operation is 59 | therefore managed on a per file base. 60 | 61 | On-the-fly indexing is a good tradeoff, but does introduce some complexity 62 | with regards to handling partial tokens. 63 | 64 | Padded operation 65 | ^^^^^^^^^^^^^^^^ 66 | 67 | The AVX-512 instruction set offers masked load and store operations, earlier 68 | instruction sets, like SSE4.2 and AVX2 do not. |project| requires input 69 | buffers to be sufficiently large enough to ensure optimized operations can 70 | safely load blocks of input data without reading past the buffer limit. This 71 | requirement is of no concern to the user when parsing files as |project| 72 | manages input buffers. The requirement is of concern when the user provides 73 | the input directly as the buffer must be null-terminated and padded. 74 | 75 | .. note:: 76 | Future releases may lift this requirement by using fallback 77 | implementations for the last set of tokens that are not sufficiently 78 | padded. 79 | 80 | 81 | Comments 82 | -------- 83 | 84 | |project| uses much of same classification algorithm, with one caveat. The 85 | DNS presentation format supports comments, which makes classification a bit 86 | more difficult. A scalar loop is used to correctly discard semicolons within 87 | strings and quotes within comments. 88 | 89 | 90 | Delimiters 91 | ---------- 92 | 93 | The DNS presentation format is less strict. JSON requires strings to be 94 | quoted, the same is not true for the DNS presentation format. Strings in TXT 95 | records maybe written as a set of contiguous characters without interior 96 | spaces or as a string beginning with a ``"`` (quote) and ending with a ``".`` 97 | :RFC:`9460` introduces SvcParams, the value of which may be quoted or not. To 98 | avoid writing more than one parser for types, store the length of the token 99 | so that the parser does not have to scan for the delimiter. 100 | 101 | Rather than storing the data once and determine by inspection of delimiting 102 | index if tape must be advanced again, use two separate tapes. Since the format 103 | is tabular, master files rarely contain structural characters other than 104 | newlines, therefore the overhead is small. The same logic is used when writing 105 | indexes to the tapes. 106 | 107 | 108 | Predictability 109 | -------------- 110 | 111 | Once the RTYPE is known, the RDATA layout is more-or-less predictable. This 112 | knowledge can be leveraged to expect the right token and fallback to a slower 113 | path. That way the optimized path can be inlined while the slower path resides 114 | in a function. Binary size hereby reduced as much as possible while token 115 | extraction is as fast as can be. This knowledge also allows for calling the 116 | correct parser functions in order, eliminating the need for more calling more 117 | generalized parser functions based on a descriptor table. 118 | -------------------------------------------------------------------------------- /src/fallback/scanner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * scanner.h -- fallback (non-simd) lexical analyzer for (DNS) zone data 3 | * 4 | * Copyright (c) 2022-2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef SCANNER_H 10 | #define SCANNER_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | nonnull_all 17 | static really_inline const char *scan_comment( 18 | parser_t *parser, const char *start, const char *end) 19 | { 20 | assert(!parser->file->state.is_escaped); 21 | 22 | while (start < end) { 23 | if (unlikely(*start == '\n')) { 24 | parser->file->state.in_comment = 0; 25 | return start; 26 | } 27 | start++; 28 | } 29 | 30 | parser->file->state.in_comment = 1; 31 | return start; 32 | } 33 | 34 | nonnull_all 35 | static really_inline const char *scan_quoted( 36 | parser_t *parser, const char *start, const char *end) 37 | { 38 | if (unlikely(parser->file->state.is_escaped && start < end)) 39 | goto escaped; 40 | 41 | while (start < end) { 42 | if (*start == '\\') { 43 | start++; 44 | escaped: 45 | if ((parser->file->state.is_escaped = (start == end))) 46 | break; 47 | assert(start < end); 48 | *parser->file->newlines.tail += (*start == '\n'); 49 | start++; 50 | } else if (*start == '\"') { 51 | parser->file->state.in_quoted = 0; 52 | *parser->file->delimiters.tail++ = start; 53 | return ++start; 54 | } else { 55 | *parser->file->newlines.tail += (*start == '\n'); 56 | start++; 57 | } 58 | } 59 | 60 | parser->file->state.in_quoted = 1; 61 | return start; 62 | } 63 | 64 | nonnull_all 65 | static really_inline const char *scan_contiguous( 66 | parser_t *parser, const char *start, const char *end) 67 | { 68 | if (parser->file->state.is_escaped && start < end) 69 | goto escaped; 70 | 71 | while (start < end) { 72 | // null-byte is considered contiguous by the indexer (for now) 73 | if (likely((classify[ (uint8_t)*start ] & ~CONTIGUOUS) == 0)) { 74 | if (unlikely(*start == '\\')) { 75 | start++; 76 | escaped: 77 | if ((parser->file->state.is_escaped = (start == end))) 78 | break; 79 | assert(start < end); 80 | parser->file->newlines.tail[0] += (*start == '\n'); 81 | } 82 | start++; 83 | } else { 84 | parser->file->state.follows_contiguous = 0; 85 | *parser->file->delimiters.tail++ = start; 86 | return start; 87 | } 88 | } 89 | 90 | parser->file->state.follows_contiguous = 1; 91 | return start; 92 | } 93 | 94 | nonnull_all 95 | static really_inline void scan( 96 | parser_t *parser, const char *start, const char *end) 97 | { 98 | if (parser->file->state.follows_contiguous) 99 | start = scan_contiguous(parser, start, end); 100 | else if (parser->file->state.in_comment) 101 | start = scan_comment(parser, start, end); 102 | else if (parser->file->state.in_quoted) 103 | start = scan_quoted(parser, start, end); 104 | 105 | while (start < end) { 106 | const int32_t code = classify[(uint8_t)*start]; 107 | if (code == BLANK) { 108 | start++; 109 | } else if ((code & ~CONTIGUOUS) == 0) { 110 | // null-byte is considered contiguous by the indexer (for now) 111 | *parser->file->fields.tail++ = start; 112 | start = scan_contiguous(parser, start, end); 113 | } else if (code == LINE_FEED) { 114 | if (*parser->file->newlines.tail) { 115 | *parser->file->fields.tail++ = line_feed; 116 | parser->file->newlines.tail++; 117 | } else { 118 | *parser->file->fields.tail++ = start; 119 | } 120 | start++; 121 | } else if (code == QUOTED) { 122 | *parser->file->fields.tail++ = start; 123 | start = scan_quoted(parser, start + 1, end); 124 | } else if (code == LEFT_PAREN || code == RIGHT_PAREN) { 125 | *parser->file->fields.tail++ = start; 126 | start++; 127 | } else { 128 | assert(code == COMMENT); 129 | start = scan_comment(parser, start, end); 130 | } 131 | } 132 | } 133 | 134 | nonnull_all 135 | warn_unused_result 136 | static really_inline int32_t reindex(parser_t *parser) 137 | { 138 | assert(parser->file->buffer.index <= parser->file->buffer.length); 139 | size_t left = parser->file->buffer.length - parser->file->buffer.index; 140 | const char *data = parser->file->buffer.data + parser->file->buffer.index; 141 | const char **tape = parser->file->fields.tail; 142 | const char **tape_limit = parser->file->fields.tape + ZONE_TAPE_SIZE; 143 | 144 | if (left >= ZONE_BLOCK_SIZE) { 145 | const char *data_limit = parser->file->buffer.data + 146 | (parser->file->buffer.length - ZONE_BLOCK_SIZE); 147 | while (data <= data_limit && ((uintptr_t)tape_limit - (uintptr_t)tape) >= ZONE_BLOCK_SIZE) { 148 | scan(parser, data, data + ZONE_BLOCK_SIZE); 149 | parser->file->buffer.index += ZONE_BLOCK_SIZE; 150 | data += ZONE_BLOCK_SIZE; 151 | tape = parser->file->fields.tail; 152 | } 153 | 154 | assert(parser->file->buffer.index <= parser->file->buffer.length); 155 | left = parser->file->buffer.length - parser->file->buffer.index; 156 | } 157 | 158 | // only scan partial blocks after reading all data 159 | if (parser->file->end_of_file) { 160 | assert(left < ZONE_BLOCK_SIZE); 161 | if (!left) { 162 | parser->file->end_of_file = NO_MORE_DATA; 163 | } else if (((uintptr_t)tape_limit - (uintptr_t)tape) >= left) { 164 | scan(parser, data, data + left); 165 | parser->file->end_of_file = NO_MORE_DATA; 166 | parser->file->buffer.index += left; 167 | parser->file->state.follows_contiguous = 0; 168 | } 169 | } 170 | 171 | return (parser->file->state.follows_contiguous | parser->file->state.in_quoted) != 0; 172 | } 173 | 174 | #endif // SCANNER_H 175 | -------------------------------------------------------------------------------- /src/generic/algorithm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * algorithm.h -- Algorithm RDATA parser 3 | * 4 | * Copyright (c) 2023, NLnet Labs. All rights reserved. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | */ 9 | #ifndef ALGORITHM_H 10 | #define ALGORITHM_H 11 | 12 | // https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml 13 | 14 | typedef struct algorithm algorithm_t; 15 | struct algorithm { 16 | struct { 17 | char name[24]; 18 | size_t length; 19 | } key; 20 | uint8_t value; 21 | }; 22 | 23 | #define BAD_ALGORITHM(value) \ 24 | { { "", 0 }, 0 } 25 | #define ALGORITHM(name, value) \ 26 | { { name, sizeof(name) - 1 }, value } 27 | 28 | static const algorithm_t algorithms[32] = { 29 | BAD_ALGORITHM(0), 30 | ALGORITHM("RSAMD5", 1), 31 | ALGORITHM("DH", 2), 32 | ALGORITHM("DSA", 3), 33 | ALGORITHM("ECC", 4), 34 | ALGORITHM("RSASHA1", 5), 35 | ALGORITHM("DSA-NSEC-SHA1", 6), 36 | ALGORITHM("RSASHA1-NSEC3-SHA1", 7), 37 | ALGORITHM("RSASHA256", 8), 38 | BAD_ALGORITHM(9), 39 | ALGORITHM("RSASHA512", 10), 40 | BAD_ALGORITHM(11), 41 | ALGORITHM("ECC-GOST", 12), 42 | ALGORITHM("ECDSAP256SHA256", 13), 43 | ALGORITHM("ECDSAP384SHA384", 14), 44 | BAD_ALGORITHM(15), 45 | ALGORITHM("INDIRECT", 252), 46 | ALGORITHM("PRIVATEDNS", 253), 47 | ALGORITHM("PRIVATEOID", 254), 48 | }; 49 | 50 | static const struct { 51 | const algorithm_t *algorithm; 52 | uint8_t mask[24]; 53 | } algorithm_hash_map[16] = { 54 | { &algorithms[2], // DH (0) 55 | { 0xdf, 0xdf, 0 } }, 56 | { &algorithms[10], // RSASHA512 (1) 57 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 58 | 0xff, 0 } }, 59 | { &algorithms[7], // RSASHA1-NSEC3-SHA1 (2) 60 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 61 | 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xdf, 0xdf, 62 | 0xdf, 0xff, 0 } }, 63 | { &algorithms[8], // RSASHA256 (3) 64 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 65 | 0xff, 0 } }, 66 | { &algorithms[13], // ECDSAP256SHA256 (4) 67 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 68 | 0xff, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } }, 69 | { &algorithms[0], // unknown 70 | { 0 } }, 71 | { &algorithms[6], // DSA-NSEC-SHA1 (6) 72 | { 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf, 73 | 0xff, 0xdf, 0xdf, 0xdf, 0xff, 0 } }, 74 | { &algorithms[1], // RSAMD5 (7) 75 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0 } }, 76 | { &algorithms[5], // RSASHA1 (8) 77 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0 } }, 78 | { &algorithms[17], // PRIVATEDNS (9) 79 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 80 | 0xdf, 0xdf, 0 } }, 81 | { &algorithms[18], // PRIVATEOID (10) 82 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 83 | 0xdf, 0xdf, 0 } }, 84 | { &algorithms[16], // INDIRECT (11) 85 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 86 | 0 } }, 87 | { &algorithms[14], // ECDSAP384SHA384 (12) 88 | { 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 89 | 0xff, 0xdf, 0xdf, 0xdf, 0xff, 0xff, 0xff, 0 } }, 90 | { &algorithms[3], // DSA (13) 91 | { 0xdf, 0xdf, 0xdf, 0 } }, 92 | { &algorithms[4], // ECC (14) 93 | { 0xdf, 0xdf, 0xdf, 0 } }, 94 | { &algorithms[12], // ECC-GHOST (15) 95 | { 0xdf, 0xdf, 0xdf, 0xff, 0xdf, 0xdf, 0xdf, 0xdf, 96 | 0 } } 97 | }; 98 | 99 | #undef UNKNOWN_ALGORITHM 100 | #undef ALGORITHM 101 | 102 | // magic value generated using algorithm-hash.c 103 | static uint8_t algorithm_hash(uint64_t value) 104 | { 105 | value = le64toh(value); 106 | uint32_t value32 = (uint32_t)((value >> 32) ^ value); 107 | return (uint8_t)((value32 * 29874llu) >> 32) & 0xf; 108 | } 109 | 110 | nonnull_all 111 | warn_unused_result 112 | static really_inline int32_t scan_algorithm( 113 | const char *data, size_t length, uint8_t *number) 114 | { 115 | static const int8_t zero_masks[48] = { 116 | -1, -1, -1, -1, -1, -1, -1, -1, 117 | -1, -1, -1, -1, -1, -1, -1, -1, 118 | -1, -1, -1, -1, -1, -1, -1, -1, 119 | -1, -1, -1, -1, -1, -1, -1, -1, 120 | 0, 0, 0, 0, 0, 0, 0, 0, 121 | 0, 0, 0, 0, 0, 0, 0, 0 122 | }; 123 | 124 | if ((uint8_t)*data - '0' > 9) { 125 | uint64_t input; 126 | memcpy(&input, data, 8); 127 | const uint64_t letter_mask = 0x4040404040404040llu; 128 | // convert to upper case 129 | input &= ~((input & letter_mask) >> 1); 130 | // zero out non-relevant bytes 131 | uint64_t zero_mask; 132 | memcpy(&zero_mask, &zero_masks[32 - (length & 0x1f)], 8); 133 | input &= zero_mask; 134 | const uint8_t index = algorithm_hash(input); 135 | assert(index < 16); 136 | const algorithm_t *algorithm = algorithm_hash_map[index].algorithm; 137 | uint64_t matches, mask, name; 138 | // compare bytes 0-7 139 | memcpy(&name, algorithm->key.name, 8); 140 | matches = input == name; 141 | // compare bytes 8-15 142 | memcpy(&input, data + 8, 8); 143 | memcpy(&mask, algorithm_hash_map[index].mask + 8, 8); 144 | memcpy(&name, algorithm->key.name + 8, 8); 145 | matches &= (input & mask) == name; 146 | // compare bytes 16-23 147 | memcpy(&input, data + 16, 8); 148 | memcpy(&mask, algorithm_hash_map[index].mask + 16, 8); 149 | memcpy(&name, algorithm->key.name + 16, 8); 150 | matches &= (input & mask) == name; 151 | *number = algorithm->value; 152 | return matches & (length == algorithm->key.length) & (*number > 0); 153 | } 154 | 155 | return scan_int8(data, length, number); 156 | } 157 | 158 | nonnull_all 159 | warn_unused_result 160 | static really_inline int32_t parse_algorithm( 161 | parser_t *parser, 162 | const type_info_t *type, 163 | const rdata_info_t *field, 164 | rdata_t *rdata, 165 | const token_t *token) 166 | { 167 | if (!scan_algorithm(token->data, token->length, rdata->octets)) 168 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 169 | rdata->octets++; 170 | return 0; 171 | } 172 | 173 | #endif // ALGORITHM_H 174 | -------------------------------------------------------------------------------- /src/generic/ip6.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * SPDX-License-Identifier: ISC 3 | * 4 | * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") 5 | * Copyright (c) 1996,1999 by Internet Software Consortium. 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 17 | * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | #ifndef IP6_H 20 | #define IP6_H 21 | 22 | #ifndef NS_INT16SZ 23 | #define NS_INT16SZ 2 24 | #endif 25 | 26 | #ifndef NS_IN6ADDRSZ 27 | #define NS_IN6ADDRSZ 16 28 | #endif 29 | 30 | #ifndef NS_INADDRSZ 31 | #define NS_INADDRSZ 4 32 | #endif 33 | 34 | /* int 35 | * inet_pton4(src, dst) 36 | * like inet_aton() but without all the hexadecimal and shorthand. 37 | * return: 38 | * length if `src' is a valid dotted quad, else 0. 39 | * notice: 40 | * does not touch `dst' unless it's returning !0. 41 | * author: 42 | * Paul Vixie, 1996. 43 | */ 44 | static int 45 | inet_pton4(const char *src, uint8_t *dst) 46 | { 47 | static const char digits[] = "0123456789"; 48 | int saw_digit, octets, ch; 49 | uint8_t tmp[NS_INADDRSZ], *tp; 50 | const char *start = src; 51 | 52 | saw_digit = 0; 53 | octets = 0; 54 | *(tp = tmp) = 0; 55 | for (; (ch = *src); src++) { 56 | const char *pch; 57 | 58 | if ((pch = strchr(digits, ch)) != NULL) { 59 | uint32_t new = *tp * 10 + (uint32_t)(pch - digits); 60 | 61 | if (new > 255) 62 | return (0); 63 | *tp = (uint8_t)new; 64 | if (! saw_digit) { 65 | if (++octets > 4) 66 | return (0); 67 | saw_digit = 1; 68 | } 69 | } else if (ch == '.' && saw_digit) { 70 | if (octets == 4) 71 | return (0); 72 | *++tp = 0; 73 | saw_digit = 0; 74 | } else 75 | break; 76 | } 77 | if (octets < 4) 78 | return (0); 79 | 80 | memcpy(dst, tmp, NS_INADDRSZ); 81 | return (int)(src - start); 82 | } 83 | 84 | /* int 85 | * inet_pton6(src, dst) 86 | * convert presentation level address to network order binary form. 87 | * return: 88 | * length if `src' is a valid [RFC1884 2.2] address, else 0. 89 | * notice: 90 | * (1) does not touch `dst' unless it's returning !0. 91 | * (2) :: in a full address is silently ignored. 92 | * credit: 93 | * inspired by Mark Andrews. 94 | * author: 95 | * Paul Vixie, 1996. 96 | */ 97 | static int 98 | inet_pton6(const char *src, uint8_t *dst) 99 | { 100 | static const char xdigits_l[] = "0123456789abcdef", 101 | xdigits_u[] = "0123456789ABCDEF"; 102 | uint8_t tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp; 103 | const char *xdigits, *curtok; 104 | int ch, saw_xdigit; 105 | uint32_t val; 106 | int len; 107 | const char *start = src; 108 | 109 | memset((tp = tmp), '\0', NS_IN6ADDRSZ); 110 | endp = tp + NS_IN6ADDRSZ; 111 | colonp = NULL; 112 | /* Leading :: requires some special handling. */ 113 | if (*src == ':') 114 | if (*++src != ':') 115 | return (0); 116 | curtok = src; 117 | saw_xdigit = 0; 118 | val = 0; 119 | for (; (ch = *src); src++) { 120 | const char *pch; 121 | 122 | if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) 123 | pch = strchr((xdigits = xdigits_u), ch); 124 | if (pch != NULL) { 125 | val <<= 4; 126 | val |= (pch - xdigits); 127 | if (val > 0xffff) 128 | return (0); 129 | saw_xdigit = 1; 130 | continue; 131 | } 132 | if (ch == ':') { 133 | curtok = src+1; 134 | if (!saw_xdigit) { 135 | if (colonp) 136 | return (0); 137 | colonp = tp; 138 | continue; 139 | } 140 | if (tp + NS_INT16SZ > endp) 141 | return (0); 142 | *tp++ = (uint8_t) (val >> 8) & 0xff; 143 | *tp++ = (uint8_t) val & 0xff; 144 | saw_xdigit = 0; 145 | val = 0; 146 | continue; 147 | } 148 | if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && 149 | (len = inet_pton4(curtok, tp)) > 0) { 150 | src = curtok + len; 151 | tp += NS_INADDRSZ; 152 | saw_xdigit = 0; 153 | break; /* '\0' was seen by inet_pton4(). */ 154 | } 155 | break; 156 | } 157 | if (saw_xdigit) { 158 | if (tp + NS_INT16SZ > endp) 159 | return (0); 160 | *tp++ = (uint8_t) (val >> 8) & 0xff; 161 | *tp++ = (uint8_t) val & 0xff; 162 | } 163 | if (colonp != NULL) { 164 | /* 165 | * Since some memmove()'s erroneously fail to handle 166 | * overlapping regions, we'll do the shift by hand. 167 | */ 168 | const int n = (int)(tp - colonp); 169 | int i; 170 | 171 | for (i = 1; i <= n; i++) { 172 | endp[- i] = colonp[n - i]; 173 | colonp[n - i] = 0; 174 | } 175 | tp = endp; 176 | } 177 | if (tp != endp) 178 | return (0); 179 | memcpy(dst, tmp, NS_IN6ADDRSZ); 180 | return (int)(src - start); 181 | } 182 | 183 | nonnull_all 184 | static really_inline int32_t scan_ip6(const char *text, uint8_t *wire) 185 | { 186 | return inet_pton6(text, wire); 187 | } 188 | 189 | nonnull_all 190 | static really_inline int32_t parse_ip6( 191 | parser_t *parser, 192 | const type_info_t *type, 193 | const rdata_info_t *item, 194 | rdata_t *rdata, 195 | const token_t *token) 196 | { 197 | if ((size_t)inet_pton6(token->data, rdata->octets) != token->length) 198 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(item), NAME(type)); 199 | rdata->octets += 16; 200 | return 0; 201 | } 202 | 203 | #endif // IP6_H 204 | -------------------------------------------------------------------------------- /src/westmere/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ip4.h -- SSE 4.1 parser for time stamps 3 | * https://lemire.me/blog/2023/07/01/parsing-time-stamps-faster-with-simd-instructions/ 4 | * 5 | * Copyright (c) 2023. Daniel Lemire 6 | * 7 | * SPDX-License-Identifier: BSD-3-Clause 8 | * 9 | */ 10 | #ifndef SSE_TIME_H 11 | #define SSE_TIME_H 12 | 13 | static const int mdays_minus_one[] = {30, 27, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30}; 14 | 15 | static const int mdays_cumulative[] = {0, 31, 59, 90, 120, 151, 181, 16 | 212, 243, 273, 304, 334, 365}; 17 | 18 | /* 19 | The 32-bit timestamp spans from year 1970 to 2106. 20 | Therefore, the only special case for leap years is 2100. 21 | We use that to produce fast functions. 22 | */ 23 | static inline uint32_t is_leap_year(uint32_t year) { 24 | return (year % 4 == 0) & (year != 2100); 25 | } 26 | 27 | static inline uint32_t leap_days(uint32_t year) { 28 | --year; 29 | return (year/4 - 1970/4) - (year >= 2100); 30 | } 31 | 32 | static bool sse_parse_time(const char *date_string, uint32_t *time_in_second) { 33 | // We load the block of digits. We subtract 0x30 (the code point value of the 34 | // character '0'), and all bytes values should be between 0 and 9, 35 | // inclusively. We know that some character must be smaller that 9, for 36 | // example, we cannot have more than 59 seconds and never 60 seconds, in the 37 | // time stamp string. So one character must be between 0 and 5. Similarly, we 38 | // start the hours at 00 and end at 23, so one character must be between 0 39 | // and 2. We do a saturating subtraction of the maximum: the result of such a 40 | // subtraction should be zero if the value is no larger. We then use a special 41 | // instruction to multiply one byte by 10, and sum it up with the next byte, 42 | // getting a 16-bit value. We then repeat the same approach as before, 43 | // checking that the result is not too large. 44 | // 45 | // We compute the month the good old ways, as an integer in [0,11], we 46 | // check for overflows later. 47 | uint64_t mo = (uint64_t)((date_string[4]-0x30)*10 + (date_string[5]-0x30) - 1); 48 | __m128i v = _mm_loadu_si128((const __m128i *)date_string); 49 | // loaded YYYYMMDDHHmmSS..... 50 | v = _mm_xor_si128(v, _mm_set1_epi8(0x30)); 51 | // W can use _mm_sub_epi8 or _mm_xor_si128 for the subtraction above. 52 | // subtracting by 0x30 (or '0'), turns all values into a byte value between 0 53 | // and 9 if the initial input was made of digits. 54 | __m128i limit = 55 | _mm_setr_epi8(9, 9, 9, 9, 1, 9, 3, 9, 2, 9, 5, 9, 5, 9, -1, -1); 56 | // credit @aqrit 57 | // overflows are still possible, if hours are in the range 24 to 29 58 | // of if days are in the range 32 to 39 59 | // or if months are in the range 12 to 19. 60 | __m128i abide_by_limits = _mm_subs_epu8(v, limit); // must be all zero 61 | 62 | #if defined __SUNPRO_C 63 | __m128i byteflip = _mm_setr_epi64((__m64){0x0607040502030001ULL}, 64 | (__m64){0x0e0f0c0d0a0b0809ULL}); 65 | #else 66 | __m128i byteflip = _mm_setr_epi64((__m64)0x0607040502030001ULL, 67 | (__m64)0x0e0f0c0d0a0b0809ULL); 68 | #endif 69 | 70 | __m128i little_endian = _mm_shuffle_epi8(v, byteflip); 71 | __m128i limit16 = _mm_setr_epi16(0x0909, 0x0909, 0x0102, 0x0301, 0x0203, 72 | 0x0509, 0x0509, -1); 73 | __m128i abide_by_limits16 = 74 | _mm_subs_epu16(little_endian, limit16); // must be all zero 75 | 76 | __m128i combined_limits = 77 | _mm_or_si128(abide_by_limits16, abide_by_limits); // must be all zero 78 | 79 | if (!_mm_test_all_zeros(combined_limits, combined_limits)) { 80 | return false; 81 | } 82 | // 0x000000SS0mmm0HHH`00DD00MM00YY00YY 83 | ////////////////////////////////////////////////////// 84 | // pmaddubsw has a high latency (e.g., 5 cycles) and is 85 | // likely a performance bottleneck. 86 | ///////////////////////////////////////////////////// 87 | const __m128i weights = _mm_setr_epi8( 88 | // Y Y Y Y m m d d H H M M S S - - 89 | 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 0, 0); 90 | v = _mm_maddubs_epi16(v, weights); 91 | 92 | uint64_t hi = (uint64_t)_mm_extract_epi64(v, 1); 93 | uint64_t seconds = (hi * 0x0384000F00004000) >> 46; 94 | uint64_t lo = (uint64_t)_mm_extract_epi64(v, 0); 95 | uint64_t yr = (lo * 0x64000100000000) >> 48; 96 | 97 | // We compute the day (starting at zero). We implicitly 98 | // check for overflows later. 99 | uint64_t dy = (uint64_t)_mm_extract_epi8(v, 6) - 1; 100 | 101 | bool is_leap_yr = (bool)is_leap_year((uint32_t)yr); 102 | if(yr < 1970 || mo > 11) { return false; } // unlikely branch 103 | if (dy > (uint64_t)mdays_minus_one[mo]) { // unlikely branch 104 | if (mo == 1 && is_leap_yr) { 105 | if (dy != 29 - 1) { 106 | return false; 107 | } 108 | } else { 109 | return false; 110 | } 111 | } 112 | uint64_t days = 365 * (yr - 1970) + (uint64_t)leap_days((uint32_t)yr); 113 | 114 | days += (uint64_t)mdays_cumulative[mo]; 115 | days += is_leap_yr & (mo > 1); 116 | 117 | days += dy; 118 | uint64_t time_in_second64 = seconds + days * 60 * 60 * 24; 119 | *time_in_second = (uint32_t)time_in_second64; 120 | return true; 121 | } 122 | 123 | nonnull_all 124 | static really_inline int32_t parse_time( 125 | parser_t *parser, 126 | const type_info_t *type, 127 | const rdata_info_t *field, 128 | rdata_t *rdata, 129 | const token_t *token) 130 | { 131 | uint32_t time; 132 | 133 | if (unlikely(token->length != 14)) 134 | return parse_int32(parser, type, field, rdata, token); 135 | if (!sse_parse_time(token->data, &time)) 136 | SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 137 | 138 | time = htobe32(time); 139 | memcpy(rdata->octets, &time, sizeof(time)); 140 | rdata->octets += sizeof(time); 141 | return 0; 142 | } 143 | 144 | #endif // TIME_H 145 | --------------------------------------------------------------------------------