Squashed 'shared/n-dhcp4/' content from commit fb1d43449

git-subtree-dir: shared/n-dhcp4
git-subtree-split: fb1d43449ba80a54c70b0fe14cd0b7fd31d8504f
This commit is contained in:
Tom Gundersen
2019-05-14 13:35:50 +02:00
commit e23b3c9c3a
40 changed files with 10483 additions and 0 deletions

12
.cherryci/ci-test Executable file
View File

@@ -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

11
.editorconfig Normal file
View File

@@ -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

9
.gitmodules vendored Normal file
View File

@@ -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

21
.travis.yml Normal file
View File

@@ -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

37
AUTHORS Normal file
View File

@@ -0,0 +1,37 @@
LICENSE:
This project is dual-licensed under both the Apache License, Version
2.0, and the GNU Lesser General Public License, Version 2.1+.
AUTHORS-ASL:
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
AUTHORS-LGPL:
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; If not, see <http://www.gnu.org/licenses/>.
COPYRIGHT: (ordered alphabetically)
Copyright (C) 2015-2019 Red Hat, Inc.
AUTHORS: (ordered alphabetically)
David Rheinsberg <david.rheinsberg@gmail.com>
Tom Gundersen <teg@jklm.no>

20
NEWS.md Normal file
View File

@@ -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

53
README.md Normal file
View File

@@ -0,0 +1,53 @@
n-dhcp4
=======
Dynamic Host Configuration Protocol for IPv4
The n-dhcp4 project implements the IPv4 Dynamic Host Configuration Protocol as
defined in RFC-2132+.
### Project
* **Website**: <https://nettools.github.io/n-dhcp4>
* **Bug Tracker**: <https://github.com/nettools/n-dhcp4/issues>
* **Mailing-List**: <https://groups.google.com/forum/#!forum/nettools-devel>
### Requirements
The requirements for this project are:
* `Linux kernel >= 3.19`
* `libc` (e.g., `glibc >= 2.16`)
At build-time, the following software is required:
* `meson >= 0.41`
* `pkg-config >= 0.29`
### Build
The meson build-system is used for this project. Contact upstream
documentation for detailed help. In most situations the following
commands are sufficient to build and install from source:
```sh
mkdir build
cd build
meson setup ..
ninja
meson test
ninja install
```
No custom configuration options are available.
### Repository:
- **web**: <https://github.com/nettools/n-dhcp4>
- **https**: `https://github.com/nettools/n-dhcp4.git`
- **ssh**: `git@github.com:nettools/n-dhcp4.git`
### License:
- **Apache-2.0** OR **LGPL-2.1-or-later**
- See AUTHORS file for details.

23
meson.build Normal file
View File

@@ -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')

66
src/libndhcp4.sym Normal file
View File

@@ -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:
*;
};

91
src/meson.build Normal file
View File

@@ -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)

1147
src/n-dhcp4-c-connection.c Normal file

File diff suppressed because it is too large Load Diff

356
src/n-dhcp4-c-lease.c Normal file
View File

@@ -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 <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"
/*
* 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;
}

1202
src/n-dhcp4-c-probe.c Normal file

File diff suppressed because it is too large Load Diff

831
src/n-dhcp4-client.c Normal file
View File

@@ -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 <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <errno.h>
#include <linux/if_ether.h>
#include <linux/if_infiniband.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"
/**
* 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;
}

431
src/n-dhcp4-incoming.c Normal file
View File

@@ -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 <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <inttypes.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#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;
}

371
src/n-dhcp4-outgoing.c Normal file
View File

@@ -0,0 +1,371 @@
/*
* DHCPv4 Outgoing Messages
*
* XXX
*/
#include <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <inttypes.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#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;
}

689
src/n-dhcp4-private.h Normal file
View File

@@ -0,0 +1,689 @@
#pragma once
#include <arpa/inet.h>
#include <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <endian.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/netdevice.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#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;
}

411
src/n-dhcp4-s-connection.c Normal file
View File

