diff options
Diffstat (limited to 'src/n-dhcp4')
28 files changed, 3923 insertions, 2 deletions
diff --git a/src/n-dhcp4/.editorconfig b/src/n-dhcp4/.editorconfig new file mode 100644 index 00000000..b10bb4f3 --- /dev/null +++ b/src/n-dhcp4/.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-dhcp4/.github/workflows/ci.yml b/src/n-dhcp4/.github/workflows/ci.yml new file mode 100644 index 00000000..3583fdaa --- /dev/null +++ b/src/n-dhcp4/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +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" diff --git a/src/n-dhcp4/.gitmodules b/src/n-dhcp4/.gitmodules new file mode 100644 index 00000000..ffe67922 --- /dev/null +++ b/src/n-dhcp4/.gitmodules @@ -0,0 +1,9 @@ +[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-stdaux"] + path = subprojects/c-stdaux + url = https://github.com/c-util/c-stdaux.git diff --git a/src/n-dhcp4/AUTHORS b/src/n-dhcp4/AUTHORS new file mode 100644 index 00000000..b59660c5 --- /dev/null +++ b/src/n-dhcp4/AUTHORS @@ -0,0 +1,37 @@ +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) + David Rheinsberg <david.rheinsberg@gmail.com> + Tom Gundersen <teg@jklm.no> diff --git a/src/n-dhcp4/NEWS.md b/src/n-dhcp4/NEWS.md new file mode 100644 index 00000000..b41de08a --- /dev/null +++ b/src/n-dhcp4/NEWS.md @@ -0,0 +1,20 @@ +# n-dhcp4 - Dynamic Host Configuration Protocol for IPv4 + +## CHANGES WITH 1: + + * Initial release of n-dhcp4, an implementation of the IPv4 Dynamic + Host Configuration Protocol as defined in RFC-2132+. + + * This is a pre-release. The code is not yet ready for production. + Furthermore, the server implementation is incomplete and subject to + change. + The client implementation is considered complete, but the API is not + set in stone. We will have to adapt it according to the needs of the + users. Hence, this release does not provide any ABI stability + guarantees, yet. + + * The n-dhcp4 project is now dual-licensed: ASL-2.0 and LGPL-2.1+ + + Contributions from: David Rheinsberg, Tom Gundersen + + - Tübingen, 2019-05-10 diff --git a/src/n-dhcp4/README.md b/src/n-dhcp4/README.md new file mode 100644 index 00000000..298b4972 --- /dev/null +++ b/src/n-dhcp4/README.md @@ -0,0 +1,53 @@ +n-dhcp4 +======= + +Dynamic Host Configuration Protocol for IPv4 + +The n-dhcp4 project implements the IPv4 Dynamic Host Configuration Protocol as +defined in RFC-2132+. + +### Project + + * **Website**: <https://nettools.github.io/n-dhcp4> + * **Bug Tracker**: <https://github.com/nettools/n-dhcp4/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 +``` + +No custom configuration options are available. + +### Repository: + + - **web**: <https://github.com/nettools/n-dhcp4> + - **https**: `https://github.com/nettools/n-dhcp4.git` + - **ssh**: `git@github.com:nettools/n-dhcp4.git` + +### License: + + - **Apache-2.0** OR **LGPL-2.1-or-later** + - See AUTHORS file for details. diff --git a/src/n-dhcp4/meson.build b/src/n-dhcp4/meson.build new file mode 100644 index 00000000..8cdcb368 --- /dev/null +++ b/src/n-dhcp4/meson.build @@ -0,0 +1,23 @@ +project( + 'n-dhcp4', + 'c', + version: '1', + license: 'Apache', + default_options: [ + 'c_std=c11', + ], +) +project_description = 'Dynamic Host Configuration Protocol for IPv4' + +add_project_arguments('-D_GNU_SOURCE', language: 'c') +mod_pkgconfig = import('pkgconfig') + +sub_clist = subproject('c-list') +sub_csiphash = subproject('c-siphash') +sub_cstdaux = subproject('libcstdaux-1') + +dep_clist = sub_clist.get_variable('libclist_dep') +dep_csiphash = sub_csiphash.get_variable('libcsiphash_dep') +dep_cstdaux = sub_cstdaux.get_variable('libcstdaux_dep') + +subdir('src') diff --git a/src/n-dhcp4/src/libndhcp4.sym b/src/n-dhcp4/src/libndhcp4.sym new file mode 100644 index 00000000..adaf6d6f --- /dev/null +++ b/src/n-dhcp4/src/libndhcp4.sym @@ -0,0 +1,71 @@ +LIBNDHCP4_1 { +global: + n_dhcp4_client_config_new; + n_dhcp4_client_config_free; + n_dhcp4_client_config_set_ifindex; + n_dhcp4_client_config_set_transport; + n_dhcp4_client_config_set_request_broadcast; + n_dhcp4_client_config_set_mac; + n_dhcp4_client_config_set_broadcast_mac; + n_dhcp4_client_config_set_client_id; + + n_dhcp4_client_probe_config_new; + n_dhcp4_client_probe_config_free; + n_dhcp4_client_probe_config_set_inform_only; + n_dhcp4_client_probe_config_set_init_reboot; + n_dhcp4_client_probe_config_set_requested_ip; + n_dhcp4_client_probe_config_set_start_delay; + n_dhcp4_client_probe_config_request_option; + n_dhcp4_client_probe_config_append_option; + + n_dhcp4_client_new; + n_dhcp4_client_ref; + n_dhcp4_client_unref; + n_dhcp4_client_get_fd; + n_dhcp4_client_dispatch; + n_dhcp4_client_pop_event; + n_dhcp4_client_update_mtu; + n_dhcp4_client_probe; + n_dhcp4_client_set_log_level; + + n_dhcp4_client_probe_free; + n_dhcp4_client_probe_get_userdata; + n_dhcp4_client_probe_set_userdata; + + n_dhcp4_client_lease_ref; + n_dhcp4_client_lease_unref; + n_dhcp4_client_lease_get_yiaddr; + n_dhcp4_client_lease_get_siaddr; + n_dhcp4_client_lease_get_basetime; + n_dhcp4_client_lease_get_lifetime; + n_dhcp4_client_lease_get_server_identifier; + n_dhcp4_client_lease_get_file; + n_dhcp4_client_lease_query; + n_dhcp4_client_lease_select; + n_dhcp4_client_lease_accept; + n_dhcp4_client_lease_decline; + + n_dhcp4_server_config_new; + n_dhcp4_server_config_free; + n_dhcp4_server_config_set_ifindex; + + n_dhcp4_server_new; + n_dhcp4_server_ref; + n_dhcp4_server_unref; + n_dhcp4_server_get_fd; + n_dhcp4_server_dispatch; + n_dhcp4_server_pop_event; + n_dhcp4_server_add_ip; + + n_dhcp4_server_ip_free; + + n_dhcp4_server_lease_ref; + n_dhcp4_server_lease_unref; + n_dhcp4_server_lease_query; + n_dhcp4_server_lease_append; + n_dhcp4_server_lease_offer; + n_dhcp4_server_lease_ack; + n_dhcp4_server_lease_nack; +local: + *; +}; diff --git a/src/n-dhcp4/src/meson.build b/src/n-dhcp4/src/meson.build new file mode 100644 index 00000000..0a3685aa --- /dev/null +++ b/src/n-dhcp4/src/meson.build @@ -0,0 +1,90 @@ +# +# target: libndhcp4.so +# + +libndhcp4_symfile = join_paths(meson.current_source_dir(), 'libndhcp4.sym') + +libndhcp4_deps = [ + dep_clist, + dep_csiphash, + dep_cstdaux, +] + +libndhcp4_private = static_library( + 'ndhcp4-private', + [ + 'n-dhcp4-c-connection.c', + 'n-dhcp4-c-lease.c', + 'n-dhcp4-c-probe.c', + 'n-dhcp4-client.c', + 'n-dhcp4-incoming.c', + 'n-dhcp4-outgoing.c', + 'n-dhcp4-s-connection.c', + 'n-dhcp4-s-lease.c', + 'n-dhcp4-server.c', + 'n-dhcp4-socket.c', + 'util/link.c', + 'util/netns.c', + 'util/packet.c', + 'util/socket.c', + ], + c_args: [ + '-fvisibility=hidden', + '-fno-common' + ], + dependencies: libndhcp4_deps, + pic: true, +) + +libndhcp4_shared = shared_library( + 'ndhcp4', + objects: libndhcp4_private.extract_all_objects(), + dependencies: libndhcp4_deps, + install: not meson.is_subproject(), + soversion: 0, + link_depends: libndhcp4_symfile, + link_args: [ + '-Wl,--version-script=@0@'.format(libndhcp4_symfile) + ], +) + +libndhcp4_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libndhcp4_private, + dependencies: libndhcp4_deps, + version: meson.project_version(), +) + +if not meson.is_subproject() + install_headers('n-dhcp4.h') + + mod_pkgconfig.generate( + libraries: libndhcp4_shared, + version: meson.project_version(), + name: 'libndhcp4', + filebase: 'libndhcp4', + description: project_description, + ) +endif + +# +# target: test-* +# + +test_api = executable('test-api', ['test-api.c'], link_with: libndhcp4_shared) +test('API Symbol Visibility', test_api) + +test_connection = executable('test-connection', ['test-connection.c'], dependencies: libndhcp4_dep) +test('Connection Handling', test_connection) + +test_message = executable('test-message', ['test-message.c'], dependencies: libndhcp4_dep) +test('Message Handling', test_message) + +test_run_client = executable('test-run-client', ['test-run-client.c'], dependencies: libndhcp4_dep) +test('Client Runner', test_run_client, args: ['--test']) + +test_socket = executable('test-socket', ['test-socket.c'], dependencies: libndhcp4_dep) +test('Socket Handling', test_socket) + +test_util_packet = executable('test-util-packet', ['util/test-packet.c'], dependencies: libndhcp4_dep) +test('Packet Utility Library', test_util_packet) diff --git a/src/n-dhcp4/src/n-dhcp4-c-connection.c b/src/n-dhcp4/src/n-dhcp4-c-connection.c index 7cb4f23f..7024b71a 100644 --- a/src/n-dhcp4/src/n-dhcp4-c-connection.c +++ b/src/n-dhcp4/src/n-dhcp4-c-connection.c @@ -1031,13 +1031,13 @@ static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection, case N_DHCP4_C_MESSAGE_REBOOT: case N_DHCP4_C_MESSAGE_REBIND: case N_DHCP4_C_MESSAGE_RENEW: + case N_DHCP4_C_MESSAGE_DECLINE: + case N_DHCP4_C_MESSAGE_RELEASE: request->userdata.base_time = timestamp; n_dhcp4_outgoing_set_xid(request, n_dhcp4_client_probe_config_get_random(connection->probe_config)); break; case N_DHCP4_C_MESSAGE_SELECT: - case N_DHCP4_C_MESSAGE_DECLINE: - case N_DHCP4_C_MESSAGE_RELEASE: break; default: c_assert(0); diff --git a/src/n-dhcp4/src/n-dhcp4-c-probe.c b/src/n-dhcp4/src/n-dhcp4-c-probe.c index a5b38bcd..ee3a8886 100644 --- a/src/n-dhcp4/src/n-dhcp4-c-probe.c +++ b/src/n-dhcp4/src/n-dhcp4-c-probe.c @@ -1319,3 +1319,30 @@ int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events) int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu) { return 0; } + +/** + * n_dhcp4_client_probe_release() - send a release request + * @probe: probe to operate on + * + * This sends a RELEASE message on the connection used by the probe. + * + * Return: 0 if successful otherwise non-zero value is returned. + */ +int n_dhcp4_client_probe_release(NDhcp4ClientProbe *probe) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + int r; + + r = n_dhcp4_c_connection_release_new(&probe->connection, &request_out, NULL); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request_out, 0); + if (r) + return r; + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT; + n_dhcp4_client_lease_unlink(probe->current_lease); + request_out = NULL; + + return 0; +} diff --git a/src/n-dhcp4/src/n-dhcp4-s-connection.c b/src/n-dhcp4/src/n-dhcp4-s-connection.c new file mode 100644 index 00000000..5ed190a8 --- /dev/null +++ b/src/n-dhcp4/src/n-dhcp4-s-connection.c @@ -0,0 +1,412 @@ +/* + * DHCPv4 Server Connection + * + * XXX + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <net/if_arp.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include "n-dhcp4-private.h" +#include "util/packet.h" + +int n_dhcp4_s_connection_init(NDhcp4SConnection *connection, int ifindex) { + int r; + + *connection = (NDhcp4SConnection)N_DHCP4_S_CONNECTION_NULL(*connection); + + r = n_dhcp4_s_socket_packet_new(&connection->fd_packet); + if (r) + return r; + + r = n_dhcp4_s_socket_udp_new(&connection->fd_udp, ifindex); + if (r) + return r; + + connection->ifindex = ifindex; + + return 0; +} + +void n_dhcp4_s_connection_deinit(NDhcp4SConnection *connection) { + c_assert(!connection->ip); + + if (connection->fd_udp >= 0) { + close(connection->fd_udp); + } + + if (connection->fd_packet >= 0) { + close(connection->fd_packet); + } + + *connection = (NDhcp4SConnection)N_DHCP4_S_CONNECTION_NULL(*connection); +} + +void n_dhcp4_s_connection_get_fd(NDhcp4SConnection *connection, int *fdp) { + *fdp = connection->fd_udp; +} + +static bool n_dhcp4_s_connection_owns_ip(NDhcp4SConnection *connection, struct in_addr addr) { + if (!connection->ip) + return false; + return (connection->ip->ip.s_addr == addr.s_addr); +} + +static int n_dhcp4_s_connection_verify_incoming(NDhcp4SConnection *connection, + NDhcp4Incoming *message, + bool broadcast) { + uint8_t type; + int r; + + r = n_dhcp4_incoming_query_message_type(message, &type); + if (r) { + if (r == N_DHCP4_E_UNSET) + return N_DHCP4_E_MALFORMED; + else + return r; + } + + switch (type) { + case N_DHCP4_MESSAGE_DISCOVER: + message->userdata.type = N_DHCP4_C_MESSAGE_DISCOVER; + break; + case N_DHCP4_MESSAGE_REQUEST: { + struct in_addr server_identifier = {}; + struct in_addr requested_ip = {}; + + r = n_dhcp4_incoming_query_server_identifier(message, &server_identifier); + if (r) { + if (r == N_DHCP4_E_UNSET) { + r = n_dhcp4_incoming_query_requested_ip(message, &requested_ip); + if (r) { + if (r == N_DHCP4_E_UNSET) { + if (broadcast) { + message->userdata.type = N_DHCP4_C_MESSAGE_REBIND; + } else { + message->userdata.type = N_DHCP4_C_MESSAGE_RENEW; + } + } else { + return r; + } + } else { + message->userdata.type = N_DHCP4_C_MESSAGE_REBOOT; + } + } else { + return r; + } + } else { + if (n_dhcp4_s_connection_owns_ip(connection, server_identifier)) { + message->userdata.type = N_DHCP4_C_MESSAGE_SELECT; + } else { + message->userdata.type = N_DHCP4_C_MESSAGE_IGNORE; + } + } + } + break; + case N_DHCP4_MESSAGE_DECLINE: + message->userdata.type = N_DHCP4_C_MESSAGE_DECLINE; + break; + case N_DHCP4_MESSAGE_RELEASE: + message->userdata.type = N_DHCP4_C_MESSAGE_RELEASE; + break; + default: + return N_DHCP4_E_UNEXPECTED; + } + + return 0; +} + +int n_dhcp4_s_connection_dispatch_io(NDhcp4SConnection *connection, NDhcp4Incoming **messagep) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + struct sockaddr_in dest = {}; + int r; + + r = n_dhcp4_s_socket_udp_recv(connection->fd_udp, + connection->buf, + sizeof(connection->buf), + &message, + &dest); + if (r) + return r; + + r = n_dhcp4_s_connection_verify_incoming(connection, + message, + dest.sin_addr.s_addr == INADDR_BROADCAST); + if (r) { + if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED) { + *messagep = NULL; + return 0; + } + + return -ENOTRECOVERABLE; + } + + *messagep = message; + message = NULL; + return 0; +} + +/* + * If the 'giaddr' field in a DHCP message from a client is non-zero, + * the server sends any return messages to the 'DHCP server' port on the + * BOOTP relay agent whose address appears in 'giaddr'. If the 'giaddr' + * field is zero and the 'ciaddr' field is nonzero, then the server + * unicasts DHCPOFFER and DHCPACK messages to the address in 'ciaddr'. + * If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is + * set, then the server broadcasts DHCPOFFER and DHCPACK messages to + * 0xffffffff. If the broadcast bit is not set and 'giaddr' is zero and + * 'ciaddr' is zero, then the server unicasts DHCPOFFER and DHCPACK + * messages to the client's hardware address and 'yiaddr' address. In + * all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK + * messages to 0xffffffff. + */ +int n_dhcp4_s_connection_send_reply(NDhcp4SConnection *connection, + const struct in_addr *server_addr, + NDhcp4Outgoing *message) { + NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); + int r; + + if (header->giaddr) { + const struct in_addr giaddr = { header->giaddr }; + + r = n_dhcp4_s_socket_udp_send(connection->fd_udp, + server_addr, + &giaddr, + message); + if (r) + return r; + } else if (header->ciaddr) { + const struct in_addr ciaddr = { header->ciaddr }; + + r = n_dhcp4_s_socket_udp_send(connection->fd_udp, + server_addr, + &ciaddr, + message); + if (r) + return r; + } else if (header->flags & htons(N_DHCP4_MESSAGE_FLAG_BROADCAST)) { + r = n_dhcp4_s_socket_udp_broadcast(connection->fd_udp, + server_addr, + message); + if (r) + return r; + } else { + r = n_dhcp4_s_socket_packet_send(connection->fd_packet, + connection->ifindex, + server_addr, + header->chaddr, + header->hlen, + &(struct in_addr){header->yiaddr}, + N_DHCP4_DSCP_DEFAULT, + message); + if (r) + return r; + } + + return 0; +} + +static void n_dhcp4_s_connection_init_reply_header(NDhcp4SConnection *connection, + NDhcp4Header *request, + NDhcp4Header *reply) { + reply->op = N_DHCP4_OP_BOOTREPLY; + + reply->htype = request->htype; + reply->hlen = request->hlen; + reply->flags = request->flags; + reply->xid = request->xid; + reply->ciaddr = request->ciaddr; + reply->giaddr = request->giaddr; + memcpy(reply->chaddr, request->chaddr, request->hlen); +} + +static int n_dhcp4_s_connection_outgoing_set_yiaddr(NDhcp4Outgoing *message, + uint32_t yiaddr, + uint32_t lifetime) { + uint32_t t1 = lifetime / 2; + uint32_t t2 = ((uint64_t)lifetime * 7) / 8; + struct in_addr addr = { .s_addr = yiaddr }; + int r; + + r = n_dhcp4_outgoing_append_lifetime(message, lifetime); + if (r) + return r; + + r = n_dhcp4_outgoing_append_t1(message, t1); + if (r) + return r; + + r = n_dhcp4_outgoing_append_t2(message, t2); + if (r) + return r; + + n_dhcp4_outgoing_set_yiaddr(message, addr); + + return 0; +} + +static int n_dhcp4_s_connection_new_reply(NDhcp4SConnection *connection, + NDhcp4Outgoing **messagep, + NDhcp4Incoming *request, + uint8_t type, + const struct in_addr *server_address) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL; + uint16_t max_message_size; + uint8_t *client_identifier; + size_t n_client_identifier; + int r; + + r = n_dhcp4_incoming_query_max_message_size(request, &max_message_size); + if (r) + return r; + + r = n_dhcp4_outgoing_new(&message, + max_message_size, + N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME); + if (r) + return r; + + n_dhcp4_s_connection_init_reply_header(connection, + n_dhcp4_incoming_get_header(request), + n_dhcp4_outgoing_get_header(message)); + + r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MESSAGE_TYPE, &type, sizeof(type)); + if (r) + return r; + + r = n_dhcp4_outgoing_append_server_identifier(message, *server_address); + if (r) + return r; + + r = n_dhcp4_incoming_query(request, + N_DHCP4_OPTION_CLIENT_IDENTIFIER, + &client_identifier, + &n_client_identifier); + if (!r) { + r = n_dhcp4_outgoing_append(message, + N_DHCP4_OPTION_CLIENT_IDENTIFIER, + client_identifier, + n_client_identifier); + if (r) + return r; + } else if (r != N_DHCP4_E_UNSET) { + return r; + } + + *messagep = message; + message = NULL; + return 0; +} + +int n_dhcp4_s_connection_offer_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address, + const struct in_addr *client_address, + uint32_t lifetime) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_s_connection_new_reply(connection, + &reply, + request, + N_DHCP4_MESSAGE_OFFER, + server_address); + if (r) + return r; + + r = n_dhcp4_s_connection_outgoing_set_yiaddr(reply, + client_address->s_addr, + lifetime); + if (r) + return r; + + *replyp = reply; + reply = NULL; + return 0; +} + +int n_dhcp4_s_connection_ack_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address, + const struct in_addr *client_address, + uint32_t lifetime) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_s_connection_new_reply(connection, + &reply, + request, + N_DHCP4_MESSAGE_ACK, + server_address); + if (r) + return r; + + r = n_dhcp4_s_connection_outgoing_set_yiaddr(reply, + client_address->s_addr, + lifetime); + if (r) + return r; + + *replyp = reply; + reply = NULL; + return 0; +} + +int n_dhcp4_s_connection_nak_new(NDhcp4SConnection *connection, + NDhcp4Outgoing **replyp, + NDhcp4Incoming *request, + const struct in_addr *server_address) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_s_connection_new_reply(connection, + &reply, + request, + N_DHCP4_MESSAGE_NAK, + server_address); + if (r) + return r; + + /* + * The RFC is a bit unclear on how NAK should be sent, on the + * one hand it says that they should be unconditionally broadcast + * (unless going through a relay agent), on the other, when they + * do go through a relay agent, they will not be. We treat them + * as any other reply and only broadcast when the broadcast bit + * is set. + */ + + *replyp = reply; + reply = NULL; + return 0; +} + +void n_dhcp4_s_connection_ip_init(NDhcp4SConnectionIp *ip, struct in_addr addr) { + *ip = (NDhcp4SConnectionIp)N_DHCP4_S_CONNECTION_IP_NULL(*ip); + ip->ip = addr; +} + +void n_dhcp4_s_connection_ip_deinit(NDhcp4SConnectionIp *ip) { + c_assert(!ip->connection); + *ip = (NDhcp4SConnectionIp)N_DHCP4_S_CONNECTION_IP_NULL(*ip); +} + +void n_dhcp4_s_connection_ip_link(NDhcp4SConnectionIp *ip, NDhcp4SConnection *connection) { + c_assert(!connection->ip); + c_assert(!ip->connection); + + connection->ip = ip; + ip->connection = connection; +} + +void n_dhcp4_s_connection_ip_unlink(NDhcp4SConnectionIp *ip) { + ip->connection->ip = NULL; + ip->connection = NULL; +} diff --git a/src/n-dhcp4/src/n-dhcp4-s-lease.c b/src/n-dhcp4/src/n-dhcp4-s-lease.c new file mode 100644 index 00000000..cb8aabf9 --- /dev/null +++ b/src/n-dhcp4/src/n-dhcp4-s-lease.c @@ -0,0 +1,103 @@ +/* + * XXX + */ + +#include <assert.h> +#include <c-list.h> +#include <c-stdaux.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" + +/** + * n_dhcp4_server_lease_new() - XXX + */ +int n_dhcp4_server_lease_new(NDhcp4ServerLease **leasep, NDhcp4Incoming *message) { + _c_cleanup_(n_dhcp4_server_lease_unrefp) NDhcp4ServerLease *lease = NULL; + + c_assert(leasep); + + lease = malloc(sizeof(*lease)); + if (!lease) + return -ENOMEM; + + *lease = (NDhcp4ServerLease)N_DHCP4_SERVER_LEASE_NULL(*lease); + + lease->request = message; + + *leasep = lease; + lease = NULL; + return 0; +} + +static void n_dhcp4_server_lease_free(NDhcp4ServerLease *lease) { + c_assert(!lease->server); + + c_list_unlink(&lease->server_link); + + n_dhcp4_incoming_free(lease->request); + free(lease); +} + +/** + * n_dhcp4_server_lease_ref() - XXX + */ +_c_public_ NDhcp4ServerLease *n_dhcp4_server_lease_ref(NDhcp4ServerLease *lease) { + if (lease) + ++lease->n_refs; + return lease; +} + +/** + * n_dhcp4_server_lease_unref() - XXX + */ +_c_public_ NDhcp4ServerLease *n_dhcp4_server_lease_unref(NDhcp4ServerLease *lease) { + if (lease && !--lease->n_refs) + n_dhcp4_server_lease_free(lease); + return NULL; +} + +/** + * n_dhcp4_server_lease_query() - XXX + */ +_c_public_ int n_dhcp4_server_lease_query(NDhcp4ServerLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap) { + switch (option) { + case N_DHCP4_OPTION_PAD: + case N_DHCP4_OPTION_REQUESTED_IP_ADDRESS: + case N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME: + case N_DHCP4_OPTION_OVERLOAD: + case N_DHCP4_OPTION_MESSAGE_TYPE: + case N_DHCP4_OPTION_SERVER_IDENTIFIER: + case N_DHCP4_OPTION_PARAMETER_REQUEST_LIST: + case N_DHCP4_OPTION_ERROR_MESSAGE: + case N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE: + case N_DHCP4_OPTION_RENEWAL_T1_TIME: + case N_DHCP4_OPTION_REBINDING_T2_TIME: + case N_DHCP4_OPTION_END: + return N_DHCP4_E_INTERNAL; + } + + return n_dhcp4_incoming_query(lease->request, option, datap, n_datap); +} + +_c_public_ int n_dhcp4_server_lease_append(NDhcp4ServerLease *lease, uint8_t option, uint8_t *data, size_t n_data) { + /* XXX */ + return -ENOTRECOVERABLE; +} + +_c_public_ int n_dhcp4_server_lease_offer(NDhcp4ServerLease *lease) { + /* XXX */ + return -ENOTRECOVERABLE; +} + +_c_public_ int n_dhcp4_server_lease_ack(NDhcp4ServerLease *lease) { + /* XXX */ + return -ENOTRECOVERABLE; +} + +_c_public_ int n_dhcp4_server_lease_nack(NDhcp4ServerLease *lease) { + /* XXX */ + return -ENOTRECOVERABLE; +} diff --git a/src/n-dhcp4/src/n-dhcp4-server.c b/src/n-dhcp4/src/n-dhcp4-server.c new file mode 100644 index 00000000..7b971eeb --- /dev/null +++ b/src/n-dhcp4/src/n-dhcp4-server.c @@ -0,0 +1,241 @@ +/* + * Server Side of the Dynamic Host Configuration Protocol for IPv4 + * + * XXX + */ + +#include <assert.h> +#include <c-list.h> +#include <c-stdaux.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/time.h> +#include <sys/timerfd.h> +#include <time.h> +#include <unistd.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" +#include "util/packet.h" + +/** + * n_dhcp4_server_config_new() - XXX + */ +_c_public_ int n_dhcp4_server_config_new(NDhcp4ServerConfig **configp) { + _c_cleanup_(n_dhcp4_server_config_freep) NDhcp4ServerConfig *config = NULL; + + config = calloc(1, sizeof(*config)); + if (!config) + return -ENOMEM; + + *config = (NDhcp4ServerConfig)N_DHCP4_SERVER_CONFIG_NULL(*config); + + *configp = config; + config = NULL; + return 0; +} + +/** + * n_dhcp4_server_config_free() - XXX + */ +_c_public_ NDhcp4ServerConfig *n_dhcp4_server_config_free(NDhcp4ServerConfig *config) { + if (!config) + return NULL; + + free(config); + + return NULL; +} + +/** + * n_dhcp4_server_config_set_ifindex() - XXX + */ +_c_public_ void n_dhcp4_server_config_set_ifindex(NDhcp4ServerConfig *config, int ifindex) { + config->ifindex = ifindex; +} + +/** + * n_dhcp4_s_event_node_new() - XXX + */ +int n_dhcp4_s_event_node_new(NDhcp4SEventNode **nodep) { + NDhcp4SEventNode *node; + + node = calloc(1, sizeof(*node)); + if (!node) + return -ENOMEM; + + *node = (NDhcp4SEventNode)N_DHCP4_S_EVENT_NODE_NULL(*node); + + *nodep = node; + return 0; +} + +/** + * n_dhcp4_s_event_node_free() - XXX + */ +NDhcp4SEventNode *n_dhcp4_s_event_node_free(NDhcp4SEventNode *node) { + if (!node) + return NULL; + + c_list_unlink(&node->server_link); + free(node); + + return NULL; +} + +/** + * n_dhcp4_server_new() - XXX + */ +_c_public_ int n_dhcp4_server_new(NDhcp4Server **serverp, NDhcp4ServerConfig *config) { + _c_cleanup_(n_dhcp4_server_unrefp) NDhcp4Server *server = NULL; + int r; + + c_assert(serverp); + + server = malloc(sizeof(*server)); + if (!server) + return -ENOMEM; + + *server = (NDhcp4Server)N_DHCP4_SERVER_NULL(*server); + + r = n_dhcp4_s_connection_init(&server->connection, config->ifindex); + if (r) + return r; + + *serverp = server; + server = NULL; + return 0; +} + +static void n_dhcp4_server_free(NDhcp4Server *server) { + NDhcp4SEventNode *node, *t_node; + + c_list_for_each_entry_safe(node, t_node, &server->event_list, server_link) + n_dhcp4_s_event_node_free(node); + + free(server); +} + +/** + * n_dhcp4_server_ref() - XXX + */ +_c_public_ NDhcp4Server *n_dhcp4_server_ref(NDhcp4Server *server) { + if (server) + ++server->n_refs; + return server; +} + +/** + * n_dhcp4_server_unref() - XXX + */ +_c_public_ NDhcp4Server *n_dhcp4_server_unref(NDhcp4Server *server) { + if (server && !--server->n_refs) + n_dhcp4_server_free(server); + return NULL; +} + +/** + * n_dhcp4_server_raise() - XXX + */ +int n_dhcp4_server_raise(NDhcp4Server *server, NDhcp4SEventNode **nodep, unsigned int event) { + NDhcp4SEventNode *node; + int r; + + r = n_dhcp4_s_event_node_new(&node); + if (r) + return r; + + node->event.event = event; + c_list_link_tail(&server->event_list, &node->server_link); + + if (nodep) + *nodep = node; + return 0; +} + +/** + * n_dhcp4_server_get_fd() - XXX + */ +_c_public_ void n_dhcp4_server_get_fd(NDhcp4Server *server, int *fdp) { + n_dhcp4_s_connection_get_fd(&server->connection, fdp); +} + +/** + * n_dhcp4_server_dispatch() - XXX + */ +_c_public_ int n_dhcp4_server_dispatch(NDhcp4Server *server) { + int r; + + for (unsigned int i = 0; i < 128; ++i) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + + r = n_dhcp4_s_connection_dispatch_io(&server->connection, &message); + if (r) { + if (r == N_DHCP4_E_AGAIN) + return 0; + return r; + } + } + + return N_DHCP4_E_PREEMPTED; +} + +/** + * n_dhcp4_server_pop_event() - XXX + */ +_c_public_ int n_dhcp4_server_pop_event(NDhcp4Server *server, NDhcp4ServerEvent **eventp) { + NDhcp4SEventNode *node, *t_node; + + c_list_for_each_entry_safe(node, t_node, &server->event_list, server_link) { + if (node->is_public) { + n_dhcp4_s_event_node_free(node); + continue; + } + + node->is_public = true; + *eventp = &node->event; + return 0; + } + + *eventp = NULL; + return 0; +} + +/** + * n_dhcp4_server_add_ip() - XXX + */ +_c_public_ int n_dhcp4_server_add_ip(NDhcp4Server *server, NDhcp4ServerIp **ipp, struct in_addr addr) { + _c_cleanup_(n_dhcp4_server_ip_freep) NDhcp4ServerIp *ip = NULL; + + /* XXX: support more than one address */ + if (server->connection.ip) + return -EBUSY; + + ip = malloc(sizeof(*ip)); + if (!ip) + return -ENOMEM; + + *ip = (NDhcp4ServerIp)N_DHCP4_SERVER_IP_NULL(*ip); + + n_dhcp4_s_connection_ip_init(&ip->ip, addr); + n_dhcp4_s_connection_ip_link(&ip->ip, &server->connection); + + *ipp = ip; + ip = NULL; + return 0; +} + +/** + * n_dhcp4_server_ip_free() - XXX + */ +_c_public_ NDhcp4ServerIp *n_dhcp4_server_ip_free(NDhcp4ServerIp *ip) { + if (!ip) + return NULL; + + n_dhcp4_s_connection_ip_unlink(&ip->ip); + n_dhcp4_s_connection_ip_deinit(&ip->ip); + + free(ip); + return NULL; +} diff --git a/src/n-dhcp4/src/n-dhcp4.h b/src/n-dhcp4/src/n-dhcp4.h index f6c87a8d..f3cf4210 100644 --- a/src/n-dhcp4/src/n-dhcp4.h +++ b/src/n-dhcp4/src/n-dhcp4.h @@ -161,6 +161,7 @@ NDhcp4ClientProbe *n_dhcp4_client_probe_free(NDhcp4ClientProbe *probe); void n_dhcp4_client_probe_set_userdata(NDhcp4ClientProbe *probe, void *userdata); void n_dhcp4_client_probe_get_userdata(NDhcp4ClientProbe *probe, void **userdatap); +int n_dhcp4_client_probe_release(NDhcp4ClientProbe *probe); /* client leases */ diff --git a/src/n-dhcp4/src/test-api.c b/src/n-dhcp4/src/test-api.c new file mode 100644 index 00000000..fac33008 --- /dev/null +++ b/src/n-dhcp4/src/test-api.c @@ -0,0 +1,157 @@ +/* + * API Visibility Tests + * This verifies the visibility and availability of the exported API. + */ + +#undef NDEBUG +#include <assert.h> +#include <stdlib.h> +#include "n-dhcp4.h" + +static void test_api_constants(void) { + assert(1 + N_DHCP4_CLIENT_START_DELAY_RFC2131); + + assert(1 + _N_DHCP4_E_SUCCESS); + assert(1 + N_DHCP4_E_PREEMPTED); + assert(1 + N_DHCP4_E_INTERNAL); + assert(1 + N_DHCP4_E_INVALID_IFINDEX); + assert(1 + N_DHCP4_E_INVALID_TRANSPORT); + assert(1 + N_DHCP4_E_INVALID_ADDRESS); + assert(1 + N_DHCP4_E_INVALID_CLIENT_ID); + assert(1 + N_DHCP4_E_DUPLICATE_OPTION); + assert(1 + N_DHCP4_E_UNSET); + assert(1 + _N_DHCP4_E_N); + + assert(1 + N_DHCP4_TRANSPORT_ETHERNET); + assert(1 + N_DHCP4_TRANSPORT_INFINIBAND); + assert(1 + _N_DHCP4_TRANSPORT_N); + + assert(1 + N_DHCP4_CLIENT_EVENT_DOWN); + assert(1 + N_DHCP4_CLIENT_EVENT_OFFER); + assert(1 + N_DHCP4_CLIENT_EVENT_GRANTED); + assert(1 + N_DHCP4_CLIENT_EVENT_RETRACTED); + assert(1 + N_DHCP4_CLIENT_EVENT_EXTENDED); + assert(1 + N_DHCP4_CLIENT_EVENT_EXPIRED); + assert(1 + N_DHCP4_CLIENT_EVENT_CANCELLED); + assert(1 + _N_DHCP4_CLIENT_EVENT_N); + + assert(1 + N_DHCP4_SERVER_EVENT_DOWN); + assert(1 + N_DHCP4_SERVER_EVENT_DISCOVER); + assert(1 + N_DHCP4_SERVER_EVENT_REQUEST); + assert(1 + N_DHCP4_SERVER_EVENT_RENEW); + assert(1 + N_DHCP4_SERVER_EVENT_DECLINE); + assert(1 + N_DHCP4_SERVER_EVENT_RELEASE); + assert(1 + _N_DHCP4_SERVER_EVENT_N); +} + +static void test_api_types(void) { + assert(sizeof(NDhcp4ClientConfig*) > 0); + assert(sizeof(NDhcp4ClientProbeConfig*) > 0); + assert(sizeof(NDhcp4Client*) > 0); + assert(sizeof(NDhcp4ClientEvent) > 0); + assert(sizeof(NDhcp4ClientProbe*) > 0); + assert(sizeof(NDhcp4ClientLease*) > 0); + assert(sizeof(NDhcp4Server*) > 0); + assert(sizeof(NDhcp4ServerConfig*) > 0); + assert(sizeof(NDhcp4ServerEvent) > 0); + assert(sizeof(NDhcp4ServerIp*) > 0); + assert(sizeof(NDhcp4ServerLease*) > 0); +} + +static void test_api_functions(void) { + void *fns[] = { + (void *)n_dhcp4_client_config_new, + (void *)n_dhcp4_client_config_free, + (void *)n_dhcp4_client_config_freep, + (void *)n_dhcp4_client_config_freev, + (void *)n_dhcp4_client_config_set_ifindex, + (void *)n_dhcp4_client_config_set_transport, + (void *)n_dhcp4_client_config_set_request_broadcast, + (void *)n_dhcp4_client_config_set_mac, + (void *)n_dhcp4_client_config_set_broadcast_mac, + (void *)n_dhcp4_client_config_set_client_id, + + (void *)n_dhcp4_client_probe_config_new, + (void *)n_dhcp4_client_probe_config_free, + (void *)n_dhcp4_client_probe_config_freep, + (void *)n_dhcp4_client_probe_config_freev, + (void *)n_dhcp4_client_probe_config_set_inform_only, + (void *)n_dhcp4_client_probe_config_set_init_reboot, + (void *)n_dhcp4_client_probe_config_set_requested_ip, + (void *)n_dhcp4_client_probe_config_set_start_delay, + (void *)n_dhcp4_client_probe_config_request_option, + (void *)n_dhcp4_client_probe_config_append_option, + + (void *)n_dhcp4_client_new, + (void *)n_dhcp4_client_ref, + (void *)n_dhcp4_client_unref, + (void *)n_dhcp4_client_unrefp, + (void *)n_dhcp4_client_unrefv, + (void *)n_dhcp4_client_get_fd, + (void *)n_dhcp4_client_dispatch, + (void *)n_dhcp4_client_pop_event, + (void *)n_dhcp4_client_update_mtu, + (void *)n_dhcp4_client_probe, + + (void *)n_dhcp4_client_probe_free, + (void *)n_dhcp4_client_probe_freep, + (void *)n_dhcp4_client_probe_freev, + (void *)n_dhcp4_client_probe_get_userdata, + (void *)n_dhcp4_client_probe_set_userdata, + + (void *)n_dhcp4_client_lease_ref, + (void *)n_dhcp4_client_lease_unref, + (void *)n_dhcp4_client_lease_unrefp, + (void *)n_dhcp4_client_lease_unrefv, + (void *)n_dhcp4_client_lease_get_yiaddr, + (void *)n_dhcp4_client_lease_get_siaddr, + (void *)n_dhcp4_client_lease_get_lifetime, + (void *)n_dhcp4_client_lease_get_server_identifier, + (void *)n_dhcp4_client_lease_get_file, + (void *)n_dhcp4_client_lease_query, + (void *)n_dhcp4_client_lease_select, + (void *)n_dhcp4_client_lease_accept, + (void *)n_dhcp4_client_lease_decline, + + (void *)n_dhcp4_server_config_new, + (void *)n_dhcp4_server_config_free, + (void *)n_dhcp4_server_config_freep, + (void *)n_dhcp4_server_config_freev, + (void *)n_dhcp4_server_config_set_ifindex, + + (void *)n_dhcp4_server_new, + (void *)n_dhcp4_server_ref, + (void *)n_dhcp4_server_unref, + (void *)n_dhcp4_server_unrefp, + (void *)n_dhcp4_server_unrefv, + (void *)n_dhcp4_server_get_fd, + (void *)n_dhcp4_server_dispatch, + (void *)n_dhcp4_server_pop_event, + (void *)n_dhcp4_server_add_ip, + + (void *)n_dhcp4_server_ip_free, + (void *)n_dhcp4_server_ip_freep, + (void *)n_dhcp4_server_ip_freev, + + (void *)n_dhcp4_server_lease_ref, + (void *)n_dhcp4_server_lease_unref, + (void *)n_dhcp4_server_lease_unrefp, + (void *)n_dhcp4_server_lease_unrefv, + (void *)n_dhcp4_server_lease_query, + (void *)n_dhcp4_server_lease_append, + (void *)n_dhcp4_server_lease_offer, + (void *)n_dhcp4_server_lease_ack, + (void *)n_dhcp4_server_lease_nack, + }; + 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-dhcp4/src/test-connection.c b/src/n-dhcp4/src/test-connection.c new file mode 100644 index 00000000..98bd2aca --- /dev/null +++ b/src/n-dhcp4/src/test-connection.c @@ -0,0 +1,390 @@ +/* + * Tests for DHCP4 Client Connections + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <poll.h> +#include <linux/if_packet.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include "n-dhcp4-private.h" +#include "test.h" +#include "util/link.h" +#include "util/netns.h" +#include "util/packet.h" + +static void test_poll_client(int efd, unsigned int u32) { + struct epoll_event event = {}; + int r; + + r = epoll_wait(efd, &event, 1, -1); + c_assert(r == 1); + c_assert(event.events == EPOLLIN); + c_assert(event.data.u32 == u32); +} + +static void test_poll_server(int fd) { + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + int r; + + r = poll(&pfd, 1, -1); + c_assert(r == 1); + c_assert(pfd.revents == POLLIN); +} + +static void test_s_connection_init(int netns, NDhcp4SConnection *connection, int ifindex) { + int r, oldns; + + netns_get(&oldns); + netns_set(netns); + + r = n_dhcp4_s_connection_init(connection, ifindex); + c_assert(!r); + + netns_set(oldns); +} + +static void test_c_connection_listen(int netns, NDhcp4CConnection *connection) { + int r, oldns; + + netns_get(&oldns); + netns_set(netns); + + r = n_dhcp4_c_connection_listen(connection); + c_assert(!r); + + netns_set(oldns); +} + +static void test_c_connection_connect(int netns, + NDhcp4CConnection *connection, + const struct in_addr *client, + const struct in_addr *server) { + int r, oldns; + + netns_get(&oldns); + netns_set(netns); + + r = n_dhcp4_c_connection_connect(connection, client, server); + c_assert(!r); + + netns_set(oldns); +} + +static void test_server_receive(NDhcp4SConnection *connection, uint8_t expected_type, NDhcp4Incoming **messagep) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + uint8_t received_type; + int r, fd; + + n_dhcp4_s_connection_get_fd(connection, &fd); + test_poll_server(fd); + + r = n_dhcp4_s_connection_dispatch_io(connection, &message); + c_assert(!r); + c_assert(message); + + r = n_dhcp4_incoming_query_message_type(message, &received_type); + c_assert(!r); + c_assert(received_type == expected_type); + + if (messagep) { + *messagep = message; + message = NULL; + } +} + +static void test_client_receive(NDhcp4CConnection *connection, uint8_t expected_type, NDhcp4Incoming **messagep) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + uint8_t received_type; + int r; + + test_poll_client(connection->fd_epoll, N_DHCP4_CLIENT_EPOLL_IO); + + r = n_dhcp4_c_connection_dispatch_io(connection, &message); + c_assert(!r); + c_assert(message); + + r = n_dhcp4_incoming_query_message_type(message, &received_type); + c_assert(!r); + c_assert(received_type == expected_type); + + if (messagep) { + *messagep = message; + message = NULL; + } +} + +static void test_discover(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + const struct in_addr *addr_server, + const struct in_addr *addr_client, + NDhcp4Incoming **offerp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL; + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply_out = NULL; + int r; + + r = n_dhcp4_c_connection_discover_new(connection_client, &request_out); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_DISCOVER, &request_in); + + r = n_dhcp4_s_connection_offer_new(connection_server, &reply_out, request_in, addr_server, addr_client, 60); + c_assert(!r); + + r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply_out); + c_assert(!r); + + test_client_receive(connection_client, N_DHCP4_MESSAGE_OFFER, offerp); +} + +static void test_select(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + NDhcp4Incoming *offer, + const struct in_addr *addr_server, + const struct in_addr *addr_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL; + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_c_connection_select_new(connection_client, &request_out, offer); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in); + + r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60); + c_assert(!r); + + r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply); + c_assert(!r); + + test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL); +} + +static void test_reboot(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + const struct in_addr *addr_server, + const struct in_addr *addr_client, + NDhcp4Incoming **ackp) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL; + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_c_connection_reboot_new(connection_client, &request_out, addr_server); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in); + + r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60); + c_assert(!r); + + r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply); + c_assert(!r); + + test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, ackp); +} + +static void test_decline(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + NDhcp4Incoming *ack) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + int r; + + r = n_dhcp4_c_connection_decline_new(connection_client, &request_out, ack, "No thanks."); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_DECLINE, NULL); +} + + +static void test_renew(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + const struct in_addr *addr_server, + const struct in_addr *addr_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL; + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_c_connection_renew_new(connection_client, &request_out); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in); + + r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60); + c_assert(!r); + + r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply); + c_assert(!r); + + test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL); +} + +static void test_rebind(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + const struct in_addr *addr_server, + const struct in_addr *addr_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL; + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL; + int r; + + r = n_dhcp4_c_connection_rebind_new(connection_client, &request_out); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in); + + r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60); + c_assert(!r); + + r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply); + c_assert(!r); + + test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL); +} + +static void test_release(NDhcp4SConnection *connection_server, + NDhcp4CConnection *connection_client, + const struct in_addr *addr_server, + const struct in_addr *addr_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL; + int r; + + r = n_dhcp4_c_connection_release_new(connection_client, &request_out, "Shutting down!"); + c_assert(!r); + + r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0); + c_assert(!r); + request_out = NULL; + + test_server_receive(connection_server, N_DHCP4_MESSAGE_RELEASE, NULL); +} + +static void test_connection(void) { + const struct in_addr addr_server = (struct in_addr){ htonl(10 << 24 | 1) }; + const struct in_addr addr_client = (struct in_addr){ htonl(10 << 24 | 2) }; + _c_cleanup_(netns_closep) int ns_server = -1, ns_client = -1; + _c_cleanup_(link_deinit) Link link_server = LINK_NULL(link_server); + _c_cleanup_(link_deinit) Link link_client = LINK_NULL(link_client); + _c_cleanup_(c_closep) int efd_client = -1; + int r; + + /* setup */ + + netns_new(&ns_server); + netns_new(&ns_client); + + link_new_veth(&link_server, &link_client, ns_server, ns_client); + link_add_ip4(&link_server, &addr_server, 8); + + efd_client = epoll_create1(EPOLL_CLOEXEC); + c_assert(efd_client >= 0); + + /* test connections */ + { + _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *client_config = NULL; + _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *probe_config = NULL; + NDhcp4SConnection connection_server = N_DHCP4_S_CONNECTION_NULL(connection_server); + NDhcp4SConnectionIp connection_server_ip = N_DHCP4_S_CONNECTION_IP_NULL(connection_server_ip); + NDhcp4CConnection connection_client = N_DHCP4_C_CONNECTION_NULL(connection_client); + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *offer = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *ack = NULL; + NDhcp4LogQueue log_queue = N_DHCP4_LOG_QUEUE_NULL_DEFUNCT(); + + test_s_connection_init(ns_server, &connection_server, link_server.ifindex); + n_dhcp4_s_connection_ip_init(&connection_server_ip, addr_server); + n_dhcp4_s_connection_ip_link(&connection_server_ip, &connection_server); + + r = n_dhcp4_client_config_new(&client_config); + c_assert(!r); + + n_dhcp4_client_config_set_ifindex(client_config, link_client.ifindex); + n_dhcp4_client_config_set_transport(client_config, N_DHCP4_TRANSPORT_ETHERNET); + n_dhcp4_client_config_set_request_broadcast(client_config, false); + n_dhcp4_client_config_set_mac(client_config, link_client.mac.ether_addr_octet, ETH_ALEN); + n_dhcp4_client_config_set_broadcast_mac(client_config, + (const uint8_t[]){ + 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + }, + ETH_ALEN); + r = n_dhcp4_client_config_set_client_id(client_config, + (void *)"client-id", + strlen("client-id")); + c_assert(!r); + + r = n_dhcp4_client_probe_config_new(&probe_config); + c_assert(!r); + + r = n_dhcp4_c_connection_init(&connection_client, + client_config, + probe_config, + &log_queue, + efd_client); + c_assert(!r); + test_c_connection_listen(ns_client, &connection_client); + + test_discover(&connection_server, &connection_client, &addr_server, &addr_client, &offer); + test_select(&connection_server, &connection_client, offer, &addr_server, &addr_client); + test_reboot(&connection_server, &connection_client, &addr_server, &addr_client, &ack); + test_rebind(&connection_server, &connection_client, &addr_server, &addr_client); + test_decline(&connection_server, &connection_client, ack); + + link_add_ip4(&link_client, &addr_client, 8); + test_c_connection_connect(ns_client, &connection_client, &addr_client, &addr_server); + + test_renew(&connection_server, &connection_client, &addr_server, &addr_client); + test_release(&connection_server, &connection_client, &addr_server, &addr_client); + + n_dhcp4_c_connection_deinit(&connection_client); + n_dhcp4_s_connection_ip_unlink(&connection_server_ip); + n_dhcp4_s_connection_ip_deinit(&connection_server_ip); + n_dhcp4_s_connection_deinit(&connection_server); + } + + /* teardown */ + + link_del_ip4(&link_client, &addr_client, 8); + link_del_ip4(&link_server, &addr_server, 8); +} + +int main(int argc, char **argv) { + test_setup(); + + test_connection(); + + return 0; +} diff --git a/src/n-dhcp4/src/test-message.c b/src/n-dhcp4/src/test-message.c new file mode 100644 index 00000000..6d4200f7 --- /dev/null +++ b/src/n-dhcp4/src/test-message.c @@ -0,0 +1,159 @@ +/* + * Tests for DHCP4 Message Handling + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4-private.h" + +static void test_outgoing(void) { + NDhcp4Outgoing *outgoing; + int r; + + /* verify basic NEW/FREE */ + + outgoing = NULL; + r = n_dhcp4_outgoing_new(&outgoing, 0, 0); + c_assert(!r); + c_assert(outgoing); + + outgoing = n_dhcp4_outgoing_free(outgoing); + c_assert(!outgoing); +} + +static void test_incoming(void) { + NDhcp4Incoming *incoming; + struct { + NDhcp4Header header; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t magic; + uint8_t options[1024]; + } m; + uint8_t *v; + size_t l; + int r; + + /* verify that messages must be at least the size of the header */ + + r = n_dhcp4_incoming_new(&incoming, NULL, 0); + c_assert(r == N_DHCP4_E_MALFORMED); + + r = n_dhcp4_incoming_new(&incoming, NULL, sizeof(m.header) + 64 + 128 + 3); + c_assert(r == N_DHCP4_E_MALFORMED); + + /* verify that magic must be set */ + + memset(&m, 0, sizeof(m)); + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(r == N_DHCP4_E_MALFORMED); + + /* verify basic NEW/FREE */ + + memset(&m, 0, sizeof(m)); + m.magic = htobe32(N_DHCP4_MESSAGE_MAGIC); + incoming = NULL; + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m.header) + 64 + 128 + 4); + c_assert(!r); + c_assert(incoming); + + incoming = n_dhcp4_incoming_free(incoming); + c_assert(!incoming); + + /* verify that PAD is properly handled */ + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + incoming = n_dhcp4_incoming_free(incoming); + + /* verify that SNAME/FILE are only looked at if OVERLOAD is set */ + + m.sname[0] = 1; + m.sname[1] = 0; + m.file[0] = 2; + m.file[1] = 0; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + incoming = n_dhcp4_incoming_free(incoming); + + m.options[0] = N_DHCP4_OPTION_OVERLOAD; + m.options[1] = 1; + m.options[2] = 0; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + incoming = n_dhcp4_incoming_free(incoming); + + m.options[0] = N_DHCP4_OPTION_OVERLOAD; + m.options[1] = 1; + m.options[2] = N_DHCP4_OVERLOAD_SNAME; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL); + c_assert(r == 0); + r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + incoming = n_dhcp4_incoming_free(incoming); + + m.options[0] = N_DHCP4_OPTION_OVERLOAD; + m.options[1] = 1; + m.options[2] = N_DHCP4_OVERLOAD_FILE; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL); + c_assert(r == N_DHCP4_E_UNSET); + r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL); + c_assert(r == 0); + incoming = n_dhcp4_incoming_free(incoming); + + m.options[0] = N_DHCP4_OPTION_OVERLOAD; + m.options[1] = 1; + m.options[2] = N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL); + c_assert(r == 0); + r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL); + c_assert(r == 0); + incoming = n_dhcp4_incoming_free(incoming); + + /* verify basic concatenation */ + + m.options[3] = 1; + m.options[4] = 1; + m.options[5] = 0xef; + m.sname[1] = 1; + m.sname[2] = 0xcf; + + r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m)); + c_assert(!r); + r = n_dhcp4_incoming_query(incoming, 1, &v, &l); + c_assert(r == 0); + c_assert(l == 2); + c_assert(v[0] == 0xef && v[1] == 0xcf); + incoming = n_dhcp4_incoming_free(incoming); +} + +int main(int argc, char **argv) { + test_outgoing(); + test_incoming(); + return 0; +} diff --git a/src/n-dhcp4/src/test-run-client.c b/src/n-dhcp4/src/test-run-client.c new file mode 100644 index 00000000..259a9608 --- /dev/null +++ b/src/n-dhcp4/src/test-run-client.c @@ -0,0 +1,713 @@ +/* + * DHCP Client Runner + * + * This test implements a DHCP client. It takes parameters via the command-line + * and runs a DHCP client. It is mainly meant for testing, as such it allows + * tweaking that an exported DHCP client should not provide. + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <getopt.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <netinet/in.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4.h" +#include "n-dhcp4-private.h" +#include "test.h" + +typedef struct Manager Manager; + +enum { + _MAIN_SUCCESS, + MAIN_EXIT, + MAIN_FAILED, +}; + +struct Manager { + NDhcp4Client *client; + NDhcp4ClientProbe *probe; +}; + +#define MANAGER_NULL(_x) {} + +static struct ether_addr main_arg_broadcast_mac = {}; +static bool main_arg_broadcast_mac_set = false; +static uint8_t* main_arg_client_id = NULL; +static size_t main_arg_n_client_id = 0; +static int main_arg_ifindex = 0; +static struct in_addr main_arg_requested_ip = { INADDR_ANY }; +static long long int main_arg_requested_lifetime = -1; +static uint8_t main_arg_requested_parameters[UINT8_MAX] = {}; +static size_t main_arg_n_requested_parameters = 0; +static struct ether_addr main_arg_mac = {}; +static bool main_arg_mac_set = false; +static bool main_arg_request_broadcast = false; +static bool main_arg_test = false; + +static Manager *manager_free(Manager *manager) { + if (!manager) + return NULL; + + n_dhcp4_client_probe_free(manager->probe); + n_dhcp4_client_unref(manager->client); + free(manager); + + return NULL; +} + +static void manager_freep(Manager **manager) { + manager_free(*manager); +} + +static int manager_new(Manager **managerp) { + _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL; + _c_cleanup_(manager_freep) Manager *manager = NULL; + int r; + + manager = malloc(sizeof(*manager)); + if (!manager) + return -ENOMEM; + + *manager = (Manager)MANAGER_NULL(*manager); + + r = n_dhcp4_client_config_new(&config); + if (r) + return r; + + n_dhcp4_client_config_set_broadcast_mac(config, + &main_arg_broadcast_mac.ether_addr_octet[0], + sizeof(main_arg_broadcast_mac.ether_addr_octet)); + n_dhcp4_client_config_set_mac(config, + &main_arg_mac.ether_addr_octet[0], + sizeof(main_arg_mac.ether_addr_octet)); + n_dhcp4_client_config_set_client_id(config, + main_arg_client_id, + main_arg_n_client_id); + n_dhcp4_client_config_set_ifindex(config, main_arg_ifindex); + n_dhcp4_client_config_set_request_broadcast(config, main_arg_request_broadcast); + n_dhcp4_client_config_set_transport(config, N_DHCP4_TRANSPORT_ETHERNET); + + r = n_dhcp4_client_new(&manager->client, config); + if (r) + return r; + + *managerp = manager; + manager = NULL; + return 0; +} + +static int manager_lease_get_dns(NDhcp4ClientLease *lease, struct in_addr *dns) { + uint8_t *data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_DOMAIN_NAME_SERVER, &data, &n_data); + if (r) + return r; + + if (n_data < sizeof(dns->s_addr)) + return N_DHCP4_E_MALFORMED; + + memcpy(&dns->s_addr, data, sizeof(dns->s_addr)); + + return 0; +} + +static int manager_lease_get_router(NDhcp4ClientLease *lease, struct in_addr *router) { + uint8_t *data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_ROUTER, &data, &n_data); + if (r) + return r; + + if (n_data < sizeof(router->s_addr)) + return N_DHCP4_E_MALFORMED; + + memcpy(&router->s_addr, data, sizeof(router->s_addr)); + + return 0; +} + +static int manager_lease_get_subnetmask(NDhcp4ClientLease *lease, struct in_addr *mask) { + uint8_t *data; + size_t n_data; + int r; + + r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_SUBNET_MASK, &data, &n_data); + if (r) + return r; + + if (n_data != sizeof(mask->s_addr)) + return N_DHCP4_E_MALFORMED; + + memcpy(&mask->s_addr, data, sizeof(mask->s_addr)); + + return 0; +} + +static int manager_lease_get_prefix(NDhcp4ClientLease *lease, unsigned int *prefixp) { + struct in_addr mask = {}; + unsigned int postfix; + int r; + + r = manager_lease_get_subnetmask(lease, &mask); + if (r) + return r; + + postfix =__builtin_ctz(ntohl(mask.s_addr)); + c_assert(postfix <= 32); + + if (postfix < 32) { + if ((~ntohl(mask.s_addr)) >> postfix != 0) + return N_DHCP4_E_MALFORMED; + } + + *prefixp = 32 - postfix; + return 0; +} + +static int manager_check(Manager *manager, NDhcp4ClientLease *lease) { + int r; + + r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_ROUTER, NULL, NULL); + if (r) { + fprintf(stderr, "No router\n"); + return r; + } + + r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_SUBNET_MASK, NULL, NULL); + if (r) { + fprintf(stderr, "No subnet mask\n"); + return r; + } + + return r; +} + +static int manager_add(Manager *manager, NDhcp4ClientLease *lease) { + char *p, ifname[IF_NAMESIZE + 1] = {}; + struct in_addr router = {}, yiaddr = {}, dns = {}; + unsigned int prefix; + uint64_t lifetime; + int r; + + n_dhcp4_client_lease_get_yiaddr(lease, &yiaddr); + n_dhcp4_client_lease_get_lifetime(lease, &lifetime); + + r = manager_lease_get_router(lease, &router); + if (r) + return r; + + r = manager_lease_get_prefix(lease, &prefix); + if (r) + return r; + + p = if_indextoname(main_arg_ifindex, ifname); + c_assert(p); + + if (lifetime == UINT64_MAX) { + r = asprintf(&p, "ip addr add %s/%u dev %s preferred_lft forever valid_lft forever", inet_ntoa(yiaddr), prefix, ifname); + c_assert(r >= 0); + } else { + r = asprintf(&p, "ip addr add %s/%u dev %s preferred_lft %llu valid_lft %llu", inet_ntoa(yiaddr), prefix, ifname, lifetime / 1000000000ULL, lifetime / 1000000000ULL); + c_assert(r >= 0); + } + r = system(p); + c_assert(r == 0); + free(p); + + r = asprintf(&p, "ip route add %s/32 dev %s", inet_ntoa(router), ifname); + c_assert(r >= 0); + r = system(p); + c_assert(r == 0); + free(p); + + r = asprintf(&p, "ip route add default via %s dev %s", inet_ntoa(router), ifname); + c_assert(r >= 0); + r = system(p); + c_assert(r == 0); + free(p); + + r = manager_lease_get_dns(lease, &dns); + if (r) { + if (r != N_DHCP4_E_UNSET) + return r; + } else { + fprintf(stderr, "DNS: %s\n", inet_ntoa(dns)); + } + + return 0; +} + +static int manager_dispatch(Manager *manager) { + NDhcp4ClientEvent *event; + int r; + + r = n_dhcp4_client_dispatch(manager->client); + if (r) { + if (r != N_DHCP4_E_PREEMPTED) { + /* + * We are level-triggered, so we do not need to react + * to preemption. We simply continue the mainloop. + */ + return r; + } + } + + for (;;) { + r = n_dhcp4_client_pop_event(manager->client, &event); + if (r) + return r; + + if (!event) + break; + + switch (event->event) { + case N_DHCP4_CLIENT_EVENT_DOWN: + fprintf(stderr, "DOWN\n"); + + break; + + case N_DHCP4_CLIENT_EVENT_OFFER: + fprintf(stderr, "OFFER\n"); + + r = manager_check(manager, event->granted.lease); + if (r) { + if (r == N_DHCP4_E_UNSET) { + fprintf(stderr, "Missing mandatory option, ignoring lease.\n"); + } else { + return r; + } + } else { + r = n_dhcp4_client_lease_select(event->offer.lease); + if (r) + return r; + } + + break; + + case N_DHCP4_CLIENT_EVENT_GRANTED: + fprintf(stderr, "GRANTED\n"); + + r = manager_add(manager, event->granted.lease); + if (r) { + if (r == N_DHCP4_E_UNSET) { + fprintf(stderr, "Missing mandatory option, declining lease.\n"); + + r = n_dhcp4_client_lease_decline(event->granted.lease, "Missing mandatory option."); + if (r) + return r; + } else { + return r; + } + } else { + r = n_dhcp4_client_lease_accept(event->granted.lease); + if (r) + return r; + } + + break; + + case N_DHCP4_CLIENT_EVENT_RETRACTED: + fprintf(stderr, "RETRACTED\n"); + + break; + + case N_DHCP4_CLIENT_EVENT_EXTENDED: + fprintf(stderr, "EXTENDED\n"); + + break; + + case N_DHCP4_CLIENT_EVENT_EXPIRED: + fprintf(stderr, "EXPIRED\n"); + + break; + + case N_DHCP4_CLIENT_EVENT_CANCELLED: + fprintf(stderr, "CANCELLED\n"); + + break; + + default: + fprintf(stderr, "Unexpected event: %u\n", event->event); + + break; + } + } + + return 0; +} + +static int manager_run(Manager *manager) { + _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL; + int r; + + r = n_dhcp4_client_probe_config_new(&config); + if (r) + return r; + + /* + * Let's speed up our tests, while still making sure the code-path + * for the deferrment is actually tested (so don't set it to zero). + */ + n_dhcp4_client_probe_config_set_start_delay(config, 10); + + n_dhcp4_client_probe_config_set_requested_ip(config, main_arg_requested_ip); + + if (main_arg_n_requested_parameters > 0) { + for (unsigned int i = 0; i < main_arg_n_requested_parameters; ++i) + n_dhcp4_client_probe_config_request_option(config, main_arg_requested_parameters[i]); + } else { + n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_ROUTER); + n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_SUBNET_MASK); + n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_DOMAIN_NAME_SERVER); + } + + if (main_arg_requested_lifetime >= 0) { + uint32_t lifetime = ntohl(main_arg_requested_lifetime); + + r = n_dhcp4_client_probe_config_append_option(config, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, &lifetime, sizeof(lifetime)); + if (r) + return r; + } + + r = n_dhcp4_client_probe(manager->client, &manager->probe, config); + if (r) + return r; + + /* + * The test-suite runs this with the --test argument. So far, we do not + * perform any fancy runtime tests, but simply exit the main-loop + * immediately. We can add more elaborate tests in the future. + */ + if (main_arg_test) + return 0; + + for (;;) { + struct pollfd pfds[] = { + { .fd = -1, .events = POLLIN }, + }; + size_t i; + int n; + + n_dhcp4_client_get_fd(manager->client, &pfds[0].fd); + + n = poll(pfds, sizeof(pfds) / sizeof(*pfds), -1); + if (n < 0) + return -errno; + + for (i = 0; i < (size_t)n; ++i) { + if (pfds[i].revents & ~POLLIN) + return -ENOTRECOVERABLE; + + if (!(pfds[i].revents & POLLIN)) + continue; + + r = manager_dispatch(manager); + if (r) + return r; + } + } + + return 0; +} + +static int run(void) { + _c_cleanup_(manager_freep) Manager *manager = NULL; + int r; + + r = manager_new(&manager); + if (r) + return r; + + return manager_run(manager); +} + +static void print_help(void) { + printf("%s [GLOBALS...] ...\n\n" + "DHCP Test Client\n\n" + " -h --help Show this help\n" + " --test Run as part of the test suite\n" + " --ifindex IDX Index of interface to run on\n" + " --mac HEX Hardware address to use\n" + " --broadcast-mac HEX Broadcast hardware address to use\n" + " --requested-ip IP Requested IP address\n" + " --requested-lifetime SECS Requested lease lifetime in seconds\n" + " --requested-parameters P1,P2,... Requested parameters\n" + " --client-id HEX Client Identifier to use\n" + , program_invocation_short_name); +} + +static int setup_test(void) { + test_setup(); + + /* --broadcast-mac */ + { + main_arg_broadcast_mac_set = true; + } + + /* --ifindex */ + { + main_arg_ifindex = 1; + } + + /* --mac */ + { + main_arg_mac_set = true; + } + + return 0; +} + +static int parse_hexstr(const char *in, uint8_t **outp, size_t *n_outp) { + _c_cleanup_(c_freep) uint8_t *out = NULL; + size_t i, n_in, n_out; + + n_in = strlen(in); + n_out = (n_in + 1) / 2; + + out = malloc(n_out); + if (!out) + return -ENOMEM; + + for (i = 0; i < n_in; ++i) { + uint8_t v = 0; + + switch (in[i]) { + case '0'...'9': + v = in[i] - '0'; + break; + case 'a'...'f': + v = in[i] - 'a' + 0xa; + break; + case 'A'...'F': + v = in[i] - 'A' + 0xa; + break; + } + + if (i % 2) { + out[i / 2] <<= 4; + out[i / 2] |= v; + } else { + out[i / 2] = v; + } + } + + *outp = out; + out = NULL; + *n_outp = n_out; + return 0; +} + +static int parse_argv(int argc, char **argv) { + enum { + _ARG_0 = 0x100, + ARG_BROADCAST_MAC, + ARG_CLIENT_ID, + ARG_IFINDEX, + ARG_MAC, + ARG_REQUEST_BROADCAST, + ARG_REQUESTED_IP, + ARG_REQUESTED_LIFETIME, + ARG_REQUESTED_PARAMETERS, + ARG_TEST, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "broadcast-mac", required_argument, NULL, ARG_BROADCAST_MAC }, + { "client-id", required_argument, NULL, ARG_CLIENT_ID }, + { "ifindex", required_argument, NULL, ARG_IFINDEX }, + { "mac", required_argument, NULL, ARG_MAC }, + { "request-broadcast", no_argument, NULL, ARG_REQUEST_BROADCAST }, + { "requested-ip", required_argument, NULL, ARG_REQUESTED_IP }, + { "requested-lifetime", required_argument, NULL, ARG_REQUESTED_LIFETIME }, + { "requested-parameters", required_argument, NULL, ARG_REQUESTED_PARAMETERS }, + { "test", no_argument, NULL, ARG_TEST }, + {} + }; + struct ether_addr *addr; + long long int lli; + size_t n; + void *t; + int r, c; + + /* + * Most of the argument-parsers are short-and-dirty hacks to make the + * conversions work. This is sufficient for a test-client, but needs + * proper error-checking if done outside of tests. + */ + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + switch (c) { + case 'h': + print_help(); + return MAIN_EXIT; + + case ARG_BROADCAST_MAC: + addr = ether_aton_r(optarg, &main_arg_broadcast_mac); + if (!addr) { + fprintf(stderr, + "%s: invalid broadcast mac address -- '%s'\n", + program_invocation_name, + optarg); + return MAIN_FAILED; + } + + main_arg_broadcast_mac_set = true; + break; + + case ARG_CLIENT_ID: + r = parse_hexstr(optarg, (uint8_t **)&t, &n); + if (r) + return r; + + free(main_arg_client_id); + main_arg_client_id = t; + main_arg_n_client_id = n; + break; + + case ARG_IFINDEX: + main_arg_ifindex = atoi(optarg); + break; + + case ARG_MAC: + addr = ether_aton_r(optarg, &main_arg_mac); + if (!addr) { + fprintf(stderr, + "%s: invalid mac address -- '%s'\n", + program_invocation_name, + optarg); + return MAIN_FAILED; + } + + main_arg_mac_set = true; + break; + + case ARG_REQUEST_BROADCAST: + main_arg_request_broadcast = true; + break; + + case ARG_REQUESTED_IP: + r = inet_pton(AF_INET, optarg, &main_arg_requested_ip); + if (r != 1) { + fprintf(stderr, + "%s: invalid requested IP -- '%s'\n", + program_invocation_name, + optarg); + return MAIN_FAILED; + } + break; + + case ARG_REQUESTED_LIFETIME: + lli = atoll(optarg); + if (lli < 0 || lli > UINT32_MAX) { + fprintf(stderr, + "%s: invalid requested lifetime -- '%s'\n", + program_invocation_name, + optarg); + return MAIN_FAILED; + } + main_arg_requested_lifetime = lli; + break; + + case ARG_REQUESTED_PARAMETERS: + for (const char *param = optarg; param; param = strchr(param, ',') ? strchr(param, ',') + 1 : NULL) { + c_assert(main_arg_n_requested_parameters <= UINT8_MAX); + + lli = atoll(param); + if (lli < 0 || lli > UINT8_MAX) { + fprintf(stderr, + "%s: invalid requested parameters -- '%s'\n", + program_invocation_name, + optarg); + return MAIN_FAILED; + } + main_arg_requested_parameters[main_arg_n_requested_parameters++] = lli; + } + break; + + case ARG_TEST: + r = setup_test(); + if (r) + return r; + + main_arg_test = true; + break; + + case '?': + /* getopt_long() prints warning */ + return MAIN_FAILED; + + default: + return -ENOTRECOVERABLE; + } + } + + if (optind != argc) { + fprintf(stderr, + "%s: invalid arguments -- '%s'\n", + program_invocation_name, + argv[optind]); + return MAIN_FAILED; + } + + if (!main_arg_broadcast_mac_set || + !main_arg_ifindex || + !main_arg_mac_set) { + fprintf(stderr, + "%s: required arguments: broadcast-mac, ifindex, mac\n", + program_invocation_name); + return MAIN_FAILED; + } + + return 0; +} + +int main(int argc, char **argv) { + int r; + + /* --client-id */ + { + uint8_t *b; + size_t n; + + n = strlen("client-id"); + b = malloc(n); + c_assert(b); + memcpy(b, "client-id", n); + + free(main_arg_client_id); + main_arg_client_id = b; + main_arg_n_client_id = n; + } + + r = parse_argv(argc, argv); + if (r) + goto exit; + + r = run(); + +exit: + if (r == MAIN_EXIT) { + r = 0; + } else if (r < 0) { + errno = -r; + fprintf(stderr, "Failed with system errno %d: %m\n", r); + r = 127; + } else if (r > 0) { + fprintf(stderr, "Failed with internal error %d\n", r); + } + + free(main_arg_client_id); + + return r; +} diff --git a/src/n-dhcp4/src/test-socket.c b/src/n-dhcp4/src/test-socket.c new file mode 100644 index 00000000..2b9303af --- /dev/null +++ b/src/n-dhcp4/src/test-socket.c @@ -0,0 +1,317 @@ +/* + * Tests for DHCP4 Socket Helpers + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <endian.h> +#include <errno.h> +#include <poll.h> +#include <linux/if_packet.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4-private.h" +#include "test.h" +#include "util/link.h" +#include "util/netns.h" +#include "util/packet.h" + +static void test_poll(int sk) { + int r; + + r = poll(&(struct pollfd){ .fd = sk, .events = POLLIN }, 1, -1); + c_assert(r == 1); +} + +static void test_client_packet_socket_new(Link *link, int *skp) { + int r, oldns; + + netns_get(&oldns); + netns_set(link->netns); + + r = n_dhcp4_c_socket_packet_new(skp, link->ifindex); + c_assert(r >= 0); + + netns_set(oldns); +} + +static void test_client_udp_socket_new(Link *link, + int *skp, + const struct in_addr *addr_client, + const struct in_addr *addr_server) { + int r, oldns; + + netns_get(&oldns); + netns_set(link->netns); + + r = n_dhcp4_c_socket_udp_new(skp, link->ifindex, addr_client, addr_server, N_DHCP4_DSCP_DEFAULT); + c_assert(r >= 0); + + netns_set(oldns); +} + +static void test_server_packet_socket_new(Link *link, int *skp) { + int r, oldns; + + netns_get(&oldns); + netns_set(link->netns); + + r = n_dhcp4_s_socket_packet_new(skp); + c_assert(r >= 0); + + netns_set(oldns); +} + +static void test_server_udp_socket_new(Link *link, int *skp) { + int r, oldns; + + netns_get(&oldns); + netns_set(link->netns); + + r = n_dhcp4_s_socket_udp_new(skp, link->ifindex); + c_assert(r >= 0); + + netns_set(oldns); +} + +static void test_client_server_packet(Link *link_server, Link *link_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL; + _c_cleanup_(c_closep) int sk_server = -1, sk_client = -1; + uint8_t buf[UINT16_MAX]; + struct sockaddr_in dest = {}; + int r; + + test_server_udp_socket_new(link_server, &sk_server); + test_client_packet_socket_new(link_client, &sk_client); + + r = n_dhcp4_outgoing_new(&outgoing, 0, 0); + c_assert(!r); + n_dhcp4_outgoing_get_header(outgoing)->op = N_DHCP4_OP_BOOTREQUEST; + + r = n_dhcp4_c_socket_packet_send(sk_client, + link_client->ifindex, + (const unsigned char[]){0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + ETH_ALEN, + -1, + outgoing); + c_assert(!r); + + test_poll(sk_server); + + r = n_dhcp4_s_socket_udp_recv(sk_server, buf, sizeof(buf), &incoming, &dest); + c_assert(!r); + c_assert(incoming); + c_assert(dest.sin_family == AF_INET); + c_assert(dest.sin_port == htons(N_DHCP4_NETWORK_SERVER_PORT)); + c_assert(dest.sin_addr.s_addr == INADDR_BROADCAST); +} + +static void test_client_server_udp(Link *link_server, Link *link_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL; + _c_cleanup_(c_closep) int sk_server = -1, sk_client = -1; + struct in_addr addr_server = (struct in_addr){ htonl(10 << 24 | 1) }; + struct in_addr addr_client = (struct in_addr){ htonl(10 << 24 | 2) }; + uint8_t buf[UINT16_MAX]; + struct sockaddr_in dest = {}; + int r; + + /* setup */ + + link_add_ip4(link_server, &addr_server, 8); + link_add_ip4(link_client, &addr_client, 8); + + /* test communication */ + + test_server_udp_socket_new(link_server, &sk_server); + test_client_udp_socket_new(link_client, &sk_client, &addr_client, &addr_server); + + r = n_dhcp4_outgoing_new(&outgoing, 0, 0); + c_assert(!r); + n_dhcp4_outgoing_get_header(outgoing)->op = N_DHCP4_OP_BOOTREQUEST; + + r = n_dhcp4_c_socket_udp_send(sk_client, outgoing); + c_assert(!r); + + test_poll(sk_server); + + r = n_dhcp4_s_socket_udp_recv(sk_server, buf, sizeof(buf), &incoming, &dest); + c_assert(!r); + c_assert(incoming); + c_assert(dest.sin_family == AF_INET); + c_assert(dest.sin_port == htons(N_DHCP4_NETWORK_SERVER_PORT)); + c_assert(dest.sin_addr.s_addr == addr_server.s_addr); + + /* teardown */ + + link_del_ip4(link_client, &addr_client, 8); + link_del_ip4(link_server, &addr_server, 8); +} + +static void test_server_client_packet(Link *link_server, Link *link_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming1 = NULL, *incoming2 = NULL; + _c_cleanup_(c_closep) int sk_server = -1, sk_client = -1; + struct in_addr addr_client = (struct in_addr){ htonl(10 << 24 | 2) }; + struct in_addr addr_server = (struct in_addr){ htonl(10 << 24 | 1) }; + uint8_t buf[UINT16_MAX]; + int r; + + /* setup */ + + link_add_ip4(link_server, &addr_server, 8); + + /* test communication */ + + test_server_packet_socket_new(link_server, &sk_server); + test_client_packet_socket_new(link_client, &sk_client); + + r = n_dhcp4_outgoing_new(&outgoing, 0, 0); + c_assert(!r); + n_dhcp4_outgoing_get_header(outgoing)->op = N_DHCP4_OP_BOOTREPLY; + + r = n_dhcp4_s_socket_packet_send(sk_server, + link_server->ifindex, + &addr_server, + link_client->mac.ether_addr_octet, + ETH_ALEN, + &addr_client, + N_DHCP4_DSCP_DEFAULT, + outgoing); + c_assert(!r); + r = n_dhcp4_s_socket_packet_send(sk_server, + link_server->ifindex, + &addr_server, + (const unsigned char[]){ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + ETH_ALEN, + &addr_client, + N_DHCP4_DSCP_DEFAULT, + outgoing); + c_assert(!r); + + test_poll(sk_client); + + r = n_dhcp4_c_socket_packet_recv(sk_client, buf, sizeof(buf), &incoming1); + c_assert(!r); + c_assert(incoming1); + + test_poll(sk_client); + + r = n_dhcp4_c_socket_packet_recv(sk_client, buf, sizeof(buf), &incoming2); + c_assert(!r); + c_assert(incoming2); + + /* teardown */ + + link_del_ip4(link_server, &addr_server, 8); +} + +static void test_server_client_udp(Link *link_server, Link *link_client) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL; + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL; + _c_cleanup_(c_closep) int sk_server = -1, sk_client = -1; + struct in_addr addr_client = (struct in_addr){ htonl(10 << 24 | 2) }; + struct in_addr addr_server = (struct in_addr){ htonl(10 << 24 | 1) }; + uint8_t buf[UINT16_MAX]; + int r; + + /* setup */ + + link_add_ip4(link_server, &addr_server, 8); + link_add_ip4(link_client, &addr_client, 8); + + /* test communication */ + + test_server_udp_socket_new(link_server, &sk_server); + test_client_udp_socket_new(link_client, &sk_client, &addr_client, &addr_server); + + r = n_dhcp4_outgoing_new(&outgoing, 0, 0); + c_assert(!r); + n_dhcp4_outgoing_get_header(outgoing)->op = N_DHCP4_OP_BOOTREPLY; + + r = n_dhcp4_s_socket_udp_send(sk_server, + &addr_server, + &addr_client, + outgoing); + c_assert(!r); + + test_poll(sk_client); + + r = n_dhcp4_c_socket_udp_recv(sk_client, buf, sizeof(buf), &incoming); + c_assert(!r); + c_assert(incoming); + + /* teardown */ + + link_del_ip4(link_client, &addr_client, 8); + link_del_ip4(link_server, &addr_server, 8); +} + +static void test_sockets(void) { + _c_cleanup_(netns_closep) int ns_server = -1, ns_client = -1; + _c_cleanup_(link_deinit) Link link_server = LINK_NULL(link_server); + _c_cleanup_(link_deinit) Link link_client = LINK_NULL(link_client); + + /* setup */ + + netns_new(&ns_server); + netns_new(&ns_client); + link_new_veth(&link_server, &link_client, ns_server, ns_client); + + /* communication tests */ + + test_client_server_packet(&link_server, &link_client); + test_client_server_udp(&link_server, &link_client); + test_server_client_packet(&link_server, &link_client); + test_server_client_udp(&link_server, &link_client); +} + +static void test_multiple_servers(void) { + _c_cleanup_(netns_closep) int netns = -1; + _c_cleanup_(link_deinit) Link link_server = LINK_NULL(link_server); + _c_cleanup_(link_deinit) Link link_client = LINK_NULL(link_client); + int r, oldns; + + /* setup */ + + netns_new(&netns); + link_new_veth(&link_server, &link_client, netns, netns); + + /* test multiple server UDP sockets on the same machine */ + + netns_get(&oldns); + netns_set(netns); + { + _c_cleanup_(c_closep) int sk1 = -1, sk2 = -1; + + /* + * DHCP servers have to bind to a fixed port, so you cannot run + * two servers on the same interface. It must be possible to + * run them on separate interfaces, though. + */ + + r = n_dhcp4_s_socket_udp_new(&sk1, link_server.ifindex); + c_assert(r >= 0); + + r = n_dhcp4_s_socket_udp_new(&sk2, link_server.ifindex); + c_assert(r == -EADDRINUSE); + + r = n_dhcp4_s_socket_udp_new(&sk2, link_client.ifindex); + c_assert(r >= 0); + } + netns_set(oldns); +} + +int main(int argc, char **argv) { + test_setup(); + + test_sockets(); + test_multiple_servers(); + + return 0; +} diff --git a/src/n-dhcp4/src/test.h b/src/n-dhcp4/src/test.h new file mode 100644 index 00000000..b5acd14b --- /dev/null +++ b/src/n-dhcp4/src/test.h @@ -0,0 +1,107 @@ +#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 <errno.h> +#include <fcntl.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +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, "/", 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-dhcp4/src/util/link.c b/src/n-dhcp4/src/util/link.c new file mode 100644 index 00000000..54d9a5c3 --- /dev/null +++ b/src/n-dhcp4/src/util/link.c @@ -0,0 +1,287 @@ +/* + * Link Management + * + * This is for our test-infrastructure only! It is not meant to be used outside + * of unit-tests. + */ + +#include <arpa/inet.h> +#include <assert.h> +#include <c-stdaux.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <netinet/in.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <unistd.h> +#include "link.h" +#include "netns.h" +#include "socket.h" + +/** + * link_deinit() - deinitialize link + * @link: link to operate on + * + * This deinitializes a link and clears it. Once this call returns the link is + * cleared to LINK_NULL(). + * + * It is safe to call this on LINK_NULL(), in which case it is a no-op. It is + * thus also safe to call this multiple times on the same link. + */ +void link_deinit(Link *link) { + netns_close(link->netns); + *link = (Link)LINK_NULL(*link); +} + +static void link_query(int netns, const char *name, int *ifindexp, struct ether_addr *macp) { + int oldns; + + netns_get(&oldns); + { + struct ifreq ifr = {}; + size_t n_name; + int r, s; + + netns_set(netns); + + n_name = strlen(name); + c_assert(n_name <= IF_NAMESIZE); + + if (ifindexp) { + *ifindexp = if_nametoindex(name); + c_assert(*ifindexp > 0); + } + + if (macp) { + s = socket(AF_INET, SOCK_DGRAM, 0); + c_assert(s >= 0); + + memcpy(ifr.ifr_name, name, n_name); + r = ioctl(s, SIOCGIFHWADDR, &ifr); + c_assert(r >= 0); + + memcpy(macp->ether_addr_octet, ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + close(s); + } + } + netns_set(oldns); +} + +static void link_move(const char *ifname, int netns) { + char *p; + int r; + + r = asprintf(&p, "ip link set %s up netns ns-test", ifname); + c_assert(r > 0); + + netns_pin(netns, "ns-test"); + r = system(p); + c_assert(r == 0); + netns_unpin("ns-test"); + + free(p); +} + +/** + * link_new_veth() - create new veth pair + * @veth_parentp: output argument for new veth parent + * @veth_childp: output argument for new veth child + * @netns_parent: target namespace for the parent + * @netns_child: target namespace for the child + * + * This creates a new veth pair in the specified namespaces. + */ +void link_new_veth(Link *veth_parentp, Link *veth_childp, int netns_parent, int netns_child) { + int oldns; + + netns_get(&oldns); + { + int r; + + /* + * Temporarily enter a new network namespace to make sure the + * interface names are fresh. + */ + netns_set_anonymous(); + + r = system("ip link add veth-parent type veth peer name veth-child"); + c_assert(r == 0); + r = system("ip link set veth-parent up addrgenmode none"); + c_assert(r == 0); + r = system("ip link set veth-child up addrgenmode none"); + c_assert(r == 0); + + link_move("veth-parent", netns_parent); + link_move("veth-child", netns_child); + } + netns_set(oldns); + + netns_new_dup(&veth_parentp->netns, netns_parent); + netns_new_dup(&veth_childp->netns, netns_child); + link_query(netns_parent, "veth-parent", &veth_parentp->ifindex, &veth_parentp->mac); + link_query(netns_child, "veth-child", &veth_childp->ifindex, &veth_childp->mac); + + /* + * XXX: After moving a link both its name and ifindex might have + * changed. Hence, link_query() might check the wrong interface. + * One way to fix this would be to rename the interfaces after + * they have been moved and queried based on their final ifindex. + * This way, we reserve the internal names for the constructor, + * and guarantee the final names will never conflict (disallowing + * parallel calls to this function). + */ +} + +/** + * link_new_bridge() - create new bridge + * @bridgep: output argument for the new bridge + * @netns: target network namespace + * + * This creates a new bridge interface in the specified target network + * namespace. + */ +void link_new_bridge(Link *bridgep, int netns) { + int oldns; + + netns_get(&oldns); + { + int r; + + netns_set(netns); + + r = system("ip link add test-bridge type bridge"); + c_assert(r == 0); + r = system("ip link set test-bridge up addrgenmode none"); + c_assert(r == 0); + } + netns_set(oldns); + + netns_new_dup(&bridgep->netns, netns); + link_query(netns, "test-bridge", &bridgep->ifindex, &bridgep->mac); +} + +/** + * link_add_ip4() - add IPv4 address to the specified link + * @link: link to operate on + * @addr: address to add + * @prefix: address prefix length + * + * This adds the specified IPv4 address to the given link. + */ +void link_add_ip4(Link *link, const struct in_addr *addr, unsigned int prefix) { + int oldns; + + netns_get(&oldns); + { + char *p, ifname[IF_NAMESIZE + 1] = {}; + int r; + + netns_set(link->netns); + + p = if_indextoname(link->ifindex, ifname); + c_assert(p); + r = asprintf(&p, "ip addr add %s/%u dev %s", inet_ntoa(*addr), prefix, ifname); + c_assert(r >= 0); + r = system(p); + c_assert(r == 0); + free(p); + } + netns_set(oldns); +} + +/** + * link_del_ip4() - delete IPv4 address from the specified link + * @link: link to operate on + * @addr: address to delete + * @prefix: address prefix length + * + * This deletes the specified IPv4 address from the given link. + */ +void link_del_ip4(Link *link, const struct in_addr *addr, unsigned int prefix) { + int oldns; + + netns_get(&oldns); + { + char *p, ifname[IF_NAMESIZE + 1] = {}; + int r; + + netns_set(link->netns); + + p = if_indextoname(link->ifindex, ifname); + c_assert(p); + r = asprintf(&p, "ip addr del %s/%u dev %s", inet_ntoa(*addr), prefix, ifname); + c_assert(r >= 0); + r = system(p); + c_assert(r == 0); + free(p); + } + netns_set(oldns); +} + +/** + * link_set_master() - change the bridge master of an interface + * @link: link to operate on + * @if_master: bridge to set as master + * + * This sets @if_master as the new master bridge of @link. The specified bridge + * must be in the same network namespace as @link. + */ +void link_set_master(Link *link, int if_master) { + int oldns; + + netns_get(&oldns); + { + char *p, ifname_master[IF_NAMESIZE + 1] = {}, ifname[IF_NAMESIZE + 1] = {}; + int r; + + netns_set(link->netns); + + p = if_indextoname(link->ifindex, ifname); + c_assert(p); + p = if_indextoname(if_master, ifname_master); + c_assert(p); + r = asprintf(&p, "ip link set %s master %s", ifname, ifname_master); + c_assert(r > 0); + r = system(p); + c_assert(r == 0); + free(p); + } + netns_set(oldns); +} + +/** + * link_socket() - create socket for link + * @link: link to operate on + * @socketp: output argument for new socket + * @family: socket family to create socket in + * @type: socket type to create socket as + * + * This creates a socket of the protocol family @family via socket(2), but + * makes sure to create it in the network-namespace where @link resides. + * Furthermore, the socket is bound to the link specified in @link. + * + * The new socket is returned in @socketp. + */ +void link_socket(Link *link, int *socketp, int family, int type) { + int oldns; + + netns_get(&oldns); + { + int r, fd; + + netns_set(link->netns); + + fd = socket(family, type, 0); + c_assert(fd >= 0); + + r = socket_bind_if(fd, link->ifindex); + c_assert(!r); + + *socketp = fd; + } + netns_set(oldns); +} diff --git a/src/n-dhcp4/src/util/link.h b/src/n-dhcp4/src/util/link.h new file mode 100644 index 00000000..cd0ad6f8 --- /dev/null +++ b/src/n-dhcp4/src/util/link.h @@ -0,0 +1,38 @@ +#pragma once + +/* + * Link Management + * + * This utility provides easy access to network links. It is meant for testing + * purposes only and relies on call-outs to ip(1). A proper implementation + * should rather use netlink directly to interact with the kernel. + * + * Furthermore, for simplification this is limited to ethernet links. + */ + +#include <c-stdaux.h> +#include <net/ethernet.h> +#include <netinet/in.h> +#include <stdlib.h> + +typedef struct Link Link; + +struct Link { + int netns; + int ifindex; + struct ether_addr mac; +}; + +#define LINK_NULL(_x) { \ + .netns = -1, \ + } + +void link_deinit(Link *link); + +void link_new_veth(Link *veth_parentp, Link *veth_childp, int netns_parent, int netns_child); +void link_new_bridge(Link *bridgep, int netns); + +void link_add_ip4(Link *link, const struct in_addr *addr, unsigned int prefix); +void link_del_ip4(Link *link, const struct in_addr *addr, unsigned int prefix); +void link_set_master(Link *link, int if_master); +void link_socket(Link *link, int *socketp, int family, int type); diff --git a/src/n-dhcp4/src/util/netns.c b/src/n-dhcp4/src/util/netns.c new file mode 100644 index 00000000..0b7b32e4 --- /dev/null +++ b/src/n-dhcp4/src/util/netns.c @@ -0,0 +1,165 @@ +/* + * Network Namespaces + * + * This is meant for testing-purposes only. It is not meant to be used outside + * of our unit-tests! + */ + +#include <assert.h> +#include <c-stdaux.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <unistd.h> +#include "netns.h" + +/** + * netns_new() - create a new network namespace + * @netnsp: output argument to store netns fd + * + * This creates a new network namespace and returns a netns fd that refers to + * the new network namespace. Note that there is no native API to create an + * anonymous network namespace, so this call has to temporarily switch to a new + * network namespace (using unshare(2)). This temporary switch does not affect + * any other threads or processes, however, it can be observed by other + * processes. + */ +void netns_new(int *netnsp) { + int r, oldns; + + netns_get(&oldns); + + r = unshare(CLONE_NEWNET); + c_assert(r >= 0); + + netns_get(netnsp); + netns_set(oldns); +} + +/** + * netns_new_dup() - duplicate network namespace descriptor + * @newnsp: output argument for duplicated descriptor + * @netns: netns descriptor to duplicate + * + * This duplicates the network namespace file descriptor. The duplicate still + * refers to the same network namespace, but is an independent file descriptor. + */ +void netns_new_dup(int *newnsp, int netns) { + *newnsp = fcntl(netns, F_DUPFD_CLOEXEC, 0); + c_assert(*newnsp >= 0); +} + +/** + * netns_close() - destroy a network namespace descriptor + * @netns: netns to operate on, or <0 + * + * This closes the given network namespace descriptor. If @netns is negative, + * this is a no-op. + * + * Return: -1 is returned. + */ +int netns_close(int netns) { + return c_close(netns); +} + +/** + * netns_get() - retrieve the current network namespace + * @netnsp: output argument to store netns fd + * + * This retrieves a file-descriptor to the current network namespace. + */ +void netns_get(int *netnsp) { + *netnsp = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC); + c_assert(*netnsp >= 0); +} + +/** + * setns_set() - change the current network namespace + * @netns: netns to set + * + * This changes the current network namespace to the netns given by the + * file-descriptor @netns. + */ +void netns_set(int netns) { + int r; + + r = setns(netns, CLONE_NEWNET); + c_assert(r >= 0); +} + +/** + * netns_set_anonymous() - enter an anonymous network namespace + * + * This is a helper that creates a new network namespace, enters it, and then + * forgets about it. + */ +void netns_set_anonymous(void) { + int r; + + r = unshare(CLONE_NEWNET); + c_assert(r >= 0); +} + +/** + * netns_pin() - pin network namespace in file-system + * @netns: netns to pin + * @name: name to pin netns under + * + * This pins the network namespace given as @netns in the file-system as + * `/run/netns/@name`. It is the responsibility of the caller to guarantee + * @name is not used by anyone else in parallel. This function will abort if + * @name is already in use. + * + * The namespace in `/run/netns/` is compatible with the namespace provided by + * the ip(1) tool, and can be used to pass network namespaces to invocations of + * ip(1). + */ +void netns_pin(int netns, const char *name) { + char *fd_path, *netns_path; + int r, fd; + + r = asprintf(&fd_path, "/proc/self/fd/%d", netns); + c_assert(r >= 0); + + r = asprintf(&netns_path, "/run/netns/%s", name); + c_assert(r >= 0); + + fd = open(netns_path, O_RDONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0); + c_assert(fd >= 0); + close(fd); + + r = mount(fd_path, netns_path, "none", MS_BIND, NULL); + c_assert(r >= 0); + + free(netns_path); + free(fd_path); +} + +/** + * netns_unpin() - unpin network namespace from file-system + * @name: name to unpin + * + * This removes a network namespace pin from the file-system. It expects the + * pin to be located at `/run/netns/@name`. This function aborts if the pin + * does not exist. + * + * See netns_pin() for ways to create such pins. + */ +void netns_unpin(const char *name) { + char *netns_path; + int r; + + r = asprintf(&netns_path, "/run/netns/%s", name); + c_assert(r >= 0); + + r = umount2(netns_path, MNT_DETACH); + c_assert(r >= 0); + + r = unlink(netns_path); + c_assert(r >= 0); + + free(netns_path); +} diff --git a/src/n-dhcp4/src/util/netns.h b/src/n-dhcp4/src/util/netns.h new file mode 100644 index 00000000..02e2e010 --- /dev/null +++ b/src/n-dhcp4/src/util/netns.h @@ -0,0 +1,27 @@ +#pragma once + +/* + * Network Namespaces + * + * The netns utility provides an object-based API to network namespaces. It is + * meant for testing purposes only. + */ + +#include <c-stdaux.h> +#include <stdlib.h> + +void netns_new(int *netnsp); +void netns_new_dup(int *newnsp, int netns); +int netns_close(int netns); + +void netns_get(int *netnsp); +void netns_set(int netns); +void netns_set_anonymous(void); + +void netns_pin(int netns, const char *name); +void netns_unpin(const char *name); + +static inline void netns_closep(int *netns) { + if (*netns >= 0) + netns_close(*netns); +} diff --git a/src/n-dhcp4/src/util/test-packet.c b/src/n-dhcp4/src/util/test-packet.c new file mode 100644 index 00000000..44dbc300 --- /dev/null +++ b/src/n-dhcp4/src/util/test-packet.c @@ -0,0 +1,411 @@ +/* + * Packet Socket Tests + */ + +#undef NDEBUG +#include <assert.h> +#include <c-stdaux.h> +#include <errno.h> +#include <net/if_arp.h> +#include <stdlib.h> +#include <string.h> +#include "n-dhcp4-private.h" +#include "link.h" +#include "netns.h" +#include "packet.h" +#include "test.h" + +typedef struct Blob { + uint16_t checksum; + uint8_t data[128]; +} Blob; + +static void test_checksum_one(Blob *blob, size_t size) { + uint16_t checksum; + + /* + * The only important property of the internet-checksum is that if the + * target blob is amended with its own checksum, the checksum + * calculation will become 0. So here we simply calculate the checksum + * with a dummy 0 in place, then put the checksum in and verify that + * the resulting checksum becomes 0. + */ + + blob->checksum = 0; + blob->checksum = packet_internet_checksum((uint8_t*)blob, size); + + checksum = packet_internet_checksum((uint8_t*)blob, size); + c_assert(!checksum); +} + +static void test_checksum_udp_one(Blob *blob, size_t size) { + uint16_t checksum; + + /* + * Like test_checksum_one(), here we calculate the target checksum, + * then place it in the source blob and calculate the checksum again. + * We expect it to be 0 in the end (i.e., pass the checksum test). + * + * Unlike the generic version, we must pass dummy UDP data into the + * helpers and also avoid a 0 checksum in the original source. + */ + + checksum = packet_internet_checksum_udp(&(struct in_addr){ htonl((10 << 24) | 2)}, + &(struct in_addr){ htonl((10 << 24) | 1)}, + 67, + 68, + blob->data, + sizeof(blob->data), + 0); + checksum = checksum ?: 0xffff; + checksum = packet_internet_checksum_udp(&(struct in_addr){ htonl((10 << 24) | 2)}, + &(struct in_addr){ htonl((10 << 24) | 1)}, + 67, + 68, + blob->data, + sizeof(blob->data), + checksum); + c_assert(!checksum); +} + +/* + * This generates some pseudo-random bytes and verifies that + * packet_internet_checksum{,_udp}() correctly calculates the checksum on this + * random-data. + */ +static void test_checksum(void) { + Blob blob = {}; + + /* fill @blob.data with some pseudo-random bytes */ + for (size_t i = 0; i < sizeof(blob.data); ++i) + blob.data[i] = i ^ (i >> 8) ^ (i >> 16) ^ (i >> 24); + + /* take chunks of @blob.data and verify their checksum */ + for (size_t j = 0; j < sizeof(uint64_t); ++j) { + for (uint32_t i = 0; i <= 0xffff; ++i) { + blob.data[0] = i & 0xff; + blob.data[1] = i >> 8; + test_checksum_one(&blob, sizeof(blob) - j); + test_checksum_udp_one(&blob, sizeof(blob) - j); + } + } +} + +static void test_new_packet_socket(Link *link, int *skp) { + struct sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = link->ifindex, + }; + int r, on = 1; + + link_socket(link, skp, AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC); + + r = setsockopt(*skp, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on)); + c_assert(r >= 0); + + r = bind(*skp, (struct sockaddr*)&addr, sizeof(addr)); + c_assert(r >= 0); +} + +static void test_packet_unicast(int ifindex, int sk, void *buf, size_t n_buf, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst, + const struct ether_addr *haddr_dst) { + struct packet_sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_halen = ETH_ALEN, + }; + size_t len; + int r; + + memcpy(addr.sll_addr, haddr_dst, ETH_ALEN); + + r = packet_sendto_udp(sk, buf, n_buf, &len, paddr_src, &addr, paddr_dst, N_DHCP4_DSCP_DEFAULT); + c_assert(!r); + c_assert(len == n_buf); +} + +static void test_packet_broadcast(int ifindex, int sk, void *buf, size_t n_buf, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + struct packet_sockaddr_ll addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_halen = ETH_ALEN, + }; + size_t len; + int r; + + memcpy(addr.sll_addr, (unsigned char[]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, ETH_ALEN); + + r = packet_sendto_udp(sk, buf, n_buf, &len, paddr_src, &addr, paddr_dst, N_DHCP4_DSCP_DEFAULT); + c_assert(!r); + c_assert(len == n_buf); +} + +static void test_packet_packet(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst = -1; + uint8_t buf[1024]; + size_t len; + int r; + + link_socket(link_src, &sk_src, AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC); + test_new_packet_socket(link_dst, &sk_dst); + + test_packet_unicast(link_src->ifindex, sk_src, buf, sizeof(buf) - 1, paddr_src, paddr_dst, &link_dst->mac); + test_packet_broadcast(link_src->ifindex, sk_src, buf, sizeof(buf) - 1, paddr_src, paddr_dst); + + r = packet_recv_udp(sk_dst, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + r = packet_recv_udp(sk_dst, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf) - 1); +} + +static void test_packet_udp(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst = -1; + uint8_t buf[1024]; + ssize_t len; + int r; + + link_socket(link_src, &sk_src, AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC); + link_socket(link_dst, &sk_dst, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + link_add_ip4(link_dst, &paddr_dst->sin_addr, 8); + + r = bind(sk_dst, (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(r >= 0); + + test_packet_unicast(link_src->ifindex, sk_src, buf, sizeof(buf) - 1, paddr_src, paddr_dst, &link_dst->mac); + test_packet_broadcast(link_src->ifindex, sk_src, buf, sizeof(buf) - 1, paddr_src, paddr_dst); + + len = recv(sk_dst, buf, sizeof(buf), 0); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + len = recv(sk_dst, buf, sizeof(buf), 0); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + link_del_ip4(link_dst, &paddr_dst->sin_addr, 8); +} + +static void test_udp_packet(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst = -1; + uint8_t buf[1024]; + ssize_t slen; + size_t len; + int r; + + link_socket(link_src, &sk_src, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + test_new_packet_socket(link_dst, &sk_dst); + link_add_ip4(link_src, &paddr_src->sin_addr, 8); + link_add_ip4(link_dst, &paddr_dst->sin_addr, 8); + + slen = sendto(sk_src, buf, sizeof(buf) - 1, 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(slen == (ssize_t)sizeof(buf) - 1); + + r = packet_recv_udp(sk_dst, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + link_del_ip4(link_dst, &paddr_dst->sin_addr, 8); + link_del_ip4(link_src, &paddr_src->sin_addr, 8); +} + +static void test_udp_udp(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst = -1; + uint8_t buf[1024]; + ssize_t len; + int r; + + link_socket(link_src, &sk_src, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + link_socket(link_dst, &sk_dst, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + link_add_ip4(link_src, &paddr_src->sin_addr, 8); + link_add_ip4(link_dst, &paddr_dst->sin_addr, 8); + + r = bind(sk_dst, (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(r >= 0); + + len = sendto(sk_src, buf, sizeof(buf) - 1, 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + len = recv(sk_dst, buf, sizeof(buf), 0); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + link_del_ip4(link_dst, &paddr_dst->sin_addr, 8); + link_del_ip4(link_src, &paddr_src->sin_addr, 8); +} + +static void test_shutdown(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst1 = -1, sk_dst2 = -1; + uint8_t buf[1024]; + ssize_t slen; + size_t len; + int r; + + link_socket(link_src, &sk_src, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + test_new_packet_socket(link_dst, &sk_dst1); + link_add_ip4(link_src, &paddr_src->sin_addr, 8); + link_add_ip4(link_dst, &paddr_dst->sin_addr, 8); + + /* 1 - send only to the packet socket */ + slen = sendto(sk_src, buf, sizeof(buf), 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(slen == (ssize_t)sizeof(buf)); + + /* create a UDP socket */ + link_socket(link_dst, &sk_dst2, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + + r = bind(sk_dst2, (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(r >= 0); + + /* 2 - send to both sockets */ + slen = sendto(sk_src, buf, sizeof(buf), 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(slen == (ssize_t)sizeof(buf)); + + /* shut down the packet socket */ + r = packet_shutdown(sk_dst1); + c_assert(r >= 0); + + /* 3 - send only to the UDP socket */ + slen = sendto(sk_src, buf, sizeof(buf), 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(slen == (ssize_t)sizeof(buf)); + + /* receive 1 and 2 on the packet socket */ + r = packet_recv_udp(sk_dst1, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf)); + r = packet_recv_udp(sk_dst1, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf)); + + /* make sure there is nothing more pending on the packet socket */ + slen = recv(sk_dst1, buf, sizeof(buf), MSG_DONTWAIT); + c_assert(slen < 0); + c_assert(errno == EAGAIN); + + /* receive 2 and 3 on the UDP socket */ + slen = recv(sk_dst2, buf, sizeof(buf), 0); + c_assert(slen == (ssize_t)sizeof(buf)); + slen = recv(sk_dst2, buf, sizeof(buf), 0); + c_assert(slen == (ssize_t)sizeof(buf)); + + /* make sure there is nothing more pending on the UDP socket */ + slen = recv(sk_dst1, buf, sizeof(buf), MSG_DONTWAIT); + c_assert(slen < 0); + c_assert(errno == EAGAIN); + + link_del_ip4(link_dst, &paddr_dst->sin_addr, 8); + link_del_ip4(link_src, &paddr_src->sin_addr, 8); +} + +static void test_ip_hdr(Link *link_src, + Link *link_dst, + const struct sockaddr_in *paddr_src, + const struct sockaddr_in *paddr_dst) { + _c_cleanup_(c_closep) int sk_src = -1, sk_dst = -1; + uint8_t ipopts[5] = { 1, 1, 1, 1, 1 }; + uint8_t buf[1024]; + ssize_t slen; + size_t len; + int r; + + /* + * This test sends a packet from a UDP socket to a packet socket, but + * appends 5-bytes of IPOPT_NOOP ip-options. With this we verify our + * packet socket correctly skips additional ip-options and does not + * interpret the ip-header as a fixed size header. + */ + + link_socket(link_src, &sk_src, AF_INET, SOCK_DGRAM | SOCK_CLOEXEC); + test_new_packet_socket(link_dst, &sk_dst); + link_add_ip4(link_src, &paddr_src->sin_addr, 8); + link_add_ip4(link_dst, &paddr_dst->sin_addr, 8); + + r = setsockopt(sk_src, IPPROTO_IP, IP_OPTIONS, ipopts, sizeof(ipopts)); + c_assert(r >= 0); + + slen = sendto(sk_src, buf, sizeof(buf) - 1, 0, + (struct sockaddr*)paddr_dst, sizeof(*paddr_dst)); + c_assert(slen == (ssize_t)sizeof(buf) - 1); + + r = packet_recv_udp(sk_dst, buf, sizeof(buf), &len); + c_assert(!r); + c_assert(len == (ssize_t)sizeof(buf) - 1); + + link_del_ip4(link_dst, &paddr_dst->sin_addr, 8); + link_del_ip4(link_src, &paddr_src->sin_addr, 8); +} + +/* + * This test verifies that we can send packets from/to packet/udp sockets. It + * tests all combinations: packet->packet, packet->udp, udp->packet, udp->udp + * + * Furthermore, this test checks for some of the behavioural properties of our + * packet socket helpers. + */ +static void test_packet(void) { + _c_cleanup_(netns_closep) int ns_src = -1, ns_dst = -1; + _c_cleanup_(link_deinit) Link link_src = LINK_NULL(link_src); + _c_cleanup_(link_deinit) Link link_dst = LINK_NULL(link_dst); + struct sockaddr_in paddr_src = { + .sin_family = AF_INET, + .sin_addr = (struct in_addr){ htonl(10<<24 | 1) }, + .sin_port = htons(10), + }; + struct sockaddr_in paddr_dst = { + .sin_family = AF_INET, + .sin_addr = (struct in_addr){ htonl(10<<24 | 2) }, + .sin_port = htons(11), + }; + + /* setup */ + + netns_new(&ns_src); + netns_new(&ns_dst); + link_new_veth(&link_src, &link_dst, ns_src, ns_dst); + + /* communication tests */ + + test_packet_packet(&link_src, &link_dst, &paddr_src, &paddr_dst); + test_packet_udp(&link_src, &link_dst, &paddr_src, &paddr_dst); + test_udp_packet(&link_src, &link_dst, &paddr_src, &paddr_dst); + test_udp_udp(&link_src, &link_dst, &paddr_src, &paddr_dst); + + /* behavior tests */ + + test_shutdown(&link_src, &link_dst, &paddr_src, &paddr_dst); + test_ip_hdr(&link_src, &link_dst, &paddr_src, &paddr_dst); +} + +int main(int argc, char **argv) { + test_setup(); + + test_checksum(); + test_packet(); + + return 0; +} diff --git a/src/n-dhcp4/subprojects/c-list b/src/n-dhcp4/subprojects/c-list new file mode 120000 index 00000000..4e274698 --- /dev/null +++ b/src/n-dhcp4/subprojects/c-list @@ -0,0 +1 @@ +../../c-list \ No newline at end of file diff --git a/src/n-dhcp4/subprojects/c-siphash b/src/n-dhcp4/subprojects/c-siphash new file mode 120000 index 00000000..70d68818 --- /dev/null +++ b/src/n-dhcp4/subprojects/c-siphash @@ -0,0 +1 @@ +../../c-siphash \ No newline at end of file |