diff --git a/shared/n-dhcp4/.cherryci/ci-test b/shared/n-dhcp4/.cherryci/ci-test
new file mode 100755
index 000000000..e1fc8b910
--- /dev/null
+++ b/shared/n-dhcp4/.cherryci/ci-test
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e
+
+rm -Rf "./ci-build"
+mkdir "./ci-build"
+cd "./ci-build"
+
+${CHERRY_LIB_MESONSETUP} . "${CHERRY_LIB_SRCDIR}"
+${CHERRY_LIB_NINJABUILD}
+${CHERRY_LIB_MESONTEST}
+# no valgrind tests, since setns(2) is not supported by it
diff --git a/shared/n-dhcp4/.editorconfig b/shared/n-dhcp4/.editorconfig
new file mode 100644
index 000000000..b10bb4f3f
--- /dev/null
+++ b/shared/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/shared/n-dhcp4/.gitmodules b/shared/n-dhcp4/.gitmodules
new file mode 100644
index 000000000..ffe679226
--- /dev/null
+++ b/shared/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/shared/n-dhcp4/.travis.yml b/shared/n-dhcp4/.travis.yml
new file mode 100644
index 000000000..99a7bb946
--- /dev/null
+++ b/shared/n-dhcp4/.travis.yml
@@ -0,0 +1,21 @@
+os: linux
+dist: trusty
+language: c
+
+services:
+ - docker
+
+before_install:
+ - curl -O -L "https://raw.githubusercontent.com/cherry-pick/cherry-images/v1/scripts/vmrun"
+ - curl -O -L "https://raw.githubusercontent.com/cherry-pick/cherry-ci/v1/scripts/cherryci"
+ - chmod +x "./vmrun" "./cherryci"
+
+jobs:
+ include:
+ - stage: test
+ script:
+ - ./vmrun -- ../src/cherryci -d ../src/.cherryci -s c-util -m
+ - script:
+ - ./vmrun -T armv7hl -- ../src/cherryci -d ../src/.cherryci -s c-util
+ - script:
+ - ./vmrun -T i686 -- ../src/cherryci -d ../src/.cherryci -s c-util
diff --git a/shared/n-dhcp4/AUTHORS b/shared/n-dhcp4/AUTHORS
new file mode 100644
index 000000000..b59660c5e
--- /dev/null
+++ b/shared/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 .
+
+COPYRIGHT: (ordered alphabetically)
+ Copyright (C) 2015-2019 Red Hat, Inc.
+
+AUTHORS: (ordered alphabetically)
+ David Rheinsberg
+ Tom Gundersen
diff --git a/shared/n-dhcp4/NEWS.md b/shared/n-dhcp4/NEWS.md
new file mode 100644
index 000000000..b41de08ab
--- /dev/null
+++ b/shared/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/shared/n-dhcp4/README.md b/shared/n-dhcp4/README.md
new file mode 100644
index 000000000..298b49727
--- /dev/null
+++ b/shared/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**:
+ * **Bug Tracker**:
+ * **Mailing-List**:
+
+### 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**: `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/shared/n-dhcp4/meson.build b/shared/n-dhcp4/meson.build
new file mode 100644
index 000000000..1dddfe4f0
--- /dev/null
+++ b/shared/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('c-stdaux')
+
+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/shared/n-dhcp4/src/libndhcp4.sym b/shared/n-dhcp4/src/libndhcp4.sym
new file mode 100644
index 000000000..b9ba800a3
--- /dev/null
+++ b/shared/n-dhcp4/src/libndhcp4.sym
@@ -0,0 +1,66 @@
+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_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_lifetime;
+ 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/shared/n-dhcp4/src/meson.build b/shared/n-dhcp4/src/meson.build
new file mode 100644
index 000000000..2ac8ac1cb
--- /dev/null
+++ b/shared/n-dhcp4/src/meson.build
@@ -0,0 +1,91 @@
+#
+# 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,--no-undefined',
+ '-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/shared/n-dhcp4/src/n-dhcp4-c-connection.c b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
new file mode 100644
index 000000000..2d099b41f
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-c-connection.c
@@ -0,0 +1,1147 @@
+/*
+ * DHCPv4 Client Connection
+ *
+ * XXX
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4-private.h"
+#include "util/packet.h"
+
+/**
+ * n_dhcp4_c_connection_init() - initialize client connection
+ * @connection: connection to operate on
+ * @client_config: client configuration to use
+ * @probe_config: client probe configuration to use
+ * @fd_epoll: epoll context to attach to, or -1
+ *
+ * This initializes a new client connection using the configuration given in
+ * @client_config and @probe_config.
+ *
+ * The client-configuration given as @client_config must survive the lifetime
+ * of @connection. It is pinned in the connection and used all over the place.
+ * The caller must guarantee that the configuration is not deallocated in the
+ * meantime. Same is true for @probe_config.
+ *
+ * The new connection automatically attaches to the epoll context given as
+ * @fd_epoll. The epoll FD is retained in the connection and the caller must
+ * guarantee that it lives as long as the connection.
+ * The caller is explicitly allowed to pass -1 as @fd_epoll, in which case the
+ * connection will initialize correctly, but will not be in a usable state.
+ * That is, any call to n_dhcp4_c_connection_listen() will fail, since it will
+ * be unable to attach to the epoll context. Such a connection can be used to
+ * get a detached object that behaves sound, but provides no runtime.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
+ NDhcp4ClientConfig *client_config,
+ NDhcp4ClientProbeConfig *probe_config,
+ int fd_epoll) {
+ *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection);
+ connection->client_config = client_config;
+ connection->probe_config = probe_config;
+ connection->fd_epoll = fd_epoll;
+
+ /*
+ * We explicitly allow initializing connections with an invalid
+ * epoll-fd. The resulting connection immediately transitions into the
+ * CLOSED state. This allows the caller to create dummy connections
+ * useful to provide asynchronous constructor-feedback in the API.
+ *
+ * The effect of this is as if you immediately call
+ * n_dhcp4_c_connection_close() on the new connection. However, by
+ * directly passing -1 in the constructor, you are guaranteed not even
+ * the constructor can ever mess with your epoll-set.
+ */
+ if (connection->fd_epoll < 0)
+ connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED;
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_connection_deinit() - deinitialize client connection
+ * @connection: connection to operate on
+ *
+ * This deinitializes a connection that was previously initialized via
+ * n_dhcp4_c_connection_init(). It will tear down all allocated state and
+ * release it.
+ *
+ * Once this function returns, @connection is re-initialized to
+ * N_DHCP4_C_CONNECTION_NULL. If this function is called on a deinitialized
+ * connection, it is a no-op.
+ */
+void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection) {
+ n_dhcp4_c_connection_close(connection);
+ *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection);
+}
+
+static void n_dhcp4_c_connection_outgoing_set_secs(NDhcp4Outgoing *message) {
+ uint32_t secs;
+
+ /*
+ * This function sets the `secs` field for outgoing messages. It
+ * expects the base-time and start-time to be already set by the
+ * caller.
+ * For a given outgoing message, its `secs` field describes the time
+ * (in seconds) between the start of the transaction this message is
+ * part of and the start of the operational process (also called the
+ * base time here).
+ *
+ * The operational process in the DHCP sense describes the entire
+ * process of requesting a lease and acquiring it. That is, it starts
+ * with the caller's intent to request a lease, and it ends when we
+ * got granted a lease. The act of refreshing a lease is, in itself, a
+ * new operational process. The base-time describes the start-time
+ * recorded when such a process as initiated.
+ *
+ * A transaction in the DHCP sense describes a request+reply
+ * combination, in most cases. That is, the time a request is sent is
+ * the start-time of a transaction. In the ideal case, the start-time
+ * of the first transaction in an operational process matches the
+ * base-time. However, transactions are often delayed with a randomized
+ * offset to reduce traffic during network bursts.
+ * In some cases, however, transactions are composed out of multiple
+ * requests+reply combinations. This includes, for instance, the SELECT
+ * message following an OFFER. The specification clearly says that
+ * those must be considered a single transaction and thus share the
+ * transaction start-time.
+ *
+ * The `secs` field, thus, describes how long a client has been busy
+ * requesting a lease. DHCP servers and proxies do use it to prioritize
+ * clients.
+ *
+ * Note: Some DHCP relays reject a `secs` value of 0 (which might look
+ * like it is uninitialized). Hence, we always clamp the value to
+ * the range `[1, INF[`.
+ */
+
+ secs = message->userdata.base_time - message->userdata.start_time;
+ secs /= 1000ULL * 1000ULL * 1000ULL; /* nsecs to secs */
+ secs = secs ?: 1; /* clamp to `[1, INF[` */
+
+ n_dhcp4_outgoing_set_secs(message, secs);
+}
+
+int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection) {
+ _c_cleanup_(c_closep) int fd_packet = -1;
+ int r;
+
+ c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_INIT);
+
+ r = n_dhcp4_c_socket_packet_new(&fd_packet, connection->client_config->ifindex);
+ if (r)
+ return r;
+
+ r = epoll_ctl(connection->fd_epoll,
+ EPOLL_CTL_ADD,
+ fd_packet,
+ &(struct epoll_event){
+ .events = EPOLLIN,
+ .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO },
+ });
+ if (r < 0)
+ return -errno;
+
+ connection->state = N_DHCP4_C_CONNECTION_STATE_PACKET;
+ connection->fd_packet = fd_packet;
+ fd_packet = -1;
+ return 0;
+}
+
+int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection,
+ const struct in_addr *client,
+ const struct in_addr *server) {
+ int r, fd_udp;
+
+ c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET);
+
+ r = n_dhcp4_c_socket_udp_new(&fd_udp,
+ connection->client_config->ifindex,
+ client,
+ server);
+ if (r)
+ return r;
+
+ r = epoll_ctl(connection->fd_epoll,
+ EPOLL_CTL_ADD,
+ fd_udp,
+ &(struct epoll_event){
+ .events = EPOLLIN,
+ .data = { .u32 = N_DHCP4_CLIENT_EPOLL_IO },
+ });
+ if (r < 0) {
+ r = -errno;
+ goto exit_fd;
+ }
+
+ r = packet_shutdown(connection->fd_packet);
+ if (r < 0)
+ goto exit_epoll;
+
+ connection->state = N_DHCP4_C_CONNECTION_STATE_DRAINING;
+ connection->fd_udp = fd_udp;
+ connection->client_ip = client->s_addr;
+ connection->server_ip = server->s_addr;
+ fd_udp = -1;
+ return 0;
+
+exit_epoll:
+ epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, fd_udp, NULL);
+exit_fd:
+ close(fd_udp);
+ return r;
+}
+
+void n_dhcp4_c_connection_close(NDhcp4CConnection *connection) {
+ if (connection->fd_udp >= 0) {
+ epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_udp, NULL);
+ connection->fd_udp = c_close(connection->fd_udp);
+ }
+
+ if (connection->fd_packet >= 0) {
+ epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
+ connection->fd_packet = c_close(connection->fd_packet);
+ }
+
+ connection->fd_epoll = -1;
+ connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED;
+}
+
+static int n_dhcp4_c_connection_verify_incoming(NDhcp4CConnection *connection,
+ NDhcp4Incoming *message,
+ uint8_t *typep) {
+ NDhcp4Header *header = n_dhcp4_incoming_get_header(message);
+ uint8_t type;
+ uint32_t request_xid;
+ uint8_t *id;
+ size_t n_id;
+ 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_OFFER:
+ case N_DHCP4_MESSAGE_ACK:
+ case N_DHCP4_MESSAGE_NAK:
+ /*
+ * Only accept replies if there is a pending request, and it
+ * has a matching transaction id.
+ */
+ if (!connection->request)
+ return N_DHCP4_E_UNEXPECTED;
+
+ n_dhcp4_outgoing_get_xid(connection->request, &request_xid);
+ if (header->xid != request_xid)
+ return N_DHCP4_E_UNEXPECTED;
+
+ break;
+ case N_DHCP4_MESSAGE_FORCERENEW:
+ /*
+ * Force renew messages are triggered by a server, and do not
+ * match a pending request.
+ */
+ break;
+ default:
+ return N_DHCP4_E_UNEXPECTED;
+ }
+
+ /*
+ * In case our transport makes use of the 'chaddr' field, make sure it
+ * matches exactly our address.
+ */
+ switch (connection->client_config->transport) {
+ case N_DHCP4_TRANSPORT_ETHERNET:
+ c_assert(connection->client_config->n_mac == ETH_ALEN);
+
+ if (header->hlen != ETH_ALEN)
+ return N_DHCP4_E_UNEXPECTED;
+ if (memcmp(header->chaddr, connection->client_config->mac, ETH_ALEN) != 0)
+ return N_DHCP4_E_UNEXPECTED;
+
+ break;
+ case N_DHCP4_TRANSPORT_INFINIBAND:
+ if (header->hlen != 0)
+ return N_DHCP4_E_UNEXPECTED;
+
+ break;
+ }
+
+ /*
+ * If a server passes us back a client ID, it must be the one we
+ * provided. We ignore any packets that have mismatching client-ids.
+ */
+ id = NULL;
+ n_id = 0;
+ r = n_dhcp4_incoming_query(message, N_DHCP4_OPTION_CLIENT_IDENTIFIER, &id, &n_id);
+ if (r) {
+ if (r != N_DHCP4_E_UNSET)
+ return r;
+ } else {
+ if (n_id != connection->client_config->n_client_id)
+ return N_DHCP4_E_UNEXPECTED;
+ if (memcmp(id, connection->client_config->client_id, n_id) != 0)
+ return N_DHCP4_E_UNEXPECTED;
+ }
+
+ *typep = type;
+ return 0;
+}
+
+void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
+ uint64_t *timeoutp) {
+ uint64_t timeout;
+ size_t n_send;
+
+ if (!connection->request) {
+ *timeoutp = 0;
+ return;
+ }
+
+ switch (connection->request->userdata.type) {
+ case N_DHCP4_C_MESSAGE_DISCOVER:
+ case N_DHCP4_C_MESSAGE_SELECT:
+ case N_DHCP4_C_MESSAGE_REBOOT:
+ case N_DHCP4_C_MESSAGE_INFORM:
+ /*
+ * Resend with an exponential backoff and a one second random
+ * slack, from a minimum of two seconds to a maximum of sixty
+ * four.
+ *
+ * Note that the RFC says to start at four rather than two
+ * seconds, and use [-1,1] slack, rather than [0,1].
+ */
+ n_send = connection->request->userdata.n_send;
+ if (n_send >= 6)
+ n_send = 6;
+
+ timeout = connection->request->userdata.send_time + ((1ULL << n_send) * 1000000000ULL) + connection->request->userdata.send_jitter;
+
+ break;
+ case N_DHCP4_C_MESSAGE_REBIND:
+ case N_DHCP4_C_MESSAGE_RENEW:
+ /*
+ * Resend every sixty seconds with a one second random slack.
+ *
+ * Note that the RFC says to do this at most once, but we do
+ * it until we are cancelled.
+ */
+ timeout = connection->request->userdata.send_time + (60ULL * 1000000000ULL) + connection->request->userdata.send_jitter;
+
+ break;
+ case N_DHCP4_C_MESSAGE_DECLINE:
+ case N_DHCP4_C_MESSAGE_RELEASE:
+ /* XXX make sure these message types are never pinned? */
+ timeout = 0;
+ break;
+ default:
+ c_assert(0);
+ }
+
+ *timeoutp = timeout;
+}
+
+static int n_dhcp4_c_connection_packet_broadcast(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *message) {
+ int r;
+
+ c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET);
+
+ r = n_dhcp4_c_socket_packet_send(connection->fd_packet,
+ connection->client_config->ifindex,
+ connection->client_config->broadcast_mac,
+ connection->client_config->n_broadcast_mac,
+ message);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int n_dhcp4_c_connection_udp_broadcast(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *message) {
+ int r;
+
+ c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING ||
+ connection->state == N_DHCP4_C_CONNECTION_STATE_UDP);
+
+ r = n_dhcp4_c_socket_udp_broadcast(connection->fd_udp, message);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int n_dhcp4_c_connection_udp_send(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *message) {
+ int r;
+
+ c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING ||
+ connection->state == N_DHCP4_C_CONNECTION_STATE_UDP);
+
+ r = n_dhcp4_c_socket_udp_send(connection->fd_udp, message);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static void n_dhcp4_c_connection_init_header(NDhcp4CConnection *connection,
+ NDhcp4Header *header) {
+ bool broadcast = connection->client_config->request_broadcast;
+
+ header->op = N_DHCP4_OP_BOOTREQUEST;
+
+ switch (connection->client_config->transport) {
+ case N_DHCP4_TRANSPORT_ETHERNET:
+ c_assert(connection->client_config->n_mac == ETH_ALEN);
+
+ header->htype = ARPHRD_ETHER;
+ header->hlen = ETH_ALEN;
+ memcpy(header->chaddr, connection->client_config->mac, ETH_ALEN);
+ break;
+ case N_DHCP4_TRANSPORT_INFINIBAND:
+ header->htype = ARPHRD_INFINIBAND;
+ header->hlen = 0;
+
+ /* infiniband mandates to request broadcasts */
+ broadcast = true;
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ if (connection->client_ip != INADDR_ANY) {
+ header->ciaddr = connection->client_ip;
+ } else {
+ /*
+ * When the IP stack has not been configured, we may
+ * not be able to receive unicast packets, depending
+ * on the hardware. If that is the case we must request
+ * replies from the server to be broadcast.
+ *
+ * Once the IP stack has been configured, receiving
+ * unicast packets is never a problem, so the broadcast
+ * flag should not be set.
+ */
+ if (broadcast)
+ header->flags |= N_DHCP4_MESSAGE_FLAG_BROADCAST;
+ }
+}
+
+static int n_dhcp4_c_connection_new_message(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **messagep,
+ uint8_t type) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ NDhcp4Header *header;
+ uint8_t message_type;
+ bool via_packet_socket = false;
+ int r;
+
+ switch (type) {
+ case N_DHCP4_C_MESSAGE_DISCOVER:
+ message_type = N_DHCP4_MESSAGE_DISCOVER;
+ via_packet_socket = true;
+ break;
+ case N_DHCP4_C_MESSAGE_INFORM:
+ message_type = N_DHCP4_MESSAGE_INFORM;
+ break;
+ case N_DHCP4_C_MESSAGE_SELECT:
+ message_type = N_DHCP4_MESSAGE_REQUEST;
+ via_packet_socket = true;
+ break;
+ case N_DHCP4_C_MESSAGE_RENEW:
+ message_type = N_DHCP4_MESSAGE_REQUEST;
+ break;
+ case N_DHCP4_C_MESSAGE_REBIND:
+ message_type = N_DHCP4_MESSAGE_REQUEST;
+ break;
+ case N_DHCP4_C_MESSAGE_REBOOT:
+ message_type = N_DHCP4_MESSAGE_REQUEST;
+ via_packet_socket = true;
+ break;
+ case N_DHCP4_C_MESSAGE_RELEASE:
+ message_type = N_DHCP4_MESSAGE_RELEASE;
+ break;
+ case N_DHCP4_C_MESSAGE_DECLINE:
+ message_type = N_DHCP4_MESSAGE_DECLINE;
+ via_packet_socket = true;
+ break;
+ default:
+ abort();
+ return -ENOTRECOVERABLE;
+ }
+
+ /*
+ * We explicitly pass 0 as maximum message size, which makes
+ * NDhcp4Outgoing use the mandated default value from the spec (see its
+ * implementation). While the transport and like layers might support
+ * bigger MTUs (and we very likely know about them through
+ * n_dhcp4_client_update_mtu()), we cannot assume the target DHCP
+ * server supports parsing packets bigger than the minimum (and it is
+ * allowed to refuse bigger IP packets, even if the network supports
+ * transmission of them).
+ *
+ * We could theoretically increase this for packets other than the
+ * initial discovery. However, clients are unlikely to ever send large
+ * packets, so we just keep the same default for all outgoing packets.
+ */
+ r = n_dhcp4_outgoing_new(&message, 0, N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME);
+ if (r)
+ return r;
+
+ header = n_dhcp4_outgoing_get_header(message);
+ n_dhcp4_c_connection_init_header(connection, header);
+
+ message->userdata.type = type;
+
+ /*
+ * Note that some implementations expect the MESSAGE_TYPE option to be
+ * the first option, and possibly even hard-code access to it. Hence,
+ * we really should make sure to pass it first as well.
+ */
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MESSAGE_TYPE, &message_type, sizeof(message_type));
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message,
+ N_DHCP4_OPTION_CLIENT_IDENTIFIER,
+ connection->client_config->client_id,
+ connection->client_config->n_client_id);
+ if (r)
+ return r;
+
+ switch (message_type) {
+ case N_DHCP4_MESSAGE_DISCOVER:
+ case N_DHCP4_MESSAGE_REQUEST:
+ case N_DHCP4_MESSAGE_INFORM: {
+ uint16_t mtu;
+
+ if (connection->probe_config->n_request_parameters > 0) {
+ r = n_dhcp4_outgoing_append(message,
+ N_DHCP4_OPTION_PARAMETER_REQUEST_LIST,
+ connection->probe_config->request_parameters,
+ connection->probe_config->n_request_parameters);
+ if (r)
+ return r;
+ }
+
+ if (via_packet_socket) {
+ /*
+ * In case of packet sockets, we do not support
+ * fragmentation. Hence, our maximum message size
+ * equals the transport MTU. In case no mtu is given,
+ * we use the minimum size mandated by the IP spec. If
+ * we omit the field, some implementations will
+ * interpret this to mean any packet size is supported,
+ * which we rather not want as default behavior (we can
+ * always support suppressing this field, if that is
+ * what the caller wants).
+ */
+ mtu = htons(connection->mtu ?: N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE);
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu));
+ if (r)
+ return r;
+ } else {
+ /*
+ * Once we use UDP sockets, we support fragmentation
+ * through the kernel IP stack. This means, the biggest
+ * message we can receive is the maximum UDP size plus
+ * the possible IP header. This would sum up to
+ * 2^16-1 + 20 (or even 2^16-1 + 60 if pedantic) and
+ * thus exceed the option field. Hence, we simply set
+ * the option to the maximum possible value.
+ */
+ mtu = htons(UINT16_MAX);
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, &mtu, sizeof(mtu));
+ if (r)
+ return r;
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ *messagep = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 3.1
+ *
+ * The client broadcasts a DHCPDISCOVER message on its local physical
+ * subnet. The DHCPDISCOVER message MAY include options that suggest
+ * values for the network address and lease duration. BOOTP relay
+ * agents may pass the message on to DHCP servers not on the same
+ * physical subnet.
+ *
+ * RFC2131 3.5
+ *
+ * [...] in its initial DHCPDISCOVER or DHCPREQUEST message, a client
+ * may provide the server with a list of specific parameters the
+ * client is interested in. If the client includes a list of
+ * parameters in a DHCPDISCOVER message, it MUST include that list in
+ * any subsequent DHCPREQUEST messages.
+ *
+ * [...]
+ *
+ * In addition, the client may suggest values for the network address
+ * and lease time in the DHCPDISCOVER message. The client may include
+ * the 'requested IP address' option to suggest that a particular IP
+ * address be assigned, and may include the 'IP address lease time'
+ * option to suggest the lease time it would like. Other options
+ * representing "hints" at configuration parameters are allowed in a
+ * DHCPDISCOVER or DHCPREQUEST message.
+ *
+ * RFC2131 4.4.1
+ *
+ * The client generates and records a random transaction identifier and
+ * inserts that identifier into the 'xid' field. The client records its
+ * own local time for later use in computing the lease expiration. The
+ * client then broadcasts the DHCPDISCOVER on the local hardware
+ * broadcast address to the 0xffffffff IP broadcast address and 'DHCP
+ * server' UDP port.
+ *
+ * If the 'xid' of an arriving DHCPOFFER message does not match the
+ * 'xid' of the most recent DHCPDISCOVER message, the DHCPOFFER message
+ * must be silently discarded. Any arriving DHCPACK messages must be
+ * silently discarded.
+ */
+int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DISCOVER);
+ if (r)
+ return r;
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ *
+ * RFC2131 4.1.1
+ *
+ * The DHCPREQUEST message contains the same 'xid' as the DHCPOFFER
+ * message.
+ *
+ * RFC2131 4.3.2
+ *
+ * Client inserts the address of the selected server in 'server
+ * identifier', 'ciaddr' MUST be zero, 'requested IP address' MUST be
+ * filled in with the yiaddr value from the chosen DHCPOFFER.
+ */
+int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp,
+ NDhcp4Incoming *offer) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ struct in_addr client;
+ struct in_addr server;
+ uint32_t xid;
+ int r;
+
+ n_dhcp4_incoming_get_yiaddr(offer, &client);
+
+ r = n_dhcp4_incoming_query_server_identifier(offer, &server);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_SELECT);
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client));
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server));
+ if (r)
+ return r;
+
+ /*
+ * SELECT continues the transaction started by DISCOVER, and as such
+ * keeps the same start time. We also have to preserve the base time
+ * of the selected lease as well as the transaction ID.
+ */
+ message->userdata.start_time = offer->userdata.start_time;
+ message->userdata.base_time = offer->userdata.base_time;
+ n_dhcp4_incoming_get_xid(offer, &xid);
+ n_dhcp4_outgoing_set_xid(message, xid);
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 4.3.2
+ *
+ * 'server identifier' MUST NOT be filled in, 'requested IP address'
+ * option MUST be filled in with client's notion of its previously
+ * assigned address. 'ciaddr' MUST be zero. The client is seeking to
+ * verify a previously allocated, cached configuration. Server SHOULD
+ * send a DHCPNAK message to the client if the 'requested IP address'
+ * is incorrect, or is on the wrong network.
+ */
+int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp,
+ const struct in_addr *client) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBOOT);
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, client, sizeof(*client));
+ if (r)
+ return r;
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 4.3.2
+ *
+ * 'server identifier' MUST NOT be filled in, 'requested IP address'
+ * option MUST NOT be filled in, 'ciaddr' MUST be filled in with
+ * client's IP address. In this situation, the client is completely
+ * configured, and is trying to extend its lease. This message will
+ * be unicast, so no relay agents will be involved in its
+ * transmission. Because 'giaddr' is therefore not filled in, the
+ * DHCP server will trust the value in 'ciaddr', and use it when
+ * replying to the client.
+ *
+ * A client MAY choose to renew or extend its lease prior to T1. The
+ * server may choose not to extend the lease (as a policy decision by
+ * the network administrator), but should return a DHCPACK message
+ * regardless.
+ *
+ * RFC2131 4.4.5
+ *
+ * At time T1 the client moves to RENEWING state and sends (via unicast)
+ * a DHCPREQUEST message to the server to extend its lease. The client
+ * sets the 'ciaddr' field in the DHCPREQUEST to its current network
+ * address. The client records the local time at which the DHCPREQUEST
+ * message is sent for computation of the lease expiration time. The
+ * client MUST NOT include a 'server identifier' in the DHCPREQUEST
+ * message.
+ */
+int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RENEW);
+ if (r)
+ return r;
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 4.3.2
+ *
+ * 'server identifier' MUST NOT be filled in, 'requested IP address'
+ * option MUST NOT be filled in, 'ciaddr' MUST be filled in with
+ * client's IP address. In this situation, the client is completely
+ * configured, and is trying to extend its lease. This message MUST
+ * be broadcast to the 0xffffffff IP broadcast address. The DHCP
+ * server SHOULD check 'ciaddr' for correctness before replying to
+ * the DHCPREQUEST.
+ *
+ * RFC2131 4.4.5
+ *
+ * If no DHCPACK arrives before time T2, the client moves to REBINDING
+ * state and sends (via broadcast) a DHCPREQUEST message to extend its
+ * lease. The client sets the 'ciaddr' field in the DHCPREQUEST to its
+ * current network address. The client MUST NOT include a 'server
+ * identifier' in the DHCPREQUEST message.
+ */
+int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_REBIND);
+ if (r)
+ return r;
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 3.2
+ *
+ * If the client detects that the IP address in the DHCPACK message
+ * is already in use, the client MUST send a DHCPDECLINE message to the
+ * server and restarts the configuration process by requesting a
+ * new network address.
+ *
+ * RFC2131 4.4.4
+ *
+ * Because the client is declining the use of the IP address supplied by
+ * the server, the client broadcasts DHCPDECLINE messages.
+ */
+int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp,
+ NDhcp4Incoming *ack,
+ const char *error) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ struct in_addr client;
+ struct in_addr server;
+ int r;
+
+ n_dhcp4_incoming_get_yiaddr(ack, &client);
+
+ r = n_dhcp4_incoming_query_server_identifier(ack, &server);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_DECLINE);
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, &client, sizeof(client));
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &server, sizeof(server));
+ if (r)
+ return r;
+
+ if (error) {
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1);
+ if (r)
+ return r;
+ }
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 3.4
+ *
+ * If a client has obtained a network address through some other means
+ * (e.g., manual configuration), it may use a DHCPINFORM request message
+ * to obtain other local configuration parameters.
+ *
+ * RFC2131 4.4
+ *
+ * The DHCPINFORM message is not shown in figure 5. A client simply
+ * sends the DHCPINFORM and waits for DHCPACK messages. Once the client
+ * has selected its parameters, it has completed the configuration
+ * process.
+ *
+ * RFC2131 4.4.3
+ *
+ * The client sends a DHCPINFORM message. The client may request
+ * specific configuration parameters by including the 'parameter request
+ * list' option. The client generates and records a random transaction
+ * identifier and inserts that identifier into the 'xid' field. The
+ * client places its own network address in the 'ciaddr' field. The
+ * client SHOULD NOT request lease time parameters.
+ *
+ * The client then unicasts the DHCPINFORM to the DHCP server if it
+ * knows the server's address, otherwise it broadcasts the message to
+ * the limited (all 1s) broadcast address. DHCPINFORM messages MUST be
+ * directed to the 'DHCP server' UDP port.
+ */
+int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_INFORM);
+ if (r)
+ return r;
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+/*
+ * RFC2131 3.1
+ *
+ * The client may choose to relinquish its lease on a network address
+ * by sending a DHCPRELEASE message to the server. The client
+ * identifies the lease to be released with its 'client identifier',
+ * or 'chaddr' and network address in the DHCPRELEASE message. If the
+ * client used a 'client identifier' when it obtained the lease, it
+ * MUST use the same 'client identifier' in the DHCPRELEASE message.
+ *
+ * RFC2131 3.2
+ *
+ * The client may choose to relinquish its lease on a network
+ * address by sending a DHCPRELEASE message to the server. The
+ * client identifies the lease to be released with its
+ * 'client identifier', or 'chaddr' and network address in the
+ * DHCPRELEASE message.
+ *
+ * Note that in this case, where the client retains its network
+ * address locally, the client will not normally relinquish its
+ * lease during a graceful shutdown. Only in the case where the
+ * client explicitly needs to relinquish its lease, e.g., the client
+ * is about to be moved to a different subnet, will the client send
+ * a DHCPRELEASE message.
+ *
+ * RFC2131 4.4.4
+ *
+ * The client unicasts DHCPRELEASE messages to the server.
+ *
+ * RFC2131 4.4.6
+ *
+ * If the client no longer requires use of its assigned network address
+ * (e.g., the client is gracefully shut down), the client sends a
+ * DHCPRELEASE message to the server. Note that the correct operation
+ * of DHCP does not depend on the transmission of DHCPRELEASE messages.
+ */
+int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **requestp,
+ const char *error) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *message = NULL;
+ int r;
+
+ r = n_dhcp4_c_connection_new_message(connection, &message, N_DHCP4_C_MESSAGE_RELEASE);
+ if (r)
+ return r;
+
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, &connection->server_ip, sizeof(connection->server_ip));
+ if (r)
+ return r;
+
+ if (error) {
+ r = n_dhcp4_outgoing_append(message, N_DHCP4_OPTION_ERROR_MESSAGE, error, strlen(error) + 1);
+ if (r)
+ return r;
+ }
+
+ *requestp = message;
+ message = NULL;
+ return 0;
+}
+
+static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *request,
+ uint64_t timestamp) {
+ int r;
+
+ /*
+ * Increment the base time and reset the xid field,
+ * where applicable. We never alter the header on
+ * resends of SELECT, as it must always match the
+ * OFFER message they are in reply to.
+ */
+ switch (request->userdata.type) {
+ case N_DHCP4_C_MESSAGE_DISCOVER:
+ case N_DHCP4_C_MESSAGE_INFORM:
+ case N_DHCP4_C_MESSAGE_REBOOT:
+ case N_DHCP4_C_MESSAGE_REBIND:
+ case N_DHCP4_C_MESSAGE_RENEW:
+ 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);
+ }
+
+ request->userdata.send_time = timestamp;
+ request->userdata.send_jitter = (n_dhcp4_client_probe_config_get_random(connection->probe_config) % 1000000000ULL);
+ n_dhcp4_c_connection_outgoing_set_secs(request);
+
+ switch (request->userdata.type) {
+ case N_DHCP4_C_MESSAGE_DISCOVER:
+ case N_DHCP4_C_MESSAGE_SELECT:
+ case N_DHCP4_C_MESSAGE_REBOOT:
+ case N_DHCP4_C_MESSAGE_DECLINE:
+ r = n_dhcp4_c_connection_packet_broadcast(connection, request);
+ if (r)
+ return r;
+ break;
+ case N_DHCP4_C_MESSAGE_INFORM:
+ case N_DHCP4_C_MESSAGE_REBIND:
+ r = n_dhcp4_c_connection_udp_broadcast(connection, request);
+ if (r)
+ return r;
+
+ break;
+ case N_DHCP4_C_MESSAGE_RENEW:
+ case N_DHCP4_C_MESSAGE_RELEASE:
+ r = n_dhcp4_c_connection_udp_send(connection, request);
+ if (r)
+ return r;
+
+ break;
+ default:
+ c_assert(0);
+ }
+
+ ++request->userdata.n_send;
+ return 0;
+}
+
+int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *request,
+ uint64_t timestamp) {
+ int r;
+
+ /*
+ * This function starts a request, but in the case of SELECT it
+ * continues a previous transaction, so we do not want to reset
+ * the start time. Only set the start time if it was not already
+ * set.
+ */
+ if (request->userdata.start_time == 0)
+ request->userdata.start_time = timestamp;
+
+ n_dhcp4_outgoing_free(connection->request);
+ connection->request = request;
+
+ r = n_dhcp4_c_connection_send_request(connection, request, timestamp);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection,
+ uint64_t timestamp) {
+ uint64_t timeout;
+ int r;
+
+ if (!connection->request)
+ return 0;
+
+ n_dhcp4_c_connection_get_timeout(connection, &timeout);
+
+ if (timeout > timestamp)
+ return 0;
+
+ r = n_dhcp4_c_connection_send_request(connection, connection->request, timestamp);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
+ NDhcp4Incoming **messagep) {
+ _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
+ uint8_t type;
+ int r;
+
+ switch (connection->state) {
+ case N_DHCP4_C_CONNECTION_STATE_PACKET:
+ r = n_dhcp4_c_socket_packet_recv(connection->fd_packet,
+ connection->scratch_buffer,
+ sizeof(connection->scratch_buffer),
+ &message);
+ if (r)
+ return r;
+
+ break;
+ case N_DHCP4_C_CONNECTION_STATE_DRAINING:
+ r = n_dhcp4_c_socket_packet_recv(connection->fd_packet,
+ connection->scratch_buffer,
+ sizeof(connection->scratch_buffer),
+ &message);
+ if (!r)
+ break;
+ else if (r != N_DHCP4_E_AGAIN)
+ return r;
+
+ /*
+ * The UDP socket is open and the packet socket has been shut down
+ * and drained, clean up the packet socket and fall through to
+ * dispatching the UDP socket.
+ */
+ r = epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
+ c_assert(!r);
+ connection->fd_packet = c_close(connection->fd_packet);
+ connection->state = N_DHCP4_C_CONNECTION_STATE_UDP;
+
+ /* fall-through */
+ case N_DHCP4_C_CONNECTION_STATE_UDP:
+ r = n_dhcp4_c_socket_udp_recv(connection->fd_udp,
+ connection->scratch_buffer,
+ sizeof(connection->scratch_buffer),
+ &message);
+ if (r)
+ return r;
+
+ break;
+ default:
+ abort();
+ return -ENOTRECOVERABLE;
+ }
+
+ r = n_dhcp4_c_connection_verify_incoming(connection, message, &type);
+ if (r)
+ return r;
+
+ switch (type) {
+ case N_DHCP4_MESSAGE_OFFER:
+ case N_DHCP4_MESSAGE_ACK:
+ case N_DHCP4_MESSAGE_NAK:
+ /*
+ * Remember the start time of the transaction, and the base
+ * time of any relative timestamps from the pending request.
+ * Thes same times applies to the response, and sholud be
+ * copied over.
+ */
+ message->userdata.start_time = connection->request->userdata.start_time;
+ message->userdata.base_time = connection->request->userdata.base_time;
+
+ if (type != N_DHCP4_MESSAGE_OFFER) {
+ /*
+ * We only allow one reply to ACK or NAK, but for OFFER we must
+ * accept several, so we do not free the pinned request.
+ */
+ connection->request = n_dhcp4_outgoing_free(connection->request);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ *messagep = message;
+ message = NULL;
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-lease.c b/shared/n-dhcp4/src/n-dhcp4-c-lease.c
new file mode 100644
index 000000000..db1ffbae8
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-c-lease.c
@@ -0,0 +1,356 @@
+/*
+ * DHCP4 Client Leases
+ *
+ * This implements the public API wrapping DHCP4 client leases. A lease object
+ * conists of the information given to us from the server, together with the
+ * timestamp recording the start of the validity of the lease.
+ *
+ * A probe may yield many OFFERS, each of which contains a lease object. One of
+ * these offers may be SELECTED, which implicitly rejects all the others.
+ * The server may then ACK or NAK the lease which tells us whether or not we
+ * are permitted to start using it. Once an ACK has been received, we can
+ * configure the address, and only then can we SELECT the lease. If we
+ * determine that the offered lease was not appropriate after all we
+ * may DECLINE it instead.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+#include "n-dhcp4-private.h"
+
+/*
+ * Compute the absolute timeouts from an incoming message. A message contains relative timeouts and the userdata
+ * of the incoming message is set to the offset we must apply to get the absolute values.
+ *
+ * The special value UINT64_MAX is returned to indicate no or infinite timeouts. In case the given timeouts
+ * are invalid relative to each other, we recompute T1 and/or T2 to take their default values. Later timeouts
+ * take predecende above earlier ones (T1 is adjusted if it conflicts with T2, etc).
+ */
+static int n_dhcp4_incoming_get_timeouts(NDhcp4Incoming *message, uint64_t *t1p, uint64_t *t2p, uint64_t *lifetimep) {
+ uint64_t lifetime, t2, t1;
+ uint32_t u32;
+ int r;
+
+ r = n_dhcp4_incoming_query_lifetime(message, &u32);
+ if (r == N_DHCP4_E_UNSET) {
+ lifetime = UINT64_MAX;
+ } else if (r) {
+ return r;
+ } else if (u32 == UINT32_MAX) {
+ lifetime = UINT64_MAX;
+ } else {
+ if (u32 == UINT32_MAX)
+ lifetime = UINT64_MAX;
+ else
+ lifetime = u32 * (1000000000ULL);
+ }
+
+ r = n_dhcp4_incoming_query_t2(message, &u32);
+ if (r == N_DHCP4_E_UNSET) {
+ if (lifetime == UINT64_MAX)
+ t2 = UINT64_MAX;
+ else
+ t2 = (lifetime * 7) / 8;
+ } else if (r) {
+ return r;
+ } else {
+ if (u32 == UINT32_MAX)
+ t2 = UINT64_MAX;
+ else
+ t2 = u32 * (1000000000ULL);
+
+ if (t2 > lifetime)
+ t2 = (lifetime * 7) / 8;
+ }
+
+ r = n_dhcp4_incoming_query_t1(message, &u32);
+ if (r == N_DHCP4_E_UNSET) {
+ if (t2 == UINT64_MAX)
+ t1 = UINT64_MAX;
+ else
+ t1 = (t2 * 4) / 7;
+ } else if (r) {
+ return r;
+ } else {
+ if (u32 == UINT32_MAX)
+ t1 = UINT64_MAX;
+ else
+ t1 = u32 * (1000000000ULL);
+
+ if (t1 > t2)
+ t1 = (t2 * 4) / 7;
+ }
+
+ if (lifetime != UINT64_MAX)
+ lifetime += message->userdata.base_time;
+ if (t2 != UINT64_MAX)
+ t2 += message->userdata.base_time;
+ if (t1 != UINT64_MAX)
+ t1 += message->userdata.base_time;
+
+ *lifetimep = lifetime;
+ *t2p = t2;
+ *t1p = t1;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_lease_new() - allocate new client lease object
+ * @leasep: output argumnet for new client lease object
+ * @message: incoming message representing the lease
+ *
+ * This creates a new client lease object. Client lease objects are simple
+ * wrappers around an incoming message representing a lease.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message) {
+ _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL;
+ int r;
+
+ c_assert(leasep);
+
+ lease = malloc(sizeof(*lease));
+ if (!lease)
+ return -ENOMEM;
+
+ *lease = (NDhcp4ClientLease)N_DHCP4_CLIENT_LEASE_NULL(*lease);
+
+ r = n_dhcp4_incoming_get_timeouts(message, &lease->t1, &lease->t2, &lease->lifetime);
+ if (r)
+ return r;
+
+ lease->message = message;
+ *leasep = lease;
+ lease = NULL;
+ return 0;
+}
+
+static void n_dhcp4_client_lease_free(NDhcp4ClientLease *lease) {
+ n_dhcp4_client_lease_unlink(lease);
+ n_dhcp4_incoming_free(lease->message);
+ free(lease);
+}
+
+/**
+ * n_dhcp4_client_lease_ref() - reference client lease
+ * @lease: the client lease object to reference
+ *
+ * Take a new reference to a client lease.
+ *
+ * Return: the lease.
+ */
+_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_ref(NDhcp4ClientLease *lease) {
+ if (lease)
+ ++lease->n_refs;
+ return lease;
+}
+
+/**
+ * n_dhcp4_client_lease_unref() - dereference client lease
+ * @lease: the client lease object to dereference
+ *
+ * Relase a reference to a client lease.
+ *
+ * Return: NULL.
+ */
+_c_public_ NDhcp4ClientLease *n_dhcp4_client_lease_unref(NDhcp4ClientLease *lease) {
+ if (lease && !--lease->n_refs)
+ n_dhcp4_client_lease_free(lease);
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_lease_link() - link lease into probe
+ * @lease: the lease to operate on
+ * @probe: the probe to link the lease into
+ *
+ * Associate a lease with a probe. The lease may not already be linked.
+ */
+void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe) {
+ c_assert(!lease->probe);
+ c_assert(!c_list_is_linked(&lease->probe_link));
+
+ lease->probe = probe;
+ c_list_link_tail(&probe->lease_list, &lease->probe_link);
+}
+
+/**
+ * n_dhcp4_client_lease_unlink() - unlinke lease from its probe
+ * @lease: the lease to operate on
+ *
+ * Dissassociate a lease from a probe if it is associated with one. Otherwise,
+ * this is a noop.
+ */
+void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease) {
+ lease->probe = NULL;
+ c_list_unlink(&lease->probe_link);
+}
+
+/**
+ * n_dhcp4_client_lease_get_yiaddr() - get the IP address
+ * @lease: the lease to operate on
+ * @yiaddr: return argument for the IP address
+ *
+ * Gets the IP address cotained in the lease. Or INADDR_ANY if the lease
+ * does not contain an IP address.
+ */
+_c_public_ void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct in_addr *yiaddr) {
+ NDhcp4Header *header = n_dhcp4_incoming_get_header(lease->message);
+
+ yiaddr->s_addr = header->yiaddr;
+}
+
+/**
+ * n_dhcp4_client_lease_get_lifetime() - get the lifetime
+ * @lease: the lease to operate on
+ * @ns_lifetimep: return argument for the lifetime in nano seconds
+ *
+ * Gets the end of the lease's lifetime in nanoseconds according to CLOCK_BOOTTIME,
+ * or (uint64_t)-1 for permanent leases.
+ */
+_c_public_ void n_dhcp4_client_lease_get_lifetime(NDhcp4ClientLease *lease, uint64_t *ns_lifetimep) {
+ *ns_lifetimep = lease->lifetime;
+}
+
+/**
+ * n_dhcp4_client_lease_query() - query the lease for an option
+ * @lease: the lease to operate on
+ * @option: the DHCP4 option code
+ * @datap: return argument of the data pointer
+ * @n_datap: return argument of data length in bytes
+ *
+ * Query the lease for a given option. Options internal to the DHCP protocol cannot
+ * be queried, and only options that were explicitly requested can be queried.
+ *
+ * Return: 0 on success,
+ * N_DCHP4_E_INTERNAL if an invalid option is queried,
+ * N_DHCP4_E_UNSET if the lease did not contain the option, or
+ * a negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_lease_query(NDhcp4ClientLease *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;
+ }
+
+ /* XXX: refuse to return options that were not requested */
+
+ return n_dhcp4_incoming_query(lease->message, option, datap, n_datap);
+}
+
+/**
+ * n_dhcp4_client_lease_select() - select an offered lease
+ * @lease: lease to operate on
+ *
+ * Select a lease. This must be a lease that was offered, once
+ * one of the leases that were offered in response to a probe was
+ * selected none of the others can be.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease) {
+ NDhcp4ClientLease *l, *t_l;
+ NDhcp4ClientProbe *probe;
+ int r;
+
+ /* XXX error handling, this must be an OFFER */
+
+ if (!lease->probe)
+ return -ENOTRECOVERABLE;
+ if (lease->probe->current_lease)
+ return -ENOTRECOVERABLE;
+
+ r = n_dhcp4_client_probe_transition_select(lease->probe, lease->message, n_dhcp4_gettime(CLOCK_BOOTTIME));
+ if (r)
+ return r;
+
+ /*
+ * Only one of the offered leases can be selected, so flush the list.
+ * All offered lease, including this one are now dead.
+ */
+ probe = lease->probe;
+ c_list_for_each_entry_safe(l, t_l, &probe->lease_list, probe_link)
+ n_dhcp4_client_lease_unlink(l);
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_lease_accept() - accept an ack'ed lease
+ * @lease: lease to operate on
+ *
+ * Accept a lease. This must be a lease that was ack'ed by the
+ * server.
+ *
+ * The offered IP address must be fully configured before the lease
+ * can be accepted.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease) {
+ int r;
+
+ /* XXX error handling, this must be an ACK */
+
+ if (!lease->probe)
+ return -ENOTRECOVERABLE;
+ if (lease->probe->current_lease != lease)
+ return -ENOTRECOVERABLE;
+
+ r = n_dhcp4_client_probe_transition_accept(lease->probe, lease->message);
+ if (r)
+ return r;
+
+ n_dhcp4_client_lease_unlink(lease);
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_lease_decline() - decline an ack'ed lease
+ * @lease: lease to operate on
+ *
+ * Decline a lease. This must be a lease that was ack'ed by the
+ * server.
+ *
+ * The offered IP address must not be used once the lease has been
+ * decline.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error) {
+ int r;
+
+ /* XXX: error handling, this must be an ACK */
+
+ if (!lease->probe)
+ return -ENOTRECOVERABLE;
+ if (lease->probe->current_lease != lease)
+ return -ENOTRECOVERABLE;
+
+ r = n_dhcp4_client_probe_transition_decline(lease->probe, lease->message, error, n_dhcp4_gettime(CLOCK_BOOTTIME));
+ if (r)
+ return r;
+
+ lease->probe->current_lease = n_dhcp4_client_lease_unref(lease->probe->current_lease);
+ n_dhcp4_client_lease_unlink(lease);
+
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-c-probe.c b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
new file mode 100644
index 000000000..67fedafff
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-c-probe.c
@@ -0,0 +1,1202 @@
+/*
+ * DHCPv4 Client Probes
+ *
+ * The probe object is used to represent the lifetime of a DHCP client session.
+ * A running probe discovers DHCP servers, requests a lease, and maintains that
+ * lease.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+#include "n-dhcp4-private.h"
+
+
+static int n_dhcp4_client_probe_option_new(NDhcp4ClientProbeOption **optionp,
+ uint8_t option,
+ const void *data,
+ uint8_t n_data) {
+ NDhcp4ClientProbeOption *op;
+
+ op = malloc(sizeof(op) + n_data);
+ if (!op)
+ return -ENOMEM;
+
+ op->option = option;
+ op->n_data = n_data;
+ memcpy(op->data, data, n_data);
+
+ *optionp = op;
+ return 0;
+}
+
+static void n_dhcp4_client_probe_option_free(NDhcp4ClientProbeOption *option) {
+ if (option)
+ free(option);
+}
+
+/**
+ * n_dhcp4_client_probe_config_new() - create new probe configuration
+ * @configp: output argument to store new configuration
+ *
+ * This creates a new probe configuration object. The object is a collection of
+ * parameters for probes. No data verification is done by the configuration
+ * object. Instead, when passing the configuration to the constructor of a
+ * probe, this constructor will perform parameter validation.
+ *
+ * A probe configuration is an unlinked object only used to pass information to
+ * a probe constructor. The caller fully owns the returned configuration object
+ * and is responsible to free it when no longer needed.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_probe_config_new(NDhcp4ClientProbeConfig **configp) {
+ _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL;
+
+ config = calloc(1, sizeof(*config));
+ if (!config)
+ return -ENOMEM;
+
+ *config = (NDhcp4ClientProbeConfig)N_DHCP4_CLIENT_PROBE_CONFIG_NULL(*config);
+
+ *configp = config;
+ config = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_config_free() - destroy probe configuration
+ * @config: configuration to operate on, or NULL
+ *
+ * This destroys a probe configuration object and deallocates all its
+ * resources.
+ *
+ * If @config is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+_c_public_ NDhcp4ClientProbeConfig *n_dhcp4_client_probe_config_free(NDhcp4ClientProbeConfig *config) {
+ if (!config)
+ return NULL;
+
+ for (unsigned int i = 0; i <= UINT8_MAX; ++i)
+ n_dhcp4_client_probe_option_free(config->options[i]);
+
+ free(config);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_probe_config_dup() - duplicate probe configuration
+ * @config: configuration to operate on
+ * @dupp: output argument for duplicate
+ *
+ * This duplicates the probe configuration given as @config and returns it in
+ * @dupp to the caller.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_client_probe_config_dup(NDhcp4ClientProbeConfig *config,
+ NDhcp4ClientProbeConfig **dupp) {
+ _c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *dup = NULL;
+ int r;
+
+ r = n_dhcp4_client_probe_config_new(&dup);
+ if (r)
+ return r;
+
+ dup->inform_only = config->inform_only;
+ dup->init_reboot = config->init_reboot;
+ dup->requested_ip = config->requested_ip;
+ dup->ms_start_delay = config->ms_start_delay;
+
+ for (unsigned int i = 0; i < config->n_request_parameters; ++i)
+ dup->request_parameters[dup->n_request_parameters++] = config->request_parameters[i];
+
+ for (unsigned int i = 0; i <= UINT8_MAX; ++i) {
+ if (!config->options[i])
+ break;
+
+ r = n_dhcp4_client_probe_option_new(&dup->options[i],
+ config->options[i]->option,
+ config->options[i]->data,
+ config->options[i]->n_data);
+ if (r)
+ return r;
+ }
+
+ *dupp = dup;
+ dup = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_config_set_inform_only() - set inform-only property
+ * @config: configuration to operate on
+ * @inform_only: value to set
+ *
+ * This sets the inform-only property of the given configuration object. This
+ * property controls whether the client probe should request a full lease, or
+ * whether it should just ask for auxiliary information without requesting an
+ * address.
+ *
+ * The default is to request a full lease and address. If inform-only is set to
+ * true, only auxiliary information will be requested.
+ *
+ * XXX: This is currently not implemented, and setting the property has no effect.
+ */
+_c_public_ void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeConfig *config, bool inform_only) {
+ config->inform_only = inform_only;
+}
+
+/**
+ * n_dhcp4_client_probe_config_set_init_reboot() - set init-reboot property
+ * @config: configuration to operate on
+ * @init_reboot: value to set
+ *
+ * This sets the init-reboot property of the given configuration object. If this
+ * is enabled, a requested IP address must also be set.
+ *
+ * The default is false. If set to true, a probe will make use of the
+ * INIT-REBOOT path, as described by the DHCP specification. In most cases, you
+ * do not want this.
+ *
+ * XXX: This is currently not implemented, and setting the property has no effect.
+ *
+ * Background: The INIT-REBOOT path allows a DHCP client to skip
+ * server-discovery when rebooting/resuming their machine. The DHCP
+ * client simply re-requests the lease it had acquired before. This
+ * saves one roundtrip in the success-case, since the DISCOVER step
+ * is skipped. However, there are little to no timeouts involved,
+ * so the roundtrip should be barely noticeable. In contrast, if
+ * the INIT-REBOOT fails (because the lease is no longer valid, or
+ * not valid on this network), the client has to wait for a
+ * possible answer to the request before actually starting the DHCP
+ * process all over. This significantly increases the time needed
+ * to switch networks.
+ * The INIT-REBOOT state might have been a real improvements with
+ * the old resend-timeouts mandated by the DHCP specification.
+ * However, on modern networks with improved timeout values we
+ * recommend against using it.
+ */
+_c_public_ void n_dhcp4_client_probe_config_set_init_reboot(NDhcp4ClientProbeConfig *config, bool init_reboot) {
+ config->init_reboot = init_reboot;
+}
+
+/**
+ * n_dhcp4_client_probe_config_set_requested_ip() - set requested-ip property
+ * @config: configuration to operate on
+ * @ip: value to set
+ *
+ * This sets the requested-ip property of the given configuration object.
+ *
+ * The default is all 0. If set to something else, the DHCP discovery will
+ * include this IP in its requests to tell DHCP servers which address to pick.
+ * Servers are not required to honor this, nor does this have any effect on
+ * servers not serving this address.
+ *
+ * This field should always be set if the caller knows of an address that was
+ * previously acquired on this network. It serves as hint to servers and will
+ * allow them to provide the same address again.
+ */
+_c_public_ void n_dhcp4_client_probe_config_set_requested_ip(NDhcp4ClientProbeConfig *config, struct in_addr ip) {
+ config->requested_ip = ip;
+}
+
+/**
+ * n_dhcp4_client_probe_config_set_start_delay() - set start delay
+ * @config: configuration to operate on
+ * @msecs: value to set
+ *
+ * This sets the start delay property of the given configuration object.
+ *
+ * The default is 9000 ms, which is based on RFC2131. In the RFC the start
+ * delay is specified to be a random value in the range 1000 to 10.000 ms.
+ * However, there does not appear to be any particular reason to
+ * unconditionally wait at least one second, so we move the range down to
+ * start at 0 ms. The reaon for the random delay is to avoid network-wide
+ * events causing too much simultaneous network traffic. However, on modern
+ * networks, a more reasonable value may be in the 10 ms range.
+ */
+_c_public_ void n_dhcp4_client_probe_config_set_start_delay(NDhcp4ClientProbeConfig *config, uint64_t msecs) {
+ config->ms_start_delay = msecs;
+}
+
+/**
+ * n_dhpc4_client_probe_config_request_option() - append option to request from the server
+ * @config: configuration to operate on
+ * @option: option to request
+ *
+ * This adds an option to the list of options to request from the server.
+ *
+ * A server may send options that we do not requst, and it may omit options
+ * that we do request. However, to increase the likelyhood of uniform behavior
+ * between server implementations, we do not expose options that were not
+ * explicitly requested.
+ *
+ * When called multiple times, the order matters. Earlier requests are
+ * considered higher priority than later requests, in case the server must omit
+ * some, due to a lack of space. If the same option is requested more than once,
+ * only the first call has an effect.
+ */
+_c_public_ void n_dhcp4_client_probe_config_request_option(NDhcp4ClientProbeConfig *config, uint8_t option) {
+ for (unsigned int i = 0; i < config->n_request_parameters; ++i) {
+ if (config->request_parameters[i] == option)
+ return;
+ }
+
+ c_assert(config->n_request_parameters <= UINT8_MAX);
+
+ config->request_parameters[config->n_request_parameters++] = option;
+}
+
+/**
+ * n_dhcp4_client_probe_config_append_option() - append option to outgoing messages
+ * @config: configuration to operate on
+ * @option: DHCP option number
+ * @data: payload
+ * @n_data: number of bytes in payload
+ *
+ * This sets extra options on a given configuration object.
+ *
+ * These options are appended verbatim to outgoing messages where
+ * that is supported by the specification. The same options are
+ * appended to all messages.
+ *
+ * No option may be appended more than once. Options considered internal
+ * to the DHCP protocol may not be appended.
+ *
+ * Return: 0 on success, N_DHCP4_E_DUPLICATE_OPTION if an option has already been
+ * appended, N_DHCP4_E_INTERNAL if the option is not configurable, or
+ * a negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_probe_config_append_option(NDhcp4ClientProbeConfig *config,
+ uint8_t option,
+ const void *data,
+ uint8_t n_data) {
+ int r;
+
+ /* XXX: filter internal options */
+
+ for (unsigned int i = 0; i <= UINT8_MAX; ++i) {
+ if (config->options[i]) {
+ if (config->options[i]->option == option)
+ return N_DHCP4_E_DUPLICATE_OPTION;
+
+ continue;
+ }
+
+ r = n_dhcp4_client_probe_option_new(&config->options[i],
+ option,
+ data,
+ n_data);
+ if (r)
+ return r;
+
+ return 0;
+ }
+
+ c_assert(0);
+ return -ENOTRECOVERABLE;
+}
+
+static void n_dhcp4_client_probe_config_initialize_random_seed(NDhcp4ClientProbeConfig *config) {
+ uint8_t hash_seed[] = {
+ 0x25, 0x3f, 0x02, 0x75, 0x3a, 0xb8, 0x4f, 0x91,
+ 0x9d, 0x0a, 0xd6, 0x15, 0x9d, 0x72, 0x7b, 0xcb,
+ };
+ CSipHash hash = C_SIPHASH_NULL;
+ unsigned short int seed16v[3];
+ const uint8_t *p;
+ uint64_t u64;
+ int r;
+
+ /*
+ * Initialize seed48_r(3)
+ *
+ * We need random jitter for all timeouts and delays, used to reduce
+ * network traffic during bursts. This is not meant as security measure
+ * but only meant to improve network utilization during bursts. The
+ * random source is thus negligible. However, we want, under all
+ * circumstances, avoid two instances running with the same seed. Thus
+ * we source the seed from AT_RANDOM, which grants us a per-process
+ * unique seed. We then add the current time to make sure consequetive
+ * instances use different seeds (to avoid clashes if processes are
+ * duplicated, or similar), and lastly we add the config memory address
+ * to avoid clashes of multiple parallel instances.
+ *
+ * Again, none of these are meant as security measure, but only to
+ * avoid *ACCIDENTAL* seed clashes. That is, in the case that many
+ * transactions are started in parallel, we delay the individual
+ * messages (as described in the spec), to reduce the traffic on the
+ * network and the chance of packets being dropped (and thus triggering
+ * timeouts and resends).
+ *
+ * We hash everything through SipHash, to avoid exposing AT_RANDOM and
+ * other sources to the network. We use a static salt to distinguish it
+ * from other implementations using the same random source.
+ */
+ c_siphash_init(&hash, hash_seed);
+
+ p = (const uint8_t *)getauxval(AT_RANDOM);
+ if (p)
+ c_siphash_append(&hash, p, 16);
+
+ u64 = n_dhcp4_gettime(CLOCK_MONOTONIC);
+ c_siphash_append(&hash, (const uint8_t *)&u64, sizeof(u64));
+
+ c_siphash_append(&hash, (const uint8_t *)&config, sizeof(config));
+
+ u64 = c_siphash_finalize(&hash);
+
+ seed16v[0] = (u64 >> 0) ^ (u64 >> 48);
+ seed16v[1] = (u64 >> 16) ^ (u64 >> 0);
+ seed16v[2] = (u64 >> 32) ^ (u64 >> 16);
+
+ r = seed48_r(seed16v, &config->entropy);
+ c_assert(!r);
+}
+
+/**
+ * n_dhcp4_client_probe_config_get_random() - get random data
+ * @config: config object to operate on
+ *
+ * Fetch the next 32bit random number from the entropy pool in @config.
+ * Note that this is in no way suitable for security purposes.
+ *
+ * Return: the random data.
+ */
+uint32_t n_dhcp4_client_probe_config_get_random(NDhcp4ClientProbeConfig *config) {
+ long int result;
+ int r;
+
+ r = mrand48_r(&config->entropy, &result);
+ c_assert(!r);
+
+ return result;
+};
+
+/**
+ * n_dhcp4_client_probe_new() - create new client probe
+ * @probep: output argument for new client probe
+ * @config: probe configuration
+ * @client: client to probe on behalf of
+ * @ns_now: the current time
+ *
+ * This creates a new client probe object.
+ *
+ * If one is already running, the new one will be immediately (but asynchronously)
+ * cancelled. Otherwise, a DISCOVER event is scheduled after a randomized delay.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
+ NDhcp4ClientProbeConfig *config,
+ NDhcp4Client *client,
+ uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_client_probe_freep) NDhcp4ClientProbe *probe = NULL;
+ bool active;
+ int r;
+
+ /*
+ * If there is already a probe attached, we create the new probe in
+ * detached state. It will not be linked into the epoll context and not
+ * be useful in any way. We immediately raise the CANCELLED event to
+ * notify the caller about it.
+ */
+ active = !client->current_probe;
+
+ probe = calloc(1, sizeof(*probe));
+ if (!probe)
+ return -ENOMEM;
+
+ *probe = (NDhcp4ClientProbe)N_DHCP4_CLIENT_PROBE_NULL(*probe);
+ probe->client = n_dhcp4_client_ref(client);
+
+ r = n_dhcp4_client_probe_config_dup(config, &probe->config);
+ if (r)
+ return r;
+
+ /*
+ * XXX: make seed initialization optional, so the entropy can be reused.
+ */
+ n_dhcp4_client_probe_config_initialize_random_seed(probe->config);
+
+ r = n_dhcp4_c_connection_init(&probe->connection,
+ client->config,
+ probe->config,
+ active ? client->fd_epoll : -1);
+ if (r)
+ return r;
+
+ if (active) {
+ /*
+ * Defer the sending of DISCOVER by a random amount (by default up to 9 seconds).
+ */
+ probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL));
+ probe->client->current_probe = probe;
+ } else {
+ r = n_dhcp4_client_probe_raise(probe,
+ NULL,
+ N_DHCP4_CLIENT_EVENT_CANCELLED);
+ if (r)
+ return r;
+ }
+
+ *probep = probe;
+ probe = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_free() - destroy a probe
+ * @probe: probe to operate on, or NULL
+ *
+ * This destroys a probe object and deallocates all its resources.
+ *
+ * If @probe is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+_c_public_ NDhcp4ClientProbe *n_dhcp4_client_probe_free(NDhcp4ClientProbe *probe) {
+ NDhcp4CEventNode *node, *t_node;
+ NDhcp4ClientLease *lease, *t_lease;
+
+ if (!probe)
+ return NULL;
+
+ c_list_for_each_entry_safe(lease, t_lease, &probe->lease_list, probe_link)
+ n_dhcp4_client_lease_unlink(lease);
+
+ c_list_for_each_entry_safe(node, t_node, &probe->event_list, probe_link)
+ n_dhcp4_c_event_node_free(node);
+
+ if (probe == probe->client->current_probe)
+ probe->client->current_probe = NULL;
+
+ n_dhcp4_c_connection_deinit(&probe->connection);
+ n_dhcp4_client_unref(probe->client);
+ n_dhcp4_client_probe_config_free(probe->config);
+
+ c_assert(c_list_is_empty(&probe->lease_list));
+ c_assert(c_list_is_empty(&probe->event_list));
+ free(probe);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_probe_set_userdata() - set userdata pointer
+ * @probe: the probe to operate on
+ * @userdata: pointer to userdata
+ *
+ * Set a userdata pointer. The pointed to data is still owned by the caller, and
+ * is completely opaque to the probe.
+ */
+_c_public_ void n_dhcp4_client_probe_set_userdata(NDhcp4ClientProbe *probe, void *userdata) {
+ probe->userdata = userdata;
+}
+
+/**
+ * n_dhcp4_client_probe_get_userdata() - get userdata pointer
+ * @probe: the probe to operate on
+ * @userdatap: return pointer for userdata pointer
+ *
+ * Get the userdata pointer. The lifetime of the userdata and making sure it is
+ * still valid when accessed via the probe is the responsibility of the caller.
+ */
+_c_public_ void n_dhcp4_client_probe_get_userdata(NDhcp4ClientProbe *probe, void **userdatap) {
+ *userdatap = probe->userdata;
+}
+
+/**
+ * n_dhcp4_client_probe_raise() - XXX
+ */
+int n_dhcp4_client_probe_raise(NDhcp4ClientProbe *probe, NDhcp4CEventNode **nodep, unsigned int event) {
+ NDhcp4CEventNode *node;
+ int r;
+
+ r = n_dhcp4_client_raise(probe->client, &node, event);
+ if (r)
+ return r;
+
+ switch (event) {
+ case N_DHCP4_CLIENT_EVENT_OFFER:
+ node->event.offer.probe = probe;
+ break;
+ case N_DHCP4_CLIENT_EVENT_GRANTED:
+ node->event.granted.probe = probe;
+ break;
+ case N_DHCP4_CLIENT_EVENT_RETRACTED:
+ node->event.retracted.probe = probe;
+ break;
+ case N_DHCP4_CLIENT_EVENT_EXTENDED:
+ node->event.extended.probe = probe;
+ break;
+ case N_DHCP4_CLIENT_EVENT_EXPIRED:
+ node->event.expired.probe = probe;
+ break;
+ case N_DHCP4_CLIENT_EVENT_CANCELLED:
+ node->event.cancelled.probe = probe;
+ break;
+ default:
+ c_assert(0);
+ n_dhcp4_c_event_node_free(node);
+ return -ENOTRECOVERABLE;
+ }
+
+ if (nodep)
+ *nodep = node;
+ return 0;
+}
+
+void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeoutp) {
+ uint64_t t1 = 0;
+ uint64_t t2 = 0;
+ uint64_t lifetime = 0;
+ uint64_t timeout = 0;
+
+ if (probe->current_lease) {
+ t1 = probe->current_lease->t1;
+ t2 = probe->current_lease->t2;
+ lifetime = probe->current_lease->lifetime;
+ }
+
+ n_dhcp4_c_connection_get_timeout(&probe->connection, &timeout);
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ if (probe->ns_deferred && (!timeout || probe->ns_deferred < timeout))
+ timeout = probe->ns_deferred;
+
+ break;
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ if (t1 && (!timeout || t1 < timeout))
+ timeout = t1;
+
+ /* fall-through */
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ if (t2 && (!timeout || t2 < timeout))
+ timeout = t2;
+
+ /* fall-through */
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ if (lifetime && (!timeout || lifetime < timeout))
+ timeout = lifetime;
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+
+ *timeoutp = timeout;
+}
+
+static int n_dhcp4_client_probe_outgoing_append_options(NDhcp4ClientProbe *probe, NDhcp4Outgoing *outgoing) {
+ int r;
+
+ for (unsigned int i = 0; i <= UINT8_MAX; ++i) {
+ if (!probe->config->options[i])
+ break;
+
+ r = n_dhcp4_outgoing_append(outgoing,
+ probe->config->options[i]->option,
+ probe->config->options[i]->data,
+ probe->config->options[i]->n_data);
+ if (r) {
+ if (r == N_DHCP4_E_NO_SPACE)
+ /* XXX */
+ break;
+
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ r = n_dhcp4_c_connection_listen(&probe->connection);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_discover_new(&probe->connection, &request);
+ if (r)
+ return r;
+
+ if (probe->config->requested_ip.s_addr != INADDR_ANY) {
+ r = n_dhcp4_outgoing_append_requested_ip(request, probe->config->requested_ip);
+ if (r)
+ return r;
+ }
+
+ r = n_dhcp4_client_probe_outgoing_append_options(probe, request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
+ if (r)
+ return r;
+ else
+ request = NULL; /* consumed */
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_SELECTING;
+ probe->ns_deferred = 0;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ abort();
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_t1(NDhcp4ClientProbe *probe, uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ r = n_dhcp4_c_connection_renew_new(&probe->connection, &request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_probe_outgoing_append_options(probe, request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
+ if (r)
+ return r;
+ else
+ request = NULL; /* consumed */
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_RENEWING;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ abort();
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_t2(NDhcp4ClientProbe *probe, uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ r = n_dhcp4_c_connection_rebind_new(&probe->connection, &request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_probe_outgoing_append_options(probe, request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
+ if (r)
+ return r;
+ else
+ request = NULL; /* consumed */
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_REBINDING;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ abort();
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_lifetime(NDhcp4ClientProbe *probe) {
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+
+ /* XXX */
+
+ r = n_dhcp4_client_probe_raise(probe,
+ NULL,
+ N_DHCP4_CLIENT_EVENT_EXPIRED);
+ if (r)
+ return r;
+
+ c_assert(probe->client->current_probe == probe);
+ probe->client->current_probe = NULL;
+
+ n_dhcp4_c_connection_close(&probe->connection);
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_EXPIRED;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ abort();
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) {
+ _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL;
+ NDhcp4CEventNode *node;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+
+ r = n_dhcp4_client_probe_raise(probe,
+ &node,
+ N_DHCP4_CLIENT_EVENT_OFFER);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_lease_new(&lease, message);
+ if (r)
+ return r;
+
+ /* message consumed, do not fail */
+
+ n_dhcp4_client_lease_link(lease, probe);
+
+ node->event.offer.lease = n_dhcp4_client_lease_ref(lease);
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) {
+ _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL;
+ NDhcp4CEventNode *node;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+
+ r = n_dhcp4_client_probe_raise(probe,
+ &node,
+ N_DHCP4_CLIENT_EVENT_EXTENDED);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_lease_new(&lease, message);
+ if (r)
+ return r;
+
+ /* message consumed, do not fail */
+
+ n_dhcp4_client_lease_link(lease, probe);
+
+ node->event.extended.lease = n_dhcp4_client_lease_ref(lease);
+ n_dhcp4_client_lease_unref(probe->current_lease);
+ probe->current_lease = n_dhcp4_client_lease_ref(lease);
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+
+ r = n_dhcp4_client_probe_raise(probe,
+ &node,
+ N_DHCP4_CLIENT_EVENT_GRANTED);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_lease_new(&lease, message);
+ if (r)
+ return r;
+
+ /* message consumed, don to fail */
+
+ n_dhcp4_client_lease_link(lease, probe);
+
+ node->event.granted.lease = n_dhcp4_client_lease_ref(lease);
+ probe->current_lease = n_dhcp4_client_lease_ref(lease);
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_GRANTED;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_probe_transition_nak(NDhcp4ClientProbe *probe) {
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+
+ /* XXX */
+
+ r = n_dhcp4_client_probe_raise(probe,
+ NULL,
+ N_DHCP4_CLIENT_EVENT_RETRACTED);
+ if (r)
+ return r;
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT;
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+int n_dhcp4_client_probe_transition_select(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ r = n_dhcp4_c_connection_select_new(&probe->connection, &request, offer);
+ if (r)
+ return r;
+
+ r = n_dhcp4_client_probe_outgoing_append_options(probe, request);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
+ if (r)
+ return r;
+ else
+ request = NULL; /* consumed */
+
+ /* XXX: ignore other offers */
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_REQUESTING;
+
+ break;
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_transition_accept() - XXX
+ */
+int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incoming *ack) {
+ struct in_addr client = {};
+ struct in_addr server = {};
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ n_dhcp4_incoming_get_yiaddr(ack, &client);
+
+ r = n_dhcp4_incoming_query_server_identifier(ack, &server);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_connect(&probe->connection, &client, &server);
+ if (r)
+ return r;
+
+ probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND;
+
+ /* XXX: trigger timers */
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * n_dhc4_client_probe_transition_decline() - XXX
+ */
+int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, const char *error, uint64_t ns_now) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL;
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ r = n_dhcp4_c_connection_decline_new(&probe->connection, &request, offer, error);
+ if (r)
+ return r;
+
+ r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now);
+ if (r)
+ return r;
+ else
+ request = NULL; /* consumed */
+
+ /* XXX: what state to transition to? */
+
+ break;
+
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_SELECTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING:
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED:
+ default:
+ /* ignore */
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_dispatch_timer() - XXX
+ */
+int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_now) {
+ int r;
+
+ switch (probe->state) {
+ case N_DHCP4_CLIENT_PROBE_STATE_INIT:
+ if (ns_now >= probe->ns_deferred) {
+ r = n_dhcp4_client_probe_transition_deferred(probe, ns_now);
+ if (r)
+ return r;
+ }
+
+ break;
+ case N_DHCP4_CLIENT_PROBE_STATE_GRANTED:
+ if (ns_now >= probe->current_lease->lifetime) {
+ r = n_dhcp4_client_probe_transition_lifetime(probe);
+ if (r)
+ return r;
+ }
+
+ break;
+ case N_DHCP4_CLIENT_PROBE_STATE_BOUND:
+ case N_DHCP4_CLIENT_PROBE_STATE_RENEWING:
+ case N_DHCP4_CLIENT_PROBE_STATE_REBINDING:
+ if (ns_now >= probe->current_lease->lifetime) {
+ r = n_dhcp4_client_probe_transition_lifetime(probe);
+ if (r)
+ return r;
+ } else if (ns_now >= probe->current_lease->t2) {
+ r = n_dhcp4_client_probe_transition_t2(probe, ns_now);
+ if (r)
+ return r;
+ } else if (ns_now >= probe->current_lease->t1) {
+ r = n_dhcp4_client_probe_transition_t1(probe, ns_now);
+ if (r)
+ return r;
+ }
+
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+
+ r = n_dhcp4_c_connection_dispatch_timer(&probe->connection, ns_now);
+ if (r)
+ return r;
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_dispatch_connection() - XXX
+ */
+int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events) {
+ _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
+ uint8_t type;
+ int r;
+
+ r = n_dhcp4_c_connection_dispatch_io(&probe->connection, &message);
+ if (r) {
+ if (r == N_DHCP4_E_AGAIN)
+ return 0;
+ else if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED) {
+ /*
+ * We fetched something from the sockets, which we
+ * discarded. We don't know whether there is more data
+ * to fetch, so we set the preempted flag to notify the
+ * caller we want to be called again.
+ */
+ probe->client->preempted = true;
+ return 0;
+ }
+
+ return r;
+ }
+
+ /*
+ * We fetched something from the sockets, which we will handle below.
+ * We don't know whether there is more data to fetch, so we set the
+ * preempted flag to notify the caller we want to be called again.
+ */
+ probe->client->preempted = true;
+
+ r = n_dhcp4_incoming_query_message_type(message, &type);
+ if (r == N_DHCP4_E_UNSET || r == N_DHCP4_E_MALFORMED)
+ /*
+ * XXX: this can never happen as we already queried the message
+ * type.
+ */
+ return 0;
+
+ switch (type) {
+ case N_DHCP4_MESSAGE_OFFER:
+ r = n_dhcp4_client_probe_transition_offer(probe, message);
+ if (r)
+ return r;
+ else
+ message = NULL; /* consumed */
+ break;
+ case N_DHCP4_MESSAGE_ACK:
+ r = n_dhcp4_client_probe_transition_ack(probe, message);
+ if (r)
+ return r;
+ else
+ message = NULL; /* consumed */
+ break;
+ case N_DHCP4_MESSAGE_NAK:
+ r = n_dhcp4_client_probe_transition_nak(probe);
+ if (r)
+ return r;
+ break;
+ default:
+ /*
+ * We receiveda message type we do not support, simply discard
+ * it.
+ */
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe_update_mtu() - XXX
+ */
+int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu) {
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-client.c b/shared/n-dhcp4/src/n-dhcp4-client.c
new file mode 100644
index 000000000..a492a74c6
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-client.c
@@ -0,0 +1,831 @@
+/*
+ * Client Side of the Dynamic Host Configuration Protocol for IPv4
+ *
+ * This implements the public API around the NDhcp4Client object. The client
+ * object is simply a context to track running probes. It manages pending
+ * events of all probes, as well as forwards the dispatching requests whenever
+ * the dispatcher is run.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+#include "n-dhcp4-private.h"
+
+/**
+ * n_dhcp4_client_config_new() - allocate new client configuration
+ * @configp: output argument for new client config
+ *
+ * This creates a new client configuration object. Client configurations are
+ * unlinked objects that merely serve as collection of parameters. They do not
+ * perform validity checks.
+ *
+ * The new client configuration is fully owned by the caller. They are
+ * responsible to free the object if no longer needed.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_config_new(NDhcp4ClientConfig **configp) {
+ _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL;
+
+ config = calloc(1, sizeof(*config));
+ if (!config)
+ return -ENOMEM;
+
+ *config = (NDhcp4ClientConfig)N_DHCP4_CLIENT_CONFIG_NULL(*config);
+
+ *configp = config;
+ config = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_config_free() - destroy client configuration
+ * @config: client configuration to operate on, or NULL
+ *
+ * This destroys a client configuration and deallocates all its resources. If
+ * NULL is passed, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+_c_public_ NDhcp4ClientConfig *n_dhcp4_client_config_free(NDhcp4ClientConfig *config) {
+ if (!config)
+ return NULL;
+
+ free(config->client_id);
+ free(config);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_config_dup() - duplicate client configuration
+ * @config: client configuration to operate on
+ * @dupp: output argument for duplicate
+ *
+ * This duplicates the client configuration given as @config and returns it in
+ * @dupp to the caller.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config, NDhcp4ClientConfig **dupp) {
+ _c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *dup = NULL;
+ int r;
+
+ r = n_dhcp4_client_config_new(&dup);
+ if (r)
+ return r;
+
+ dup->ifindex = config->ifindex;
+ dup->transport = config->transport;
+ dup->request_broadcast = config->request_broadcast;
+ memcpy(dup->mac, config->mac, sizeof(dup->mac));
+ dup->n_mac = config->n_mac;
+ memcpy(dup->broadcast_mac, config->broadcast_mac, sizeof(dup->broadcast_mac));
+ dup->n_broadcast_mac = config->n_broadcast_mac;
+
+ r = n_dhcp4_client_config_set_client_id(dup,
+ config->client_id,
+ config->n_client_id);
+ if (r)
+ return r;
+
+ *dupp = dup;
+ dup = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_config_set_ifindex() - set ifindex property
+ * @config: client configuration to operate on
+ * @ifindex: ifindex to set
+ *
+ * This sets the ifindex property of the client configuration. The ifindex
+ * specifies the network device that a DHCP client will run on.
+ */
+_c_public_ void n_dhcp4_client_config_set_ifindex(NDhcp4ClientConfig *config, int ifindex) {
+ config->ifindex = ifindex;
+}
+
+/**
+ * n_dhcp4_client_config_set_transport() - set transport property
+ * @config: client configuration to operate on
+ * @transport: transport to set
+ *
+ * This sets the transport property of the client configuration. The transport
+ * defines the hardware transport of the network device that a DHCP client
+ * runs on.
+ *
+ * This takes one of the N_DHCP4_TRANSPORT_* identifiers as argument.
+ */
+_c_public_ void n_dhcp4_client_config_set_transport(NDhcp4ClientConfig *config, unsigned int transport) {
+ config->transport = transport;
+}
+
+/**
+ * n_dhcp4_client_config_set_request_broadcast() - set request-broadcast property
+ * @config: configuration to operate on
+ * @request_broadcast: value to set
+ *
+ * This sets the request_broadcast property of the given configuration object.
+ *
+ * The default is false. If set to true, a the server will be told to not unicast
+ * replies to the client's IP address before it has been configured, but broadcast
+ * to INADDR_ANY instead. In most cases, you do not want this.
+ *
+ * Background: OFFER and ACK messages from DHCP servers to clients are unicast
+ * to the IP address handed out, even before the IP address has
+ * been configured on the taregt interface. This usually works
+ * because the correct destination hardware address is explicitly
+ * set on the outgoing packets, rather than being resolved (which
+ * would not work). However, some hardware does not accept incoming
+ * IP packets destined for addresses they do not own, even if the
+ * hardware address is correct. In this case, the server must
+ * broadcast the replies in order for the client to receive them.
+ * In general, unneccesary broadcasting is something one wants to
+ * avoid, and some networks will not deliver broadcasts to the
+ * client at all, in which case this flag must not be set.
+ */
+_c_public_ void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, bool request_broadcast) {
+ config->request_broadcast = request_broadcast;
+}
+
+/**
+ * n_dhcp4_client_config_set_mac() - set mac property
+ * @config: client configuration to operate on
+ * @mac: hardware address to set
+ * @n_mac: length of the hardware address
+ *
+ * This sets the mac property of the client configuration. It specifies the
+ * hardware address of the local interface that the DHCP client runs on.
+ *
+ * This function copies the specified hardware address into @config. Any
+ * hardware address is supported. It is up to the consumer of the client
+ * configuration to verify the validity of the hardware address.
+ *
+ * Note: This function may truncate the hardware address internally, but
+ * retains the original length. The consumer of this configuration can
+ * thus tell whether the data was truncated and will refuse it.
+ * The internal buffer is big enough to hold any hardware address of all
+ * supported transports. Thus, truncation only happens if you use
+ * unsupported transports, and those will be rejected, anyway.
+ */
+_c_public_ void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac) {
+ config->n_mac = n_mac;
+ memcpy(config->mac, mac, c_min(n_mac, sizeof(config->mac)));
+}
+
+/**
+ * n_dhcp4_client_config_set_broadcast_mac() - set broadcast-mac property
+ * @config: client configuration to operate on
+ * @mac: hardware address to set
+ * @n_mac: length of the hardware address
+ *
+ * This sets the broadcast-mac property of the client configuration. It
+ * specifies the destination hardware address to use for broadcasts on the
+ * local interface that the DHCP client runs on.
+ *
+ * This function copies the specified hardware address into @config. Any
+ * hardware address is supported. It is up to the consumer of the client
+ * configuration to verify the validity of the hardware address.
+ *
+ * Note: This function may truncate the hardware address internally, but
+ * retains the original length. The consumer of this configuration can
+ * thus tell whether the data was truncated and will refuse it.
+ * The internal buffer is big enough to hold any hardware address of all
+ * supported transports. Thus, truncation only happens if you use
+ * unsupported transports, and those will be rejected, anyway.
+ */
+_c_public_ void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac) {
+ config->n_broadcast_mac = n_mac;
+ memcpy(config->broadcast_mac, mac, c_min(n_mac, sizeof(config->broadcast_mac)));
+}
+
+/**
+ * n_dhcp4_client_config_set_client_id() - set client-id property
+ * @config: client configuration to operate on
+ * @id: client id
+ * @n_id: length of the client id in bytes
+ *
+ * This sets the client-id property of @config. It copies the entire client-id
+ * buffer into the configuration.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id) {
+ uint8_t *t;
+
+ t = malloc(n_id + 1);
+ if (!t)
+ return -ENOMEM;
+
+ free(config->client_id);
+ config->client_id = t;
+ config->n_client_id = n_id;
+
+ memcpy(config->client_id, id, n_id);
+ config->client_id[n_id] = 0; /* safety 0 for debugging */
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_event_node_new() - allocate new event
+ * @nodep: output argument for new event
+ *
+ * This allocates a new event node and returns it to the caller. The caller
+ * fully owns the event-node and is reposonsible to either link it somewhere,
+ * or release it.
+ *
+ * Event nodes can be linked on a client object, as well as optionally on a
+ * probe object. As long as an event-node is linked, it will be retrievable by
+ * the API user through n_dhcp4_client_pop_event(). Furthermore, destruction of
+ * the client, or probe respectively, will clean-up all pending events.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_c_event_node_new(NDhcp4CEventNode **nodep) {
+ NDhcp4CEventNode *node;
+
+ node = calloc(1, sizeof(*node));
+ if (!node)
+ return -ENOMEM;
+
+ *node = (NDhcp4CEventNode)N_DHCP4_C_EVENT_NODE_NULL(*node);
+
+ *nodep = node;
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_event_node_free() - deallocate event
+ * @node: node to operate on, or NULL
+ *
+ * This deallocates the node given as @node. If the node is linked on a client
+ * or probe, it is unlinked automatically.
+ *
+ * If @probe is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node) {
+ if (!node)
+ return NULL;
+
+ switch (node->event.event) {
+ case N_DHCP4_CLIENT_EVENT_OFFER:
+ node->event.offer.lease = n_dhcp4_client_lease_unref(node->event.offer.lease);
+ break;
+ case N_DHCP4_CLIENT_EVENT_GRANTED:
+ node->event.granted.lease = n_dhcp4_client_lease_unref(node->event.granted.lease);
+ break;
+ case N_DHCP4_CLIENT_EVENT_EXTENDED:
+ node->event.extended.lease = n_dhcp4_client_lease_unref(node->event.extended.lease);
+ break;
+ default:
+ break;
+ }
+
+ c_list_unlink(&node->probe_link);
+ c_list_unlink(&node->client_link);
+ free(node);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_new() - allocate new client
+ * @clientp: output argument for new client
+ * @config: configuration to use
+ *
+ * This allocates a new DHCP4 client object and returns it in @clientp to the
+ * caller. The caller then owns a single ref-count to the object and is
+ * responsible to drop it, when no longer needed.
+ *
+ * The configuration given as @config is used to initialize the client. The
+ * caller is free to destroy the configuration once this function returns.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *config) {
+ _c_cleanup_(n_dhcp4_client_unrefp) NDhcp4Client *client = NULL;
+ struct epoll_event ev = {
+ .events = EPOLLIN,
+ };
+ int r;
+
+ c_assert(clientp);
+
+ /* verify configuration */
+ {
+ if (config->ifindex < 1)
+ return N_DHCP4_E_INVALID_IFINDEX;
+
+ switch (config->transport) {
+ case N_DHCP4_TRANSPORT_ETHERNET:
+ if (config->n_mac != ETH_ALEN ||
+ config->n_broadcast_mac != ETH_ALEN)
+ return N_DHCP4_E_INVALID_ADDRESS;
+
+ break;
+ case N_DHCP4_TRANSPORT_INFINIBAND:
+ if (config->n_mac != INFINIBAND_ALEN ||
+ config->n_broadcast_mac != INFINIBAND_ALEN)
+ return N_DHCP4_E_INVALID_ADDRESS;
+
+ break;
+ default:
+ return N_DHCP4_E_INVALID_TRANSPORT;
+ }
+
+ if (config->n_client_id < 1)
+ return N_DHCP4_E_INVALID_CLIENT_ID;
+ }
+
+ client = malloc(sizeof(*client));
+ if (!client)
+ return -ENOMEM;
+
+ *client = (NDhcp4Client)N_DHCP4_CLIENT_NULL(*client);
+
+ r = n_dhcp4_client_config_dup(config, &client->config);
+ if (r)
+ return r;
+
+ client->fd_epoll = epoll_create1(EPOLL_CLOEXEC);
+ if (client->fd_epoll < 0)
+ return -errno;
+
+ client->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK);
+ if (client->fd_timer < 0)
+ return -errno;
+
+ ev.data.u32 = N_DHCP4_CLIENT_EPOLL_TIMER;
+ r = epoll_ctl(client->fd_epoll, EPOLL_CTL_ADD, client->fd_timer, &ev);
+ if (r < 0)
+ return -errno;
+
+ *clientp = client;
+ client = NULL;
+ return 0;
+}
+
+static void n_dhcp4_client_free(NDhcp4Client *client) {
+ NDhcp4CEventNode *node, *t_node;
+
+ c_assert(!client->current_probe);
+
+ c_list_for_each_entry_safe(node, t_node, &client->event_list, client_link)
+ n_dhcp4_c_event_node_free(node);
+
+ if (client->fd_timer >= 0) {
+ epoll_ctl(client->fd_epoll, EPOLL_CTL_DEL, client->fd_timer, NULL);
+ close(client->fd_timer);
+ }
+
+ if (client->fd_epoll >= 0)
+ close(client->fd_epoll);
+
+ n_dhcp4_client_config_free(client->config);
+ free(client);
+}
+
+/**
+ * n_dhcp4_client_ref() - acquire client reference
+ * @client: client to operate on, or NULL
+ *
+ * This acquires a reference to the client given as @client. If @client is
+ * NULL, this function is a no-op.
+ *
+ * Return: @client is returned.
+ */
+_c_public_ NDhcp4Client *n_dhcp4_client_ref(NDhcp4Client *client) {
+ if (client)
+ ++client->n_refs;
+ return client;
+}
+
+/**
+ * n_dhcp4_client_unref() - release client reference
+ * @client: client to operate on, or NULL
+ *
+ * This releases a reference to the client given as @client. If @client is
+ * NULL, this is a no-op.
+ *
+ * Once the last reference is dropped, the client object will get destroyed and
+ * deallocated.
+ *
+ * Return: NULL is returned.
+ */
+_c_public_ NDhcp4Client *n_dhcp4_client_unref(NDhcp4Client *client) {
+ if (client && !--client->n_refs)
+ n_dhcp4_client_free(client);
+ return NULL;
+}
+
+/**
+ * n_dhcp4_client_raise() - raise event
+ * @client: client to operate on
+ * @nodep: output argument for new event, or NULL
+ * @event: event type to use
+ *
+ * This creates a new event-node on @client, setting the event-type to @event.
+ * The newly created event-node is returned to the caller in @nodep (unless
+ * @nodep is NULL).
+ *
+ * The event-node is automatically linked on @client.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigned int event) {
+ NDhcp4CEventNode *node;
+ int r;
+
+ r = n_dhcp4_c_event_node_new(&node);
+ if (r)
+ return r;
+
+ node->event.event = event;
+ c_list_link_tail(&client->event_list, &node->client_link);
+
+ if (nodep)
+ *nodep = node;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_arm_timer() - update timer
+ * @client: client to operate on
+ *
+ * This updates the timer on @client to fire on the next pending timeout. This
+ * must be called whenever a timeout on @client might have changed.
+ */
+void n_dhcp4_client_arm_timer(NDhcp4Client *client) {
+ uint64_t timeout = 0;
+ int r;
+
+ if (client->current_probe)
+ n_dhcp4_client_probe_get_timeout(client->current_probe, &timeout);
+
+ if (timeout != client->scheduled_timeout) {
+ r = timerfd_settime(client->fd_timer,
+ TFD_TIMER_ABSTIME,
+ &(struct itimerspec){
+ .it_value = {
+ .tv_sec = timeout / UINT64_C(1000000000),
+ .tv_nsec = timeout % UINT64_C(1000000000),
+ },
+ },
+ NULL);
+ c_assert(r >= 0);
+
+ client->scheduled_timeout = timeout;
+ }
+}
+
+/**
+ * n_dhcp4_client_get_fd() - retrieve event FD
+ * @client: client to operate on
+ * @fdp: output argument to store FD
+ *
+ * This retrieves the FD used by the client object given as @client. The FD is
+ * always valid, and returned in @fdp.
+ *
+ * The caller is expected to poll this FD for readable events and call
+ * n_dhcp4_client_dispatch() whenever the FD is readable.
+ */
+_c_public_ void n_dhcp4_client_get_fd(NDhcp4Client *client, int *fdp) {
+ *fdp = client->fd_epoll;
+}
+
+static int n_dhcp4_client_dispatch_timer(NDhcp4Client *client, struct epoll_event *event) {
+ uint64_t v, ns_now;
+ int r;
+
+ if (event->events & (EPOLLHUP | EPOLLERR)) {
+ /*
+ * There is no way to handle either gracefully. If we ignored
+ * them, we would busy-loop, so lets rather forward the error
+ * to the caller.
+ */
+ return -ENOTRECOVERABLE;
+ }
+
+ if (event->events & EPOLLIN) {
+ r = read(client->fd_timer, &v, sizeof(v));
+ if (r < 0) {
+ if (errno == EAGAIN) {
+ /*
+ * There are no more pending events, so nothing
+ * to be done. Return to the caller.
+ */
+ return 0;
+ }
+
+ /*
+ * Something failed. We use CLOCK_BOOTTIME/MONOTONIC,
+ * so ECANCELED cannot happen. Hence, there is no error
+ * that we could gracefully handle. Fail hard and let
+ * the caller deal with it.
+ */
+ return -errno;
+ } else if (r != sizeof(v) || v == 0) {
+ /*
+ * Kernel guarantees 8-byte reads, and only to return
+ * data if at least one timer triggered; fail hard if
+ * it suddenly starts exposing unexpected behavior.
+ */
+ return -ENOTRECOVERABLE;
+ }
+
+ /*
+ * Forward the timer-event to the active probe. Timers should
+ * not fire if there is no probe running, but lets ignore them
+ * for now, so probe-internals are not leaked to this generic
+ * client dispatcher.
+ */
+ if (client->current_probe) {
+ /*
+ * Read the current time *after* dispatching the timer,
+ * to make sure we do not miss wakeups.
+ */
+ ns_now = n_dhcp4_gettime(CLOCK_BOOTTIME);
+
+ r = n_dhcp4_client_probe_dispatch_timer(client->current_probe,
+ ns_now);
+ if (r)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int n_dhcp4_client_dispatch_io(NDhcp4Client *client, struct epoll_event *event) {
+ int r;
+
+ if (client->current_probe)
+ r = n_dhcp4_client_probe_dispatch_io(client->current_probe,
+ event->events);
+ else
+ return -ENOTRECOVERABLE;
+
+ return r;
+}
+
+/**
+ * n_dhcp4_client_dispatch() - dispatch client
+ * @client: client to operate on
+ *
+ * This dispatches pending operations on @client. It will read incoming
+ * messages, write pending data, and handle any timeouts.
+ *
+ * This function never blocks.
+ *
+ * If there are more events to dispatch, than would be reasonable to do in a
+ * single dispatch, this will return N_DHCP4_E_PREEMPTED. In this case the
+ * caller is expected to call into this function again when it is ready to
+ * dispatch more events.
+ * If your event loop is level-triggered (it very likely is), you can
+ * optionally ignore this return code and treat it as success.
+ *
+ * Return: 0 on success, negative error code on failure, N_DHCP4_E_PREEMPTED if
+ * there is more data to dispatch.
+ */
+_c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) {
+ struct epoll_event events[2];
+ int n, i, r = 0;
+
+ n = epoll_wait(client->fd_epoll, events, sizeof(events) / sizeof(*events), 0);
+ if (n < 0) {
+ /* Linux never returns EINTR if `timeout == 0'. */
+ return -errno;
+ }
+
+ client->preempted = false;
+
+ for (i = 0; i < n; ++i) {
+ switch (events[i].data.u32) {
+ case N_DHCP4_CLIENT_EPOLL_TIMER:
+ r = n_dhcp4_client_dispatch_timer(client, events + i);
+ break;
+ case N_DHCP4_CLIENT_EPOLL_IO:
+ r = n_dhcp4_client_dispatch_io(client, events + i);
+ break;
+ default:
+ c_assert(0);
+ r = 0;
+ break;
+ }
+
+ if (r) {
+ if (r == N_DHCP4_E_DOWN) {
+ r = n_dhcp4_client_raise(client,
+ NULL,
+ N_DHCP4_CLIENT_EVENT_DOWN);
+ if (r)
+ return r;
+
+ /* continue normally */
+ } else if (r) {
+ c_assert(r < _N_DHCP4_E_INTERNAL);
+ return r;
+ }
+ }
+ }
+
+ n_dhcp4_client_arm_timer(client);
+
+ return client->preempted ? N_DHCP4_E_PREEMPTED : 0;
+}
+
+/**
+ * n_dhcp4_client_pop_event() - fetch pending event
+ * @client: client to operate on
+ * @eventp: output argument to store next event
+ *
+ * This fetches the next pending event from the event-queue and returns it to a
+ * caller. A pointer to the event is stored in @eventp. If there is no more
+ * event queued, NULL is returned.
+ *
+ * If a valid event is returned, it is accessible until the next call to this
+ * function, or the destruction of the context object (this might be either the
+ * client object or the probe object, pointed to by the event), whichever
+ * happens first.
+ * That is, the caller should not pin the returned event object, but copy
+ * required information into their own state tracking contexts.
+ *
+ * The possible events are:
+ * * N_DHCP4_CLIENT_EVENT_OFFER: A lease offered from a server in response
+ * to a probe. Several such offers may be
+ * received until one of them is selected by
+ * the caller. Only one lease may be selected.
+ * The attached lease object may be queried
+ * for information in order to decide which
+ * lease to select, though the information is
+ * not guaranteed to stay the same in the
+ * final lease.
+ * * N_DHCP4_CLIENT_EVENT_GRANTED: A selected lease was granted by the server.
+ * The information in the attached lease
+ * object should be used to configure the
+ * client. Once the client has been
+ * configured, the lease should be accepted.
+ * * N_DHCP4_CLIENT_EVENT_RETRACTED: A selected lease offer was retracted by the
+ * server. This can happen in case the server
+ * offers the same lease to several clients,
+ * or the server discovers that the IP address
+ * in the lease is already in use.
+ * * N_DHCP4_CLIENT_EVENT_EXTENDED: An active lease is extended, if applicable
+ * the kernel should be updated with the new
+ * lifetime information for addresses and/or
+ * routes.
+ * * N_DHCP4_CLIENT_EVENT_EXPIRED: An active lease failed to be extended by
+ * the end of its lifetime. The client should
+ * immediately stop using the information
+ * contained in the lease.
+ * * N_DHCP4_CLIENT_EVENT_DOWN: The network interface was put down down.
+ * The user is recommended to reestablish the
+ * lease at the first opportunity when the
+ * network comes back up. Note that this is
+ * purely informational, the probe will keep
+ * running, and if the network topology does
+ * not change any lease we have will still be
+ * valid.
+ * * N_DHCP4_CLIENT_EVENT_CANCELLED: The probe was cancelled. This can happen if
+ * the client attempted several incompatible
+ * probes in parallel, then the most recent
+ * ones will be cancelled asynchronously.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_pop_event(NDhcp4Client *client, NDhcp4ClientEvent **eventp) {
+ NDhcp4CEventNode *node, *t_node;
+
+ c_list_for_each_entry_safe(node, t_node, &client->event_list, client_link) {
+ if (node->is_public) {
+ n_dhcp4_c_event_node_free(node);
+ continue;
+ }
+
+ node->is_public = true;
+ *eventp = &node->event;
+ return 0;
+ }
+
+ *eventp = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_update_mtu() - update link mtu
+ * @client: client to operate on
+ * @mtu: new mtu
+ *
+ * This updates the link MTU used by the client object. By default, the minimum
+ * requirement given by the IP specification is assumed, which means 576
+ * bytes. The caller is advised to update this to the actual MTU used by the
+ * link layer.
+ *
+ * This value reflects the MTU of the link layer. That is, it is the maximum
+ * packet size that you can send on that link, excluding the link-header but
+ * including the IP-header. On ethernet-v2 this would be 1500.
+ *
+ * If unsure, it is safe to leave this unset. However, in this case a DHCP
+ * server will be required to omit information if it does not fit into the
+ * default MTU.
+ *
+ * Unless you keep the default MTU, you should update the MTU whenever the link
+ * MTU changes. That is, when it is increased *and* when it is decreased.
+ * However, you must be aware that decreasing the MTU on a link might cause
+ * temporary data loss.
+ *
+ * Background: Knowing the link MTU guarantees that we can possibly transmit
+ * packets bigger than the IP minimum (i.e., 576 bytes). However,
+ * it does not guarantee that a possible target supports parsing
+ * packets bigger than the IP minimum. Hence, the MTU is used by a
+ * client to send a hint to a server that it can receive replies
+ * bigger than the minimum. As such, a server can reply with more
+ * information than otherwise possible.
+ * Since this DHCP client does not support fragmented packets, we
+ * simply set the allowed packet-size to the local link MTU.
+ * Note that DHCP relays might cause DHCP packets to be routed.
+ * However, such relays are required to always reassemble any
+ * fragments they receive into full DHCP packets, before they
+ * forward them either way. This guarantees that incoming packets
+ * are never fragmented, unless they exceed the local link MTU
+ * (this would otherwise not neccessarily be true, if some other
+ * part of the routed network had a lower MTU).
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu) {
+ int r;
+
+ if (mtu == client->mtu)
+ return 0;
+
+ if (client->current_probe) {
+ r = n_dhcp4_client_probe_update_mtu(client->current_probe, mtu);
+ if (r)
+ return r;
+ }
+
+ client->mtu = mtu;
+ return 0;
+}
+
+/**
+ * n_dhcp4_client_probe() - create a new probe
+ * @client: client to operate on
+ * @probep: output argument to store new probe
+ * @config: probe configuration to use
+ *
+ * This creates a new probe on @client. Probes represent DHCP requests and
+ * track the state over the entire lifetime of a lease. Once a probe is created
+ * it will start looking for DHCP servers, request a lease from them, and renew
+ * the lease continously whenever it expires. Furthermore, if a lease cannot be
+ * renewed, a new lease will be requested.
+ *
+ * The API allows for many probes to be run at the same time. However, the DHCP
+ * specification forbids many of those cases (e.g., you must not reuse a client
+ * id, otherwise it will be impossible to track who to forward received packets
+ * to). Hence, so far only a single probe can run at a time. If you create a
+ * new probe, all older probes that conflict with that probe will be canceled
+ * (their state machine is halted and a N_DHCP4_CLIENT_EVENT_CANCELLED event is
+ * raised.
+ * This might change in the future, though. There might be cases where multiple
+ * probes can be run in parallel (e.g., with different client-ids, or an INFORM
+ * in parallel to a REQUEST, ...).
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+_c_public_ int n_dhcp4_client_probe(NDhcp4Client *client,
+ NDhcp4ClientProbe **probep,
+ NDhcp4ClientProbeConfig *config) {
+ _c_cleanup_(n_dhcp4_client_probe_freep) NDhcp4ClientProbe *probe = NULL;
+ uint64_t ns_now;
+ int r;
+
+ ns_now = n_dhcp4_gettime(CLOCK_BOOTTIME);
+
+ r = n_dhcp4_client_probe_new(&probe, config, client, ns_now);
+ if (r)
+ return r;
+
+ n_dhcp4_client_arm_timer(client);
+
+ *probep = probe;
+ probe = NULL;
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-incoming.c b/shared/n-dhcp4/src/n-dhcp4-incoming.c
new file mode 100644
index 000000000..255da4584
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-incoming.c
@@ -0,0 +1,431 @@
+/*
+ * DHCPv4 Incoming Messages
+ *
+ * This file implements the message parser object for incoming DHCP4 messages.
+ * It takes a linear data blob as input, and provides accessors for the message
+ * content.
+ *
+ * This wrapper mainly deals with the OPTIONs array. That is, in hides the
+ * different overload-sections the DHCP4 spec defines, it concatenates
+ * duplicate option fields (as described by the spec), and provides a
+ * consistent view to the caller.
+ *
+ * Internally, for every incoming message we linearize its OPTIONs. This means,
+ * we create a copy of the contents, and merge all duplicate options into a
+ * single option entry. We then provide accessors to the caller to easily get
+ * O(1) access to individual fields.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+#include "n-dhcp4-private.h"
+
+static void n_dhcp4_incoming_prefetch(NDhcp4Incoming *incoming, size_t *offset, uint8_t option, const uint8_t *raw, size_t n_raw) {
+ uint8_t o, l;
+ size_t pos;
+
+ for (pos = 0; pos < n_raw; ) {
+ o = raw[pos++];
+ if (o == N_DHCP4_OPTION_PAD)
+ continue;
+ if (o == N_DHCP4_OPTION_END)
+ return;
+
+ /* bail out if no remaining space for length field */
+ if (pos >= n_raw)
+ return;
+
+ /* bail out if length exceeds the available space */
+ l = raw[pos++];
+ if (l > n_raw || pos > n_raw - l)
+ return;
+
+ /* prefetch content if it matches @option */
+ if (o == option) {
+ memcpy((uint8_t *)&incoming->message + *offset, raw + pos, l);
+ *offset += l;
+ }
+
+ pos += l;
+ }
+}
+
+static void n_dhcp4_incoming_merge(NDhcp4Incoming *incoming, size_t *offset, uint8_t overload, uint8_t option) {
+ uint8_t *m = (uint8_t *)&incoming->message;
+ size_t pos;
+
+ /*
+ * Prefetch all options matching @option from the 3 sections,
+ * concatenating their content. Remember the offset and size of the
+ * option in our message state.
+ */
+
+ pos = *offset;
+
+ /* prefetch option from OPTIONS */
+ n_dhcp4_incoming_prefetch(incoming, offset, option,
+ m + offsetof(NDhcp4Message, options),
+ incoming->n_message - offsetof(NDhcp4Message, options));
+
+ /* prefetch option from FILE */
+ if (overload & N_DHCP4_OVERLOAD_FILE)
+ n_dhcp4_incoming_prefetch(incoming, offset, option,
+ m + offsetof(NDhcp4Message, file),
+ sizeof(incoming->message.file));
+
+ /* prefetch option from SNAME */
+ if (overload & N_DHCP4_OVERLOAD_SNAME)
+ n_dhcp4_incoming_prefetch(incoming, offset, option,
+ m + offsetof(NDhcp4Message, sname),
+ sizeof(incoming->message.sname));
+
+ incoming->options[option].value = m + pos;
+ incoming->options[option].size = *offset - pos;
+}
+
+static void n_dhcp4_incoming_linearize(NDhcp4Incoming *incoming) {
+ uint8_t *m, o, l, overload;
+ size_t i, pos, end, offset;
+
+ /*
+ * Linearize all OPTIONs of the incoming message. We know that
+ * @incoming->message is preallocated to be big enough to hold the
+ * entire linearized message _trailing_ the original copy. All we have
+ * to do is walk the raw message in @incoming->message and for each
+ * option we find, copy it into the trailing space, concatenating all
+ * instances we find.
+ *
+ * Before we can copy the individual options, we must scan for the
+ * OVERLOAD option. This is required so our prefetcher knows which data
+ * arrays to scan for prefetching.
+ *
+ * So far, we require the OVERLOAD option to be present in the
+ * options-array (which is obvious and a given). However, if the option
+ * occurs multiple times outside of the options-array (i.e., SNAME or
+ * FILE), we silently ignore them. The specification does not allow
+ * multiple OVERLOAD options, anyway. Hence, this behavior only defines
+ * what we do when we see broken implementations, and we currently seem
+ * to support all styles we saw in the wild so far.
+ */
+
+ m = (uint8_t *)&incoming->message;
+ offset = incoming->n_message;
+
+ n_dhcp4_incoming_merge(incoming, &offset, 0, N_DHCP4_OPTION_OVERLOAD);
+ if (incoming->options[N_DHCP4_OPTION_OVERLOAD].size >= 1)
+ overload = *incoming->options[N_DHCP4_OPTION_OVERLOAD].value;
+ else
+ overload = 0;
+
+ for (i = 0; i < 3; ++i) {
+ if (i == 0) { /* walk OPTIONS */
+ pos = offsetof(NDhcp4Message, options);
+ end = incoming->n_message;
+ } else if (i == 1) { /* walk FILE */
+ if (!(overload & N_DHCP4_OVERLOAD_FILE))
+ continue;
+
+ pos = offsetof(NDhcp4Message, file);
+ end = pos + sizeof(incoming->message.file);
+ } else { /* walk SNAME */
+ if (!(overload & N_DHCP4_OVERLOAD_SNAME))
+ continue;
+
+ pos = offsetof(NDhcp4Message, sname);
+ end = pos + sizeof(incoming->message.sname);
+ }
+
+ while (pos < end) {
+ o = m[pos++];
+ if (o == N_DHCP4_OPTION_PAD)
+ continue;
+ if (o == N_DHCP4_OPTION_END)
+ break;
+ if (pos >= end)
+ break;
+
+ l = m[pos++];
+ if (l > end || pos > end - l)
+ break;
+
+ if (!incoming->options[o].value)
+ n_dhcp4_incoming_merge(incoming, &offset, overload, o);
+
+ pos += l;
+ }
+ }
+}
+
+/**
+ * n_dhcp4_incoming_new() - Allocate new incoming message object
+ * @incomingp: output argument for new object
+ * @raw: raw message blob
+ * @n_raw: length of the raw message blob
+ *
+ * This function allocates a new incoming-message object to wrap a received
+ * message blob. It performs basic verification of the message length and
+ * header, and then linearizes the DHCP4 options.
+ *
+ * The incoming-message object mainly provides accessors for the option-array.
+ * It handles all the different quirks around parsing and concatenating the
+ * options array, and provides the assembled data to the caller. It does not,
+ * however, in any way interpret the data of the individual options. This is up
+ * to the caller to do.
+ *
+ * Return: 0 on success, negative error code on failure, N_DHCP4_E_MALFORMED if
+ * the message is not a valid DHCP4 message.
+ */
+int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw) {
+ _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL;
+ size_t size;
+
+ if (n_raw < sizeof(NDhcp4Message) || n_raw > UINT16_MAX)
+ return N_DHCP4_E_MALFORMED;
+
+ /*
+ * Allocate enough space for book-keeping, a copy of @raw and trailing
+ * space for linearized options. The trailing space must be big enough
+ * to hold the entire options array unmodified (linearizing can only
+ * make it smaller). Hence, just allocate enough space to hold the raw
+ * message without the header.
+ */
+ size = sizeof(*incoming) + n_raw - sizeof(NDhcp4Message);
+ size += n_raw - sizeof(NDhcp4Header);
+
+ incoming = calloc(1, size);
+ if (!incoming)
+ return -ENOMEM;
+
+ *incoming = (NDhcp4Incoming)N_DHCP4_INCOMING_NULL(*incoming);
+ incoming->n_message = n_raw;
+ memcpy(&incoming->message, raw, n_raw);
+
+ if (incoming->message.magic != htobe32(N_DHCP4_MESSAGE_MAGIC))
+ return N_DHCP4_E_MALFORMED;
+
+ /* linearize options */
+ n_dhcp4_incoming_linearize(incoming);
+
+ *incomingp = incoming;
+ incoming = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_incoming_free() - Deallocate message object
+ * @incoming: object to operate on, or NULL
+ *
+ * This deallocates and frees the given incoming-message object. If NULL is
+ * passed, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming) {
+ if (!incoming)
+ return NULL;
+
+ free(incoming);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_incoming_get_header() - Return message header
+ * @incoming: message to operate on
+ *
+ * This returns a pointer to the message header. Note that modifications to
+ * this header are permanent and will affect the original message.
+ *
+ * Return: A pointer to the message header is returned.
+ */
+NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming) {
+ return &incoming->message.header;
+}
+
+/**
+ * n_dhcp4_incoming_get_raw() - Get access to the raw original message
+ * @incoming: message to operate on
+ * @rawp: output argument for the raw blob, or NULL
+ *
+ * This hands out a pointer to the raw message blob to the caller. This will
+ * point to the original message content, rather than the linearized version.
+ *
+ * Note that if the caller queried the contents of the message before, any
+ * modifications done by the caller will not affect the original message. It
+ * only affects the linearized content (which is a duplicate trailing the
+ * original message). However, modifications to the message header *DO* also
+ * appear in the original, since the message header is not duplicated.
+ *
+ * In either case, it is better to never modify the message, if you intend to
+ * forward it further.
+ *
+ * Return: The length of the raw message blob is returned.
+ */
+size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp) {
+ if (rawp)
+ *rawp = &incoming->message;
+ return incoming->n_message;
+}
+
+/**
+ * n_dhcp4_incoming_query() - Query the contents of a specific option
+ * @incoming: message to query
+ * @option: option to look for
+ * @datap: output argument for the option-data, or NULL
+ * @n_datap: output argument for the length of the option, or NULL
+ *
+ * This returns a pointer to the requested option blob in the message. It
+ * points to a linearized version of all respective option-fields of the same
+ * type. Hence, the caller is not required to deal with multiple occurrences of
+ * the same option.
+ *
+ * If an option was not present in the incoming message, N_DHCP4_E_UNSET is
+ * returned. Note that this is different from an empty option! And empty option
+ * will return a valid pointer and size 0.
+ *
+ * Note that the pointer to the option-blob does not point to the original
+ * message, but a duplicated version. Modifications to the blob will not be
+ * reflected in the original message, but they will be permanent regarding
+ * further queries through this function.
+ *
+ * Note that the original message alignment might no longer be reflected in the
+ * returned blob. You must not alias the content of the blob, but always copy
+ * it out, or consume piecemeal.
+ *
+ * This function runs in O(1).
+ *
+ * Return: 0 on success, negative error code on failure, N_DHCP4_E_UNSET if the
+ * option was not found,
+ */
+int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap) {
+ if (!incoming->options[option].value)
+ return N_DHCP4_E_UNSET;
+
+ if (datap)
+ *datap = incoming->options[option].value;
+ if (n_datap)
+ *n_datap = incoming->options[option].size;
+ return 0;
+}
+
+static int n_dhcp4_incoming_query_u8(NDhcp4Incoming *message, uint8_t option, uint8_t *u8p) {
+ uint8_t *data;
+ size_t n_data;
+ int r;
+
+ r = n_dhcp4_incoming_query(message, option, &data, &n_data);
+ if (r)
+ return r;
+ else if (n_data != sizeof(*data))
+ return N_DHCP4_E_MALFORMED;
+
+ *u8p = *data;
+ return 0;
+}
+
+static int n_dhcp4_incoming_query_u16(NDhcp4Incoming *message, uint8_t option, uint16_t *u16p) {
+ uint8_t *data;
+ size_t n_data;
+ uint16_t be16;
+ int r;
+
+ r = n_dhcp4_incoming_query(message, option, &data, &n_data);
+ if (r)
+ return r;
+ else if (n_data != sizeof(be16))
+ return N_DHCP4_E_MALFORMED;
+
+ memcpy(&be16, data, sizeof(be16));
+
+ *u16p = ntohs(be16);
+ return 0;
+}
+
+static int n_dhcp4_incoming_query_u32(NDhcp4Incoming *message, uint8_t option, uint32_t *u32p) {
+ uint8_t *data;
+ size_t n_data;
+ uint32_t be32;
+ int r;
+
+ r = n_dhcp4_incoming_query(message, option, &data, &n_data);
+ if (r)
+ return r;
+ else if (n_data != sizeof(be32))
+ return N_DHCP4_E_MALFORMED;
+
+ memcpy(&be32, data, sizeof(be32));
+
+ if (be32 == (uint32_t)-1)
+ *u32p = 0;
+ else
+ *u32p = ntohl(be32);
+ return 0;
+}
+
+static int n_dhcp4_incoming_query_in_addr(NDhcp4Incoming *message, uint8_t option, struct in_addr *addrp) {
+ uint8_t *data;
+ size_t n_data;
+ uint32_t be32;
+ int r;
+
+ r = n_dhcp4_incoming_query(message, option, &data, &n_data);
+ if (r)
+ return r;
+ else if (n_data != sizeof(be32))
+ return N_DHCP4_E_MALFORMED;
+
+ memcpy(&be32, data, sizeof(be32));
+
+ addrp->s_addr = be32;
+ return 0;
+}
+
+int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep) {
+ return n_dhcp4_incoming_query_u8(message, N_DHCP4_OPTION_MESSAGE_TYPE, typep);
+}
+
+int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep) {
+ return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, lifetimep);
+}
+
+int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p) {
+ return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_REBINDING_T2_TIME, t2p);
+}
+
+int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p) {
+ return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_RENEWAL_T1_TIME, t1p);
+}
+
+int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp) {
+ return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, idp);
+}
+
+int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep) {
+ return n_dhcp4_incoming_query_u16(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, max_message_sizep);
+}
+
+int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp) {
+ return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, requested_ipp);
+}
+
+void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp) {
+ NDhcp4Header *header = n_dhcp4_incoming_get_header(message);
+
+ *xidp = header->xid;
+}
+
+void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr) {
+ NDhcp4Header *header = n_dhcp4_incoming_get_header(message);
+
+ yiaddr->s_addr = header->yiaddr;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-outgoing.c b/shared/n-dhcp4/src/n-dhcp4-outgoing.c
new file mode 100644
index 000000000..bb33bbdbe
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-outgoing.c
@@ -0,0 +1,371 @@
+/*
+ * DHCPv4 Outgoing Messages
+ *
+ * XXX
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+#include "n-dhcp4-private.h"
+
+/**
+ * N_DHCP4_OUTGOING_MAX_PHDR - maximum protocol header size
+ *
+ * All DHCP4 messages-limits specify the size of the entire packet including
+ * the protocol layer (i.e., including the IP headers and UDP headers). To
+ * calculate the size we have remaining for the actual DHCP message, we need to
+ * substract the maximum possible header-length the linux-kernel might prepend
+ * to our messages. This turns out to be the maximum IP-header size (including
+ * optional IP headers, hence 60 bytes) plus the UDP header size (i.e., 8
+ * bytes).
+ */
+#define N_DHCP4_OUTGOING_MAX_PHDR (N_DHCP4_NETWORK_IP_MAXIMUM_HEADER_SIZE + sizeof(struct udphdr))
+
+/**
+ * n_dhcp4_outgoing_new() - Allocate new outgoing message
+ * @outgoingp: output argument to return allocate object through
+ * @max_size: maximum transmission size to use
+ * @overload: select sections to overload
+ *
+ * This allocates a new outgoing message and returns it to the caller. The
+ * caller can then append data to it and send it over the wire.
+ *
+ * The @max_size parameter specifies the transport-layer MTU to consider. If 0,
+ * N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE is used. Note that this argument
+ * specifies the maximum packet size *INCLUDING* the IP-headers and UDP-header.
+ * Internally, the allocator makes sure to never create packets bigger than the
+ * specified MTU. The append functions will return an error, if the packet size
+ * would exceed the MTU.
+ * If you use a full UDP stack that supports packet fragmentation, you can
+ * specify the maximum packet size here (e.g., UINT16_MAX).
+ *
+ * Return: 0 on success, error code on failure.
+ */
+int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t overload) {
+ _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *outgoing = NULL;
+
+ c_assert(!(overload & ~(N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME)));
+
+ /*
+ * Make sure the minimum limit is bigger than the maximum protocol
+ * header plus the DHCP-message-header plus a single OPTION_END byte.
+ */
+ static_assert(N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE >= N_DHCP4_OUTGOING_MAX_PHDR +
+ sizeof(NDhcp4Message) + 1,
+ "Invalid minimum IP packet limit");
+
+ outgoing = calloc(1, sizeof(*outgoing));
+ if (!outgoing)
+ return -ENOMEM;
+
+ *outgoing = (NDhcp4Outgoing)N_DHCP4_OUTGOING_NULL(*outgoing);
+ outgoing->n_message = N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE - N_DHCP4_OUTGOING_MAX_PHDR;
+ outgoing->i_message = offsetof(NDhcp4Message, options);
+ outgoing->max_size = outgoing->n_message;
+ outgoing->overload = overload;
+
+ if (max_size > N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE)
+ outgoing->max_size = max_size - N_DHCP4_OUTGOING_MAX_PHDR;
+
+ outgoing->message = calloc(1, outgoing->n_message);
+ if (!outgoing->message)
+ return -ENOMEM;
+
+ outgoing->message->magic = htonl(N_DHCP4_MESSAGE_MAGIC);
+ outgoing->message->options[0] = N_DHCP4_OPTION_END;
+
+ *outgoingp = outgoing;
+ outgoing = NULL;
+ return 0;
+}
+
+/**
+ * n_dhcp4_outgoing_free() - Deallocate outgoing message
+ * @outgoing: message to deallocate, or NULL
+ *
+ * This is the opposite to n_dhcp4_outgoing_new(). It deallocates and frees the
+ * passed object. If @outgoing is NULL, this is a no-op.
+ *
+ * Return: NULL is returned.
+ */
+NDhcp4Outgoing *n_dhcp4_outgoing_free(NDhcp4Outgoing *outgoing) {
+ if (!outgoing)
+ return NULL;
+
+ free(outgoing->message);
+ free(outgoing);
+
+ return NULL;
+}
+
+/**
+ * n_dhcp4_outgoing_get_header() - Get pointer to the message header
+ * @outgoing: message to operate on
+ *
+ * This returns a pointer to the DHCP4 message header to the caller. The caller
+ * can use this to fill-in the header-fields. Note that all fields are
+ * initialized to their default values. Hence, you only need to override the
+ * fields where the default is not sufficient.
+ *
+ * Return: A pointer to the message header is returned.
+ */
+NDhcp4Header *n_dhcp4_outgoing_get_header(NDhcp4Outgoing *outgoing) {
+ return &outgoing->message->header;
+}
+
+/**
+ * n_dhcp4_outgoing_get_raw() - Get the raw message blob
+ * @outgoing: message to operat on
+ * @rawp: output argument for the message-blob
+ *
+ * This function gives the caller access to the raw message-blob. That is, once
+ * message-marshaling is complete, use this to get the raw blob for sending.
+ * Note that this blob is only valid as long as you no longer append any
+ * further options to the message, nor modify it in any other way.
+ *
+ * Return: The size of the raw message blob is returned.
+ */
+size_t n_dhcp4_outgoing_get_raw(NDhcp4Outgoing *outgoing, const void **rawp) {
+ if (rawp)
+ *rawp = outgoing->message;
+
+ /*
+ * Return the DHCP message until the END option, excluding any
+ * trailing padding. We overallocate during append, so the
+ * allocated message might be bigger than what we want to
+ * send on the wire.
+ */
+ return outgoing->i_message + 1;
+}
+
+static void n_dhcp4_outgoing_append_option(NDhcp4Outgoing *outgoing,
+ uint8_t option,
+ const void *data,
+ uint8_t n_data) {
+ uint8_t *blob = (void *)outgoing->message;
+
+ blob[outgoing->i_message++] = option;
+ blob[outgoing->i_message++] = n_data;
+ memcpy(blob + outgoing->i_message, data, n_data);
+ outgoing->i_message += n_data;
+ blob[outgoing->i_message] = N_DHCP4_OPTION_END;
+}
+
+/**
+ * n_dhcp4_outgoing_append() - Append option to outgoing message
+ * @outgoing: message to operate on
+ * @option: option code to append
+ * @data: data to append in the option
+ * @n_data: length of the data blob
+ *
+ * This appends another option to the given outgoing message. The data is taken
+ * verbatim and copied into the message. Note that no validation is done. If
+ * you provide an option multiple times, it will be added multiple times (spec
+ * then requires them to be interpreted as concatenated option, in case the
+ * option is marked as such).
+ *
+ * The size of a message is limited, based on the restriction passed to the
+ * outgoing-message constructor. If there is not enough free space to copy in
+ * the new option, N_DHCP4_E_NO_SPACE is returned.
+ *
+ * The order in which you append options might matter to some implementations.
+ * For example, the message-type is often expected to be the first option. We
+ * do not place such restrictions, but for compatibility with external
+ * implementations, you should follow these recommendations.
+ * Furthermore, we do not implement any kind of smart allocators. That is, all
+ * options are simply appended when you call this. But due to the overloading
+ * feature, fragmentation might matter. Hence, if you use overloading, overly
+ * big options might cause padding, and as such waste space.
+ *
+ * Return: 0 on success, negative error code on failure, N_DHCP4_E_NO_SPACE
+ * when there is not sufficient free space in the message.
+ */
+int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing,
+ uint8_t option,
+ const void *data,
+ uint8_t n_data) {
+ NDhcp4Message *m;
+ uint8_t overload;
+ size_t rem, n;
+
+ c_assert(option != N_DHCP4_OPTION_PAD);
+ c_assert(option != N_DHCP4_OPTION_END);
+ c_assert(option != N_DHCP4_OPTION_OVERLOAD);
+
+ /*
+ * If the iterator is on the OPTIONs field, try appending the new blob.
+ * We need 2 header-bytes plus @n_data bytes. Additionally, we always
+ * reserve 3 trailing bytes for a possible OVERLOAD option, and 1 byte
+ * for the END marker.
+ */
+ if (outgoing->i_message >= offsetof(NDhcp4Message, options)) {
+ rem = outgoing->n_message - outgoing->i_message;
+
+ /* try fitting into remaining OPTIONs space */
+ if (rem >= n_data + 2U + 3U + 1U) {
+ n_dhcp4_outgoing_append_option(outgoing, option, data, n_data);
+ return 0;
+ }
+
+ /* try fitting into allowed OPTIONs space */
+ if (outgoing->max_size - outgoing->i_message >= n_data + 2U + 3U + 1U) {
+ /* try over-allocation to reduce allocation pressure */
+ n = c_min(outgoing->max_size,
+ outgoing->n_message + n_data + 128);
+ m = realloc(outgoing->message, n);
+ if (!m)
+ return -ENOMEM;
+
+ memset((void *)m + outgoing->i_message, 0, n - outgoing->i_message);
+ outgoing->message = m;
+ outgoing->n_message = n;
+ n_dhcp4_outgoing_append_option(outgoing, option, data, n_data);
+ return 0;
+ }
+
+ /* not enough remaining space, try OVERLOAD */
+ if (!outgoing->overload)
+ return N_DHCP4_E_NO_SPACE;
+
+ /*
+ * We ran out of space in the OPTIONs array, but overloading
+ * was enabled. This means, we can insert an OVERLOAD option
+ * and then use SNAME/FILE to store more options.
+ * Note that the three different sections cannot overlap and
+ * all must have an END marker. So as soon as we add the
+ * OVERLOAD option, we must make sure the other sections have
+ * the valid END marker. From then on, our *_append_option()
+ * helper makes sure to move the END marker with every
+ * insertion.
+ */
+ overload = outgoing->overload;
+ n_dhcp4_outgoing_append_option(outgoing, N_DHCP4_OPTION_OVERLOAD, &overload, 1);
+
+ if (overload & N_DHCP4_OVERLOAD_FILE)
+ outgoing->message->file[0] = N_DHCP4_OPTION_END;
+ if (overload & N_DHCP4_OVERLOAD_SNAME)
+ outgoing->message->sname[0] = N_DHCP4_OPTION_END;
+
+ if (overload & N_DHCP4_OVERLOAD_FILE)
+ outgoing->i_message = offsetof(NDhcp4Message, file);
+ else if (overload & N_DHCP4_OVERLOAD_SNAME)
+ outgoing->i_message = offsetof(NDhcp4Message, sname);
+ }
+
+ /*
+ * The OPTIONs section is full and OVERLOAD was enabled. Try writing
+ * into the FILE section. Always reserve 1 byte for the trailing END
+ * marker.
+ */
+ if (outgoing->i_message >= offsetof(NDhcp4Message, file)) {
+ rem = sizeof(outgoing->message->file);
+ rem -= outgoing->i_message - offsetof(NDhcp4Message, file);
+
+ if (rem >= n_data + 2U + 1U) {
+ n_dhcp4_outgoing_append_option(outgoing, option, data, n_data);
+ return 0;
+ }
+
+ if (overload & N_DHCP4_OVERLOAD_SNAME)
+ outgoing->i_message = offsetof(NDhcp4Message, sname);
+ else
+ return N_DHCP4_E_NO_SPACE;
+ }
+
+ /*
+ * OPTIONs and FILE are full, try putting data into the SNAME section
+ * as a last resort.
+ */
+ if (outgoing->i_message >= offsetof(NDhcp4Message, sname)) {
+ rem = sizeof(outgoing->message->sname);
+ rem -= outgoing->i_message - offsetof(NDhcp4Message, sname);
+
+ if (rem >= n_data + 2U + 1U) {
+ n_dhcp4_outgoing_append_option(outgoing, option, data, n_data);
+ return 0;
+ }
+ }
+
+ return N_DHCP4_E_NO_SPACE;
+}
+
+static int n_dhcp4_outgoing_append_u32(NDhcp4Outgoing *message, uint8_t option, uint32_t u32) {
+ uint32_t be32 = htonl(u32);
+ int r;
+
+ r = n_dhcp4_outgoing_append(message, option, &be32, sizeof(be32));
+ if (r)
+ return r;
+
+ return 0;
+}
+
+static int n_dhcp4_outgoing_append_in_addr(NDhcp4Outgoing *message, uint8_t option, struct in_addr addr) {
+ int r;
+
+ r = n_dhcp4_outgoing_append(message, option, &addr.s_addr, sizeof(addr.s_addr));
+ if (r)
+ return r;
+
+ return 0;
+}
+
+int n_dhcp4_outgoing_append_t1(NDhcp4Outgoing *message, uint32_t t1) {
+ return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_RENEWAL_T1_TIME, t1);
+}
+
+int n_dhcp4_outgoing_append_t2(NDhcp4Outgoing *message, uint32_t t2) {
+ return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_REBINDING_T2_TIME, t2);
+}
+
+int n_dhcp4_outgoing_append_lifetime(NDhcp4Outgoing *message, uint32_t lifetime) {
+ return n_dhcp4_outgoing_append_u32(message, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, lifetime);
+}
+
+int n_dhcp4_outgoing_append_server_identifier(NDhcp4Outgoing *message, struct in_addr addr) {
+ return n_dhcp4_outgoing_append_in_addr(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, addr);
+}
+
+int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_addr addr) {
+ return n_dhcp4_outgoing_append_in_addr(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, addr);
+}
+
+void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint32_t secs) {
+ NDhcp4Header *header = n_dhcp4_outgoing_get_header(message);
+
+ /*
+ * Some DHCP servers will reject DISCOVER or REQUEST messages if 'secs'
+ * is not set (i.e., set to 0), even though the spec allows it.
+ */
+ c_assert(secs);
+
+ header->secs = htonl(secs);
+}
+
+void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid) {
+ NDhcp4Header *header = n_dhcp4_outgoing_get_header(message);
+
+ header->xid = xid;
+}
+
+void n_dhcp4_outgoing_get_xid(NDhcp4Outgoing *message, uint32_t *xidp) {
+ NDhcp4Header *header = n_dhcp4_outgoing_get_header(message);
+
+ *xidp = header->xid;
+}
+
+void n_dhcp4_outgoing_set_yiaddr(NDhcp4Outgoing *message, struct in_addr yiaddr) {
+ NDhcp4Header *header = n_dhcp4_outgoing_get_header(message);
+
+ header->yiaddr = yiaddr.s_addr;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-private.h b/shared/n-dhcp4/src/n-dhcp4-private.h
new file mode 100644
index 000000000..b896a93dc
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-private.h
@@ -0,0 +1,689 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4.h"
+
+typedef struct NDhcp4CConnection NDhcp4CConnection;
+typedef struct NDhcp4CEventNode NDhcp4CEventNode;
+typedef struct NDhcp4ClientProbeOption NDhcp4ClientProbeOption;
+typedef struct NDhcp4Header NDhcp4Header;
+typedef struct NDhcp4Incoming NDhcp4Incoming;
+typedef struct NDhcp4Message NDhcp4Message;
+typedef struct NDhcp4Outgoing NDhcp4Outgoing;
+typedef struct NDhcp4SConnection NDhcp4SConnection;
+typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp;
+typedef struct NDhcp4SEventNode NDhcp4SEventNode;
+
+/* specs */
+
+#define N_DHCP4_NETWORK_IP_MAXIMUM_HEADER_SIZE (60) /* See RFC791 */
+#define N_DHCP4_NETWORK_IP_MINIMUM_MAX_SIZE (576) /* See RFC791 */
+#define N_DHCP4_NETWORK_SERVER_PORT (67)
+#define N_DHCP4_NETWORK_CLIENT_PORT (68)
+#define N_DHCP4_MESSAGE_MAGIC ((uint32_t)(0x63825363))
+#define N_DHCP4_MESSAGE_FLAG_BROADCAST (htons(0x8000))
+
+enum {
+ N_DHCP4_OP_BOOTREQUEST = 1,
+ N_DHCP4_OP_BOOTREPLY = 2,
+};
+
+enum {
+ N_DHCP4_OPTION_PAD = 0,
+ N_DHCP4_OPTION_SUBNET_MASK = 1,
+ N_DHCP4_OPTION_TIME_OFFSET = 2,
+ N_DHCP4_OPTION_ROUTER = 3,
+ N_DHCP4_OPTION_DOMAIN_NAME_SERVER = 6,
+ N_DHCP4_OPTION_HOST_NAME = 12,
+ N_DHCP4_OPTION_BOOT_FILE_SIZE = 13,
+ N_DHCP4_OPTION_DOMAIN_NAME = 15,
+ N_DHCP4_OPTION_ROOT_PATH = 17,
+ N_DHCP4_OPTION_ENABLE_IP_FORWARDING = 19,
+ N_DHCP4_OPTION_ENABLE_IP_FORWARDING_NL = 20,
+ N_DHCP4_OPTION_POLICY_FILTER = 21,
+ N_DHCP4_OPTION_INTERFACE_MDR = 22,
+ N_DHCP4_OPTION_INTERFACE_TTL = 23,
+ N_DHCP4_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24,
+ N_DHCP4_OPTION_INTERFACE_MTU = 26,
+ N_DHCP4_OPTION_BROADCAST = 28,
+ N_DHCP4_OPTION_STATIC_ROUTE = 33,
+ N_DHCP4_OPTION_NTP_SERVER = 42,
+ N_DHCP4_OPTION_VENDOR_SPECIFIC = 43,
+ N_DHCP4_OPTION_REQUESTED_IP_ADDRESS = 50,
+ N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME = 51,
+ N_DHCP4_OPTION_OVERLOAD = 52,
+ N_DHCP4_OPTION_MESSAGE_TYPE = 53,
+ N_DHCP4_OPTION_SERVER_IDENTIFIER = 54,
+ N_DHCP4_OPTION_PARAMETER_REQUEST_LIST = 55,
+ N_DHCP4_OPTION_ERROR_MESSAGE = 56,
+ N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE = 57,
+ N_DHCP4_OPTION_RENEWAL_T1_TIME = 58,
+ N_DHCP4_OPTION_REBINDING_T2_TIME = 59,
+ N_DHCP4_OPTION_VENDOR_CLASS_IDENTIFIER = 60,
+ N_DHCP4_OPTION_CLIENT_IDENTIFIER = 61,
+ N_DHCP4_OPTION_FQDN = 81,
+ N_DHCP4_OPTION_NEW_POSIX_TIMEZONE = 100,
+ N_DHCP4_OPTION_NEW_TZDB_TIMEZONE = 101,
+ N_DHCP4_OPTION_CLASSLESS_STATIC_ROUTE = 121,
+ N_DHCP4_OPTION_PRIVATE_BASE = 224,
+ N_DHCP4_OPTION_PRIVATE_LAST = 254,
+ N_DHCP4_OPTION_END = 255,
+ _N_DHCP4_OPTION_N = 256,
+};
+
+enum {
+ N_DHCP4_OVERLOAD_FILE = 1,
+ N_DHCP4_OVERLOAD_SNAME = 2,
+};
+
+enum {
+ N_DHCP4_MESSAGE_DISCOVER = 1,
+ N_DHCP4_MESSAGE_OFFER = 2,
+ N_DHCP4_MESSAGE_REQUEST = 3,
+ N_DHCP4_MESSAGE_DECLINE = 4,
+ N_DHCP4_MESSAGE_ACK = 5,
+ N_DHCP4_MESSAGE_NAK = 6,
+ N_DHCP4_MESSAGE_RELEASE = 7,
+ N_DHCP4_MESSAGE_INFORM = 8,
+ N_DHCP4_MESSAGE_FORCERENEW = 9,
+};
+
+struct NDhcp4Header {
+ uint8_t op;
+ uint8_t htype;
+ uint8_t hlen;
+ uint8_t hops;
+ uint32_t xid;
+ uint16_t secs;
+ uint16_t flags;
+ uint32_t ciaddr;
+ uint32_t yiaddr;
+ uint32_t siaddr;
+ uint32_t giaddr;
+ uint8_t chaddr[16];
+} _c_packed_;
+
+struct NDhcp4Message {
+ NDhcp4Header header;
+ uint8_t sname[64];
+ uint8_t file[128];
+ uint32_t magic;
+ uint8_t options[];
+} _c_packed_;
+
+/* objects */
+
+enum {
+ _N_DHCP4_E_INTERNAL = _N_DHCP4_E_N,
+
+ N_DHCP4_E_UNEXPECTED,
+
+ N_DHCP4_E_NO_SPACE,
+ N_DHCP4_E_MALFORMED,
+
+ N_DHCP4_E_DROPPED,
+ N_DHCP4_E_DOWN,
+ N_DHCP4_E_AGAIN,
+};
+
+enum {
+ N_DHCP4_C_CONNECTION_STATE_INIT,
+ N_DHCP4_C_CONNECTION_STATE_PACKET,
+ N_DHCP4_C_CONNECTION_STATE_DRAINING,
+ N_DHCP4_C_CONNECTION_STATE_UDP,
+ N_DHCP4_C_CONNECTION_STATE_CLOSED,
+};
+
+enum {
+ N_DHCP4_CLIENT_EPOLL_TIMER,
+ N_DHCP4_CLIENT_EPOLL_IO,
+};
+
+enum {
+ N_DHCP4_CLIENT_PROBE_STATE_INIT,
+ N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT,
+ N_DHCP4_CLIENT_PROBE_STATE_SELECTING,
+ N_DHCP4_CLIENT_PROBE_STATE_REBOOTING,
+ N_DHCP4_CLIENT_PROBE_STATE_REQUESTING,
+ N_DHCP4_CLIENT_PROBE_STATE_GRANTED,
+ N_DHCP4_CLIENT_PROBE_STATE_BOUND,
+ N_DHCP4_CLIENT_PROBE_STATE_RENEWING,
+ N_DHCP4_CLIENT_PROBE_STATE_REBINDING,
+ N_DHCP4_CLIENT_PROBE_STATE_EXPIRED,
+};
+
+enum {
+ N_DHCP4_CLIENT_LEASE_STATE_INIT,
+ N_DHCP4_CLIENT_LEASE_STATE_OFFERED,
+ N_DHCP4_CLIENT_LEASE_STATE_SELECTED,
+ N_DHCP4_CLIENT_LEASE_STATE_DECLINED,
+ N_DHCP4_CLIENT_LEASE_STATE_ACKED,
+};
+
+enum {
+ N_DHCP4_SERVER_EPOLL_TIMER,
+ N_DHCP4_SERVER_EPOLL_IO,
+};
+
+enum {
+ _N_DHCP4_C_MESSAGE_INVALID = 0,
+ N_DHCP4_C_MESSAGE_DISCOVER,
+ N_DHCP4_C_MESSAGE_INFORM,
+ N_DHCP4_C_MESSAGE_SELECT,
+ N_DHCP4_C_MESSAGE_IGNORE,
+ N_DHCP4_C_MESSAGE_RENEW,
+ N_DHCP4_C_MESSAGE_REBIND,
+ N_DHCP4_C_MESSAGE_REBOOT,
+ N_DHCP4_C_MESSAGE_RELEASE,
+ N_DHCP4_C_MESSAGE_DECLINE,
+};
+
+struct NDhcp4Outgoing {
+ NDhcp4Message *message;
+ size_t n_message;
+ size_t i_message;
+ size_t max_size;
+
+ uint8_t overload : 2;
+
+ struct {
+ uint8_t type;
+ uint64_t start_time;
+ uint64_t base_time;
+ uint64_t send_time;
+ uint64_t send_jitter;
+ size_t n_send;
+ } userdata;
+};
+
+#define N_DHCP4_OUTGOING_NULL(_x) { \
+ }
+
+struct NDhcp4Incoming {
+ struct {
+ uint8_t *value;
+ size_t size;
+ } options[_N_DHCP4_OPTION_N];
+
+ struct {
+ uint8_t type;
+ uint64_t start_time;
+ uint64_t base_time;
+ } userdata;
+
+ size_t n_message;
+ NDhcp4Message message;
+ /* @message must be the last member */
+};
+
+#define N_DHCP4_INCOMING_NULL(_x) { \
+ }
+
+struct NDhcp4ClientConfig {
+ int ifindex;
+ unsigned int transport;
+ bool request_broadcast;
+ uint8_t mac[MAX_ADDR_LEN];
+ size_t n_mac;
+ uint8_t broadcast_mac[MAX_ADDR_LEN];
+ size_t n_broadcast_mac;
+ uint8_t *client_id;
+ size_t n_client_id;
+};
+
+#define N_DHCP4_CLIENT_CONFIG_NULL(_x) { \
+ .transport = _N_DHCP4_TRANSPORT_N, \
+ }
+
+struct NDhcp4ClientProbeOption {
+ uint8_t option;
+ uint8_t n_data;
+ uint8_t data[];
+};
+
+#define N_DHCP4_CLIENT_PROBE_OPTION_NULL(_x) { \
+ .option = N_DHCP4_OPTION_PAD, \
+ }
+
+struct NDhcp4ClientProbeConfig {
+ bool inform_only;
+ bool init_reboot;
+ struct in_addr requested_ip;
+ struct drand48_data entropy; /* entropy pool */
+ uint64_t ms_start_delay; /* max ms to wait before starting probe */
+ NDhcp4ClientProbeOption *options[UINT8_MAX + 1];
+ int8_t request_parameters[UINT8_MAX + 1];
+ size_t n_request_parameters;
+};
+
+#define N_DHCP4_CLIENT_PROBE_CONFIG_NULL(_x) { \
+ .ms_start_delay = N_DHCP4_CLIENT_START_DELAY_RFC2131, \
+ }
+
+struct NDhcp4CEventNode {
+ CList client_link;
+ CList probe_link;
+ NDhcp4ClientEvent event;
+ bool is_public : 1;
+};
+
+#define N_DHCP4_C_EVENT_NODE_NULL(_x) { \
+ .client_link = C_LIST_INIT((_x).client_link), \
+ .probe_link = C_LIST_INIT((_x).probe_link), \
+ }
+
+struct NDhcp4CConnection {
+ NDhcp4ClientConfig *client_config;
+ NDhcp4ClientProbeConfig *probe_config;
+ int fd_epoll;
+
+ unsigned int state; /* current connection state */
+ int fd_packet; /* packet socket */
+ int fd_udp; /* udp socket */
+
+ NDhcp4Outgoing *request; /* current request */
+
+ uint32_t client_ip; /* client IP address, or 0 */
+ uint32_t server_ip; /* server IP address, or 0 */
+ uint16_t mtu; /* client mtu, or 0 */
+
+ /*
+ * When we get DHCP packets from the kernel, we need a buffer to read
+ * the data into. Since UDP packets can be up to 2^16 bytes in size, we
+ * avoid placing it on the stack and instead read into this scratch
+ * buffer. It is purely meant as stack replacement, no data is returned
+ * through this buffer.
+ */
+ uint8_t scratch_buffer[UINT16_MAX];
+};
+
+#define N_DHCP4_C_CONNECTION_NULL(_x) { \
+ .fd_packet = -1, \
+ .fd_udp = -1, \
+ }
+
+struct NDhcp4Client {
+ unsigned long n_refs;
+ NDhcp4ClientConfig *config;
+ CList event_list;
+ int fd_epoll;
+ int fd_timer;
+
+ uint16_t mtu;
+ NDhcp4ClientProbe *current_probe;
+ uint64_t scheduled_timeout;
+
+ bool preempted : 1;
+};
+
+#define N_DHCP4_CLIENT_NULL(_x) { \
+ .n_refs = 1, \
+ .event_list = C_LIST_INIT((_x).event_list), \
+ .fd_epoll = -1, \
+ .fd_timer = -1, \
+ }
+
+struct NDhcp4ClientProbe {
+ NDhcp4ClientProbeConfig *config;
+ NDhcp4Client *client;
+ CList event_list;
+ CList lease_list;
+ void *userdata;
+
+ unsigned int state; /* current probe state */
+ uint64_t ns_deferred; /* timeout for deferred action */
+ NDhcp4ClientLease *current_lease; /* current lease */
+
+ NDhcp4CConnection connection; /* client connection wrapper */
+};
+
+#define N_DHCP4_CLIENT_PROBE_NULL(_x) { \
+ .event_list = C_LIST_INIT((_x).event_list), \
+ .lease_list = C_LIST_INIT((_x).lease_list), \
+ .connection = N_DHCP4_C_CONNECTION_NULL((_x).connection), \
+ }
+
+struct NDhcp4ClientLease {
+ unsigned long n_refs;
+
+ NDhcp4ClientProbe *probe;
+ CList probe_link;
+
+ NDhcp4Incoming *message;
+
+ uint64_t t1;
+ uint64_t t2;
+ uint64_t lifetime;
+};
+
+#define N_DHCP4_CLIENT_LEASE_NULL(_x) { \
+ .n_refs = 1, \
+ .probe_link = C_LIST_INIT((_x).probe_link), \
+ }
+
+struct NDhcp4ServerConfig {
+ int ifindex;
+};
+
+#define N_DHCP4_SERVER_CONFIG_NULL(_x) { \
+ }
+
+struct NDhcp4SEventNode {
+ CList server_link;
+ NDhcp4ServerEvent event;
+ bool is_public : 1;
+};
+
+#define N_DHCP4_S_EVENT_NODE_NULL(_x) { \
+ .server_link = C_LIST_INIT((_x).server_link), \
+ }
+
+struct NDhcp4SConnection {
+ int ifindex; /* interface index */
+ int fd_packet; /* packet socket */
+ int fd_udp; /* udp socket */
+ uint8_t buf[UINT16_MAX]; /* scratch recevie buffer */
+
+ /* XXX: support a set of server addresses */
+ NDhcp4SConnectionIp *ip; /* server IP address, or NULL */
+};
+
+#define N_DHCP4_S_CONNECTION_NULL(_x) { \
+ .fd_packet = -1, \
+ .fd_udp = -1, \
+ }
+
+struct NDhcp4SConnectionIp {
+ NDhcp4SConnection *connection;
+ struct in_addr ip;
+};
+
+#define N_DHCP4_S_CONNECTION_IP_NULL(_x) { \
+}
+
+struct NDhcp4Server {
+ unsigned long n_refs;
+ CList event_list;
+ CList lease_list;
+
+ bool preempted : 1;
+
+ NDhcp4SConnection connection;
+};
+
+#define N_DHCP4_SERVER_NULL(_x) { \
+ .n_refs = 1, \
+ .event_list = C_LIST_INIT((_x).event_list), \
+ .lease_list = C_LIST_INIT((_x).lease_list), \
+ .connection = N_DHCP4_S_CONNECTION_NULL((_x).connection), \
+ }
+
+struct NDhcp4ServerIp {
+ NDhcp4SConnectionIp ip;
+};
+
+#define N_DHCP4_SERVER_IP_NULL(_x) { \
+ .ip = N_DHCP4_S_CONNECTION_IP_NULL((_x).ip), \
+ }
+
+struct NDhcp4ServerLease {
+ unsigned long n_refs;
+
+ NDhcp4Server *server;
+ CList server_link;
+
+ NDhcp4Incoming *request;
+ NDhcp4Incoming *reply;
+};
+
+#define N_DHCP4_SERVER_LEASE_NULL(_x) { \
+ .n_refs = 1, \
+ .server_link = C_LIST_INIT((_x).server_link), \
+ }
+
+/* outgoing messages */
+
+int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t overload);
+NDhcp4Outgoing *n_dhcp4_outgoing_free(NDhcp4Outgoing *outgoing);
+
+NDhcp4Header *n_dhcp4_outgoing_get_header(NDhcp4Outgoing *outgoing);
+size_t n_dhcp4_outgoing_get_raw(NDhcp4Outgoing *outgoing, const void **rawp);
+int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, uint8_t option, const void *data, uint8_t n_data);
+
+int n_dhcp4_outgoing_append_t1(NDhcp4Outgoing *message, uint32_t t1);
+int n_dhcp4_outgoing_append_t2(NDhcp4Outgoing *message, uint32_t t2);
+int n_dhcp4_outgoing_append_lifetime(NDhcp4Outgoing *message, uint32_t lifetime);
+int n_dhcp4_outgoing_append_server_identifier(NDhcp4Outgoing *message, struct in_addr addr);
+int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_addr addr);
+
+void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint32_t secs);
+void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid);
+void n_dhcp4_outgoing_set_yiaddr(NDhcp4Outgoing *message, struct in_addr yiaddr);
+
+void n_dhcp4_outgoing_get_xid(NDhcp4Outgoing *message, uint32_t *xidp);
+
+/* incoming messages */
+
+int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw);
+NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming);
+
+NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming);
+size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp);
+int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap);
+
+int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep);
+int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep);
+int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p);
+int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p);
+int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp);
+int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep);
+int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp);
+
+void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp);
+void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr);
+
+/* sockets */
+
+int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex);
+int n_dhcp4_c_socket_udp_new(int *sockfdp,
+ int ifindex,
+ const struct in_addr *client_addr,
+ const struct in_addr *server_addr);
+int n_dhcp4_s_socket_packet_new(int *sockfdp);
+int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex);
+
+int n_dhcp4_c_socket_packet_send(int sockfd,
+ int ifindex,
+ const unsigned char *dest_haddr,
+ unsigned char halen,
+ NDhcp4Outgoing *message);
+int n_dhcp4_c_socket_udp_send(int sockfd, NDhcp4Outgoing *message);
+int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message);
+int n_dhcp4_s_socket_packet_send(int sockfd,
+ int ifindex,
+ const struct in_addr *src_inaddr,
+ const unsigned char *dest_haddr,
+ unsigned char halen,
+ const struct in_addr *dest_inaddr,
+ NDhcp4Outgoing *message);
+int n_dhcp4_s_socket_udp_send(int sockfd,
+ const struct in_addr *inaddr_src,
+ const struct in_addr *inaddr_dest,
+ NDhcp4Outgoing *message);
+int n_dhcp4_s_socket_udp_broadcast(int sockfd,
+ const struct in_addr *inaddr_src,
+ NDhcp4Outgoing *message);
+
+int n_dhcp4_c_socket_packet_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep);
+int n_dhcp4_c_socket_udp_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep);
+int n_dhcp4_s_socket_udp_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep,
+ struct sockaddr_in *dest);
+
+/* client configs */
+
+int n_dhcp4_client_config_dup(NDhcp4ClientConfig *config,
+ NDhcp4ClientConfig **dupp);
+
+/* client probe configs */
+
+int n_dhcp4_client_probe_config_dup(NDhcp4ClientProbeConfig *config,
+ NDhcp4ClientProbeConfig **dupp);
+uint32_t n_dhcp4_client_probe_config_get_random(NDhcp4ClientProbeConfig *config);
+
+/* client events */
+
+int n_dhcp4_c_event_node_new(NDhcp4CEventNode **nodep);
+NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node);
+
+/* client connections */
+
+int n_dhcp4_c_connection_init(NDhcp4CConnection *connection,
+ NDhcp4ClientConfig *client_config,
+ NDhcp4ClientProbeConfig *probe_config,
+ int fd_epoll);
+void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection);
+
+int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection);
+int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection,
+ const struct in_addr *client,
+ const struct in_addr *server);
+void n_dhcp4_c_connection_close(NDhcp4CConnection *connection);
+
+void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
+ uint64_t *timeoutp);
+
+int n_dhcp4_c_connection_discover_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request);
+int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request,
+ NDhcp4Incoming *offer);
+int n_dhcp4_c_connection_reboot_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request,
+ const struct in_addr *client);
+int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request);
+int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request);
+int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request,
+ NDhcp4Incoming *ack,
+ const char *error);
+int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request);
+int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection,
+ NDhcp4Outgoing **request,
+ const char *error);
+
+int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection,
+ NDhcp4Outgoing *request,
+ uint64_t timestamp);
+int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection,
+ uint64_t timestamp);
+int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
+ NDhcp4Incoming **messagep);
+
+/* clients */
+
+int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigned int event);
+void n_dhcp4_client_arm_timer(NDhcp4Client *client);
+
+/* client probes */
+
+int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep,
+ NDhcp4ClientProbeConfig *config,
+ NDhcp4Client *client,
+ uint64_t ns_now);
+
+int n_dhcp4_client_probe_raise(NDhcp4ClientProbe *probe, NDhcp4CEventNode **nodep, unsigned int event);
+void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeoutp);
+int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_now);
+int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events);
+int n_dhcp4_client_probe_transition_select(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, uint64_t ns_now);
+int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incoming *ack);
+int n_dhcp4_client_probe_transition_decline(NDhcp4ClientProbe *probe, NDhcp4Incoming *offer, const char *error, uint64_t ns_now);
+int n_dhcp4_client_probe_update_mtu(NDhcp4ClientProbe *probe, uint16_t mtu);
+
+/* client leases */
+
+int n_dhcp4_client_lease_new(NDhcp4ClientLease **leasep, NDhcp4Incoming *message);
+void n_dhcp4_client_lease_link(NDhcp4ClientLease *lease, NDhcp4ClientProbe *probe);
+void n_dhcp4_client_lease_unlink(NDhcp4ClientLease *lease);
+
+/* server connections */
+
+int n_dhcp4_s_connection_init(NDhcp4SConnection *connection, int ifindex);
+void n_dhcp4_s_connection_deinit(NDhcp4SConnection *connection);
+
+void n_dhcp4_s_connection_get_fd(NDhcp4SConnection *connection, int *fdp);
+int n_dhcp4_s_connection_dispatch_io(NDhcp4SConnection *connection, NDhcp4Incoming **messagep);
+
+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);
+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);
+int n_dhcp4_s_connection_nak_new(NDhcp4SConnection *connection,
+ NDhcp4Outgoing **replyp,
+ NDhcp4Incoming *request,
+ const struct in_addr *server_address);
+
+int n_dhcp4_s_connection_send_reply(NDhcp4SConnection *connection,
+ const struct in_addr *server_addr,
+ NDhcp4Outgoing *reply);
+
+/* server connection ips */
+
+void n_dhcp4_s_connection_ip_init(NDhcp4SConnectionIp *ip, struct in_addr addr);
+void n_dhcp4_s_connection_ip_deinit(NDhcp4SConnectionIp *ip);
+
+void n_dhcp4_s_connection_ip_link(NDhcp4SConnectionIp *ip, NDhcp4SConnection *connection);
+void n_dhcp4_s_connection_ip_unlink(NDhcp4SConnectionIp *ip);
+
+/* inline helpers */
+
+static inline void n_dhcp4_outgoing_freep(NDhcp4Outgoing **outgoing) {
+ if (*outgoing)
+ n_dhcp4_outgoing_free(*outgoing);
+}
+
+static inline void n_dhcp4_incoming_freep(NDhcp4Incoming **incoming) {
+ if (*incoming)
+ n_dhcp4_incoming_free(*incoming);
+}
+
+static inline uint64_t n_dhcp4_gettime(clockid_t clock) {
+ struct timespec ts;
+ int r;
+
+ r = clock_gettime(clock, &ts);
+ c_assert(r >= 0);
+
+ return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4-s-connection.c b/shared/n-dhcp4/src/n-dhcp4-s-connection.c
new file mode 100644
index 000000000..71ae0be82
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-s-connection.c
@@ -0,0 +1,411 @@
+/*
+ * DHCPv4 Server Connection
+ *
+ * XXX
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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},
+ 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 unconditinoally 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/shared/n-dhcp4/src/n-dhcp4-s-lease.c b/shared/n-dhcp4/src/n-dhcp4-s-lease.c
new file mode 100644
index 000000000..cb8aabf91
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-s-lease.c
@@ -0,0 +1,103 @@
+/*
+ * XXX
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#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/shared/n-dhcp4/src/n-dhcp4-server.c b/shared/n-dhcp4/src/n-dhcp4-server.c
new file mode 100644
index 000000000..7b971eeb2
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-server.c
@@ -0,0 +1,241 @@
+/*
+ * Server Side of the Dynamic Host Configuration Protocol for IPv4
+ *
+ * XXX
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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/shared/n-dhcp4/src/n-dhcp4-socket.c b/shared/n-dhcp4/src/n-dhcp4-socket.c
new file mode 100644
index 000000000..0bcc2419d
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4-socket.c
@@ -0,0 +1,656 @@
+/*
+ * DHCP specific low-level socket helpers
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "n-dhcp4-private.h"
+#include "util/packet.h"
+#include "util/socket.h"
+
+/**
+ * n_dhcp4_c_socket_packet_new() - create a new DHCP4 client packet socket
+ * @sockfdp: return argumnet for the new socket
+ * @ifindex: interface index to bind to
+ *
+ * Create a new AF_PACKET/SOCK_DGRAM socket usable to listen to and send DHCP client
+ * packets before an IP address has been configured.
+ *
+ * Only unfragmented DHCP packets from a server to a client destined for the given
+ * ifindex is returned.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int n_dhcp4_c_socket_packet_new(int *sockfdp, int ifindex) {
+ _c_cleanup_(c_closep) int sockfd = -1;
+ struct sock_filter filter[] = {
+ /*
+ * IP
+ *
+ * Check
+ * - UDP
+ * - Unfragmented
+ * - Large enough to fit the DHCP header
+ *
+ * Leave X the size of the IP header, for future indirect reads.
+ */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- IP protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, frag_off)), /* A <- Flags */
+ BPF_STMT(BPF_ALU + BPF_AND + BPF_K, ntohs(IP_MF | IP_OFFMASK)), /* A <- A & (IP_MF | IP_OFFMASK) */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* fragmented packet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A -= X */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct udphdr) + sizeof(NDhcp4Message), 1, 0), /* packet >= DHCPPacket ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ /*
+ * UDP
+ *
+ * Check
+ * - DHCP client port
+ *
+ * Leave X the size of IP and UDP headers, for future indirect reads.
+ */
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct udphdr, dest)), /* A <- UDP destination port */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_NETWORK_CLIENT_PORT, 1, 0), /* UDP destination port == DHCP client port ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
+ BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+
+ /*
+ * DHCP
+ *
+ * Check
+ * - BOOTREPLY (from server to client)
+ * - DHCP magic cookie
+ */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ };
+ struct sock_fprog fprog = {
+ .filter = filter,
+ .len = sizeof(filter) / sizeof(filter[0]),
+ };
+ struct sockaddr_ll addr = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_IP),
+ .sll_ifindex = ifindex,
+ };
+ int r, on = 1;
+
+ sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ /* We need the flag that tells us if the checksum is correct. */
+ r = setsockopt(sockfd, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on));
+ if (r < 0)
+ return -errno;
+
+ r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
+ if (r < 0)
+ return -errno;
+
+ *sockfdp = sockfd;
+ sockfd = -1;
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_socket_udp_new() - create a new DHCP4 client UDP socket
+ * @sockfdp: return argumnet for the new socket
+ * @ifindex: interface index to bind to
+ * @client_addr: client address to bind to
+ * @server_addr: server address to connect to
+ *
+ * Create a new AF_INET/SOCK_DGRAM socket usable to listen to and send DHCP client
+ * packets.
+ *
+ * The client address given in @addr must be configured on the interface @ifindex
+ * before the socket is created.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int n_dhcp4_c_socket_udp_new(int *sockfdp,
+ int ifindex,
+ const struct in_addr *client_addr,
+ const struct in_addr *server_addr) {
+ _c_cleanup_(c_closep) int sockfd = -1;
+ struct sock_filter filter[] = {
+ /*
+ * IP/UDP
+ *
+ * Set X to the size of IP and UDP headers, for future indirect reads.
+ */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
+ BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
+ BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+
+ /*
+ * DHCP
+ *
+ * Check
+ * - BOOTREPLY (from server to client)
+ * - DHCP magic cookie
+ */
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ };
+ struct sock_fprog fprog = {
+ .filter = filter,
+ .len = sizeof(filter) / sizeof(filter[0]),
+ };
+ struct sockaddr_in saddr = {
+ .sin_family = AF_INET,
+ .sin_addr = *client_addr,
+ .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
+ };
+ struct sockaddr_in daddr = {
+ .sin_family = AF_INET,
+ .sin_addr = *server_addr,
+ .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
+ };
+ int r, tos = IPTOS_CLASS_CS6, on = 1;
+
+ sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = socket_bind_if(sockfd, ifindex);
+ if (r)
+ return r;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+ if (r < 0)
+ return -errno;
+
+ r = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
+ if (r < 0)
+ return -errno;
+
+ r = connect(sockfd, (struct sockaddr*)&daddr, sizeof(daddr));
+ if (r < 0)
+ return -errno;
+
+ *sockfdp = sockfd;
+ sockfd = -1;
+ return 0;
+}
+
+/**
+ * n_dhcp4_s_socket_packet_new() - create a new DHCP4 server packet socket
+ * @sockfdp: return argumnet for the new socket
+ *
+ * Create a new AF_PACKET/SOCK_DGRAM socket usable to send DHCP packets to clients
+ * before they have an IP address configured, on the given interface.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int n_dhcp4_s_socket_packet_new(int *sockfdp) {
+ _c_cleanup_(c_closep) int sockfd = -1;
+
+ sockfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ *sockfdp = sockfd;
+ sockfd = -1;
+ return 0;
+}
+
+/**
+ * n_dhcp4_s_socket_udp_new() - create a new DHCP4 server UDP socket
+ * @sockfdp: return argumnet for the new socket
+ * @ifindex: intercafe index to bind to
+ *
+ * Create a new AF_INET/SOCK_DGRAM socket usable to listen to DHCP server packets,
+ * on the given interface.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int n_dhcp4_s_socket_udp_new(int *sockfdp, int ifindex) {
+ _c_cleanup_(c_closep) int sockfd = -1;
+ struct sock_filter filter[] = {
+ /*
+ * IP/UDP
+ *
+ * Set X to the size of IP and UDP headers, for future indirect reads.
+ */
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), /* X <- IP header length */
+ BPF_STMT(BPF_LD + BPF_W + BPF_K, sizeof(struct udphdr)), /* A <- size of UDP header */
+ BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), /* A += X */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+
+ /*
+ * DHCP
+ *
+ * Check
+ * - BOOTREQUEST (from client to server)
+ * - DHCP magic cookie
+ */
+
+ BPF_STMT(BPF_LD + BPF_B + BPF_IND, offsetof(NDhcp4Header, op)), /* A <- DHCP op */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_OP_BOOTREQUEST, 1, 0), /* op == BOOTREQUEST ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_LD + BPF_W + BPF_IND, offsetof(NDhcp4Message, magic)), /* A <- DHCP magic cookie */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, N_DHCP4_MESSAGE_MAGIC, 1, 0), /* cookie == DHCP magic cookie ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ };
+ struct sock_fprog fprog = {
+ .filter = filter,
+ .len = sizeof(filter) / sizeof(filter[0]),
+ };
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr = { INADDR_ANY },
+ .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
+ };
+ int r, tos = IPTOS_CLASS_CS6, on = 1;
+
+ sockfd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (sockfd < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = socket_bind_if(sockfd, ifindex);
+ if (r)
+ return r;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
+ if (r < 0)
+ return -errno;
+
+ r = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
+ if (r < 0)
+ return -errno;
+
+ *sockfdp = sockfd;
+ sockfd = -1;
+ return 0;
+}
+
+static int n_dhcp4_socket_packet_send(int sockfd,
+ int ifindex,
+ const struct sockaddr_in *src_paddr,
+ const unsigned char *dest_haddr,
+ unsigned char halen,
+ const struct sockaddr_in *dest_paddr,
+ NDhcp4Outgoing *message) {
+ struct packet_sockaddr_ll haddr = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_IP),
+ .sll_ifindex = ifindex,
+ .sll_halen = halen,
+ };
+ const void *buf;
+ size_t n_buf, len;
+ int r;
+
+ c_assert(halen <= sizeof(haddr.sll_addr));
+
+ memcpy(haddr.sll_addr, dest_haddr, halen);
+
+ n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
+
+ r = packet_sendto_udp(sockfd, buf, n_buf, &len, src_paddr, &haddr, dest_paddr);
+ if (r < 0) {
+ if (r == -EAGAIN || r == -ENOBUFS)
+ return N_DHCP4_E_DROPPED;
+ else if (r == -ENETDOWN || r == -ENXIO)
+ return N_DHCP4_E_DOWN;
+ else
+ return r;
+ } else if (len != n_buf) {
+ return N_DHCP4_E_DROPPED;
+ }
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_socket_packet_send() - XXX
+ */
+int n_dhcp4_c_socket_packet_send(int sockfd,
+ int ifindex,
+ const unsigned char *dest_haddr,
+ unsigned char halen,
+ NDhcp4Outgoing *message) {
+ struct sockaddr_in src_paddr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
+ .sin_addr = { INADDR_ANY },
+ };
+ struct sockaddr_in dest_paddr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
+ .sin_addr = { INADDR_BROADCAST }
+ };
+
+ return n_dhcp4_socket_packet_send(sockfd,
+ ifindex,
+ &src_paddr,
+ dest_haddr,
+ halen,
+ &dest_paddr,
+ message);
+}
+
+/**
+ * n_dhcp4_c_socket_udp_send() - XXX
+ */
+int n_dhcp4_c_socket_udp_send(int sockfd,
+ NDhcp4Outgoing *message) {
+ const void *buf;
+ size_t n_buf;
+ ssize_t len;
+
+ n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
+
+ len = send(sockfd, buf, n_buf, 0);
+ if (len < 0) {
+ if (errno == EAGAIN || errno == ENOBUFS)
+ return N_DHCP4_E_DROPPED;
+ else if (errno == ENETDOWN || errno == ENXIO)
+ return N_DHCP4_E_DOWN;
+ else
+ return -errno;
+ } else if ((size_t)len != n_buf)
+ return N_DHCP4_E_DROPPED;
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_c_socket_udp_broadcast() - XXX
+ */
+int n_dhcp4_c_socket_udp_broadcast(int sockfd, NDhcp4Outgoing *message) {
+ struct sockaddr_in sockaddr_dest = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
+ .sin_addr = { INADDR_BROADCAST },
+ };
+ const void *buf;
+ size_t n_buf;
+ ssize_t len;
+
+ n_buf = n_dhcp4_outgoing_get_raw(message, &buf);
+
+ len = sendto(sockfd,
+ buf,
+ n_buf,
+ 0,
+ (struct sockaddr*)&sockaddr_dest,
+ sizeof(sockaddr_dest));
+ if (len < 0) {
+ if (errno == EAGAIN || errno == ENOBUFS)
+ return N_DHCP4_E_DROPPED;
+ else if (errno == ENETDOWN || errno == ENXIO)
+ return N_DHCP4_E_DOWN;
+ else
+ return -errno;
+ } else if ((size_t)len != n_buf)
+ return N_DHCP4_E_DROPPED;
+
+ return 0;
+}
+
+/**
+ * n_dhcp4_s_socket_packet_send() - XXX
+ */
+int n_dhcp4_s_socket_packet_send(int sockfd,
+ int ifindex,
+ const struct in_addr *src_inaddr,
+ const unsigned char *dest_haddr,
+ unsigned char halen,
+ const struct in_addr *dest_inaddr,
+ NDhcp4Outgoing *message) {
+ struct sockaddr_in src_paddr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT),
+ .sin_addr = *src_inaddr,
+ };
+ struct sockaddr_in dest_paddr = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
+ .sin_addr = *dest_inaddr,
+ };
+
+ return n_dhcp4_socket_packet_send(sockfd,
+ ifindex,
+ &src_paddr,
+ dest_haddr,
+ halen,
+ &dest_paddr,
+ message);
+}
+
+/**
+ * n_dhcp4_s_socket_udp_send() - XXX
+ */
+int n_dhcp4_s_socket_udp_send(int sockfd,
+ const struct in_addr *inaddr_src,
+ const struct in_addr *inaddr_dest,
+ NDhcp4Outgoing *message) {
+ struct sockaddr_in sockaddr_dest = {
+ .sin_family = AF_INET,
+ .sin_port = htons(N_DHCP4_NETWORK_CLIENT_PORT),
+ .sin_addr = *inaddr_dest,
+ };
+ struct iovec iov = {};
+ union {
+ struct cmsghdr align; /* ensure correct stack alignment */
+ char buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+ } control = {};
+ struct in_pktinfo pktinfo = {
+ .ipi_spec_dst = *inaddr_src,
+ };
+ struct msghdr msg = {
+ .msg_name = (void*)&sockaddr_dest,
+ .msg_namelen = sizeof(sockaddr_dest),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control.buf,
+ .msg_controllen = sizeof(control.buf),
+ };
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+ ssize_t len;
+
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ memcpy(CMSG_DATA(cmsg), &pktinfo, sizeof(pktinfo));
+
+ iov.iov_len = n_dhcp4_outgoing_get_raw(message, (const void **)&iov.iov_base);
+
+ len = sendmsg(sockfd, &msg, 0);
+ if (len < 0) {
+ if (errno == EAGAIN || errno == ENOBUFS)
+ return N_DHCP4_E_DROPPED;
+ else if (errno == ENETDOWN || errno == ENXIO)
+ return N_DHCP4_E_DOWN;
+ else
+ return -errno;
+ } else if ((size_t)len != iov.iov_len)
+ return N_DHCP4_E_DROPPED;
+
+ return 0;
+}
+
+int n_dhcp4_s_socket_udp_broadcast(int sockfd,
+ const struct in_addr *inaddr_src,
+ NDhcp4Outgoing *message) {
+ return n_dhcp4_s_socket_udp_send(sockfd,
+ inaddr_src,
+ &(const struct in_addr){INADDR_BROADCAST},
+ message);
+}
+
+int n_dhcp4_c_socket_packet_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep) {
+ _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
+ size_t len;
+ int r;
+
+ r = packet_recv_udp(sockfd, buf, n_buf, &len);
+ if (r < 0) {
+ if (r == -ENETDOWN)
+ return N_DHCP4_E_DOWN;
+ else if (r == -EAGAIN)
+ return N_DHCP4_E_AGAIN;
+ else
+ return -errno;
+ } else if (len == 0) {
+ return N_DHCP4_E_MALFORMED;
+ }
+
+ r = n_dhcp4_incoming_new(&message, buf, len);
+ if (r)
+ return r;
+
+ *messagep = message;
+ message = NULL;
+ return 0;
+}
+
+static int n_dhcp4_socket_udp_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep,
+ struct in_pktinfo *pktinfo) {
+ _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
+ struct iovec iov = {
+ .iov_base = buf,
+ .iov_len = n_buf,
+ };
+ uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
+ struct msghdr msg = {
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = cmsgbuf,
+ .msg_controllen = sizeof(cmsgbuf),
+ };
+ ssize_t len;
+ int r;
+
+ len = recvmsg(sockfd, &msg, MSG_TRUNC);
+ if (len < 0) {
+ if (errno == ENETDOWN)
+ return N_DHCP4_E_DOWN;
+ else if (errno == EAGAIN)
+ return N_DHCP4_E_AGAIN;
+ else
+ return -errno;
+ } else if (len == 0 || (size_t)len > n_buf) {
+ return N_DHCP4_E_MALFORMED;
+ }
+
+ r = n_dhcp4_incoming_new(&message, buf, len);
+ if (r)
+ return r;
+
+ if (pktinfo) {
+ struct cmsghdr *cmsg;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ c_assert(cmsg);
+ c_assert(cmsg->cmsg_level == IPPROTO_IP);
+ c_assert(cmsg->cmsg_type == IP_PKTINFO);
+ c_assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo)));
+
+ memcpy(pktinfo, (void*)CMSG_DATA(cmsg), sizeof(struct in_pktinfo));
+ }
+
+ *messagep = message;
+ message = NULL;
+ return 0;
+}
+
+int n_dhcp4_c_socket_udp_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep) {
+ return n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, NULL);
+}
+
+int n_dhcp4_s_socket_udp_recv(int sockfd,
+ uint8_t *buf,
+ size_t n_buf,
+ NDhcp4Incoming **messagep,
+ struct sockaddr_in *dest) {
+ struct in_pktinfo pktinfo = {};
+ int r;
+
+ r = n_dhcp4_socket_udp_recv(sockfd, buf, n_buf, messagep, &pktinfo);
+ if (r)
+ return r;
+
+ if (dest) {
+ dest->sin_family = AF_INET;
+ dest->sin_port = htons(N_DHCP4_NETWORK_SERVER_PORT);
+ dest->sin_addr.s_addr = pktinfo.ipi_addr.s_addr;
+ }
+
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/n-dhcp4.h b/shared/n-dhcp4/src/n-dhcp4.h
new file mode 100644
index 000000000..58a3cb803
--- /dev/null
+++ b/shared/n-dhcp4/src/n-dhcp4.h
@@ -0,0 +1,286 @@
+#pragma once
+
+/*
+ * Dynamic Host Configuration Protocol for IPv4
+ *
+ * This is the public header of the n-dhcp4 library, implementing IPv4 Dynamic
+ * Host Configuration Protocol as described in RFC-2132. This header defines
+ * the public API and all entry points of n-dhcp4.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+
+typedef struct NDhcp4Client NDhcp4Client;
+typedef struct NDhcp4ClientConfig NDhcp4ClientConfig;
+typedef struct NDhcp4ClientEvent NDhcp4ClientEvent;
+typedef struct NDhcp4ClientLease NDhcp4ClientLease;
+typedef struct NDhcp4ClientProbe NDhcp4ClientProbe;
+typedef struct NDhcp4ClientProbeConfig NDhcp4ClientProbeConfig;
+typedef struct NDhcp4Server NDhcp4Server;
+typedef struct NDhcp4ServerConfig NDhcp4ServerConfig;
+typedef struct NDhcp4ServerEvent NDhcp4ServerEvent;
+typedef struct NDhcp4ServerIp NDhcp4ServerIp;
+typedef struct NDhcp4ServerLease NDhcp4ServerLease;
+
+#define N_DHCP4_CLIENT_START_DELAY_RFC2131 (UINT64_C(9000))
+
+enum {
+ _N_DHCP4_E_SUCCESS,
+
+ N_DHCP4_E_PREEMPTED,
+ N_DHCP4_E_INTERNAL,
+
+ N_DHCP4_E_INVALID_IFINDEX,
+ N_DHCP4_E_INVALID_TRANSPORT,
+ N_DHCP4_E_INVALID_ADDRESS,
+ N_DHCP4_E_INVALID_CLIENT_ID,
+ N_DHCP4_E_DUPLICATE_OPTION,
+ N_DHCP4_E_UNSET,
+
+ _N_DHCP4_E_N,
+};
+
+enum {
+ N_DHCP4_TRANSPORT_ETHERNET,
+ N_DHCP4_TRANSPORT_INFINIBAND,
+ _N_DHCP4_TRANSPORT_N,
+};
+
+enum {
+ N_DHCP4_CLIENT_EVENT_DOWN,
+ N_DHCP4_CLIENT_EVENT_OFFER,
+ N_DHCP4_CLIENT_EVENT_GRANTED,
+ N_DHCP4_CLIENT_EVENT_RETRACTED,
+ N_DHCP4_CLIENT_EVENT_EXTENDED,
+ N_DHCP4_CLIENT_EVENT_EXPIRED,
+ N_DHCP4_CLIENT_EVENT_CANCELLED,
+ _N_DHCP4_CLIENT_EVENT_N,
+};
+
+enum {
+ N_DHCP4_SERVER_EVENT_DOWN,
+ N_DHCP4_SERVER_EVENT_DISCOVER,
+ N_DHCP4_SERVER_EVENT_REQUEST,
+ N_DHCP4_SERVER_EVENT_RENEW,
+ N_DHCP4_SERVER_EVENT_DECLINE,
+ N_DHCP4_SERVER_EVENT_RELEASE,
+ _N_DHCP4_SERVER_EVENT_N,
+};
+
+struct NDhcp4ClientEvent {
+ unsigned int event;
+ union {
+ struct {
+ } down;
+ struct {
+ NDhcp4ClientProbe *probe;
+ NDhcp4ClientLease *lease;
+ } offer, granted, extended;
+ struct {
+ NDhcp4ClientProbe *probe;
+ } retracted, expired, cancelled;
+ };
+};
+
+struct NDhcp4ServerEvent {
+ unsigned int event;
+ union {
+ struct {
+ } down;
+ struct {
+ NDhcp4ServerLease *lease;
+ } discover, request, decline, release;
+ };
+};
+
+/* client configs */
+
+int n_dhcp4_client_config_new(NDhcp4ClientConfig **configp);
+NDhcp4ClientConfig *n_dhcp4_client_config_free(NDhcp4ClientConfig *config);
+
+void n_dhcp4_client_config_set_ifindex(NDhcp4ClientConfig *config, int ifindex);
+void n_dhcp4_client_config_set_transport(NDhcp4ClientConfig *config, unsigned int transport);
+void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig *config, bool request_broadcast);
+void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac);
+void n_dhcp4_client_config_set_broadcast_mac(NDhcp4ClientConfig *config, const uint8_t *mac, size_t n_mac);
+int n_dhcp4_client_config_set_client_id(NDhcp4ClientConfig *config, const uint8_t *id, size_t n_id);
+
+/* client-probe configs */
+
+int n_dhcp4_client_probe_config_new(NDhcp4ClientProbeConfig **configp);
+NDhcp4ClientProbeConfig *n_dhcp4_client_probe_config_free(NDhcp4ClientProbeConfig *config);
+
+void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeConfig *config, bool inform_only);
+void n_dhcp4_client_probe_config_set_init_reboot(NDhcp4ClientProbeConfig *config, bool init_reboot);
+void n_dhcp4_client_probe_config_set_requested_ip(NDhcp4ClientProbeConfig *config, struct in_addr ip);
+void n_dhcp4_client_probe_config_set_start_delay(NDhcp4ClientProbeConfig *config, uint64_t msecs);
+void n_dhcp4_client_probe_config_request_option(NDhcp4ClientProbeConfig *config, uint8_t option);
+int n_dhcp4_client_probe_config_append_option(NDhcp4ClientProbeConfig *config,
+ uint8_t option,
+ const void *data,
+ uint8_t n_data);
+
+/* clients */
+
+int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *config);
+NDhcp4Client *n_dhcp4_client_ref(NDhcp4Client *client);
+NDhcp4Client *n_dhcp4_client_unref(NDhcp4Client *client);
+
+void n_dhcp4_client_get_fd(NDhcp4Client *client, int *fdp);
+int n_dhcp4_client_dispatch(NDhcp4Client *client);
+int n_dhcp4_client_pop_event(NDhcp4Client *client, NDhcp4ClientEvent **eventp);
+
+int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu);
+
+int n_dhcp4_client_probe(NDhcp4Client *client,
+ NDhcp4ClientProbe **probep,
+ NDhcp4ClientProbeConfig *config);
+
+/* client probes */
+
+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);
+
+/* client leases */
+
+NDhcp4ClientLease *n_dhcp4_client_lease_ref(NDhcp4ClientLease *lease);
+NDhcp4ClientLease *n_dhcp4_client_lease_unref(NDhcp4ClientLease *lease);
+
+void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct in_addr *yiaddr);
+void n_dhcp4_client_lease_get_lifetime(NDhcp4ClientLease *lease, uint64_t *ns_lifetimep);
+int n_dhcp4_client_lease_query(NDhcp4ClientLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap);
+
+int n_dhcp4_client_lease_select(NDhcp4ClientLease *lease);
+int n_dhcp4_client_lease_accept(NDhcp4ClientLease *lease);
+int n_dhcp4_client_lease_decline(NDhcp4ClientLease *lease, const char *error);
+
+/* server configs */
+
+int n_dhcp4_server_config_new(NDhcp4ServerConfig **configp);
+NDhcp4ServerConfig *n_dhcp4_server_config_free(NDhcp4ServerConfig *config);
+
+void n_dhcp4_server_config_set_ifindex(NDhcp4ServerConfig *config, int ifindex);
+
+/* servers */
+
+int n_dhcp4_server_new(NDhcp4Server **serverp, NDhcp4ServerConfig *config);
+NDhcp4Server *n_dhcp4_server_ref(NDhcp4Server *server);
+NDhcp4Server *n_dhcp4_server_unref(NDhcp4Server *server);
+
+void n_dhcp4_server_get_fd(NDhcp4Server *server, int *fdp);
+int n_dhcp4_server_dispatch(NDhcp4Server *server);
+int n_dhcp4_server_pop_event(NDhcp4Server *server, NDhcp4ServerEvent **eventp);
+
+int n_dhcp4_server_add_ip(NDhcp4Server *server, NDhcp4ServerIp **ipp, struct in_addr ip);
+
+/* server ip addresses */
+
+NDhcp4ServerIp *n_dhcp4_server_ip_free(NDhcp4ServerIp *ip);
+
+/* server leases */
+
+NDhcp4ServerLease *n_dhcp4_server_lease_ref(NDhcp4ServerLease *lease);
+NDhcp4ServerLease *n_dhcp4_server_lease_unref(NDhcp4ServerLease *lease);
+
+int n_dhcp4_server_lease_query(NDhcp4ServerLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap);
+int n_dhcp4_server_lease_append(NDhcp4ServerLease *lease, uint8_t option, uint8_t *data, size_t n_data);
+
+int n_dhcp4_server_lease_offer(NDhcp4ServerLease *lease);
+int n_dhcp4_server_lease_ack(NDhcp4ServerLease *lease);
+int n_dhcp4_server_lease_nack(NDhcp4ServerLease *lease);
+
+/* inline helpers */
+
+static inline void n_dhcp4_client_config_freep(NDhcp4ClientConfig **p) {
+ if (*p)
+ n_dhcp4_client_config_free(*p);
+}
+
+static inline void n_dhcp4_client_config_freev(NDhcp4ClientConfig *p) {
+ n_dhcp4_client_config_free(p);
+}
+
+static inline void n_dhcp4_client_probe_config_freep(NDhcp4ClientProbeConfig **p) {
+ if (*p)
+ n_dhcp4_client_probe_config_free(*p);
+}
+
+static inline void n_dhcp4_client_probe_config_freev(NDhcp4ClientProbeConfig *p) {
+ n_dhcp4_client_probe_config_free(p);
+}
+
+static inline void n_dhcp4_client_unrefp(NDhcp4Client **p) {
+ if (*p)
+ n_dhcp4_client_unref(*p);
+}
+
+static inline void n_dhcp4_client_unrefv(NDhcp4Client *p) {
+ n_dhcp4_client_unref(p);
+}
+
+static inline void n_dhcp4_client_probe_freep(NDhcp4ClientProbe **p) {
+ if (*p)
+ n_dhcp4_client_probe_free(*p);
+}
+
+static inline void n_dhcp4_client_probe_freev(NDhcp4ClientProbe *p) {
+ n_dhcp4_client_probe_free(p);
+}
+
+static inline void n_dhcp4_client_lease_unrefp(NDhcp4ClientLease **p) {
+ if (*p)
+ n_dhcp4_client_lease_unref(*p);
+}
+
+static inline void n_dhcp4_client_lease_unrefv(NDhcp4ClientLease *p) {
+ n_dhcp4_client_lease_unref(p);
+}
+
+static inline void n_dhcp4_server_config_freep(NDhcp4ServerConfig **p) {
+ if (*p)
+ n_dhcp4_server_config_free(*p);
+}
+
+static inline void n_dhcp4_server_config_freev(NDhcp4ServerConfig *p) {
+ n_dhcp4_server_config_free(p);
+}
+
+static inline void n_dhcp4_server_unrefp(NDhcp4Server **p) {
+ if (*p)
+ n_dhcp4_server_unref(*p);
+}
+
+static inline void n_dhcp4_server_unrefv(NDhcp4Server *p) {
+ n_dhcp4_server_unref(p);
+}
+
+static inline void n_dhcp4_server_ip_freep(NDhcp4ServerIp **p) {
+ if (*p)
+ n_dhcp4_server_ip_free(*p);
+}
+
+static inline void n_dhcp4_server_ip_freev(NDhcp4ServerIp *p) {
+ n_dhcp4_server_ip_free(p);
+}
+
+static inline void n_dhcp4_server_lease_unrefp(NDhcp4ServerLease **p) {
+ if (*p)
+ n_dhcp4_server_lease_unref(*p);
+}
+
+static inline void n_dhcp4_server_lease_unrefv(NDhcp4ServerLease *p) {
+ n_dhcp4_server_lease_unref(p);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/shared/n-dhcp4/src/test-api.c b/shared/n-dhcp4/src/test-api.c
new file mode 100644
index 000000000..9e7b6bcaa
--- /dev/null
+++ b/shared/n-dhcp4/src/test-api.c
@@ -0,0 +1,154 @@
+/*
+ * API Visibility Tests
+ * This verifies the visibility and availability of the exported API.
+ */
+
+#undef NDEBUG
+#include
+#include
+#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_lifetime,
+ (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/shared/n-dhcp4/src/test-connection.c b/shared/n-dhcp4/src/test-connection.c
new file mode 100644
index 000000000..360076e7b
--- /dev/null
+++ b/shared/n-dhcp4/src/test-connection.c
@@ -0,0 +1,388 @@
+/*
+ * Tests for DHCP4 Client Connections
+ */
+
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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;
+
+ 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,
+ 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_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_rebind(&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/shared/n-dhcp4/src/test-message.c b/shared/n-dhcp4/src/test-message.c
new file mode 100644
index 000000000..6d4200f78
--- /dev/null
+++ b/shared/n-dhcp4/src/test-message.c
@@ -0,0 +1,159 @@
+/*
+ * Tests for DHCP4 Message Handling
+ */
+
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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/shared/n-dhcp4/src/test-run-client.c b/shared/n-dhcp4/src/test-run-client.c
new file mode 100644
index 000000000..d29dbf1f2
--- /dev/null
+++ b/shared/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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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 adress\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_aton(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/shared/n-dhcp4/src/test-socket.c b/shared/n-dhcp4/src/test-socket.c
new file mode 100644
index 000000000..7d1756534
--- /dev/null
+++ b/shared/n-dhcp4/src/test-socket.c
@@ -0,0 +1,314 @@
+/*
+ * Tests for DHCP4 Socket Helpers
+ */
+
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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);
+ 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,
+ 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,
+ 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,
+ 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/shared/n-dhcp4/src/test.h b/shared/n-dhcp4/src/test.h
new file mode 100644
index 000000000..6933982e8
--- /dev/null
+++ b/shared/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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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 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/shared/n-dhcp4/src/util/link.c b/shared/n-dhcp4/src/util/link.c
new file mode 100644
index 000000000..2a3ac7fe1
--- /dev/null
+++ b/shared/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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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);
+
+ strncpy(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/shared/n-dhcp4/src/util/link.h b/shared/n-dhcp4/src/util/link.h
new file mode 100644
index 000000000..cd0ad6f84
--- /dev/null
+++ b/shared/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
+#include
+#include
+#include
+
+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/shared/n-dhcp4/src/util/netns.c b/shared/n-dhcp4/src/util/netns.c
new file mode 100644
index 000000000..0b7b32e43
--- /dev/null
+++ b/shared/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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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/shared/n-dhcp4/src/util/netns.h b/shared/n-dhcp4/src/util/netns.h
new file mode 100644
index 000000000..02e2e0108
--- /dev/null
+++ b/shared/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
+#include
+
+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/shared/n-dhcp4/src/util/packet.c b/shared/n-dhcp4/src/util/packet.c
new file mode 100644
index 000000000..38cb399d5
--- /dev/null
+++ b/shared/n-dhcp4/src/util/packet.c
@@ -0,0 +1,456 @@
+/*
+ * Packet Sockets
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "packet.h"
+
+/**
+ * packet_internet_checksum() - compute the internet checksum
+ * @data: the data to checksum
+ * @size: the length of @data in bytes
+ *
+ * Computes the internet checksum for a given blob according to RFC1071.
+ *
+ * The internet checksum is the one's complement of the one's complement sum of
+ * the 16-bit words of the data, padded with zero-bytes if the data does not
+ * end on a 16-bit boundary.
+ *
+ * Return: Checksum is returned.
+ */
+uint16_t packet_internet_checksum(const uint8_t *data, size_t size) {
+ uint64_t acc = 0;
+ uint32_t local;
+
+ while (size >= sizeof(local)) {
+ memcpy(&local, data, sizeof(local));
+ acc += local;
+
+ data += sizeof(local);
+ size -= sizeof(local);
+ }
+
+ if (size) {
+ local = 0;
+ memcpy(&local, data, size);
+ acc += local;
+ }
+
+ while (acc >> 16)
+ acc = (acc & 0xffff) + (acc >> 16);
+
+ return ~acc;
+}
+
+/**
+ * packet_internet_checksum_udp() - compute the internet checkum for UDP packets
+ * @src_addr: source IP address
+ * @dst_addr: destination IP address
+ * @src_port: source port
+ * @dst_port: destination port
+ * @data: payload
+ * @size: length of payload in bytes
+ * @checksum: current checksum, or 0
+ *
+ * Computes the internet checksum for a UDP packet, given the relevant IP and
+ * UDP header fields.
+ *
+ * Note that since a UDP packet contains the checksum itself, the resulting
+ * checksum will always be 0 (this fact is used to verify that a UDP packet is
+ * valid).
+ * Inversely, when calculating the checksum for outgoing packets, you have to
+ * specify 0 as @checksum, and this function will return the checksum for the
+ * caller to use for the packet. In this case, though, the caller must check
+ * whether the returned checksum might coincidentally be 0, in which case it
+ * must be flipped to -1 (0xffff), since 0 is not allowed as checksum in UDP
+ * packets, and -1 is arithmetically equivalent in the checksum calculation.
+ *
+ * Return: Checksum is returned.
+ */
+uint16_t packet_internet_checksum_udp(const struct in_addr *src_addr,
+ const struct in_addr *dst_addr,
+ uint16_t src_port,
+ uint16_t dst_port,
+ const uint8_t *data,
+ size_t size,
+ uint16_t checksum) {
+ struct {
+ uint32_t src;
+ uint32_t dst;
+ uint8_t _zeros;
+ uint8_t protocol;
+ uint16_t length;
+ struct udphdr udp;
+ } _c_packed_ udp_phdr = {
+ .src = src_addr->s_addr,
+ .dst = dst_addr->s_addr,
+ .protocol = IPPROTO_UDP,
+ .length = htons(sizeof(struct udphdr) + size),
+ .udp = {
+ .source = htons(src_port),
+ .dest = htons(dst_port),
+ .len = htons(sizeof(struct udphdr) + size),
+ .check = checksum,
+ },
+ };
+ const uint8_t *iter;
+ uint64_t acc = 0;
+ uint32_t local;
+
+ _Static_assert(!(sizeof(udp_phdr) % sizeof(local)),
+ "UDP header structure size is not a multiple of 4");
+
+ for (iter = (const uint8_t *)&udp_phdr;
+ iter < (const uint8_t *)(&udp_phdr + 1);
+ iter += sizeof(local)) {
+ memcpy(&local, iter, sizeof(local));
+ acc += local;
+ }
+
+ while (size >= sizeof(local)) {
+ memcpy(&local, data, sizeof(local));
+ acc += local;
+
+ data += sizeof(local);
+ size -= sizeof(local);
+ }
+
+ if (size) {
+ local = 0;
+ memcpy(&local, data, size);
+ acc += local;
+ }
+
+ while (acc >> 16)
+ acc = (acc & 0xffff) + (acc >> 16);
+
+ return ~acc;
+}
+
+/**
+ * packet_sendto_udp() - send UDP packet on AF_PACKET socket
+ * @sockfd: AF_PACKET/SOCK_DGRAM socket
+ * @buf: payload
+ * @n_buf: length of payload in bytes
+ * @n_transmittedp: output argument for number of transmitted bytes
+ * @src_paddr: source protocol address, see ip(7)
+ * @dest_haddr: destination hardware address, see packet(7)
+ * @dest_paddr: destination protocol address, see ip(7)
+ *
+ * Sends an UDP packet on a AF_PACKET socket directly to a hardware
+ * address. The difference between this and sendto() on an AF_INET
+ * socket is that no routing is performed, so the packet is delivered
+ * even if the destination IP is not yet configured on the destination
+ * host.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int packet_sendto_udp(int sockfd,
+ const void *buf,
+ size_t n_buf,
+ size_t *n_transmittedp,
+ const struct sockaddr_in *src_paddr,
+ const struct packet_sockaddr_ll *dest_haddr,
+ const struct sockaddr_in *dest_paddr) {
+ struct iphdr ip_hdr = {
+ .version = IPVERSION,
+ .ihl = sizeof(ip_hdr) / 4, /* Length of header in multiples of four bytes */
+ .tos = IPTOS_CLASS_CS6, /* Class Selector for network control */
+ .tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + n_buf),
+ .frag_off = htons(IP_DF), /* Do not fragment */
+ .ttl = IPDEFTTL,
+ .protocol = IPPROTO_UDP,
+ .saddr = src_paddr->sin_addr.s_addr,
+ .daddr = dest_paddr->sin_addr.s_addr,
+ };
+ struct udphdr udp_hdr = {
+ .source = src_paddr->sin_port,
+ .dest = dest_paddr->sin_port,
+ .len = htons(sizeof(udp_hdr) + n_buf),
+ };
+ struct iovec iov[3] = {
+ {
+ .iov_base = &ip_hdr,
+ .iov_len = sizeof(ip_hdr),
+ },
+ {
+ .iov_base = &udp_hdr,
+ .iov_len = sizeof(udp_hdr),
+ },
+ {
+ .iov_base = (void *)buf,
+ .iov_len = n_buf,
+ },
+ };
+ struct msghdr msg = {
+ .msg_name = (void*)dest_haddr,
+ .msg_namelen = sizeof(*dest_haddr),
+ .msg_iov = iov,
+ .msg_iovlen = sizeof(iov) / sizeof(iov[0]),
+ };
+ ssize_t pktlen;
+
+ ip_hdr.check = packet_internet_checksum((void*)&ip_hdr, sizeof(ip_hdr));
+ udp_hdr.check = packet_internet_checksum_udp(&src_paddr->sin_addr,
+ &dest_paddr->sin_addr,
+ ntohs(src_paddr->sin_port),
+ ntohs(dest_paddr->sin_port),
+ buf,
+ n_buf,
+ 0);
+
+ /*
+ * 0x0000 and 0xffff are equivalent for computing the UDP checksum,
+ * but 0x0000 is reserved in UDP headers, to mean that the checksum is
+ * not set and should be ignored by the receiver. Hence, flip it to
+ * 0xffff in that case.
+ */
+ udp_hdr.check = udp_hdr.check ?: 0xffff;
+
+ pktlen = sendmsg(sockfd, &msg, 0);
+ if (pktlen < 0)
+ return -errno;
+
+ /*
+ * Kernel never truncates. Worst case, we get -EMSGSIZE. Kernel *might*
+ * prepend VNET headers, in which case a bigger length than sent is
+ * returned.
+ * Lets assert on this, and then return to the caller the proportion of
+ * its own buffer that we sent (which is always exactly the requested
+ * size).
+ */
+ c_assert((size_t)pktlen >= sizeof(ip_hdr) + sizeof(udp_hdr) + n_buf);
+ *n_transmittedp = n_buf;
+ return 0;
+}
+
+/**
+ * packet_recvfrom_upd() - receive UDP packet from AF_PACKET socket
+ * @sockfd: AF_PACKET/SOCK_DGRAM socket
+ * @buf: buffor for payload
+ * @n_buf: max length of payload in bytes
+ * @n_transmittedp: output argument for number transmitted bytes
+ * @src: return argumnet for source address, or NULL, see ip(7)
+ *
+ * Receives an UDP packet on a AF_PACKET socket. The difference between
+ * this and recvfrom() on an AF_INET socket is that the packet will be
+ * received even if the destination IP address has not been configured
+ * on the interface.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int packet_recvfrom_udp(int sockfd,
+ void *buf,
+ size_t n_buf,
+ size_t *n_transmittedp,
+ struct sockaddr_in *src) {
+ union {
+ struct iphdr hdr;
+ /*
+ * Maximum IP-header length is 15 * 4, since it is specified in
+ * the `ihl` field, which is four bits and interpreted as
+ * factor of 4. So maximum `ihl` value is `(2^4 - 1) * 4`.
+ */
+ uint8_t data[15 * 4];
+ } ip_hdr;
+ struct udphdr udp_hdr;
+ struct iovec iov[3] = {
+ {
+ .iov_base = &ip_hdr,
+ },
+ {
+ .iov_base = &udp_hdr,
+ .iov_len = sizeof(udp_hdr),
+ },
+ {
+ .iov_base = buf,
+ .iov_len = n_buf,
+ },
+ };
+ uint8_t cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))];
+ struct msghdr msg = {
+ .msg_iov = iov,
+ .msg_iovlen = sizeof(iov) / sizeof(iov[0]),
+ .msg_control = cmsgbuf,
+ .msg_controllen = sizeof(cmsgbuf),
+ };
+ struct cmsghdr *cmsg;
+ bool checksum = true;
+ ssize_t pktlen;
+ size_t hdrlen;
+
+ /* Peek packet to obtain the real IP header length */
+ pktlen = recv(sockfd, &ip_hdr.hdr, sizeof(ip_hdr.hdr), MSG_PEEK);
+ if (pktlen < 0)
+ return -errno;
+
+ if ((size_t)pktlen < sizeof(ip_hdr.hdr)) {
+ /*
+ * Received packet is smaller than the minimal IP header length,
+ * discard it.
+ */
+ recv(sockfd, NULL, 0, 0);
+ *n_transmittedp = 0;
+ return 0;
+ }
+
+ if (ip_hdr.hdr.version != IPVERSION) {
+ /*
+ * This is not an IPv4 packet, discard it.
+ */
+ recv(sockfd, NULL, 0, 0);
+ *n_transmittedp = 0;
+ return 0;
+ }
+
+ hdrlen = ip_hdr.hdr.ihl * 4;
+ if (hdrlen < sizeof(ip_hdr.hdr)) {
+ /*
+ * The length given in the header is smaller than the minimum
+ * header length, discard the packet.
+ */
+ recv(sockfd, NULL, 0, 0);
+ *n_transmittedp = 0;
+ return 0;
+ }
+
+ /*
+ * Now that we know the ip-header length, we can prepare the iovec to
+ * read the entire packet into the correct buffers.
+ */
+ iov[0].iov_len = hdrlen;
+ pktlen = recvmsg(sockfd, &msg, 0);
+ if (pktlen < 0)
+ return -errno;
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg) {
+ if (cmsg->cmsg_level == SOL_PACKET &&
+ cmsg->cmsg_type == PACKET_AUXDATA &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) {
+ struct tpacket_auxdata *aux = (void *)CMSG_DATA(cmsg);
+ checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+ }
+ }
+
+ if (ntohs(ip_hdr.hdr.tot_len) > pktlen) {
+ /*
+ * The IP-packet is bigger than the chunk returned by the
+ * kernel. So either the packet is corrupt, or our caller
+ * provided too small a buffer. In both cases, we simply drop
+ * the packet.
+ */
+ *n_transmittedp = 0;
+ return 0;
+ }
+
+ /* Truncate trailing garbage. */
+ pktlen = ntohs(ip_hdr.hdr.tot_len);
+
+ if ((size_t)pktlen < hdrlen + sizeof(udp_hdr)) {
+ /*
+ * The packet is too small to even contain an entire UDP
+ * header, so discard it entirely.
+ */
+ *n_transmittedp = 0;
+ return 0;
+ } else if ((size_t)pktlen < hdrlen + ntohs(udp_hdr.len)) {
+ /*
+ * The UDP header specified a longer length than the returned
+ * packet, so discard it entirely.
+ */
+ *n_transmittedp = 0;
+ return 0;
+ }
+
+ /*
+ * Make @pktlen the length of the packet payload, without IP/UDP
+ * headers, since that is what the caller is interested in.
+ */
+ pktlen = ntohs(udp_hdr.len) - sizeof(struct udphdr);
+
+ /* IP */
+
+ if (ip_hdr.hdr.protocol != IPPROTO_UDP) {
+ *n_transmittedp = 0;
+ return 0; /* not a UDP packet, discard it */
+ } else if (ip_hdr.hdr.frag_off & htons(IP_MF | IP_OFFMASK)) {
+ *n_transmittedp = 0;
+ return 0; /* fragmented packet, discard it */
+ } else if (checksum && packet_internet_checksum(ip_hdr.data, hdrlen)) {
+ *n_transmittedp = 0;
+ return 0; /* invalid checksum, discard it */
+ }
+
+ /* UDP */
+
+ if (checksum && udp_hdr.check) {
+ /*
+ * Computing the checksum of a packet that has the checksum set
+ * must yield 0. If it does not yield 0, the packet is invalid,
+ * in which case we discard it.
+ */
+ if (packet_internet_checksum_udp(&(struct in_addr){ ip_hdr.hdr.saddr },
+ &(struct in_addr){ ip_hdr.hdr.daddr },
+ ntohs(udp_hdr.source),
+ ntohs(udp_hdr.dest),
+ buf,
+ pktlen,
+ udp_hdr.check)) {
+ *n_transmittedp = 0;
+ return 0;
+ }
+ }
+
+ if (src) {
+ src->sin_family = AF_INET;
+ src->sin_addr.s_addr = ip_hdr.hdr.saddr;
+ src->sin_port = udp_hdr.source;
+ }
+
+ /* Return length of UDP payload (i.e., data written to @buf). */
+ *n_transmittedp = pktlen;
+ return 0;
+}
+
+/**
+ * packet_shutdown() - shutdown socket for future receive operations
+ * @sockfd: socket
+ *
+ * Partially emulates `shutdown(sockfd, SHUT_RD)`, in the sense that no
+ * further packets may be queued on the socket. All packets that are
+ * already queued will still be delivered, but once -EAGAIN is returned
+ * we are guaranteed never to be able to read more packets in the future.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int packet_shutdown(int sockfd) {
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_RET + BPF_K, 0), /* discard all packets */
+ };
+ struct sock_fprog fprog = {
+ .filter = filter,
+ .len = sizeof(filter) / sizeof(filter[0]),
+ };
+ int r;
+
+ r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/util/packet.h b/shared/n-dhcp4/src/util/packet.h
new file mode 100644
index 000000000..edbf2a3e4
--- /dev/null
+++ b/shared/n-dhcp4/src/util/packet.h
@@ -0,0 +1,62 @@
+#pragma once
+
+/*
+ * Packet Sockets
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/*
+ * `struct sockaddr_ll` is too small to fit the Infiniband hardware address.
+ * Introduce `struct packet_sockaddr_ll` which is the same as the original,
+ * except the `sl_addr` field is extended to fit all the supported hardware
+ * addresses.
+ */
+struct packet_sockaddr_ll {
+ unsigned short sll_family;
+ __be16 sll_protocol;
+ int sll_ifindex;
+ unsigned short sll_hatype;
+ unsigned char sll_pkttype;
+ unsigned char sll_halen;
+ unsigned char sll_addr[MAX_ADDR_LEN];
+};
+
+uint16_t packet_internet_checksum(const uint8_t *data, size_t len);
+uint16_t packet_internet_checksum_udp(const struct in_addr *src_addr,
+ const struct in_addr *dst_addr,
+ uint16_t src_port,
+ uint16_t dst_port,
+ const uint8_t *data,
+ size_t size,
+ uint16_t checksum);
+
+int packet_sendto_udp(int sockfd,
+ const void *buf,
+ size_t n_buf,
+ size_t *n_transmittedp,
+ const struct sockaddr_in *src_paddr,
+ const struct packet_sockaddr_ll *dest_haddr,
+ const struct sockaddr_in *dest_paddr);
+int packet_recvfrom_udp(int sockfd,
+ void *buf,
+ size_t n_buf,
+ size_t *n_transmittedp,
+ struct sockaddr_in *src);
+
+int packet_shutdown(int sockfd);
+
+/* inline helpers */
+
+static inline int packet_recv_udp(int sockfd,
+ void *buf,
+ size_t n_buf,
+ size_t *n_transmittedp) {
+ return packet_recvfrom_udp(sockfd, buf, n_buf, n_transmittedp, NULL);
+}
diff --git a/shared/n-dhcp4/src/util/socket.c b/shared/n-dhcp4/src/util/socket.c
new file mode 100644
index 000000000..c25f76fdd
--- /dev/null
+++ b/shared/n-dhcp4/src/util/socket.c
@@ -0,0 +1,121 @@
+/*
+ * Socket Utilities
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "socket.h"
+
+/**
+ * socket_SIOCGIFNAME() - resolve an ifindex to an ifname
+ * @socket: socket to operate on
+ * @ifindex: index of network interface to resolve
+ * @ifname: buffer to store resolved name
+ *
+ * This uses the SIOCGIFNAME ioctl to resolve an ifindex to an ifname. The
+ * buffer provided in @ifnamep must be at least IFNAMSIZ bytes in size. The
+ * maximum ifname length is IFNAMSIZ-1, and this function always
+ * zero-terminates the result.
+ *
+ * This function is similar to if_indextoname(3) provided by glibc, but it
+ * allows to specify the target socket explicitly. This allows the caller to
+ * control the target network-namespace, rather than relying on the network
+ * namespace of the running process.
+ *
+ * Return: 0 on success, negative kernel error code on failure.
+ */
+int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]) {
+ struct ifreq req = { .ifr_ifindex = ifindex };
+ int r;
+
+ r = ioctl(socket, SIOCGIFNAME, &req);
+ if (r < 0)
+ return -errno;
+
+ /*
+ * The linux kernel guarantees that an interface name is always
+ * zero-terminated, and it always fully fits into IFNAMSIZ bytes,
+ * including the zero-terminator.
+ */
+ memcpy(ifnamep, req.ifr_name, IFNAMSIZ);
+ return 0;
+}
+
+/**
+ * socket_bind_if() - bind socket to a network interface
+ * @socket: socket to operate on
+ * @ifindex: index of network interface to bind to, or 0
+ *
+ * This binds the socket given via @socket to the network interface specified
+ * via @ifindex. It uses the underlying SO_BINDTODEVICE ioctl of the linux
+ * kernel. However, if available, if prefers the newer SO_BINDTOIFINDEX ioctl,
+ * which avoids resolving the interface name temporarily, and thus does not
+ * suffer from a race-condition.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int socket_bind_if(int socket, int ifindex) {
+ char ifname[IFNAMSIZ] = {};
+ int r;
+
+ c_assert(ifindex >= 0);
+
+ /*
+ * We first try the newer SO_BINDTOIFINDEX. If it is not available on
+ * the running kernel, we fall back to SO_BINDTODEVICE. This, however,
+ * requires us to first resolve the ifindex to an ifname. Note that
+ * this is racy, since the device name might theoretically change
+ * asynchronously.
+ *
+ * Using 0 as ifindex will remove the device-binding. For
+ * SO_BINDTOIFINDEX we simply pass-through the 0 to the kernel, which
+ * recognizes this correctly. For SO_BINDTODEVICE we pass the empty
+ * string, which the kernel recognizes as a request to remove the
+ * binding.
+ *
+ * The commit introducing SO_BINDTOIFINDEX first appeared in linux-5.1:
+ *
+ * commit f5dd3d0c9638a9d9a02b5964c4ad636f06cf7e2c
+ * Author: David Herrmann
+ * Date: Tue Jan 15 14:42:14 2019 +0100
+ *
+ * net: introduce SO_BINDTOIFINDEX sockopt
+ *
+ * In older kernels, setsockopt(2) is guaranteed to return ENOPROTOOPT
+ * for this ioctl.
+ */
+
+#ifdef SO_BINDTOIFINDEX
+ r = setsockopt(socket,
+ SOL_SOCKET,
+ SO_BINDTOIFINDEX,
+ &ifindex,
+ sizeof(ifindex));
+ if (r >= 0)
+ return 0;
+ else if (errno != ENOPROTOOPT)
+ return -errno;
+#endif /* SO_BINDTOIFINDEX */
+
+ if (ifindex > 0) {
+ r = socket_SIOCGIFNAME(socket, ifindex, &ifname);
+ if (r)
+ return r;
+ }
+
+ r = setsockopt(socket,
+ SOL_SOCKET,
+ SO_BINDTODEVICE,
+ ifname,
+ strlen(ifname));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
diff --git a/shared/n-dhcp4/src/util/socket.h b/shared/n-dhcp4/src/util/socket.h
new file mode 100644
index 000000000..b5ecf2c21
--- /dev/null
+++ b/shared/n-dhcp4/src/util/socket.h
@@ -0,0 +1,11 @@
+#pragma once
+
+/*
+ * Socket Utilities
+ */
+
+#include
+#include
+
+int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]);
+int socket_bind_if(int socket, int ifindex);
diff --git a/shared/n-dhcp4/src/util/test-packet.c b/shared/n-dhcp4/src/util/test-packet.c
new file mode 100644
index 000000000..0f5cba63a
--- /dev/null
+++ b/shared/n-dhcp4/src/util/test-packet.c
@@ -0,0 +1,411 @@
+/*
+ * Packet Socket Tests
+ */
+
+#undef NDEBUG
+#include
+#include
+#include
+#include
+#include
+#include
+#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);
+ 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);
+ 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/shared/n-dhcp4/subprojects/c-list b/shared/n-dhcp4/subprojects/c-list
new file mode 160000
index 000000000..2e4b605c6
--- /dev/null
+++ b/shared/n-dhcp4/subprojects/c-list
@@ -0,0 +1 @@
+Subproject commit 2e4b605c6217cd3c8a1ef773f82f5cc329ba650d
diff --git a/shared/n-dhcp4/subprojects/c-siphash b/shared/n-dhcp4/subprojects/c-siphash
new file mode 160000
index 000000000..7c42c5925
--- /dev/null
+++ b/shared/n-dhcp4/subprojects/c-siphash
@@ -0,0 +1 @@
+Subproject commit 7c42c592581906fef19458372b8db2b643278211
diff --git a/shared/n-dhcp4/subprojects/c-stdaux b/shared/n-dhcp4/subprojects/c-stdaux
new file mode 160000
index 000000000..11930d259
--- /dev/null
+++ b/shared/n-dhcp4/subprojects/c-stdaux
@@ -0,0 +1 @@
+Subproject commit 11930d259212605a15430523472ef54e0c7654ee