@@ -0,0 +1,411 @@
/*
* DHCPv4 Server Connection
*
* XXX
*/
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <net/if_arp.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include "n-dhcp4-private.h"
#include "util/packet.h"
int n_dhcp4_s_connection_init(NDhcp4SConnection *connection, int ifindex) {
int r;
*connection = (NDhcp4SConnection)N_DHCP4_S_CONNECTION_NULL(*connection);
r = n_dhcp4_s_socket_packet_new(&connection->fd_packet);
if (r)
return r;
r = n_dhcp4_s_socket_udp_new(&connection->fd_udp, ifindex);
if (r)
return r;
connection->ifindex = ifindex;
return 0;
}
void n_dhcp4_s_connection_deinit(NDhcp4SConnection *connection) {
c_assert(!connection->ip);
if (connection->fd_udp >= 0) {
close(connection->fd_udp);
}
if (connection->fd_packet >= 0) {
close(connection->fd_packet);
}
*connection = (NDhcp4SConnection)N_DHCP4_S_CONNECTION_NULL(*connection);
}
void n_dhcp4_s_connection_get_fd(NDhcp4SConnection *connection, int *fdp) {
*fdp = connection->fd_udp;
}
static bool n_dhcp4_s_connection_owns_ip(NDhcp4SConnection *connection, struct in_addr addr) {
if (!connection->ip)
return false;
return (connection->ip->ip.s_addr == addr.s_addr);
}
static int n_dhcp4_s_connection_verify_incoming(NDhcp4SConnection *connection,
NDhcp4Incoming *message,
bool broadcast) {
uint8_t type;
int r;
r = n_dhcp4_incoming_query_message_type(message, &type);
if (r) {
if (r == N_DHCP4_E_UNSET)
return N_DHCP4_E_MALFORMED;
else
return r;
}
switch (type) {
case N_DHCP4_MESSAGE_DISCOVER:
message->userdata.type = N_DHCP4_C_MESSAGE_DISCOVER;
break;
case N_DHCP4_MESSAGE_REQUEST: {
struct in_addr server_identifier = {};
struct in_addr requested_ip = {};
r = n_dhcp4_incoming_query_server_identifier(message, &server_identifier);
if (r) {
if (r == N_DHCP4_E_UNSET) {
r = n_dhcp4_incoming_query_requested_ip(message, &requested_ip);
if (r) {
if (r == N_DHCP4_E_UNSET) {
if (broadcast) {
message->userdata.type = N_DHCP4_C_MESSAGE_REBIND;
} else {
message->userdata.type = N_DHCP4_C_MESSAGE_RENEW;
}
} else {
return r;
}
} else {
message->userdata.type = N_DHCP4_C_MESSAGE_REBOOT;
}
} else {
return r;
}
} else {
if (n_dhcp4_s_connection_owns_ip(connection, server_identifier)) {
message->userdata.type = N_DHCP4_C_MESSAGE_SELECT;
} else {
message->userdata.type = N_DHCP4_C_MESSAGE_IGNORE;
}
}
}
break;
case N_DHCP4_MESSAGE_DECLINE:
message->userdata.type = N_DHCP4_C_MESSAGE_DECLINE;
break;
case N_DHCP4_MESSAGE_RELEASE:
message->userdata.type = N_DHCP4_C_MESSAGE_RELEASE;
break;
default:
return N_DHCP4_E_UNEXPECTED;
}
return 0;
}
int n_dhcp4_s_connection_dispatch_io(NDhcp4SConnection *connection, NDhcp4Incoming **messagep) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
struct sockaddr_in dest = {};
int r;
r = n_dhcp4_s_socket_udp_recv(connection->fd_udp,
connection->buf,
sizeof(connection->buf),
&message,
&dest);
if (r)
return r;
r = n_dhcp4_s_connection_verify_incoming(connection,
message,
dest.sin_addr.s_addr == INADDR_BROADCAST);
if (r) {
if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED) {
*messagep = NULL;
return 0;
}
return -ENOTRECOVERABLE;
}
*messagep = message;
message = NULL;
return 0;
}
/*
* If the 'giaddr' field in a DHCP message from a client is non-zero,
* the server sends any return messages to the 'DHCP server' port on the
* BOOTP relay agent whose address appears in 'giaddr'. If the 'giaddr'
* field is zero and the 'ciaddr' field is nonzero, then the server
* unicasts DHCPOFFER and DHCPACK messages to the address in 'ciaddr'.
* If 'giaddr' is zero and 'ciaddr' is zero, and the broadcast bit is
* set, then the server broadcasts DHCPOFFER and DHCPACK messages to
* 0xffffffff. If the broadcast bit is not set and 'giaddr' is zero and
* 'ciaddr' is zero, then the server unicasts DHCPOFFER and DHCPACK
* messages to the client's hardware address and 'yiaddr' address. In
* all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK
* messages to 0xffffffff.
*/
int n_dhcp4_s_connection_send_reply(NDhcp4SConnection *connection,
const struct in_addr *server_addr,
NDhcp4Outgoing *message) {
NDhcp4Header *header = n_dhcp4_outgoing_get_header(message);
int r;
if (header->giaddr) {
const struct in_addr giaddr = { header->giaddr };
r = n_dhcp4_s_socket_udp_send(connection->fd_udp,
server_addr,
&giaddr,
message);
if (r)
return r;
} else if (header->ciaddr) {
const struct in_addr ciaddr = { header->ciaddr };
r = n_dhcp4_s_socket_udp_send(connection->fd_udp,
server_addr,
&ciaddr,
message);
if (r)
return r;
} else if (header->flags & htons(N_DHCP4_MESSAGE_FLAG_BROADCAST)) {
r = n_dhcp4_s_socket_udp_broadcast(connection->fd_udp,
server_addr,
message);
if (r)
return r;
} else {
r = n_dhcp4_s_socket_packet_send(connection->fd_packet,
connection->ifindex,
server_addr,
header->chaddr,
header->hlen,
&(struct in_addr){header->yiaddr},
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;
}

103
src/n-dhcp4-s-lease.c Normal file
View File

@@ -0,0 +1,103 @@
/*
* XXX
*/
#include <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"
/**
* n_dhcp4_server_lease_new() - XXX
*/
int n_dhcp4_server_lease_new(NDhcp4ServerLease **leasep, NDhcp4Incoming *message) {
_c_cleanup_(n_dhcp4_server_lease_unrefp) NDhcp4ServerLease *lease = NULL;
c_assert(leasep);
lease = malloc(sizeof(*lease));
if (!lease)
return -ENOMEM;
*lease = (NDhcp4ServerLease)N_DHCP4_SERVER_LEASE_NULL(*lease);
lease->request = message;
*leasep = lease;
lease = NULL;
return 0;
}
static void n_dhcp4_server_lease_free(NDhcp4ServerLease *lease) {
c_assert(!lease->server);
c_list_unlink(&lease->server_link);
n_dhcp4_incoming_free(lease->request);
free(lease);
}
/**
* n_dhcp4_server_lease_ref() - XXX
*/
_c_public_ NDhcp4ServerLease *n_dhcp4_server_lease_ref(NDhcp4ServerLease *lease) {
if (lease)
++lease->n_refs;
return lease;
}
/**
* n_dhcp4_server_lease_unref() - XXX
*/
_c_public_ NDhcp4ServerLease *n_dhcp4_server_lease_unref(NDhcp4ServerLease *lease) {
if (lease && !--lease->n_refs)
n_dhcp4_server_lease_free(lease);
return NULL;
}
/**
* n_dhcp4_server_lease_query() - XXX
*/
_c_public_ int n_dhcp4_server_lease_query(NDhcp4ServerLease *lease, uint8_t option, uint8_t **datap, size_t *n_datap) {
switch (option) {
case N_DHCP4_OPTION_PAD:
case N_DHCP4_OPTION_REQUESTED_IP_ADDRESS:
case N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME:
case N_DHCP4_OPTION_OVERLOAD:
case N_DHCP4_OPTION_MESSAGE_TYPE:
case N_DHCP4_OPTION_SERVER_IDENTIFIER:
case N_DHCP4_OPTION_PARAMETER_REQUEST_LIST:
case N_DHCP4_OPTION_ERROR_MESSAGE:
case N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE:
case N_DHCP4_OPTION_RENEWAL_T1_TIME:
case N_DHCP4_OPTION_REBINDING_T2_TIME:
case N_DHCP4_OPTION_END:
return N_DHCP4_E_INTERNAL;
}
return n_dhcp4_incoming_query(lease->request, option, datap, n_datap);
}
_c_public_ int n_dhcp4_server_lease_append(NDhcp4ServerLease *lease, uint8_t option, uint8_t *data, size_t n_data) {
/* XXX */
return -ENOTRECOVERABLE;
}
_c_public_ int n_dhcp4_server_lease_offer(NDhcp4ServerLease *lease) {
/* XXX */
return -ENOTRECOVERABLE;
}
_c_public_ int n_dhcp4_server_lease_ack(NDhcp4ServerLease *lease) {
/* XXX */
return -ENOTRECOVERABLE;
}
_c_public_ int n_dhcp4_server_lease_nack(NDhcp4ServerLease *lease) {
/* XXX */
return -ENOTRECOVERABLE;
}

241
src/n-dhcp4-server.c Normal file
View File

@@ -0,0 +1,241 @@
/*
* Server Side of the Dynamic Host Configuration Protocol for IPv4
*
* XXX
*/
#include <assert.h>
#include <c-list.h>
#include <c-stdaux.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"
#include "util/packet.h"
/**
* n_dhcp4_server_config_new() - XXX
*/
_c_public_ int n_dhcp4_server_config_new(NDhcp4ServerConfig **configp) {
_c_cleanup_(n_dhcp4_server_config_freep) NDhcp4ServerConfig *config = NULL;
config = calloc(1, sizeof(*config));
if (!config)
return -ENOMEM;
*config = (NDhcp4ServerConfig)N_DHCP4_SERVER_CONFIG_NULL(*config);
*configp = config;
config = NULL;
return 0;
}
/**
* n_dhcp4_server_config_free() - XXX
*/
_c_public_ NDhcp4ServerConfig *n_dhcp4_server_config_free(NDhcp4ServerConfig *config) {
if (!config)
return NULL;
free(config);
return NULL;
}
/**
* n_dhcp4_server_config_set_ifindex() - XXX
*/
_c_public_ void n_dhcp4_server_config_set_ifindex(NDhcp4ServerConfig *config, int ifindex) {
config->ifindex = ifindex;
}
/**
* n_dhcp4_s_event_node_new() - XXX
*/
int n_dhcp4_s_event_node_new(NDhcp4SEventNode **nodep) {
NDhcp4SEventNode *node;
node = calloc(1, sizeof(*node));
if (!node)
return -ENOMEM;
*node = (NDhcp4SEventNode)N_DHCP4_S_EVENT_NODE_NULL(*node);
*nodep = node;
return 0;
}
/**
* n_dhcp4_s_event_node_free() - XXX
*/
NDhcp4SEventNode *n_dhcp4_s_event_node_free(NDhcp4SEventNode *node) {
if (!node)
return NULL;
c_list_unlink(&node->server_link);
free(node);
return NULL;
}
/**
* n_dhcp4_server_new() - XXX
*/
_c_public_ int n_dhcp4_server_new(NDhcp4Server **serverp, NDhcp4ServerConfig *config) {
_c_cleanup_(n_dhcp4_server_unrefp) NDhcp4Server *server = NULL;
int r;
c_assert(serverp);
server = malloc(sizeof(*server));
if (!server)
return -ENOMEM;
*server = (NDhcp4Server)N_DHCP4_SERVER_NULL(*server);
r = n_dhcp4_s_connection_init(&server->connection, config->ifindex);
if (r)
return r;
*serverp = server;
server = NULL;
return 0;
}
static void n_dhcp4_server_free(NDhcp4Server *server) {
NDhcp4SEventNode *node, *t_node;
c_list_for_each_entry_safe(node, t_node, &server->event_list, server_link)
n_dhcp4_s_event_node_free(node);
free(server);
}
/**
* n_dhcp4_server_ref() - XXX
*/
_c_public_ NDhcp4Server *n_dhcp4_server_ref(NDhcp4Server *server) {
if (server)
++server->n_refs;
return server;
}
/**
* n_dhcp4_server_unref() - XXX
*/
_c_public_ NDhcp4Server *n_dhcp4_server_unref(NDhcp4Server *server) {
if (server && !--server->n_refs)
n_dhcp4_server_free(server);
return NULL;
}
/**
* n_dhcp4_server_raise() - XXX
*/
int n_dhcp4_server_raise(NDhcp4Server *server, NDhcp4SEventNode **nodep, unsigned int event) {
NDhcp4SEventNode *node;
int r;
r = n_dhcp4_s_event_node_new(&node);
if (r)
return r;
node->event.event = event;
c_list_link_tail(&server->event_list, &node->server_link);
if (nodep)
*nodep = node;
return 0;
}
/**
* n_dhcp4_server_get_fd() - XXX
*/
_c_public_ void n_dhcp4_server_get_fd(NDhcp4Server *server, int *fdp) {
n_dhcp4_s_connection_get_fd(&server->connection, fdp);
}
/**
* n_dhcp4_server_dispatch() - XXX
*/
_c_public_ int n_dhcp4_server_dispatch(NDhcp4Server *server) {
int r;
for (unsigned int i = 0; i < 128; ++i) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
r = n_dhcp4_s_connection_dispatch_io(&server->connection, &message);
if (r) {
if (r == N_DHCP4_E_AGAIN)
return 0;
return r;
}
}
return N_DHCP4_E_PREEMPTED;
}
/**
* n_dhcp4_server_pop_event() - XXX
*/
_c_public_ int n_dhcp4_server_pop_event(NDhcp4Server *server, NDhcp4ServerEvent **eventp) {
NDhcp4SEventNode *node, *t_node;
c_list_for_each_entry_safe(node, t_node, &server->event_list, server_link) {
if (node->is_public) {
n_dhcp4_s_event_node_free(node);
continue;
}
node->is_public = true;
*eventp = &node->event;
return 0;
}
*eventp = NULL;
return 0;
}
/**
* n_dhcp4_server_add_ip() - XXX
*/
_c_public_ int n_dhcp4_server_add_ip(NDhcp4Server *server, NDhcp4ServerIp **ipp, struct in_addr addr) {
_c_cleanup_(n_dhcp4_server_ip_freep) NDhcp4ServerIp *ip = NULL;
/* XXX: support more than one address */
if (server->connection.ip)
return -EBUSY;
ip = malloc(sizeof(*ip));
if (!ip)
return -ENOMEM;
*ip = (NDhcp4ServerIp)N_DHCP4_SERVER_IP_NULL(*ip);
n_dhcp4_s_connection_ip_init(&ip->ip, addr);
n_dhcp4_s_connection_ip_link(&ip->ip, &server->connection);
*ipp = ip;
ip = NULL;
return 0;
}
/**
* n_dhcp4_server_ip_free() - XXX
*/
_c_public_ NDhcp4ServerIp *n_dhcp4_server_ip_free(NDhcp4ServerIp *ip) {
if (!ip)
return NULL;
n_dhcp4_s_connection_ip_unlink(&ip->ip);
n_dhcp4_s_connection_ip_deinit(&ip->ip);
free(ip);
return NULL;
}

656
src/n-dhcp4-socket.c Normal file
View File

@@ -0,0 +1,656 @@
/*
* DHCP specific low-level socket helpers
*/
#include <c-stdaux.h>
#include <errno.h>
#include <linux/filter.h>
#include <linux/if_packet.h>
#include <linux/udp.h>
#include <net/if.h>
#include <netinet/ip.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#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;
}

