diff options
Diffstat (limited to 'src/n-acd')
| -rw-r--r-- | src/n-acd/.editorconfig | 11 | ||||
| -rw-r--r-- | src/n-acd/.github/workflows/ci.yml | 122 | ||||
| -rw-r--r-- | src/n-acd/.gitmodules | 12 | ||||
| -rw-r--r-- | src/n-acd/AUTHORS | 39 | ||||
| -rw-r--r-- | src/n-acd/NEWS.md | 46 | ||||
| -rw-r--r-- | src/n-acd/README.md | 60 | ||||
| -rw-r--r-- | src/n-acd/meson.build | 27 | ||||
| -rw-r--r-- | src/n-acd/meson_options.txt | 1 | ||||
| -rw-r--r-- | src/n-acd/src/libnacd.sym | 28 | ||||
| -rw-r--r-- | src/n-acd/src/meson.build | 95 | ||||
| -rw-r--r-- | src/n-acd/src/test-api.c | 88 | ||||
| -rw-r--r-- | src/n-acd/src/test-bpf.c | 226 | ||||
| -rw-r--r-- | src/n-acd/src/test-loopback.c | 82 | ||||
| -rw-r--r-- | src/n-acd/src/test-twice.c | 97 | ||||
| -rw-r--r-- | src/n-acd/src/test-unplug.c | 84 | ||||
| -rw-r--r-- | src/n-acd/src/test-unused.c | 63 | ||||
| -rw-r--r-- | src/n-acd/src/test-veth.c | 240 | ||||
| -rw-r--r-- | src/n-acd/src/test.h | 213 | ||||
| -rw-r--r-- | src/n-acd/src/util/test-timer.c | 177 | ||||
| l--------- | src/n-acd/subprojects/c-list | 1 | ||||
| l--------- | src/n-acd/subprojects/c-rbtree | 1 | ||||
| l--------- | src/n-acd/subprojects/c-siphash | 1 | ||||
| l--------- | src/n-acd/subprojects/libcstdaux-1 | 1 |
23 files changed, 1715 insertions, 0 deletions
diff --git a/src/n-acd/.editorconfig b/src/n-acd/.editorconfig new file mode 100644 index 00000000..b10bb4f3 --- /dev/null +++ b/src/n-acd/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{c,h}] +indent_style = space +indent_size = 8 diff --git a/src/n-acd/.github/workflows/ci.yml b/src/n-acd/.github/workflows/ci.yml new file mode 100644 index 00000000..22fc8141 --- /dev/null +++ b/src/n-acd/.github/workflows/ci.yml @@ -0,0 +1,122 @@ +name: Continuous Integration + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + ci: + name: CI with Default Configuration + runs-on: ubuntu-latest + + steps: + # + # Prepare CI + # + # We cannot use the github-action of the `ci-c-util` project, because we + # need privileges in the container. Therefore, fetch the CI sources and + # build the container manually. + # + - name: Fetch CI + uses: actions/checkout@v2 + with: + repository: c-util/automation + ref: v1 + path: automation + - name: Build CI + working-directory: automation/src/ci-c-util + run: docker build --tag ci-c-util:v1 . + + # + # Run CI + # + # Take the CI image we built and run the CI with the default project + # configuration. We do not use valgrind, since it falls-over with bpf(2) + # syscalls. + # + - name: Fetch Sources + uses: actions/checkout@v2 + with: + path: source + - name: Run through C-Util CI + run: | + docker run \ + --privileged \ + -v "$(pwd)/source:/github/workspace" \ + "ci-c-util:v1" \ + "--m32=1" \ + "--source=/github/workspace" + + ci-no-ebpf: + name: CI without eBPF + runs-on: ubuntu-latest + + steps: + # See above in 'ci' job. + - name: Fetch CI + uses: actions/checkout@v2 + with: + repository: c-util/automation + ref: v1 + path: automation + - name: Build CI + working-directory: automation/src/ci-c-util + run: docker build --tag ci-c-util:v1 . + + # + # Run CI + # + # This again runs the CI, but this time disables eBPF. We do support the + # legacy BPF fallback, so lets make sure we test for it. + # + - name: Fetch Sources + uses: actions/checkout@v2 + with: + path: source + - name: Run through C-Util CI + run: | + docker run \ + --privileged \ + -v "$(pwd)/source:/github/workspace" \ + "ci-c-util:v1" \ + "--m32=1" \ + "--mesonargs=-Debpf=false" \ + "--source=/github/workspace" + + ci-valgrind: + name: CI through Valgrind + runs-on: ubuntu-latest + + steps: + # See above in 'ci' job. + - name: Fetch CI + uses: actions/checkout@v2 + with: + repository: c-util/automation + ref: v1 + path: automation + - name: Build CI + working-directory: automation/src/ci-c-util + run: docker build --tag ci-c-util:v1 . + + # + # Run CI + # + # This again runs the CI, but this time through valgrind. Since some + # syscalls are not implemented on x86-64 32bit compat (e.g., bpf(2)), we + # disable the m32 mode. + # + - name: Fetch Sources + uses: actions/checkout@v2 + with: + path: source + - name: Run through C-Util CI + run: | + docker run \ + --privileged \ + -v "$(pwd)/source:/github/workspace" \ + "ci-c-util:v1" \ + "--source=/github/workspace" \ + "--valgrind=1" diff --git a/src/n-acd/.gitmodules b/src/n-acd/.gitmodules new file mode 100644 index 00000000..04829bdb --- /dev/null +++ b/src/n-acd/.gitmodules @@ -0,0 +1,12 @@ +[submodule "subprojects/c-list"] + path = subprojects/c-list + url = https://github.com/c-util/c-list.git +[submodule "subprojects/c-siphash"] + path = subprojects/c-siphash + url = https://github.com/c-util/c-siphash.git +[submodule "subprojects/c-rbtree"] + path = subprojects/c-rbtree + url = https://github.com/c-util/c-rbtree.git +[submodule "subprojects/c-stdaux"] + path = subprojects/c-stdaux + url = https://github.com/c-util/c-stdaux.git diff --git a/src/n-acd/AUTHORS b/src/n-acd/AUTHORS new file mode 100644 index 00000000..98ff1482 --- /dev/null +++ b/src/n-acd/AUTHORS @@ -0,0 +1,39 @@ +LICENSE: + This project is dual-licensed under both the Apache License, Version + 2.0, and the GNU Lesser General Public License, Version 2.1+. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see <http://www.gnu.org/licenses/>. + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2015-2019 Red Hat, Inc. + +AUTHORS: (ordered alphabetically) + Beniamino Galvani <bgalvani@redhat.com> + David Rheinsberg <david.rheinsberg@gmail.com> + Thomas Haller <thaller@redhat.com> + Tom Gundersen <teg@jklm.no> diff --git a/src/n-acd/NEWS.md b/src/n-acd/NEWS.md new file mode 100644 index 00000000..7a9ccd67 --- /dev/null +++ b/src/n-acd/NEWS.md @@ -0,0 +1,46 @@ +# n-acd - IPv4 Address Conflict Detection + +## CHANGES WITH 2: + + * All public destructors now include a variant that returns `void`. + This was requested for easier integration with `glib` and friends. + Similar to the `cleanup` variants, these variants are denoted by a + single-character function-name suffix. E.g., `n_acd_freev()` + + * A fallback to `CLOCK_MONOTONIC` is now provided in case + `CLOCK_BOOTTIME` is not supported by the kernel. Note that this is in + no way signalled through the API, so if timers should follow the + `BOOTTIME` rather than monotonic clock, a kernel with this clock is + required. + + * The `c-sundry` dependency is no longer needed. + + * The `transport` configuration property is now mandatory for + `n_acd_new()`. It defaulted to `ETHERNET` before, by mistake. + + * In-source documentation for the public API is now provided. + + Contributions from: Beniamino Galvani, David Herrmann, David + Rheinsberg, Thomas Haller, Tom Gundersen + + - Tübingen, 2019-03-20 + +## CHANGES WITH 1: + + * Initial release of n-acd. This project implements the IPv4 Address + Conflict Detection standard as defined in RFC-5227. The state machine + is implemented in a shared library and provides a stable ISO-C11 API. + The implementation is linux-only and relies heavily on the API + behavior of recent linux kernel releases. + + * Compared to the pre-releases, this release supports many parallel + probes on a single n-acd context. This reduces the number of + allocated network resources to O(1), based on the number of running + parallel probes. + + * The n-acd project is now dual-licensed: ASL-2.0 and LGPL-2.1+ + + Contributions from: Beniamino Galvani, David Herrmann, Thomas Haller, + Tom Gundersen + + - Tübingen, 2018-08-08 diff --git a/src/n-acd/README.md b/src/n-acd/README.md new file mode 100644 index 00000000..08954182 --- /dev/null +++ b/src/n-acd/README.md @@ -0,0 +1,60 @@ +n-acd +===== + +IPv4 Address Conflict Detection + +The n-acd project implements the IPv4 Address Conflict Detection standard as +defined in RFC-5227. The state machine is implemented in a shared library and +provides a stable ISO-C11 API. The implementation is linux-only and relies +heavily on the API behavior of recent linux kernel releases. + +### Project + + * **Website**: <https://nettools.github.io/n-acd> + * **Bug Tracker**: <https://github.com/nettools/n-acd/issues> + * **Mailing-List**: <https://groups.google.com/forum/#!forum/nettools-devel> + +### Requirements + +The requirements for this project are: + + * `Linux kernel >= 3.19` + * `libc` (e.g., `glibc >= 2.16`) + +At build-time, the following software is required: + + * `meson >= 0.41` + * `pkg-config >= 0.29` + +### Build + +The meson build-system is used for this project. Contact upstream +documentation for detailed help. In most situations the following +commands are sufficient to build and install from source: + +```sh +mkdir build +cd build +meson setup .. +ninja +meson test +ninja install +``` + +The following configuration options are available: + + * `ebpf`: This boolean controls whether `ebpf` features are used to improve + the package filtering performance. If disabled, classic bpf will be + used. This feature requires a rather recent kernel (>=3.19). + Default is: true + +### Repository: + + - **web**: <https://github.com/nettools/n-acd> + - **https**: `https://github.com/nettools/n-acd.git` + - **ssh**: `git@github.com:nettools/n-acd.git` + +### License: + + - **Apache-2.0** OR **LGPL-2.1-or-later** + - See AUTHORS file for details. diff --git a/src/n-acd/meson.build b/src/n-acd/meson.build new file mode 100644 index 00000000..6479eb1a --- /dev/null +++ b/src/n-acd/meson.build @@ -0,0 +1,27 @@ +project( + 'n-acd', + 'c', + version: '2', + license: 'Apache', + default_options: [ + 'c_std=c11', + ], +) +project_description = 'IPv4 Address Conflict Detection' + +add_project_arguments('-D_GNU_SOURCE', language: 'c') +mod_pkgconfig = import('pkgconfig') + +sub_clist = subproject('c-list') +sub_crbtree = subproject('c-rbtree') +sub_csiphash = subproject('c-siphash') +sub_cstdaux = subproject('libcstdaux-1') + +dep_clist = sub_clist.get_variable('libclist_dep') +dep_crbtree = sub_crbtree.get_variable('libcrbtree_dep') +dep_csiphash = sub_csiphash.get_variable('libcsiphash_dep') +dep_cstdaux = sub_cstdaux.get_variable('libcstdaux_dep') + +use_ebpf = get_option('ebpf') + +subdir('src') diff --git a/src/n-acd/meson_options.txt b/src/n-acd/meson_options.txt new file mode 100644 index 00000000..b024ee1d --- /dev/null +++ b/src/n-acd/meson_options.txt @@ -0,0 +1 @@ +option('ebpf', type: 'boolean', value: true, description: 'Enable eBPF packet filtering') diff --git a/src/n-acd/src/libnacd.sym b/src/n-acd/src/libnacd.sym new file mode 100644 index 00000000..f85e13ac --- /dev/null +++ b/src/n-acd/src/libnacd.sym @@ -0,0 +1,28 @@ +LIBNACD_2 { +global: + n_acd_config_new; + n_acd_config_free; + n_acd_config_set_ifindex; + n_acd_config_set_transport; + n_acd_config_set_mac; + + n_acd_probe_config_new; + n_acd_probe_config_free; + n_acd_probe_config_set_ip; + n_acd_probe_config_set_timeout; + + n_acd_new; + n_acd_ref; + n_acd_unref; + n_acd_get_fd; + n_acd_dispatch; + n_acd_pop_event; + n_acd_probe; + + n_acd_probe_free; + n_acd_probe_set_userdata; + n_acd_probe_get_userdata; + n_acd_probe_announce; +local: + *; +}; diff --git a/src/n-acd/src/meson.build b/src/n-acd/src/meson.build new file mode 100644 index 00000000..3e92681f --- /dev/null +++ b/src/n-acd/src/meson.build @@ -0,0 +1,95 @@ +# +# target: libnacd.so +# + +libnacd_symfile = join_paths(meson.current_source_dir(), 'libnacd.sym') + +libnacd_deps = [ + dep_clist, + dep_crbtree, + dep_csiphash, + dep_cstdaux, +] + +libnacd_sources = [ + 'n-acd.c', + 'n-acd-probe.c', + 'util/timer.c', +] + +if use_ebpf + libnacd_sources += [ + 'n-acd-bpf.c', + ] +else + libnacd_sources += [ + 'n-acd-bpf-fallback.c', + ] +endif + +libnacd_private = static_library( + 'nacd-private', + libnacd_sources, + c_args: [ + '-fvisibility=hidden', + '-fno-common' + ], + dependencies: libnacd_deps, + pic: true, +) + +libnacd_shared = shared_library( + 'nacd', + objects: libnacd_private.extract_all_objects(), + dependencies: libnacd_deps, + install: not meson.is_subproject(), + soversion: 0, + link_depends: libnacd_symfile, + link_args: [ + '-Wl,--no-undefined', + '-Wl,--version-script=@0@'.format(libnacd_symfile) + ], +) + +libnacd_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libnacd_private, + dependencies: libnacd_deps, + version: meson.project_version(), +) + +if not meson.is_subproject() + install_headers('n-acd.h') + + mod_pkgconfig.generate( + libraries: libnacd_shared, + version: meson.project_version(), + name: 'libnacd', + filebase: 'libnacd', + description: project_description, + ) +endif + +# +# target: test-* +# + +test_api = executable('test-api', ['test-api.c'], link_with: libnacd_shared) +test('API Symbol Visibility', test_api) + +if use_ebpf + test_bpf = executable('test-bpf', ['test-bpf.c'], dependencies: libnacd_dep) + test('eBPF socket filtering', test_bpf) +endif + +test_loopback = executable('test-loopback', ['test-loopback.c'], dependencies: libnacd_dep) +test('Echo Suppression via Loopback', test_loopback) + +test_timer = executable('test-timer', ['util/test-timer.c'], dependencies: libnacd_dep) +test('Timer helper', test_timer) + +#test_unplug = executable('test-unplug', ['test-unplug.c'], dependencies: libnacd_dep) +#test('Async Interface Hotplug', test_unplug) + +test_veth = executable('test-veth', ['test-veth.c'], dependencies: libnacd_dep) +test('Parallel ACD instances', test_veth) diff --git a/src/n-acd/src/test-api.c b/src/n-acd/src/test-api.c new file mode 100644 index 00000000..70f75208 --- /dev/null +++ b/src/n-acd/src/test-api.c @@ -0,0 +1,88 @@ +/* + * Tests for n-acd API + * This verifies the visibility and availability of the public API. + */ + +#undef NDEBUG +#include <assert.h> +#include <stdlib.h> +#include "n-acd.h" + +static void test_api_constants(void) { + assert(1 + N_ACD_TIMEOUT_RFC5227); + + assert(1 + _N_ACD_E_SUCCESS); + assert(1 + N_ACD_E_PREEMPTED); + assert(1 + N_ACD_E_INVALID_ARGUMENT); + assert(1 + _N_ACD_E_N); + + assert(1 + N_ACD_TRANSPORT_ETHERNET); + assert(1 + _N_ACD_TRANSPORT_N); + + assert(1 + N_ACD_EVENT_READY); + assert(1 + N_ACD_EVENT_USED); + assert(1 + N_ACD_EVENT_DEFENDED); + assert(1 + N_ACD_EVENT_CONFLICT); + assert(1 + N_ACD_EVENT_DOWN); + assert(1 + _N_ACD_EVENT_N); + + assert(1 + N_ACD_DEFEND_NEVER); + assert(1 + N_ACD_DEFEND_ONCE); + assert(1 + N_ACD_DEFEND_ALWAYS); + assert(1 + _N_ACD_DEFEND_N); +} + +static void test_api_types(void) { + assert(sizeof(NAcdEvent*)); + assert(sizeof(NAcdConfig*)); + assert(sizeof(NAcdProbeConfig*)); + assert(sizeof(NAcd*)); + assert(sizeof(NAcdProbe*)); +} + +static void test_api_functions(void) { + void *fns[] = { + (void *)n_acd_config_new, + (void *)n_acd_config_free, + (void *)n_acd_config_set_ifindex, + (void *)n_acd_config_set_transport, + (void *)n_acd_config_set_mac, + (void *)n_acd_probe_config_new, + (void *)n_acd_probe_config_free, + (void *)n_acd_probe_config_set_ip, + (void *)n_acd_probe_config_set_timeout, + + (void *)n_acd_new, + (void *)n_acd_ref, + (void *)n_acd_unref, + (void *)n_acd_get_fd, + (void *)n_acd_dispatch, + (void *)n_acd_pop_event, + (void *)n_acd_probe, + + (void *)n_acd_probe_free, + (void *)n_acd_probe_set_userdata, + (void *)n_acd_probe_get_userdata, + (void *)n_acd_probe_announce, + + (void *)n_acd_config_freep, + (void *)n_acd_config_freev, + (void *)n_acd_probe_config_freep, + (void *)n_acd_probe_config_freev, + (void *)n_acd_unrefp, + (void *)n_acd_unrefv, + (void *)n_acd_probe_freep, + (void *)n_acd_probe_freev, + }; + size_t i; + + for (i = 0; i < sizeof(fns) / sizeof(*fns); ++i) + assert(!!fns[i]); +} + +int main(int argc, char **argv) { + test_api_constants(); + test_api_types(); + test_api_functions(); + return 0; +} diff --git a/src/n-acd/src/test-bpf.c b/src/n-acd/src/test-bpf.c new file mode 100644 index 00000000..78f9d0f1 --- /dev/null +++ b/src/n-acd/src/test-bpf.c @@ -0,0 +1,226 @@ +/* + * eBPF socket filter tests + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <inttypes.h> +#include <netinet/if_ether.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include "n-acd.h" +#include "n-acd-private.h" +#include "test.h" + +#define ETHER_ARP_PACKET_INIT(_op, _mac, _sip, _tip) { \ + .ea_hdr = { \ + .ar_hrd = htobe16(ARPHRD_ETHER), \ + .ar_pro = htobe16(ETHERTYPE_IP), \ + .ar_hln = 6, \ + .ar_pln = 4, \ + .ar_op = htobe16(_op), \ + }, \ + .arp_sha[0] = (_mac)->ether_addr_octet[0], \ + .arp_sha[1] = (_mac)->ether_addr_octet[1], \ + .arp_sha[2] = (_mac)->ether_addr_octet[2], \ + .arp_sha[3] = (_mac)->ether_addr_octet[3], \ + .arp_sha[4] = (_mac)->ether_addr_octet[4], \ + .arp_sha[5] = (_mac)->ether_addr_octet[5], \ + .arp_spa[0] = (be32toh((_sip)->s_addr) >> 24) & 0xff, \ + .arp_spa[1] = (be32toh((_sip)->s_addr) >> 16) & 0xff, \ + .arp_spa[2] = (be32toh((_sip)->s_addr) >> 8) & 0xff, \ + .arp_spa[3] = be32toh((_sip)->s_addr) & 0xff, \ + .arp_tpa[0] = (be32toh((_tip)->s_addr) >> 24) & 0xff, \ + .arp_tpa[1] = (be32toh((_tip)->s_addr) >> 16) & 0xff, \ + .arp_tpa[2] = (be32toh((_tip)->s_addr) >> 8) & 0xff, \ + .arp_tpa[3] = be32toh((_tip)->s_addr) & 0xff, \ + } + +static void test_map(void) { + int r, mapfd = -1; + struct in_addr addr = { 1 }; + + r = n_acd_bpf_map_create(&mapfd, 8); + c_assert(r >= 0); + c_assert(mapfd >= 0); + + r = n_acd_bpf_map_remove(mapfd, &addr); + c_assert(r == -ENOENT); + + r = n_acd_bpf_map_add(mapfd, &addr); + c_assert(r >= 0); + + r = n_acd_bpf_map_add(mapfd, &addr); + c_assert(r == -EEXIST); + + r = n_acd_bpf_map_remove(mapfd, &addr); + c_assert(r >= 0); + + r = n_acd_bpf_map_remove(mapfd, &addr); + c_assert(r == -ENOENT); + + close(mapfd); +} + +static void verify_success(struct ether_arp *packet, int out_fd, int in_fd) { + uint8_t buf[sizeof(struct ether_arp)]; + int r; + + r = send(out_fd, packet, sizeof(struct ether_arp), 0); + c_assert(r == sizeof(struct ether_arp)); + + r = recv(in_fd, buf, sizeof(buf), 0); + c_assert(r == sizeof(struct ether_arp)); +} + +static void verify_failure(struct ether_arp *packet, int out_fd, int in_fd) { + uint8_t buf[sizeof(struct ether_arp)]; + int r; + + r = send(out_fd, packet, sizeof(struct ether_arp), 0); + c_assert(r == sizeof(struct ether_arp)); + + r = recv(in_fd, buf, sizeof(buf), 0); + c_assert(r < 0); + c_assert(errno == EAGAIN); +} + +static void test_filter(void) { + uint8_t buf[sizeof(struct ether_arp) + 1] = {}; + struct ether_addr mac1 = { { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 } }; + struct ether_addr mac2 = { { 0x01, 0x02, 0x03, 0x04, 0x05, 0x07 } }; + struct in_addr ip0 = { 0 }; + struct in_addr ip1 = { 1 }; + struct in_addr ip2 = { 2 }; + struct ether_arp *packet = (struct ether_arp *)buf; + int r, mapfd = -1, progfd = -1, pair[2]; + + r = n_acd_bpf_map_create(&mapfd, 1); + c_assert(r >= 0); + + r = n_acd_bpf_compile(&progfd, mapfd, &mac1); + c_assert(r >= 0); + c_assert(progfd >= 0); + + r = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, pair); + c_assert(r >= 0); + + r = setsockopt(pair[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, + sizeof(progfd)); + c_assert(r >= 0); + + r = n_acd_bpf_map_add(mapfd, &ip1); + c_assert(r >= 0); + + /* valid */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + verify_success(packet, pair[0], pair[1]); + + /* valid: reply instead of request */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REPLY, &mac2, &ip1, &ip2); + verify_success(packet, pair[0], pair[1]); + + /* valid: to us instead of from us */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip0, &ip1); + verify_success(packet, pair[0], pair[1]); + + /* invalid header type */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + packet->arp_hrd += 1; + verify_failure(packet, pair[0], pair[1]); + + /* invalid protocol */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + packet->arp_pro += 1; + verify_failure(packet, pair[0], pair[1]); + + /* invalid hw addr length */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + packet->arp_hln += 1; + verify_failure(packet, pair[0], pair[1]); + + /* invalid protocol addr length */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + packet->arp_pln += 1; + verify_failure(packet, pair[0], pair[1]); + + /* invalid operation */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_NAK, &mac2, &ip1, &ip2); + packet->arp_hln += 1; + verify_failure(packet, pair[0], pair[1]); + + /* own mac */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac1, &ip1, &ip2); + verify_failure(packet, pair[0], pair[1]); + + /* not to, nor from us, with source */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip2, &ip2); + verify_failure(packet, pair[0], pair[1]); + + /* not to, nor from us, without source */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip0, &ip2); + verify_failure(packet, pair[0], pair[1]); + + /* to us instead of from us, but reply */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REPLY, &mac2, &ip0, &ip1); + verify_failure(packet, pair[0], pair[1]); + + /* long */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + r = send(pair[0], buf, sizeof(struct ether_arp) + 1, 0); + c_assert(r == sizeof(struct ether_arp) + 1); + + r = recv(pair[1], buf, sizeof(buf), 0); + c_assert(r == sizeof(struct ether_arp)); + + /* short */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + r = send(pair[0], buf, sizeof(struct ether_arp) - 1, 0); + c_assert(r == sizeof(struct ether_arp) - 1); + + r = recv(pair[1], buf, sizeof(buf), 0); + c_assert(r < 0); + c_assert(errno == EAGAIN); + + /* + * Send one packet before and one packet after modifying the map, + * verify that the modification applies at the time of send(), not recv(). + */ + *packet = (struct ether_arp)ETHER_ARP_PACKET_INIT(ARPOP_REQUEST, &mac2, &ip1, &ip2); + r = send(pair[0], buf, sizeof(struct ether_arp), 0); + c_assert(r == sizeof(struct ether_arp)); + + r = n_acd_bpf_map_remove(mapfd, &ip1); + c_assert(r >= 0); + + r = send(pair[0], buf, sizeof(struct ether_arp), 0); + c_assert(r == sizeof(struct ether_arp)); + + r = recv(pair[1], buf, sizeof(buf), 0); + c_assert(r == sizeof(struct ether_arp)); + + r = recv(pair[1], buf, sizeof(buf), 0); + c_assert(r < 0); + c_assert(errno == EAGAIN); + + close(pair[0]); + close(pair[1]); + close(progfd); + close(mapfd); +} + +int main(int argc, char **argv) { + test_setup(); + + test_map(); + test_filter(); + + return 0; +} diff --git a/src/n-acd/src/test-loopback.c b/src/n-acd/src/test-loopback.c new file mode 100644 index 00000000..0671cf66 --- /dev/null +++ b/src/n-acd/src/test-loopback.c @@ -0,0 +1,82 @@ +/* + * Test on loopback device + * This runs the ACD engine on the loopback device, effectively testing the BPF + * filter of ACD to discard its own packets. This might happen on + * non-spanning-tree networks, or on networks that echo packets. + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <stdlib.h> +#include "test.h" + +static void test_loopback(int ifindex, uint8_t *mac, size_t n_mac) { + NAcdConfig *config; + NAcd *acd; + struct pollfd pfds; + int r, fd; + + r = n_acd_config_new(&config); + c_assert(!r); + + n_acd_config_set_ifindex(config, ifindex); + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac(config, mac, n_mac); + + r = n_acd_new(&acd, config); + c_assert(!r); + + n_acd_config_free(config); + + { + NAcdProbeConfig *probe_config; + NAcdProbe *probe; + struct in_addr ip = { htobe32((192 << 24) | (168 << 16) | (1 << 0)) }; + + r = n_acd_probe_config_new(&probe_config); + c_assert(!r); + + n_acd_probe_config_set_ip(probe_config, ip); + n_acd_probe_config_set_timeout(probe_config, 100); + + r = n_acd_probe(acd, &probe, probe_config); + c_assert(!r); + + n_acd_probe_config_free(probe_config); + + n_acd_get_fd(acd, &fd); + + for (;;) { + NAcdEvent *event; + pfds = (struct pollfd){ .fd = fd, .events = POLLIN }; + r = poll(&pfds, 1, -1); + c_assert(r >= 0); + + r = n_acd_dispatch(acd); + c_assert(!r); + + r = n_acd_pop_event(acd, &event); + c_assert(!r); + if (event) { + c_assert(event->event == N_ACD_EVENT_READY); + break; + } + } + + n_acd_probe_free(probe); + } + + n_acd_unref(acd); +} + +int main(int argc, char **argv) { + struct ether_addr mac; + int ifindex; + + test_setup(); + + test_loopback_up(&ifindex, &mac); + test_loopback(ifindex, mac.ether_addr_octet, sizeof(mac.ether_addr_octet)); + + return 0; +} diff --git a/src/n-acd/src/test-twice.c b/src/n-acd/src/test-twice.c new file mode 100644 index 00000000..b474502e --- /dev/null +++ b/src/n-acd/src/test-twice.c @@ -0,0 +1,97 @@ +/* + * Test with unused address twice in parallel + * This runs the ACD engine with an unused address on a veth pair, but it runs + * it on both ends. We expect the PROBE to fail on at least one of the devices. + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <stdlib.h> +#include "test.h" + +static void test_unused(int ifindex1, uint8_t *mac1, size_t n_mac1, int ifindex2, uint8_t *mac2, size_t n_mac2) { + NAcdConfig config1 = { + .ifindex = ifindex1, + .transport = N_ACD_TRANSPORT_ETHERNET, + .mac = mac1, + .n_mac = n_mac1, + .ip = { htobe32((192 << 24) | (168 << 16) | (1 << 0)) }, + .timeout_msec = 100, + }; + NAcdConfig config2 = { + .ifindex = ifindex2, + .transport = N_ACD_TRANSPORT_ETHERNET, + .mac = mac2, + .n_mac = n_mac2, + .ip = { htobe32((192 << 24) | (168 << 16) | (1 << 0)) }, + .timeout_msec = 100, + }; + struct pollfd pfds[2]; + NAcd *acd1, *acd2; + int r, fd1, fd2, state1, state2; + + r = n_acd_new(&acd1); + c_assert(!r); + r = n_acd_new(&acd2); + c_assert(!r); + + n_acd_get_fd(acd1, &fd1); + n_acd_get_fd(acd2, &fd2); + + r = n_acd_start(acd1, &config1); + c_assert(!r); + r = n_acd_start(acd2, &config2); + c_assert(!r); + + for (state1 = state2 = -1; state1 == -1 || state2 == -1; ) { + NAcdEvent *event; + pfds[0] = (struct pollfd){ .fd = fd1, .events = (state1 == -1) ? POLLIN : 0 }; + pfds[1] = (struct pollfd){ .fd = fd2, .events = (state2 == -1) ? POLLIN : 0 }; + + r = poll(pfds, sizeof(pfds) / sizeof(*pfds), -1); + c_assert(r >= 0); + + if (state1 == -1) { + r = n_acd_dispatch(acd1); + c_assert(!r); + + r = n_acd_pop_event(acd1, &event); + if (!r) { + c_assert(event->event == N_ACD_EVENT_READY || event->event == N_ACD_EVENT_USED); + state1 = !!(event->event == N_ACD_EVENT_READY); + } else { + c_assert(r == N_ACD_E_DONE); + } + } + + if (state2 == -1) { + r = n_acd_dispatch(acd2); + c_assert(!r); + + r = n_acd_pop_event(acd2, &event); + if (!r) { + c_assert(event->event == N_ACD_EVENT_READY || event->event == N_ACD_EVENT_USED); + state2 = !!(event->event == N_ACD_EVENT_READY); + } else { + c_assert(r == N_ACD_E_DONE); + } + } + } + + n_acd_free(acd1); + n_acd_free(acd2); + + c_assert(!state1 || !state2); +} + +int main(int argc, char **argv) { + struct ether_addr mac1, mac2; + int ifindex1, ifindex2; + + test_setup(); + + test_veth_new(&ifindex1, &mac1, &ifindex2, &mac2); + test_unused(ifindex1, mac1.ether_addr_octet, sizeof(mac2.ether_addr_octet), ifindex2, mac2.ether_addr_octet, sizeof(mac2.ether_addr_octet)); + + return 0; +} diff --git a/src/n-acd/src/test-unplug.c b/src/n-acd/src/test-unplug.c new file mode 100644 index 00000000..9ad88a91 --- /dev/null +++ b/src/n-acd/src/test-unplug.c @@ -0,0 +1,84 @@ +/* + * Unplug device during test run + * Run the ACD engine with an address that is not used by anyone else on the + * link, but DOWN or UNPLUG the device while running. + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <stdlib.h> +#include "test.h" + +static void test_unplug_down(int ifindex, uint8_t *mac, size_t n_mac, unsigned int run) { + NAcdConfig config = { + .ifindex = ifindex, + .transport = N_ACD_TRANSPORT_ETHERNET, + .mac = mac, + .n_mac = n_mac, + .ip = { htobe32((192 << 24) | (168 << 16) | (1 << 0)) }, + .timeout_msec = 100, + }; + struct pollfd pfds; + NAcd *acd; + int r, fd; + + if (!run--) + test_veth_cmd(ifindex, "down"); + + r = n_acd_new(&acd); + c_assert(!r); + + if (!run--) + test_veth_cmd(ifindex, "down"); + + n_acd_get_fd(acd, &fd); + r = n_acd_start(acd, &config); + c_assert(!r); + + if (!run--) + test_veth_cmd(ifindex, "down"); + + for (;;) { + NAcdEvent *event; + pfds = (struct pollfd){ .fd = fd, .events = POLLIN }; + r = poll(&pfds, 1, -1); + c_assert(r >= 0); + + if (!run--) + test_veth_cmd(ifindex, "down"); + + r = n_acd_dispatch(acd); + c_assert(!r); + + r = n_acd_pop_event(acd, &event); + if (!r) { + if (event->event == N_ACD_EVENT_DOWN) { + break; + } else { + c_assert(event->event == N_ACD_EVENT_READY); + test_veth_cmd(ifindex, "down"); + } + } else { + c_assert(r == N_ACD_E_DONE); + } + } + + n_acd_free(acd); +} + +int main(int argc, char **argv) { + struct ether_addr mac; + unsigned int i; + int ifindex; + + test_setup(); + + test_veth_new(&ifindex, &mac, NULL, NULL); + + for (i = 0; i < 5; ++i) { + test_unplug_down(ifindex, mac.ether_addr_octet, sizeof(mac.ether_addr_octet), i); + test_veth_cmd(ifindex, "up"); + } + + return 0; +} diff --git a/src/n-acd/src/test-unused.c b/src/n-acd/src/test-unused.c new file mode 100644 index 00000000..67ec2e4c --- /dev/null +++ b/src/n-acd/src/test-unused.c @@ -0,0 +1,63 @@ +/* + * Test with unused address + * Run the ACD engine with an address that is not used by anyone else on the + * link. This should just pass through, with a short, random timeout. + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <stdlib.h> +#include "test.h" + +static void test_unused(int ifindex, const uint8_t *mac, size_t n_mac) { + NAcdConfig config = { + .ifindex = ifindex, + .transport = N_ACD_TRANSPORT_ETHERNET, + .mac = mac, + .n_mac = n_mac, + .ip = { htobe32((192 << 24) | (168 << 16) | (1 << 0)) }, + .timeout_msec = 100, + }; + struct pollfd pfds; + NAcd *acd; + int r, fd; + + r = n_acd_new(&acd); + c_assert(!r); + + n_acd_get_fd(acd, &fd); + r = n_acd_start(acd, &config); + c_assert(!r); + + for (;;) { + NAcdEvent *event; + pfds = (struct pollfd){ .fd = fd, .events = POLLIN }; + r = poll(&pfds, 1, -1); + c_assert(r >= 0); + + r = n_acd_dispatch(acd); + c_assert(!r); + + r = n_acd_pop_event(acd, &event); + if (!r) { + c_assert(event->event == N_ACD_EVENT_READY); + break; + } else { + c_assert(r == N_ACD_E_DONE); + } + } + + n_acd_free(acd); +} + +int main(int argc, char **argv) { + struct ether_addr mac; + int ifindex; + + test_setup(); + + test_veth_new(&ifindex, &mac, NULL, NULL); + test_unused(ifindex, mac.ether_addr_octet, sizeof(mac.ether_addr_octet)); + + return 0; +} diff --git a/src/n-acd/src/test-veth.c b/src/n-acd/src/test-veth.c new file mode 100644 index 00000000..d1923683 --- /dev/null +++ b/src/n-acd/src/test-veth.c @@ -0,0 +1,240 @@ +/* + * Test on a veth link + * + * This essentially mimics a real network with two peers. + * + * Run one ACD context on each end of the tunnel. On one end probe for N, + * addresses on the other end pre-configure N/3 of the same addresses and probe + * for another N/3 of the addresses. + * + * Verify that in the case of simultaneous probes of the same address at most one + * succeed, in the case of probing for a configured address it always fails, and + * probing for a non-existent address always succeeds. + * + * Make sure to keep N fairly high as the protocol is probabilistic, and we also + * want to verify that resizing the internal maps works correctly. + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <stdlib.h> +#include "test.h" + +#define TEST_ACD_N_PROBES (9) + +typedef enum { + TEST_ACD_STATE_UNKNOWN, + TEST_ACD_STATE_USED, + TEST_ACD_STATE_READY, +} TestAcdState; + +static void test_veth(int ifindex1, uint8_t *mac1, size_t n_mac1, + int ifindex2, uint8_t *mac2, size_t n_mac2) { + NAcdConfig *config; + NAcd *acd1, *acd2; + NAcdProbe *probes1[TEST_ACD_N_PROBES]; + NAcdProbe *probes2[TEST_ACD_N_PROBES]; + unsigned long state1, state2; + size_t n_running = 0; + int r; + + r = n_acd_config_new(&config); + c_assert(!r); + + n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET); + + n_acd_config_set_ifindex(config, ifindex1); + n_acd_config_set_mac(config, mac1, n_mac1); + r = n_acd_new(&acd1, config); + c_assert(!r); + + n_acd_config_set_ifindex(config, ifindex2); + n_acd_config_set_mac(config, mac2, n_mac2); + r = n_acd_new(&acd2, config); + c_assert(!r); + + n_acd_config_free(config); + + { + NAcdProbeConfig *probe_config; + + r = n_acd_probe_config_new(&probe_config); + c_assert(!r); + n_acd_probe_config_set_timeout(probe_config, 1024); + + c_assert(TEST_ACD_N_PROBES <= 10 << 24); + + for (size_t i = 0; i < TEST_ACD_N_PROBES; ++i) { + struct in_addr ip = { htobe32((10 << 24) | i) }; + + n_acd_probe_config_set_ip(probe_config, ip); + + switch (i % 3) { + case 0: + /* + * Probe on one side, and leave the address + * unset on the other. The probe must succeed. + */ + break; + case 1: + /* + * Preconfigure the address on one side, and + * probe on the other. The probe must fail. + */ + test_add_child_ip(&ip); + break; + case 2: + /* + * Probe both sides for the same address, at + * most one may succeed. + */ + + r = n_acd_probe(acd2, &probes2[i], probe_config); + c_assert(!r); + + ++n_running; + break; + default: + c_assert(0); + abort(); + break; + } + + r = n_acd_probe(acd1, &probes1[i], probe_config); + c_assert(!r); + + ++n_running; + } + + n_acd_probe_config_free(probe_config); + + while (n_running > 0) { + NAcdEvent *event; + struct pollfd pfds[2] = { + { .events = POLLIN }, + { .events = POLLIN }, + }; + + n_acd_get_fd(acd1, &pfds[0].fd); + n_acd_get_fd(acd2, &pfds[1].fd); + + r = poll(pfds, 2, -1); + c_assert(r >= 0); + + if (pfds[0].revents & POLLIN) { + r = n_acd_dispatch(acd1); + c_assert(!r || r == N_ACD_E_PREEMPTED); + + for (;;) { + r = n_acd_pop_event(acd1, &event); + c_assert(!r); + if (event) { + switch (event->event) { + case N_ACD_EVENT_READY: + n_acd_probe_get_userdata(event->ready.probe, (void**)&state1); + c_assert(state1 == TEST_ACD_STATE_UNKNOWN); + state1 = TEST_ACD_STATE_READY; + n_acd_probe_set_userdata(event->ready.probe, (void*)state1); + + break; + case N_ACD_EVENT_USED: + n_acd_probe_get_userdata(event->used.probe, (void**)&state1); + c_assert(state1 == TEST_ACD_STATE_UNKNOWN); + state1 = TEST_ACD_STATE_USED; + n_acd_probe_set_userdata(event->used.probe, (void*)state1); + + break; + default: + c_assert(0); + } + + --n_running; + } else { + break; + } + } + } + + if (pfds[1].revents & POLLIN) { + r = n_acd_dispatch(acd2); + c_assert(!r || r == N_ACD_E_PREEMPTED); + + for (;;) { + r = n_acd_pop_event(acd2, &event); + c_assert(!r); + if (event) { + switch (event->event) { + case N_ACD_EVENT_READY: + n_acd_probe_get_userdata(event->ready.probe, (void**)&state2); + c_assert(state2 == TEST_ACD_STATE_UNKNOWN); + state2 = TEST_ACD_STATE_READY; + n_acd_probe_set_userdata(event->ready.probe, (void*)state2); + + break; + case N_ACD_EVENT_USED: + n_acd_probe_get_userdata(event->used.probe, (void**)&state2); + c_assert(state2 == TEST_ACD_STATE_UNKNOWN); + state2 = TEST_ACD_STATE_USED; + n_acd_probe_set_userdata(event->used.probe, (void*)state2); + + break; + default: + c_assert(0); + } + + --n_running; + } else { + break; + } + } + } + } + + for (size_t i = 0; i < TEST_ACD_N_PROBES; ++i) { + struct in_addr ip = { htobe32((10 << 24) | i) }; + + switch (i % 3) { + case 0: + n_acd_probe_get_userdata(probes1[i], (void **)&state1); + c_assert(state1 == TEST_ACD_STATE_READY); + + break; + case 1: + test_del_child_ip(&ip); + + n_acd_probe_get_userdata(probes1[i], (void **)&state1); + c_assert(state1 == TEST_ACD_STATE_USED); + + break; + case 2: + n_acd_probe_get_userdata(probes1[i], (void **)&state1); + n_acd_probe_get_userdata(probes2[i], (void **)&state2); + c_assert(state1 != TEST_ACD_STATE_UNKNOWN); + c_assert(state2 != TEST_ACD_STATE_UNKNOWN); + c_assert(state1 == TEST_ACD_STATE_USED || state2 == TEST_ACD_STATE_USED); + n_acd_probe_free(probes2[i]); + + break; + } + n_acd_probe_free(probes1[i]); + } + } + + n_acd_unref(acd2); + n_acd_unref(acd1); +} + +int main(int argc, char **argv) { + struct ether_addr mac1, mac2; + int ifindex1, ifindex2; + + test_setup(); + + test_veth_new(&ifindex1, &mac1, &ifindex2, &mac2); + for (unsigned int i = 0; i < 8; ++i) { + test_veth(ifindex1, mac1.ether_addr_octet, sizeof(mac1.ether_addr_octet), + ifindex2, mac2.ether_addr_octet, sizeof(mac2.ether_addr_octet)); + } + + return 0; +} diff --git a/src/n-acd/src/test.h b/src/n-acd/src/test.h new file mode 100644 index 00000000..69a786a0 --- /dev/null +++ b/src/n-acd/src/test.h @@ -0,0 +1,213 @@ +#pragma once + +/* + * Test Helpers + * Bunch of helpers to setup the environment for networking tests. This + * includes net-namespace setups, veth setups, and more. + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <poll.h> +#include <sched.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "n-acd.h" + +static inline void test_add_child_ip(const struct in_addr *addr) { + char *p; + int r; + + r = asprintf(&p, "ip addr add dev veth1 %s/8", inet_ntoa(*addr)); + c_assert(r >= 0); + + r = system(p); + c_assert(r >= 0); + + free(p); +} + +static inline void test_del_child_ip(const struct in_addr *addr) { + char *p; + int r; + + r = asprintf(&p, "ip addr del dev veth1 %s/8", inet_ntoa(*addr)); + c_assert(r >= 0); + + r = system(p); + c_assert(r >= 0); + + free(p); +} + +static inline void test_if_query(const char *name, int *indexp, struct ether_addr *macp) { + struct ifreq ifr = {}; + size_t l; + int r, s; + + l = strlen(name); + c_assert(l <= IF_NAMESIZE); + + if (indexp) { + *indexp = if_nametoindex(name); + c_assert(*indexp > 0); + } + + if (macp) { + s = socket(AF_INET, SOCK_DGRAM, 0); + c_assert(s >= 0); + + strncpy(ifr.ifr_name, name, l + 1); + r = ioctl(s, SIOCGIFHWADDR, &ifr); + c_assert(r >= 0); + + memcpy(macp->ether_addr_octet, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + close(s); + } +} + +static inline void test_veth_cmd(int ifindex, const char *cmd) { + char *p, name[IF_NAMESIZE + 1] = {}; + int r; + + p = if_indextoname(ifindex, name); + c_assert(p); + + r = asprintf(&p, "ip link set %s %s", name, cmd); + c_assert(r >= 0); + + /* Again: Ewwww... */ + r = system(p); + c_assert(r == 0); + + free(p); +} + +static inline void test_veth_new(int *parent_indexp, + struct ether_addr *parent_macp, + int *child_indexp, + struct ether_addr *child_macp) { + int r; + + /* Eww... but it works. */ + r = system("ip link add type veth"); + c_assert(r == 0); + r = system("ip link set veth0 up"); + c_assert(r == 0); + r = system("ip link set veth1 up"); + c_assert(r == 0); + + test_if_query("veth0", parent_indexp, parent_macp); + test_if_query("veth1", child_indexp, child_macp); +} + +static inline void test_loopback_up(int *indexp, struct ether_addr *macp) { + int r; + + r = system("ip link set lo up"); + c_assert(r == 0); + + test_if_query("lo", indexp, macp); +} + +static inline void test_raise_memlock(void) { + const size_t wanted = 64 * 1024 * 1024; + struct rlimit get, set; + int r; + + r = getrlimit(RLIMIT_MEMLOCK, &get); + c_assert(!r); + + /* try raising limit to @wanted */ + set.rlim_cur = wanted; + set.rlim_max = (wanted > get.rlim_max) ? wanted : get.rlim_max; + r = setrlimit(RLIMIT_MEMLOCK, &set); + if (r) { + c_assert(errno == EPERM); + + /* not privileged to raise limit, so maximize soft limit */ + set.rlim_cur = get.rlim_max; + set.rlim_max = get.rlim_max; + r = setrlimit(RLIMIT_MEMLOCK, &set); + c_assert(!r); + } +} + +static inline void test_unshare_user_namespace(void) { + uid_t euid; + gid_t egid; + int r, fd; + + /* + * Enter a new user namespace as root:root. + */ + + euid = geteuid(); + egid = getegid(); + + r = unshare(CLONE_NEWUSER); + c_assert(r >= 0); + + fd = open("/proc/self/uid_map", O_WRONLY); + c_assert(fd >= 0); + r = dprintf(fd, "0 %d 1\n", euid); + c_assert(r >= 0); + close(fd); + + fd = open("/proc/self/setgroups", O_WRONLY); + c_assert(fd >= 0); + r = dprintf(fd, "deny"); + c_assert(r >= 0); + close(fd); + + fd = open("/proc/self/gid_map", O_WRONLY); + c_assert(fd >= 0); + r = dprintf(fd, "0 %d 1\n", egid); + c_assert(r >= 0); + close(fd); +} + +static inline void test_setup(void) { + int r; + + /* + * Move into a new network and mount namespace both associated + * with a new user namespace where the current eUID is mapped to + * 0. Then create a private instance of /run/netns. This ensures + * that any network devices or network namespaces are private to + * the test process. + */ + + test_raise_memlock(); + test_unshare_user_namespace(); + + r = unshare(CLONE_NEWNET | CLONE_NEWNS); + c_assert(r >= 0); + + r = mount(NULL, "/", "", MS_PRIVATE | MS_REC, NULL); + c_assert(r >= 0); + + r = mount(NULL, "/run", "tmpfs", 0, NULL); + c_assert(r >= 0); + + r = mkdir("/run/netns", 0755); + c_assert(r >= 0); +} diff --git a/src/n-acd/src/util/test-timer.c b/src/n-acd/src/util/test-timer.c new file mode 100644 index 00000000..a0c908bd --- /dev/null +++ b/src/n-acd/src/util/test-timer.c @@ -0,0 +1,177 @@ +/* + * Tests for timer utility library + */ + +#undef NDEBUG +#include <c-stdaux.h> +#include <errno.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/timerfd.h> +#include "timer.h" + +#define N_TIMEOUTS (10000) + +static void test_api(void) { + Timer timer = TIMER_NULL(timer); + Timeout t1 = TIMEOUT_INIT(t1), t2 = TIMEOUT_INIT(t2), *t; + int r; + + r = timer_init(&timer); + c_assert(!r); + + timeout_schedule(&t1, &timer, 1); + timeout_schedule(&t2, &timer, 2); + + r = timer_pop_timeout(&timer, 10, &t); + c_assert(!r); + c_assert(t == &t1); + + timeout_unschedule(&t2); + + r = timer_pop_timeout(&timer, 10, &t); + c_assert(!r); + c_assert(!t); + + timer_deinit(&timer); +} + +static void test_pop(void) { + Timer timer = TIMER_NULL(timer); + Timeout timeouts[N_TIMEOUTS] = {}; + uint64_t times[N_TIMEOUTS] = {}; + size_t n_timeouts = 0; + bool armed; + Timeout *t; + int r; + + r = timer_init(&timer); + c_assert(!r); + + for(size_t i = 0; i < N_TIMEOUTS; ++i) { + timeouts[i] = (Timeout)TIMEOUT_INIT(timeouts[i]); + times[i] = rand() % 128 + 1; + timeout_schedule(&timeouts[i], &timer, times[i]); + } + + armed = true; + + for(size_t i = 0; i <= 128; ++i) { + if (armed) { + struct pollfd pfd = { + .fd = timer.fd, + .events = POLLIN, + }; + uint64_t count; + + r = poll(&pfd, 1, -1); + c_assert(r == 1); + + r = read(timer.fd, &count, sizeof(count)); + c_assert(r == sizeof(count)); + c_assert(count == 1); + armed = false; + } + + for (;;) { + uint64_t current_time; + + r = timer_pop_timeout(&timer, i, &t); + c_assert(!r); + if (!t) { + timer_rearm(&timer); + break; + } + + current_time = times[t - timeouts]; + c_assert(current_time == i); + ++n_timeouts; + armed = true; + } + } + + c_assert(n_timeouts == N_TIMEOUTS); + + r = timer_pop_timeout(&timer, (uint64_t)-1, &t); + c_assert(!r); + c_assert(!t); + + timer_deinit(&timer); +} + +void test_arm(void) { + struct itimerspec spec = { + .it_value = { + .tv_sec = 1000, + }, + }; + int fd1, fd2, r; + + fd1 = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + c_assert(fd1 >= 0); + + fd2 = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + c_assert(fd1 >= 0); + + r = timerfd_settime(fd1, 0, &spec, NULL); + c_assert(r >= 0); + + r = timerfd_settime(fd2, 0, &spec, NULL); + c_assert(r >= 0); + + r = timerfd_gettime(fd1, &spec); + c_assert(r >= 0); + c_assert(spec.it_value.tv_sec); + + r = timerfd_gettime(fd2, &spec); + c_assert(r >= 0); + c_assert(spec.it_value.tv_sec); + + spec = (struct itimerspec){}; + + r = timerfd_settime(fd1, 0, &spec, NULL); + c_assert(r >= 0); + + r = timerfd_gettime(fd1, &spec); + c_assert(r >= 0); + c_assert(!spec.it_value.tv_sec); + c_assert(!spec.it_value.tv_nsec); + + r = timerfd_gettime(fd2, &spec); + c_assert(r >= 0); + c_assert(spec.it_value.tv_sec); + + spec = (struct itimerspec){ .it_value = { .tv_nsec = 1, }, }; + + r = timerfd_settime(fd1, 0, &spec, NULL); + c_assert(r >= 0); + + r = poll(&(struct pollfd) { .fd = fd1, .events = POLLIN }, 1, -1); + c_assert(r == 1); + + r = timerfd_settime(fd2, 0, &spec, NULL); + c_assert(r >= 0); + + r = poll(&(struct pollfd) { .fd = fd2, .events = POLLIN }, 1, -1); + c_assert(r == 1); + + spec = (struct itimerspec){}; + + r = timerfd_settime(fd1, 0, &spec, NULL); + c_assert(r >= 0); + + r = poll(&(struct pollfd) { .fd = fd2, .events = POLLIN }, 1, -1); + c_assert(r == 1); + + close(fd2); + close(fd1); +} + +int main(int argc, char **argv) { + test_arm(); + test_api(); + test_pop(); + return 0; +} diff --git a/src/n-acd/subprojects/c-list b/src/n-acd/subprojects/c-list new file mode 120000 index 00000000..4e274698 --- /dev/null +++ b/src/n-acd/subprojects/c-list @@ -0,0 +1 @@ +../../c-list \ No newline at end of file diff --git a/src/n-acd/subprojects/c-rbtree b/src/n-acd/subprojects/c-rbtree new file mode 120000 index 00000000..49264a87 --- /dev/null +++ b/src/n-acd/subprojects/c-rbtree @@ -0,0 +1 @@ +../../c-rbtree \ No newline at end of file diff --git a/src/n-acd/subprojects/c-siphash b/src/n-acd/subprojects/c-siphash new file mode 120000 index 00000000..70d68818 --- /dev/null +++ b/src/n-acd/subprojects/c-siphash @@ -0,0 +1 @@ +../../c-siphash \ No newline at end of file diff --git a/src/n-acd/subprojects/libcstdaux-1 b/src/n-acd/subprojects/libcstdaux-1 new file mode 120000 index 00000000..589984f3 --- /dev/null +++ b/src/n-acd/subprojects/libcstdaux-1 @@ -0,0 +1 @@ +../../c-stdaux \ No newline at end of file |