286
src/n-dhcp4.h Normal file
View File

@@ -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 <inttypes.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdlib.h>
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

154
src/test-api.c Normal file
View File

@@ -0,0 +1,154 @@
/*
* API Visibility Tests
* This verifies the visibility and availability of the exported API.
*/
#undef NDEBUG
#include <assert.h>
#include <stdlib.h>
#include "n-dhcp4.h"
static void test_api_constants(void) {
assert(1 + N_DHCP4_CLIENT_START_DELAY_RFC2131);
assert(1 + _N_DHCP4_E_SUCCESS);
assert(1 + N_DHCP4_E_PREEMPTED);
assert(1 + N_DHCP4_E_INTERNAL);
assert(1 + N_DHCP4_E_INVALID_IFINDEX);
assert(1 + N_DHCP4_E_INVALID_TRANSPORT);
assert(1 + N_DHCP4_E_INVALID_ADDRESS);
assert(1 + N_DHCP4_E_INVALID_CLIENT_ID);
assert(1 + N_DHCP4_E_DUPLICATE_OPTION);
assert(1 + N_DHCP4_E_UNSET);
assert(1 + _N_DHCP4_E_N);
assert(1 + N_DHCP4_TRANSPORT_ETHERNET);
assert(1 + N_DHCP4_TRANSPORT_INFINIBAND);
assert(1 + _N_DHCP4_TRANSPORT_N);
assert(1 + N_DHCP4_CLIENT_EVENT_DOWN);
assert(1 + N_DHCP4_CLIENT_EVENT_OFFER);
assert(1 + N_DHCP4_CLIENT_EVENT_GRANTED);
assert(1 + N_DHCP4_CLIENT_EVENT_RETRACTED);
assert(1 + N_DHCP4_CLIENT_EVENT_EXTENDED);
assert(1 + N_DHCP4_CLIENT_EVENT_EXPIRED);
assert(1 + N_DHCP4_CLIENT_EVENT_CANCELLED);
assert(1 + _N_DHCP4_CLIENT_EVENT_N);
assert(1 + N_DHCP4_SERVER_EVENT_DOWN);
assert(1 + N_DHCP4_SERVER_EVENT_DISCOVER);
assert(1 + N_DHCP4_SERVER_EVENT_REQUEST);
assert(1 + N_DHCP4_SERVER_EVENT_RENEW);
assert(1 + N_DHCP4_SERVER_EVENT_DECLINE);
assert(1 + N_DHCP4_SERVER_EVENT_RELEASE);
assert(1 + _N_DHCP4_SERVER_EVENT_N);
}
static void test_api_types(void) {
assert(sizeof(NDhcp4ClientConfig*) > 0);
assert(sizeof(NDhcp4ClientProbeConfig*) > 0);
assert(sizeof(NDhcp4Client*) > 0);
assert(sizeof(NDhcp4ClientEvent) > 0);
assert(sizeof(NDhcp4ClientProbe*) > 0);
assert(sizeof(NDhcp4ClientLease*) > 0);
assert(sizeof(NDhcp4Server*) > 0);
assert(sizeof(NDhcp4ServerConfig*) > 0);
assert(sizeof(NDhcp4ServerEvent) > 0);
assert(sizeof(NDhcp4ServerIp*) > 0);
assert(sizeof(NDhcp4ServerLease*) > 0);
}
static void test_api_functions(void) {
void *fns[] = {
(void *)n_dhcp4_client_config_new,
(void *)n_dhcp4_client_config_free,
(void *)n_dhcp4_client_config_freep,
(void *)n_dhcp4_client_config_freev,
(void *)n_dhcp4_client_config_set_ifindex,
(void *)n_dhcp4_client_config_set_transport,
(void *)n_dhcp4_client_config_set_request_broadcast,
(void *)n_dhcp4_client_config_set_mac,
(void *)n_dhcp4_client_config_set_broadcast_mac,
(void *)n_dhcp4_client_config_set_client_id,
(void *)n_dhcp4_client_probe_config_new,
(void *)n_dhcp4_client_probe_config_free,
(void *)n_dhcp4_client_probe_config_freep,
(void *)n_dhcp4_client_probe_config_freev,
(void *)n_dhcp4_client_probe_config_set_inform_only,
(void *)n_dhcp4_client_probe_config_set_init_reboot,
(void *)n_dhcp4_client_probe_config_set_requested_ip,
(void *)n_dhcp4_client_probe_config_set_start_delay,
(void *)n_dhcp4_client_probe_config_request_option,
(void *)n_dhcp4_client_probe_config_append_option,
(void *)n_dhcp4_client_new,
(void *)n_dhcp4_client_ref,
(void *)n_dhcp4_client_unref,
(void *)n_dhcp4_client_unrefp,
(void *)n_dhcp4_client_unrefv,
(void *)n_dhcp4_client_get_fd,
(void *)n_dhcp4_client_dispatch,
(void *)n_dhcp4_client_pop_event,
(void *)n_dhcp4_client_update_mtu,
(void *)n_dhcp4_client_probe,
(void *)n_dhcp4_client_probe_free,
(void *)n_dhcp4_client_probe_freep,
(void *)n_dhcp4_client_probe_freev,
(void *)n_dhcp4_client_probe_get_userdata,
(void *)n_dhcp4_client_probe_set_userdata,
(void *)n_dhcp4_client_lease_ref,
(void *)n_dhcp4_client_lease_unref,
(void *)n_dhcp4_client_lease_unrefp,
(void *)n_dhcp4_client_lease_unrefv,
(void *)n_dhcp4_client_lease_get_yiaddr,
(void *)n_dhcp4_client_lease_get_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;
}

388
src/test-connection.c Normal file
View File

@@ -0,0 +1,388 @@
/*
* Tests for DHCP4 Client Connections
*/
#undef NDEBUG
#include <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <poll.h>
#include <linux/if_packet.h>
#include <net/if_arp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include "n-dhcp4-private.h"
#include "test.h"
#include "util/link.h"
#include "util/netns.h"
#include "util/packet.h"
static void test_poll_client(int efd, unsigned int u32) {
struct epoll_event event = {};
int r;
r = epoll_wait(efd, &event, 1, -1);
c_assert(r == 1);
c_assert(event.events == EPOLLIN);
c_assert(event.data.u32 == u32);
}
static void test_poll_server(int fd) {
struct pollfd pfd = { .fd = fd, .events = POLLIN };
int r;
r = poll(&pfd, 1, -1);
c_assert(r == 1);
c_assert(pfd.revents == POLLIN);
}
static void test_s_connection_init(int netns, NDhcp4SConnection *connection, int ifindex) {
int r, oldns;
netns_get(&oldns);
netns_set(netns);
r = n_dhcp4_s_connection_init(connection, ifindex);
c_assert(!r);
netns_set(oldns);
}
static void test_c_connection_listen(int netns, NDhcp4CConnection *connection) {
int r, oldns;
netns_get(&oldns);
netns_set(netns);
r = n_dhcp4_c_connection_listen(connection);
c_assert(!r);
netns_set(oldns);
}
static void test_c_connection_connect(int netns,
NDhcp4CConnection *connection,
const struct in_addr *client,
const struct in_addr *server) {
int r, oldns;
netns_get(&oldns);
netns_set(netns);
r = n_dhcp4_c_connection_connect(connection, client, server);
c_assert(!r);
netns_set(oldns);
}
static void test_server_receive(NDhcp4SConnection *connection, uint8_t expected_type, NDhcp4Incoming **messagep) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
uint8_t received_type;
int r, fd;
n_dhcp4_s_connection_get_fd(connection, &fd);
test_poll_server(fd);
r = n_dhcp4_s_connection_dispatch_io(connection, &message);
c_assert(!r);
c_assert(message);
r = n_dhcp4_incoming_query_message_type(message, &received_type);
c_assert(!r);
c_assert(received_type == expected_type);
if (messagep) {
*messagep = message;
message = NULL;
}
}
static void test_client_receive(NDhcp4CConnection *connection, uint8_t expected_type, NDhcp4Incoming **messagep) {
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL;
uint8_t received_type;
int r;
test_poll_client(connection->fd_epoll, N_DHCP4_CLIENT_EPOLL_IO);
r = n_dhcp4_c_connection_dispatch_io(connection, &message);
c_assert(!r);
c_assert(message);
r = n_dhcp4_incoming_query_message_type(message, &received_type);
c_assert(!r);
c_assert(received_type == expected_type);
if (messagep) {
*messagep = message;
message = NULL;
}
}
static void test_discover(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
const struct in_addr *addr_server,
const struct in_addr *addr_client,
NDhcp4Incoming **offerp) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL;
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply_out = NULL;
int r;
r = n_dhcp4_c_connection_discover_new(connection_client, &request_out);
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_DISCOVER, &request_in);
r = n_dhcp4_s_connection_offer_new(connection_server, &reply_out, request_in, addr_server, addr_client, 60);
c_assert(!r);
r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply_out);
c_assert(!r);
test_client_receive(connection_client, N_DHCP4_MESSAGE_OFFER, offerp);
}
static void test_select(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
NDhcp4Incoming *offer,
const struct in_addr *addr_server,
const struct in_addr *addr_client) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL;
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL;
int r;
r = n_dhcp4_c_connection_select_new(connection_client, &request_out, offer);
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in);
r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60);
c_assert(!r);
r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply);
c_assert(!r);
test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL);
}
static void test_reboot(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
const struct in_addr *addr_server,
const struct in_addr *addr_client,
NDhcp4Incoming **ackp) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL;
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL;
int r;
r = n_dhcp4_c_connection_reboot_new(connection_client, &request_out, addr_server);
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in);
r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60);
c_assert(!r);
r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply);
c_assert(!r);
test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, ackp);
}
static void test_decline(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
NDhcp4Incoming *ack) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
int r;
r = n_dhcp4_c_connection_decline_new(connection_client, &request_out, ack, "No thanks.");
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_DECLINE, NULL);
}
static void test_renew(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
const struct in_addr *addr_server,
const struct in_addr *addr_client) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL;
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL;
int r;
r = n_dhcp4_c_connection_renew_new(connection_client, &request_out);
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in);
r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60);
c_assert(!r);
r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply);
c_assert(!r);
test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL);
}
static void test_rebind(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
const struct in_addr *addr_server,
const struct in_addr *addr_client) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *request_in = NULL;
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *reply = NULL;
int r;
r = n_dhcp4_c_connection_rebind_new(connection_client, &request_out);
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_REQUEST, &request_in);
r = n_dhcp4_s_connection_ack_new(connection_server, &reply, request_in, addr_server, addr_client, 60);
c_assert(!r);
r = n_dhcp4_s_connection_send_reply(connection_server, addr_server, reply);
c_assert(!r);
test_client_receive(connection_client, N_DHCP4_MESSAGE_ACK, NULL);
}
static void test_release(NDhcp4SConnection *connection_server,
NDhcp4CConnection *connection_client,
const struct in_addr *addr_server,
const struct in_addr *addr_client) {
_c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request_out = NULL;
int r;
r = n_dhcp4_c_connection_release_new(connection_client, &request_out, "Shutting down!");
c_assert(!r);
r = n_dhcp4_c_connection_start_request(connection_client, request_out, 0);
c_assert(!r);
request_out = NULL;
test_server_receive(connection_server, N_DHCP4_MESSAGE_RELEASE, NULL);
}
static void test_connection(void) {
const struct in_addr addr_server = (struct in_addr){ htonl(10 << 24 | 1) };
const struct in_addr addr_client = (struct in_addr){ htonl(10 << 24 | 2) };
_c_cleanup_(netns_closep) int ns_server = -1, ns_client = -1;
_c_cleanup_(link_deinit) Link link_server = LINK_NULL(link_server);
_c_cleanup_(link_deinit) Link link_client = LINK_NULL(link_client);
_c_cleanup_(c_closep) int efd_client = -1;
int r;
/* setup */
netns_new(&ns_server);
netns_new(&ns_client);
link_new_veth(&link_server, &link_client, ns_server, ns_client);
link_add_ip4(&link_server, &addr_server, 8);
efd_client = epoll_create1(EPOLL_CLOEXEC);
c_assert(efd_client >= 0);
/* test connections */
{
_c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *client_config = NULL;
_c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *probe_config = NULL;
NDhcp4SConnection connection_server = N_DHCP4_S_CONNECTION_NULL(connection_server);
NDhcp4SConnectionIp connection_server_ip = N_DHCP4_S_CONNECTION_IP_NULL(connection_server_ip);
NDhcp4CConnection connection_client = N_DHCP4_C_CONNECTION_NULL(connection_client);
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *offer = NULL;
_c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *ack = NULL;
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;
}

159
src/test-message.c Normal file
View File

@@ -0,0 +1,159 @@
/*
* Tests for DHCP4 Message Handling
*/
#undef NDEBUG
#include <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4-private.h"
static void test_outgoing(void) {
NDhcp4Outgoing *outgoing;
int r;
/* verify basic NEW/FREE */
outgoing = NULL;
r = n_dhcp4_outgoing_new(&outgoing, 0, 0);
c_assert(!r);
c_assert(outgoing);
outgoing = n_dhcp4_outgoing_free(outgoing);
c_assert(!outgoing);
}
static void test_incoming(void) {
NDhcp4Incoming *incoming;
struct {
NDhcp4Header header;
uint8_t sname[64];
uint8_t file[128];
uint32_t magic;
uint8_t options[1024];
} m;
uint8_t *v;
size_t l;
int r;
/* verify that messages must be at least the size of the header */
r = n_dhcp4_incoming_new(&incoming, NULL, 0);
c_assert(r == N_DHCP4_E_MALFORMED);
r = n_dhcp4_incoming_new(&incoming, NULL, sizeof(m.header) + 64 + 128 + 3);
c_assert(r == N_DHCP4_E_MALFORMED);
/* verify that magic must be set */
memset(&m, 0, sizeof(m));
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(r == N_DHCP4_E_MALFORMED);
/* verify basic NEW/FREE */
memset(&m, 0, sizeof(m));
m.magic = htobe32(N_DHCP4_MESSAGE_MAGIC);
incoming = NULL;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m.header) + 64 + 128 + 4);
c_assert(!r);
c_assert(incoming);
incoming = n_dhcp4_incoming_free(incoming);
c_assert(!incoming);
/* verify that PAD is properly handled */
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
incoming = n_dhcp4_incoming_free(incoming);
/* verify that SNAME/FILE are only looked at if OVERLOAD is set */
m.sname[0] = 1;
m.sname[1] = 0;
m.file[0] = 2;
m.file[1] = 0;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
incoming = n_dhcp4_incoming_free(incoming);
m.options[0] = N_DHCP4_OPTION_OVERLOAD;
m.options[1] = 1;
m.options[2] = 0;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
incoming = n_dhcp4_incoming_free(incoming);
m.options[0] = N_DHCP4_OPTION_OVERLOAD;
m.options[1] = 1;
m.options[2] = N_DHCP4_OVERLOAD_SNAME;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL);
c_assert(r == 0);
r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
incoming = n_dhcp4_incoming_free(incoming);
m.options[0] = N_DHCP4_OPTION_OVERLOAD;
m.options[1] = 1;
m.options[2] = N_DHCP4_OVERLOAD_FILE;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL);
c_assert(r == N_DHCP4_E_UNSET);
r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL);
c_assert(r == 0);
incoming = n_dhcp4_incoming_free(incoming);
m.options[0] = N_DHCP4_OPTION_OVERLOAD;
m.options[1] = 1;
m.options[2] = N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, NULL, NULL);
c_assert(r == 0);
r = n_dhcp4_incoming_query(incoming, 2, NULL, NULL);
c_assert(r == 0);
incoming = n_dhcp4_incoming_free(incoming);
/* verify basic concatenation */
m.options[3] = 1;
m.options[4] = 1;
m.options[5] = 0xef;
m.sname[1] = 1;
m.sname[2] = 0xcf;
r = n_dhcp4_incoming_new(&incoming, &m, sizeof(m));
c_assert(!r);
r = n_dhcp4_incoming_query(incoming, 1, &v, &l);
c_assert(r == 0);
c_assert(l == 2);
c_assert(v[0] == 0xef && v[1] == 0xcf);
incoming = n_dhcp4_incoming_free(incoming);
}
int main(int argc, char **argv) {
test_outgoing();
test_incoming();
return 0;
}

713
src/test-run-client.c Normal file
View File

@@ -0,0 +1,713 @@
/*
* DHCP Client Runner
*
* This test implements a DHCP client. It takes parameters via the command-line
* and runs a DHCP client. It is mainly meant for testing, as such it allows
* tweaking that an exported DHCP client should not provide.
*/
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <getopt.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4.h"
#include "n-dhcp4-private.h"
#include "test.h"
typedef struct Manager Manager;
enum {
_MAIN_SUCCESS,
MAIN_EXIT,
MAIN_FAILED,
};
struct Manager {
NDhcp4Client *client;
NDhcp4ClientProbe *probe;
};
#define MANAGER_NULL(_x) {}
static struct ether_addr main_arg_broadcast_mac = {};
static bool main_arg_broadcast_mac_set = false;
static uint8_t* main_arg_client_id = NULL;
static size_t main_arg_n_client_id = 0;
static int main_arg_ifindex = 0;
static struct in_addr main_arg_requested_ip = { INADDR_ANY };
static long long int main_arg_requested_lifetime = -1;
static uint8_t main_arg_requested_parameters[UINT8_MAX] = {};
static size_t main_arg_n_requested_parameters = 0;
static struct ether_addr main_arg_mac = {};
static bool main_arg_mac_set = false;
static bool main_arg_request_broadcast = false;
static bool main_arg_test = false;
static Manager *manager_free(Manager *manager) {
if (!manager)
return NULL;
n_dhcp4_client_probe_free(manager->probe);
n_dhcp4_client_unref(manager->client);
free(manager);
return NULL;
}
static void manager_freep(Manager **manager) {
manager_free(*manager);
}
static int manager_new(Manager **managerp) {
_c_cleanup_(n_dhcp4_client_config_freep) NDhcp4ClientConfig *config = NULL;
_c_cleanup_(manager_freep) Manager *manager = NULL;
int r;
manager = malloc(sizeof(*manager));
if (!manager)
return -ENOMEM;
*manager = (Manager)MANAGER_NULL(*manager);
r = n_dhcp4_client_config_new(&config);
if (r)
return r;
n_dhcp4_client_config_set_broadcast_mac(config,
&main_arg_broadcast_mac.ether_addr_octet[0],
sizeof(main_arg_broadcast_mac.ether_addr_octet));
n_dhcp4_client_config_set_mac(config,
&main_arg_mac.ether_addr_octet[0],
sizeof(main_arg_mac.ether_addr_octet));
n_dhcp4_client_config_set_client_id(config,
main_arg_client_id,
main_arg_n_client_id);
n_dhcp4_client_config_set_ifindex(config, main_arg_ifindex);
n_dhcp4_client_config_set_request_broadcast(config, main_arg_request_broadcast);
n_dhcp4_client_config_set_transport(config, N_DHCP4_TRANSPORT_ETHERNET);
r = n_dhcp4_client_new(&manager->client, config);
if (r)
return r;
*managerp = manager;
manager = NULL;
return 0;
}
static int manager_lease_get_dns(NDhcp4ClientLease *lease, struct in_addr *dns) {
uint8_t *data;
size_t n_data;
int r;
r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_DOMAIN_NAME_SERVER, &data, &n_data);
if (r)
return r;
if (n_data < sizeof(dns->s_addr))
return N_DHCP4_E_MALFORMED;
memcpy(&dns->s_addr, data, sizeof(dns->s_addr));
return 0;
}
static int manager_lease_get_router(NDhcp4ClientLease *lease, struct in_addr *router) {
uint8_t *data;
size_t n_data;
int r;
r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_ROUTER, &data, &n_data);
if (r)
return r;
if (n_data < sizeof(router->s_addr))
return N_DHCP4_E_MALFORMED;
memcpy(&router->s_addr, data, sizeof(router->s_addr));
return 0;
}
static int manager_lease_get_subnetmask(NDhcp4ClientLease *lease, struct in_addr *mask) {
uint8_t *data;
size_t n_data;
int r;
r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_SUBNET_MASK, &data, &n_data);
if (r)
return r;
if (n_data != sizeof(mask->s_addr))
return N_DHCP4_E_MALFORMED;
memcpy(&mask->s_addr, data, sizeof(mask->s_addr));
return 0;
}
static int manager_lease_get_prefix(NDhcp4ClientLease *lease, unsigned int *prefixp) {
struct in_addr mask = {};
unsigned int postfix;
int r;
r = manager_lease_get_subnetmask(lease, &mask);
if (r)
return r;
postfix =__builtin_ctz(ntohl(mask.s_addr));
c_assert(postfix <= 32);
if (postfix < 32) {
if ((~ntohl(mask.s_addr)) >> postfix != 0)
return N_DHCP4_E_MALFORMED;
}
*prefixp = 32 - postfix;
return 0;
}
static int manager_check(Manager *manager, NDhcp4ClientLease *lease) {
int r;
r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_ROUTER, NULL, NULL);
if (r) {
fprintf(stderr, "No router\n");
return r;
}
r = n_dhcp4_client_lease_query(lease, N_DHCP4_OPTION_SUBNET_MASK, NULL, NULL);
if (r) {
fprintf(stderr, "No subnet mask\n");
return r;
}
return r;
}
static int manager_add(Manager *manager, NDhcp4ClientLease *lease) {
char *p, ifname[IF_NAMESIZE + 1] = {};
struct in_addr router = {}, yiaddr = {}, dns = {};
unsigned int prefix;
uint64_t lifetime;
int r;
n_dhcp4_client_lease_get_yiaddr(lease, &yiaddr);
n_dhcp4_client_lease_get_lifetime(lease, &lifetime);
r = manager_lease_get_router(lease, &router);
if (r)
return r;
r = manager_lease_get_prefix(lease, &prefix);
if (r)
return r;
p = if_indextoname(main_arg_ifindex, ifname);
c_assert(p);
if (lifetime == UINT64_MAX) {
r = asprintf(&p, "ip addr add %s/%u dev %s preferred_lft forever valid_lft forever", inet_ntoa(yiaddr), prefix, ifname);
c_assert(r >= 0);
} else {
r = asprintf(&p, "ip addr add %s/%u dev %s preferred_lft %llu valid_lft %llu", inet_ntoa(yiaddr), prefix, ifname, lifetime / 1000000000ULL, lifetime / 1000000000ULL);
c_assert(r >= 0);
}
r = system(p);
c_assert(r == 0);
free(p);
r = asprintf(&p, "ip route add %s/32 dev %s", inet_ntoa(router), ifname);
c_assert(r >= 0);
r = system(p);
c_assert(r == 0);
free(p);
r = asprintf(&p, "ip route add default via %s dev %s", inet_ntoa(router), ifname);
c_assert(r >= 0);
r = system(p);
c_assert(r == 0);
free(p);
r = manager_lease_get_dns(lease, &dns);
if (r) {
if (r != N_DHCP4_E_UNSET)
return r;
} else {
fprintf(stderr, "DNS: %s\n", inet_ntoa(dns));
}
return 0;
}
static int manager_dispatch(Manager *manager) {
NDhcp4ClientEvent *event;
int r;
r = n_dhcp4_client_dispatch(manager->client);
if (r) {
if (r != N_DHCP4_E_PREEMPTED) {
/*
* We are level-triggered, so we do not need to react
* to preemption. We simply continue the mainloop.
*/
return r;
}
}
for (;;) {
r = n_dhcp4_client_pop_event(manager->client, &event);
if (r)
return r;
if (!event)
break;
switch (event->event) {
case N_DHCP4_CLIENT_EVENT_DOWN:
fprintf(stderr, "DOWN\n");
break;
case N_DHCP4_CLIENT_EVENT_OFFER:
fprintf(stderr, "OFFER\n");
r = manager_check(manager, event->granted.lease);
if (r) {
if (r == N_DHCP4_E_UNSET) {
fprintf(stderr, "Missing mandatory option, ignoring lease.\n");
} else {
return r;
}
} else {
r = n_dhcp4_client_lease_select(event->offer.lease);
if (r)
return r;
}
break;
case N_DHCP4_CLIENT_EVENT_GRANTED:
fprintf(stderr, "GRANTED\n");
r = manager_add(manager, event->granted.lease);
if (r) {
if (r == N_DHCP4_E_UNSET) {
fprintf(stderr, "Missing mandatory option, declining lease.\n");
r = n_dhcp4_client_lease_decline(event->granted.lease, "Missing mandatory option.");
if (r)
return r;
} else {
return r;
}
} else {
r = n_dhcp4_client_lease_accept(event->granted.lease);
if (r)
return r;
}
break;
case N_DHCP4_CLIENT_EVENT_RETRACTED:
fprintf(stderr, "RETRACTED\n");
break;
case N_DHCP4_CLIENT_EVENT_EXTENDED:
fprintf(stderr, "EXTENDED\n");
break;
case N_DHCP4_CLIENT_EVENT_EXPIRED:
fprintf(stderr, "EXPIRED\n");
break;
case N_DHCP4_CLIENT_EVENT_CANCELLED:
fprintf(stderr, "CANCELLED\n");
break;
default:
fprintf(stderr, "Unexpected event: %u\n", event->event);
break;
}
}
return 0;
}
static int manager_run(Manager *manager) {
_c_cleanup_(n_dhcp4_client_probe_config_freep) NDhcp4ClientProbeConfig *config = NULL;
int r;
r = n_dhcp4_client_probe_config_new(&config);
if (r)
return r;
/*
* Let's speed up our tests, while still making sure the code-path
* for the deferrment is actually tested (so don't set it to zero).
*/
n_dhcp4_client_probe_config_set_start_delay(config, 10);
n_dhcp4_client_probe_config_set_requested_ip(config, main_arg_requested_ip);
if (main_arg_n_requested_parameters > 0) {
for (unsigned int i = 0; i < main_arg_n_requested_parameters; ++i)
n_dhcp4_client_probe_config_request_option(config, main_arg_requested_parameters[i]);
} else {
n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_ROUTER);
n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_SUBNET_MASK);
n_dhcp4_client_probe_config_request_option(config, N_DHCP4_OPTION_DOMAIN_NAME_SERVER);
}
if (main_arg_requested_lifetime >= 0) {
uint32_t lifetime = ntohl(main_arg_requested_lifetime);
r = n_dhcp4_client_probe_config_append_option(config, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, &lifetime, sizeof(lifetime));
if (r)
return r;
}
r = n_dhcp4_client_probe(manager->client, &manager->probe, config);
if (r)
return r;
/*
* The test-suite runs this with the --test argument. So far, we do not
* perform any fancy runtime tests, but simply exit the main-loop
* immediately. We can add more elaborate tests in the future.
*/
if (main_arg_test)
return 0;
for (;;) {
struct pollfd pfds[] = {
{ .fd = -1, .events = POLLIN },
};
size_t i;
int n;
n_dhcp4_client_get_fd(manager->client, &pfds[0].fd);
n = poll(pfds, sizeof(pfds) / sizeof(*pfds), -1);
if (n < 0)
return -errno;
for (i = 0; i < (size_t)n; ++i) {
if (pfds[i].revents & ~POLLIN)
return -ENOTRECOVERABLE;
if (!(pfds[i].revents & POLLIN))
continue;
r = manager_dispatch(manager);
if (r)
return r;
}
}
return 0;
}
static int run(void) {
_c_cleanup_(manager_freep) Manager *manager = NULL;
int r;
r = manager_new(&manager);
if (r)
return r;
return manager_run(manager);
}
static void print_help(void) {
printf("%s [GLOBALS...] ...\n\n"
"DHCP Test Client\n\n"
" -h --help Show this help\n"
" --test Run as part of the test suite\n"
" --ifindex IDX Index of interface to run on\n"
" --mac HEX Hardware address to use\n"
" --broadcast-mac HEX Broadcast hardware address to use\n"
" --requested-ip IP Requested IP 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;
}

314
src/test-socket.c Normal file
View File

@@ -0,0 +1,314 @@
/*
* Tests for DHCP4 Socket Helpers
*/
#undef NDEBUG
#include <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <poll.h>
#include <linux/if_packet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4-private.h"
#include "test.h"
#include "util/link.h"
#include "util/netns.h"
#include "util/packet.h"
static void test_poll(int sk) {
int r;
r = poll(&(struct pollfd){ .fd = sk, .events = POLLIN }, 1, -1);
c_assert(r == 1);
}
static void test_client_packet_socket_new(Link *link, int *skp) {
int r, oldns;
netns_get(&oldns);
netns_set(link->netns);
r = n_dhcp4_c_socket_packet_new(skp, link->ifindex);
c_assert(r >= 0);
netns_set(oldns);
}
static void test_client_udp_socket_new(Link *link,
int *skp,
const struct in_addr *addr_client,
const struct in_addr *addr_server) {
int r, oldns;
netns_get(&oldns);
netns_set(link->netns);
r = n_dhcp4_c_socket_udp_new(skp, link->ifindex, addr_client, addr_server);
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;
}

107
src/test.h Normal file
View File

@@ -0,0 +1,107 @@
#pragma once
/*
* Test Helpers
* Bunch of helpers to setup the environment for networking tests. This
* includes net-namespace setups, veth setups, and more.
*/
#undef NDEBUG
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <fcntl.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static inline void test_raise_memlock(void) {
const size_t wanted = 64 * 1024 * 1024;
struct rlimit get, set;
int r;
r = getrlimit(RLIMIT_MEMLOCK, &get);
c_assert(!r);
/* try raising limit to @wanted */
set.rlim_cur = wanted;
set.rlim_max = (wanted > get.rlim_max) ? wanted : get.rlim_max;
r = setrlimit(RLIMIT_MEMLOCK, &set);
if (r) {
c_assert(errno == EPERM);
/* not privileged to raise limit, so maximize soft limit */
set.rlim_cur = get.rlim_max;
set.rlim_max = get.rlim_max;
r = setrlimit(RLIMIT_MEMLOCK, &set);
c_assert(!r);
}
}
static inline void test_unshare_user_namespace(void) {
uid_t euid;
gid_t egid;
int r, fd;
/*
* Enter a new user namespace as root:root.
*/
euid = geteuid();
egid = getegid();
r = unshare(CLONE_NEWUSER);
c_assert(r >= 0);
fd = open("/proc/self/uid_map", O_WRONLY);
c_assert(fd >= 0);
r = dprintf(fd, "0 %d 1\n", euid);
c_assert(r >= 0);
close(fd);
fd = open("/proc/self/setgroups", O_WRONLY);
c_assert(fd >= 0);
r = dprintf(fd, "deny");
c_assert(r >= 0);
close(fd);
fd = open("/proc/self/gid_map", O_WRONLY);
c_assert(fd >= 0);
r = dprintf(fd, "0 %d 1\n", egid);
c_assert(r >= 0);
close(fd);
}
static inline void test_setup(void) {
int r;
/*
* Move into a new network and mount namespace both associated
* with a new user namespace where the current eUID is mapped to
* 0. Then create a 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);
}

287
src/util/link.c Normal file
View File

@@ -0,0 +1,287 @@
/*
* Link Management
*
* This is for our test-infrastructure only! It is not meant to be used outside
* of unit-tests.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <c-stdaux.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include "link.h"
#include "netns.h"
#include "socket.h"
/**
* link_deinit() - deinitialize link
* @link: link to operate on
*
* This deinitializes a link and clears it. Once this call returns the link is
* cleared to LINK_NULL().
*
* It is safe to call this on LINK_NULL(), in which case it is a no-op. It is
* thus also safe to call this multiple times on the same link.
*/
void link_deinit(Link *link) {
netns_close(link->netns);
*link = (Link)LINK_NULL(*link);
}
static void link_query(int netns, const char *name, int *ifindexp, struct ether_addr *macp) {
int oldns;
netns_get(&oldns);
{
struct ifreq ifr = {};
size_t n_name;
int r, s;
netns_set(netns);
n_name = strlen(name);
c_assert(n_name <= IF_NAMESIZE);
if (ifindexp) {
*ifindexp = if_nametoindex(name);
c_assert(*ifindexp > 0);
}
if (macp) {
s = socket(AF_INET, SOCK_DGRAM, 0);
c_assert(s >= 0);
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);
}

38
src/util/link.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
/*
* Link Management
*
* This utility provides easy access to network links. It is meant for testing
* purposes only and relies on call-outs to ip(1). A proper implementation
* should rather use netlink directly to interact with the kernel.
*
* Furthermore, for simplification this is limited to ethernet links.
*/
#include <c-stdaux.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <stdlib.h>
typedef struct Link Link;
struct Link {
int netns;
int ifindex;
struct ether_addr mac;
};
#define LINK_NULL(_x) { \
.netns = -1, \
}
void link_deinit(Link *link);
void link_new_veth(Link *veth_parentp, Link *veth_childp, int netns_parent, int netns_child);
void link_new_bridge(Link *bridgep, int netns);
void link_add_ip4(Link *link, const struct in_addr *addr, unsigned int prefix);
void link_del_ip4(Link *link, const struct in_addr *addr, unsigned int prefix);
void link_set_master(Link *link, int if_master);
void link_socket(Link *link, int *socketp, int family, int type);

165
src/util/netns.c Normal file
View File

@@ -0,0 +1,165 @@
/*
* Network Namespaces
*
* This is meant for testing-purposes only. It is not meant to be used outside
* of our unit-tests!
*/
#include <assert.h>
#include <c-stdaux.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <unistd.h>
#include "netns.h"
/**
* netns_new() - create a new network namespace
* @netnsp: output argument to store netns fd
*
* This creates a new network namespace and returns a netns fd that refers to
* the new network namespace. Note that there is no native API to create an
* anonymous network namespace, so this call has to temporarily switch to a new
* network namespace (using unshare(2)). This temporary switch does not affect
* any other threads or processes, however, it can be observed by other
* processes.
*/
void netns_new(int *netnsp) {
int r, oldns;
netns_get(&oldns);
r = unshare(CLONE_NEWNET);
c_assert(r >= 0);
netns_get(netnsp);
netns_set(oldns);
}
/**
* netns_new_dup() - duplicate network namespace descriptor
* @newnsp: output argument for duplicated descriptor
* @netns: netns descriptor to duplicate
*
* This duplicates the network namespace file descriptor. The duplicate still
* refers to the same network namespace, but is an independent file descriptor.
*/
void netns_new_dup(int *newnsp, int netns) {
*newnsp = fcntl(netns, F_DUPFD_CLOEXEC, 0);
c_assert(*newnsp >= 0);
}
/**
* netns_close() - destroy a network namespace descriptor
* @netns: netns to operate on, or <0
*
* This closes the given network namespace descriptor. If @netns is negative,
* this is a no-op.
*
* Return: -1 is returned.
*/
int netns_close(int netns) {
return c_close(netns);
}
/**
* netns_get() - retrieve the current network namespace
* @netnsp: output argument to store netns fd
*
* This retrieves a file-descriptor to the current network namespace.
*/
void netns_get(int *netnsp) {
*netnsp = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
c_assert(*netnsp >= 0);
}
/**
* setns_set() - change the current network namespace
* @netns: netns to set
*
* This changes the current network namespace to the netns given by the
* file-descriptor @netns.
*/
void netns_set(int netns) {
int r;
r = setns(netns, CLONE_NEWNET);
c_assert(r >= 0);
}
/**
* netns_set_anonymous() - enter an anonymous network namespace
*
* This is a helper that creates a new network namespace, enters it, and then
* forgets about it.
*/
void netns_set_anonymous(void) {
int r;
r = unshare(CLONE_NEWNET);
c_assert(r >= 0);
}
/**
* netns_pin() - pin network namespace in file-system
* @netns: netns to pin
* @name: name to pin netns under
*
* This pins the network namespace given as @netns in the file-system as
* `/run/netns/@name`. It is the responsibility of the caller to guarantee
* @name is not used by anyone else in parallel. This function will abort if
* @name is already in use.
*
* The namespace in `/run/netns/` is compatible with the namespace provided by
* the ip(1) tool, and can be used to pass network namespaces to invocations of
* ip(1).
*/
void netns_pin(int netns, const char *name) {
char *fd_path, *netns_path;
int r, fd;
r = asprintf(&fd_path, "/proc/self/fd/%d", netns);
c_assert(r >= 0);
r = asprintf(&netns_path, "/run/netns/%s", name);
c_assert(r >= 0);
fd = open(netns_path, O_RDONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0);
c_assert(fd >= 0);
close(fd);
r = mount(fd_path, netns_path, "none", MS_BIND, NULL);
c_assert(r >= 0);
free(netns_path);
free(fd_path);
}
/**
* netns_unpin() - unpin network namespace from file-system
* @name: name to unpin
*
* This removes a network namespace pin from the file-system. It expects the
* pin to be located at `/run/netns/@name`. This function aborts if the pin
* does not exist.
*
* See netns_pin() for ways to create such pins.
*/
void netns_unpin(const char *name) {
char *netns_path;
int r;
r = asprintf(&netns_path, "/run/netns/%s", name);
c_assert(r >= 0);
r = umount2(netns_path, MNT_DETACH);
c_assert(r >= 0);
r = unlink(netns_path);
c_assert(r >= 0);
free(netns_path);
}

27
src/util/netns.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
/*
* Network Namespaces
*
* The netns utility provides an object-based API to network namespaces. It is
* meant for testing purposes only.
*/
#include <c-stdaux.h>
#include <stdlib.h>
void netns_new(int *netnsp);
void netns_new_dup(int *newnsp, int netns);
int netns_close(int netns);
void netns_get(int *netnsp);
void netns_set(int netns);
void netns_set_anonymous(void);
void netns_pin(int netns, const char *name);
void netns_unpin(const char *name);
static inline void netns_closep(int *netns) {
if (*netns >= 0)
netns_close(*netns);
}

456
src/util/packet.c Normal file
View File

@@ -0,0 +1,456 @@
/*
* Packet Sockets
*/
#include <assert.h>
#include <c-stdaux.h>
#include <endian.h>
#include <errno.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/udp.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#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;
}

62
src/util/packet.h Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
/*
* Packet Sockets
*/
#include <c-stdaux.h>
#include <inttypes.h>
#include <linux/if_packet.h>
#include <linux/netdevice.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
/*
* `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);
}

121
src/util/socket.c Normal file
View File

@@ -0,0 +1,121 @@
/*
* Socket Utilities
*/
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#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 <dh.herrmann@gmail.com>
* 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;
}

11
src/util/socket.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
/*
* Socket Utilities
*/
#include <c-stdaux.h>
#include <stdlib.h>
int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]);
int socket_bind_if(int socket, int ifindex);

411
src/util/test-packet.c Normal file
View File

@@ -0,0 +1,411 @@
/*
* Packet Socket Tests
*/
#undef NDEBUG
#include <assert.h>
#include <c-stdaux.h>
#include <errno.h>
#include <net/if_arp.h>
#include <stdlib.h>
#include <string.h>
#include "n-dhcp4-private.h"
#include "link.h"
#include "netns.h"
#include "packet.h"
#include "test.h"
typedef struct Blob {
uint16_t checksum;
uint8_t data[128];
} Blob;
static void test_checksum_one(Blob *blob, size_t size) {
uint16_t checksum;
/*
* The only important property of the internet-checksum is that if the
* target blob is amended with its own checksum, the checksum
* calculation will become 0. So here we simply calculate the checksum
* with a dummy 0 in place, then put the checksum in and verify that
* the resulting checksum becomes 0.
*/
blob->checksum = 0;
blob->checksum = packet_internet_checksum((uint8_t*)blob, size);
checksum = packet_internet_checksum((uint8_t*)blob, size);
c_assert(!checksum);
}
static void test_checksum_udp_one(Blob *blob, size_t size) {
uint16_t checksum;
/*
* Like test_checksum_one(), here we calculate the target checksum,
* then place it in the source blob and calculate the checksum again.
* We expect it to be 0 in the end (i.e., pass the checksum test).
*
* Unlike the generic version, we must pass dummy UDP data into the
* helpers and also avoid a 0 checksum in the original source.
*/
checksum = packet_internet_checksum_udp(&(struct in_addr){ htonl((10 << 24) | 2)},
&(struct in_addr){ htonl((10 << 24) | 1)},
67,
68,
blob->data,
sizeof(blob->data),
0);
checksum = checksum ?: 0xffff;
checksum = packet_internet_checksum_udp(&(struct in_addr){ htonl((10 << 24) | 2)},
&(struct in_addr){ htonl((10 << 24) | 1)},
67,
68,
blob->data,
sizeof(blob->data),
checksum);
c_assert(!checksum);
}
/*
* This generates some pseudo-random bytes and verifies that
* packet_internet_checksum{,_udp}() correctly calculates the checksum on this
* random-data.
*/
static void test_checksum(void) {
Blob blob = {};
/* fill @blob.data with some pseudo-random bytes */
for (size_t i = 0; i < sizeof(blob.data); ++i)
blob.data[i] = i ^ (i >> 8) ^ (i >> 16) ^ (i >> 24);
/* take chunks of @blob.data and verify their checksum */
for (size_t j = 0; j < sizeof(uint64_t); ++j) {
for (uint32_t i = 0; i <= 0xffff; ++i) {
blob.data[0] = i & 0xff;
blob.data[1] = i >> 8;
test_checksum_one(&blob, sizeof(blob) - j);
test_checksum_udp_one(&blob, sizeof(blob) - j);
}
}
}
static void test_new_packet_socket(Link *link, int *skp) {
struct sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = link->ifindex,
};
int r, on = 1;
link_socket(link, skp, AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC);
r = setsockopt(*skp, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on));
c_assert(r >= 0);
r = bind(*skp, (struct sockaddr*)&addr, sizeof(addr));
c_assert(r >= 0);
}
static void test_packet_unicast(int ifindex, int sk, void *buf, size_t n_buf,
const struct sockaddr_in *paddr_src,
const struct sockaddr_in *paddr_dst,
const struct ether_addr *haddr_dst) {
struct packet_sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_IP),
.sll_ifindex = ifindex,
.sll_halen = ETH_ALEN,
};
size_t len;
int r;
memcpy(addr.sll_addr, haddr_dst, ETH_ALEN);
r = packet_sendto_udp(sk, buf, n_buf, &len, paddr_src, &addr, paddr_dst);
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;
}

1
subprojects/c-list Submodule

Submodule subprojects/c-list added at 2e4b605c62

1
subprojects/c-siphash Submodule

Submodule subprojects/c-siphash added at 7c42c59258

1
subprojects/c-stdaux Submodule

Submodule subprojects/c-stdaux added at 11930d2592