From cc3e23d2e522b37d468122fcb1951226534d91d6 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 3 Jun 2020 22:21:50 +0200 Subject: [PATCH] Squashed 'shared/n-dhcp4/' changes from fb1d43449ba8..6876b73ec113 6876b73ec113 all: rework logging to append logging messages to the event queue 65ca0e4bea32 client: check length of client id for n_dhcp4_client_config_set_client_id() 6a9ca10b7ad6 client: close timer file descriptor explicitly when EPOLL_CTL_ADD fails e5f3b27f5301 connection: don't fail dispatch in case of receive errors 68790d5ec950 connection: keep trying after a failure in send() 788b03891cd0 n-dhcp4: fix logging macro 6cd0fd5e8b3f probe: request previous address after expiration ef11c7079ad7 connection: avoid double free of NDhcp4Outgoing 922a9a2326e7 n-dhcp4: fix initialization of the 'secs' DHCP header field 7fff54117436 outgoing: fix uninitialized variable in n_dhcp4_outgoing_append() 27547faaf258 probe: move back to INIT after lease expires d07cb4e32884 incoming: accept options that are longer than requested 34d365e2267f client: handle invalid return codes gracefully during n_dhcp4_client_dispatch() 2e8c6017f85b n-dhcp4: use packet socket in rebinding state 9a7c82143c06 n-dhcp4: support init-reboot state e615e3f5b063 n-dhcp4/socket: use SO_REUSEADDR on UDP socket 8dbfa1ad2549 merge branch 'bengal:log' 6e50189fba86 n-dhcp4: log outgoing packets aea0315d3862 n-dhcp4: log incoming packets 1ca64a82393d n-dhcp4: add logging API 72c16a0028ac client-probe: fix state transitions on timer dispatch c023ae2ad829 merge branch 'leasetime' 780f94c333ca lease: add n_dhcp4_client_lease_get_basetime() a94378274cf4 incoming: don't handle 0xFFFFFFFF timestamps special in n_dhcp4_incoming_query_u32() 6bb277aefe0b probe: unconditionally pass ownership of message in n_dhcp4_client_probe_dispatch_io() 1cf87c90c6a1 probe: fix leaking message during client probe 55239cef2d63 lease: remove unreachable code in n_dhcp4_incoming_get_timeouts() fa5ee7903061 lease: expose the server IP address 0cfce4a4fe07 all: merge branch 'th/build-centos76' cae4868e5f7e all: avoid {net,linux}/if.h clashes on old distros 05859a3e4d32 all: avoid c_min() macro to work with old GCC 0be7033dd940 probe: arm timers in bound state 8735cdb0f95f ci: switch to c-util automation a0bb7c69a11a client: fallback to CLOCK_MONOTONIC for timerfd 308cb242caf7 ci: disable valgrind run 468c93195ad3 build: update submodules 9d0f690f9ee8 ci: switch to github actions 9ba143a037bb probe: allocate memory of right size in n_dhcp4_client_probe_option_new() 2546aa2c809d util/link: suppress gcc warning e4a01f5870a6 ci: drop broken armv7hl 2e2fbfa18f2c build: update submodules 6277dfd20eca Merge pull request #2 from NetworkManager/th/declaration-after-statement 1b660ae2d12a outgoing: avoid "-Werror=declaration-after-statement" warning with static_assert f34a54cd9719 client/probe: fix memory leak 21e68f3bba80 client/connection: fix memory leak git-subtree-dir: shared/n-dhcp4 git-subtree-split: 6876b73ec113328712a5cdc2ffb9497fd774c0e2 --- .cherryci/ci-test | 12 --- .github/workflows/ci.yml | 50 ++++++++++ .travis.yml | 21 ----- src/libndhcp4.sym | 1 + src/n-dhcp4-c-connection.c | 162 +++++++++++++++++++++++++++----- src/n-dhcp4-c-lease.c | 31 ++++++- src/n-dhcp4-c-probe.c | 158 +++++++++++++++++++++++++------ src/n-dhcp4-client.c | 185 ++++++++++++++++++++++++++++++++++--- src/n-dhcp4-incoming.c | 13 +-- src/n-dhcp4-outgoing.c | 14 +-- src/n-dhcp4-private.h | 78 +++++++++++++++- src/n-dhcp4-socket.c | 9 +- src/n-dhcp4.h | 13 +++ src/test-api.c | 1 + src/test-connection.c | 4 +- src/util/link.c | 2 +- src/util/packet.h | 3 +- subprojects/c-list | 2 +- subprojects/c-siphash | 2 +- subprojects/c-stdaux | 2 +- 20 files changed, 633 insertions(+), 130 deletions(-) delete mode 100755 .cherryci/ci-test create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.cherryci/ci-test b/.cherryci/ci-test deleted file mode 100755 index e1fc8b910..000000000 --- a/.cherryci/ci-test +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e - -rm -Rf "./ci-build" -mkdir "./ci-build" -cd "./ci-build" - -${CHERRY_LIB_MESONSETUP} . "${CHERRY_LIB_SRCDIR}" -${CHERRY_LIB_NINJABUILD} -${CHERRY_LIB_MESONTEST} -# no valgrind tests, since setns(2) is not supported by it diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..3583fdaad --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: Continuous Integration + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + ci: + name: CI with Default Configuration + runs-on: ubuntu-latest + + steps: + # + # Prepare CI + # + # We cannot use the github-action of the `ci-c-util` project, because we + # need privileges in the container. Therefore, fetch the CI sources and + # build the container manually. + # + - name: Fetch CI + uses: actions/checkout@v2 + with: + repository: c-util/automation + ref: v1 + path: automation + - name: Build CI + working-directory: automation/src/ci-c-util + run: docker build --tag ci-c-util:v1 . + + # + # Run CI + # + # Take the CI image we built and run the CI with the default project + # configuration. We do not use valgrind, since it falls-over with bpf(2) + # syscalls. + # + - name: Fetch Sources + uses: actions/checkout@v2 + with: + path: source + - name: Run through C-Util CI + run: | + docker run \ + --privileged \ + -v "$(pwd)/source:/github/workspace" \ + "ci-c-util:v1" \ + "--m32=1" \ + "--source=/github/workspace" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 99a7bb946..000000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -os: linux -dist: trusty -language: c - -services: - - docker - -before_install: - - curl -O -L "https://raw.githubusercontent.com/cherry-pick/cherry-images/v1/scripts/vmrun" - - curl -O -L "https://raw.githubusercontent.com/cherry-pick/cherry-ci/v1/scripts/cherryci" - - chmod +x "./vmrun" "./cherryci" - -jobs: - include: - - stage: test - script: - - ./vmrun -- ../src/cherryci -d ../src/.cherryci -s c-util -m - - script: - - ./vmrun -T armv7hl -- ../src/cherryci -d ../src/.cherryci -s c-util - - script: - - ./vmrun -T i686 -- ../src/cherryci -d ../src/.cherryci -s c-util diff --git a/src/libndhcp4.sym b/src/libndhcp4.sym index b9ba800a3..0659926be 100644 --- a/src/libndhcp4.sym +++ b/src/libndhcp4.sym @@ -34,6 +34,7 @@ global: n_dhcp4_client_lease_ref; n_dhcp4_client_lease_unref; n_dhcp4_client_lease_get_yiaddr; + n_dhcp4_client_lease_get_siaddr; n_dhcp4_client_lease_get_lifetime; n_dhcp4_client_lease_query; n_dhcp4_client_lease_select; diff --git a/src/n-dhcp4-c-connection.c b/src/n-dhcp4-c-connection.c index 2d099b41f..8c32a984d 100644 --- a/src/n-dhcp4-c-connection.c +++ b/src/n-dhcp4-c-connection.c @@ -8,6 +8,8 @@ #include #include #include +#include /* needed by linux/netdevice.h */ +#include #include #include #include @@ -21,6 +23,7 @@ * @connection: connection to operate on * @client_config: client configuration to use * @probe_config: client probe configuration to use + * @log_queue: the log queue for logging events * @fd_epoll: epoll context to attach to, or -1 * * This initializes a new client connection using the configuration given in @@ -45,11 +48,13 @@ int n_dhcp4_c_connection_init(NDhcp4CConnection *connection, NDhcp4ClientConfig *client_config, NDhcp4ClientProbeConfig *probe_config, + NDhcp4LogQueue *log_queue, int fd_epoll) { *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection); connection->client_config = client_config; connection->probe_config = probe_config; connection->fd_epoll = fd_epoll; + connection->log_queue = log_queue; /* * We explicitly allow initializing connections with an invalid @@ -82,11 +87,12 @@ int n_dhcp4_c_connection_init(NDhcp4CConnection *connection, */ void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection) { n_dhcp4_c_connection_close(connection); + n_dhcp4_outgoing_free(connection->request); *connection = (NDhcp4CConnection)N_DHCP4_C_CONNECTION_NULL(*connection); } static void n_dhcp4_c_connection_outgoing_set_secs(NDhcp4Outgoing *message) { - uint32_t secs; + uint64_t secs; /* * This function sets the `secs` field for outgoing messages. It @@ -122,12 +128,12 @@ static void n_dhcp4_c_connection_outgoing_set_secs(NDhcp4Outgoing *message) { * * Note: Some DHCP relays reject a `secs` value of 0 (which might look * like it is uninitialized). Hence, we always clamp the value to - * the range `[1, INF[`. + * the range `[1, 65535]`. */ secs = message->userdata.base_time - message->userdata.start_time; secs /= 1000ULL * 1000ULL * 1000ULL; /* nsecs to secs */ - secs = secs ?: 1; /* clamp to `[1, INF[` */ + secs = C_CLAMP(secs, 1, UINT16_MAX); n_dhcp4_outgoing_set_secs(message, secs); } @@ -136,7 +142,22 @@ int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection) { _c_cleanup_(c_closep) int fd_packet = -1; int r; - c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_INIT); + if (connection->state == N_DHCP4_C_CONNECTION_STATE_PACKET) + return 0; + + c_assert(connection->state == N_DHCP4_C_CONNECTION_STATE_INIT || + connection->state == N_DHCP4_C_CONNECTION_STATE_DRAINING || + connection->state == N_DHCP4_C_CONNECTION_STATE_UDP); + + if (connection->fd_packet >= 0) { + epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL); + connection->fd_packet = c_close(connection->fd_packet); + } + + if (connection->fd_udp >= 0) { + epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_udp, NULL); + connection->fd_udp = c_close(connection->fd_udp); + } r = n_dhcp4_c_socket_packet_new(&fd_packet, connection->client_config->ifindex); if (r) @@ -316,7 +337,6 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection, switch (connection->request->userdata.type) { case N_DHCP4_C_MESSAGE_DISCOVER: case N_DHCP4_C_MESSAGE_SELECT: - case N_DHCP4_C_MESSAGE_REBOOT: case N_DHCP4_C_MESSAGE_INFORM: /* * Resend with an exponential backoff and a one second random @@ -335,6 +355,7 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection, break; case N_DHCP4_C_MESSAGE_REBIND: case N_DHCP4_C_MESSAGE_RENEW: + case N_DHCP4_C_MESSAGE_REBOOT: /* * Resend every sixty seconds with a one second random slack. * @@ -510,6 +531,7 @@ static int n_dhcp4_c_connection_new_message(NDhcp4CConnection *connection, n_dhcp4_c_connection_init_header(connection, header); message->userdata.type = type; + message->userdata.message_type = message_type; /* * Note that some implementations expect the MESSAGE_TYPE option to be @@ -687,6 +709,7 @@ int n_dhcp4_c_connection_select_new(NDhcp4CConnection *connection, */ message->userdata.start_time = offer->userdata.start_time; message->userdata.base_time = offer->userdata.base_time; + message->userdata.client_addr = client.s_addr; n_dhcp4_incoming_get_xid(offer, &xid); n_dhcp4_outgoing_set_xid(message, xid); @@ -760,6 +783,7 @@ int n_dhcp4_c_connection_renew_new(NDhcp4CConnection *connection, if (r) return r; + message->userdata.client_addr = connection->client_ip; *requestp = message; message = NULL; return 0; @@ -793,6 +817,7 @@ int n_dhcp4_c_connection_rebind_new(NDhcp4CConnection *connection, if (r) return r; + message->userdata.client_addr = connection->client_ip; *requestp = message; message = NULL; return 0; @@ -844,6 +869,7 @@ int n_dhcp4_c_connection_decline_new(NDhcp4CConnection *connection, return r; } + message->userdata.client_addr = client.s_addr; *requestp = message; message = NULL; return 0; @@ -886,6 +912,7 @@ int n_dhcp4_c_connection_inform_new(NDhcp4CConnection *connection, if (r) return r; + message->userdata.client_addr = connection->client_ip; *requestp = message; message = NULL; return 0; @@ -952,10 +979,39 @@ int n_dhcp4_c_connection_release_new(NDhcp4CConnection *connection, return 0; } +static const char *message_type_to_str(uint8_t type) { + switch (type) { + case N_DHCP4_MESSAGE_DISCOVER: + return "DISCOVER"; + case N_DHCP4_MESSAGE_OFFER: + return "OFFER"; + case N_DHCP4_MESSAGE_REQUEST: + return "REQUEST"; + case N_DHCP4_MESSAGE_DECLINE: + return "DECLINE"; + case N_DHCP4_MESSAGE_ACK: + return "ACK"; + case N_DHCP4_MESSAGE_NAK: + return "NACK"; + case N_DHCP4_MESSAGE_RELEASE: + return "RELEASE"; + case N_DHCP4_MESSAGE_INFORM: + return "INFORM"; + case N_DHCP4_MESSAGE_FORCERENEW: + return "FORCERENEW"; + default: + return "UNKNOWN"; + } +} + static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection, NDhcp4Outgoing *request, uint64_t timestamp) { + char server_addr[INET_ADDRSTRLEN]; + char client_addr[INET_ADDRSTRLEN]; + char error_msg[128]; int r; + bool broadcast = false; /* * Increment the base time and reset the xid field, @@ -990,28 +1046,52 @@ static int n_dhcp4_c_connection_send_request(NDhcp4CConnection *connection, case N_DHCP4_C_MESSAGE_SELECT: case N_DHCP4_C_MESSAGE_REBOOT: case N_DHCP4_C_MESSAGE_DECLINE: + case N_DHCP4_C_MESSAGE_REBIND: + broadcast = true; r = n_dhcp4_c_connection_packet_broadcast(connection, request); - if (r) - return r; break; case N_DHCP4_C_MESSAGE_INFORM: - case N_DHCP4_C_MESSAGE_REBIND: + broadcast = true; r = n_dhcp4_c_connection_udp_broadcast(connection, request); - if (r) - return r; - break; case N_DHCP4_C_MESSAGE_RENEW: case N_DHCP4_C_MESSAGE_RELEASE: r = n_dhcp4_c_connection_udp_send(connection, request); - if (r) - return r; - break; default: c_assert(0); } + if (r) { + snprintf(error_msg, sizeof(error_msg), ": error %d", r); + } else { + error_msg[0] = '\0'; + } + + if (request->userdata.client_addr == INADDR_ANY) { + n_dhcp4_log(connection->log_queue, + LOG_INFO, + "send %s to %s%s", + message_type_to_str(request->userdata.message_type), + broadcast ? + "255.255.255.255" : + inet_ntop(AF_INET, &connection->server_ip, + server_addr, sizeof(server_addr)), + error_msg); + } else { + n_dhcp4_log(connection->log_queue, + LOG_INFO, + "send %s of %s to %s%s", + message_type_to_str(request->userdata.message_type), + inet_ntop(AF_INET, &request->userdata.client_addr, + client_addr, sizeof(client_addr)), + broadcast ? + "255.255.255.255" : + inet_ntop(AF_INET, &connection->server_ip, + server_addr, sizeof(server_addr)), + error_msg); + } + ++request->userdata.n_send; return 0; } @@ -1030,13 +1110,14 @@ int n_dhcp4_c_connection_start_request(NDhcp4CConnection *connection, if (request->userdata.start_time == 0) request->userdata.start_time = timestamp; - n_dhcp4_outgoing_free(connection->request); - connection->request = request; + connection->request = n_dhcp4_outgoing_free(connection->request); r = n_dhcp4_c_connection_send_request(connection, request, timestamp); if (r) return r; + connection->request = request; + return 0; } @@ -1060,9 +1141,18 @@ int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection, return 0; } +/* + * Returns: + * 0 on success + * N_DHCP4_E_MALFORMED if a malformed packet was received + * N_DHCP4_E_UNEXPECTED if the packet received contains unexpected data + * N_DHCP4_E_AGAIN if there was another error (non fatal for the client) + */ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, NDhcp4Incoming **messagep) { _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = NULL; + char serv_addr[INET_ADDRSTRLEN]; + char client_addr[INET_ADDRSTRLEN]; uint8_t type; int r; @@ -1072,10 +1162,11 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, connection->scratch_buffer, sizeof(connection->scratch_buffer), &message); - if (r) + if (!r) + break; + else if (r == N_DHCP4_E_MALFORMED) return r; - - break; + return N_DHCP4_E_AGAIN; case N_DHCP4_C_CONNECTION_STATE_DRAINING: r = n_dhcp4_c_socket_packet_recv(connection->fd_packet, connection->scratch_buffer, @@ -1083,8 +1174,10 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, &message); if (!r) break; - else if (r != N_DHCP4_E_AGAIN) + else if (r == N_DHCP4_E_MALFORMED) return r; + else if (r != N_DHCP4_E_AGAIN) + return N_DHCP4_E_AGAIN; /* * The UDP socket is open and the packet socket has been shut down @@ -1102,18 +1195,39 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection, connection->scratch_buffer, sizeof(connection->scratch_buffer), &message); - if (r) + if (!r) + break; + else if (r == N_DHCP4_E_MALFORMED) return r; - - break; + return N_DHCP4_E_AGAIN; default: abort(); return -ENOTRECOVERABLE; } r = n_dhcp4_c_connection_verify_incoming(connection, message, &type); - if (r) + if (r == N_DHCP4_E_MALFORMED || r == N_DHCP4_E_UNEXPECTED) return r; + else if (r != 0) + return N_DHCP4_E_AGAIN; + + if (type == N_DHCP4_MESSAGE_OFFER || type == N_DHCP4_MESSAGE_ACK) { + n_dhcp4_log(connection->log_queue, + LOG_INFO, + "received %s of %s from %s", + message_type_to_str(type), + inet_ntop(AF_INET, &message->message.header.yiaddr, + client_addr, sizeof(client_addr)), + inet_ntop(AF_INET, &message->message.header.siaddr, + serv_addr, sizeof(serv_addr))); + } else { + n_dhcp4_log(connection->log_queue, + LOG_INFO, + "received %s from %s", + message_type_to_str(type), + inet_ntop(AF_INET, &message->message.header.siaddr, + serv_addr, sizeof(serv_addr))); + } switch (type) { case N_DHCP4_MESSAGE_OFFER: diff --git a/src/n-dhcp4-c-lease.c b/src/n-dhcp4-c-lease.c index db1ffbae8..695a112ab 100644 --- a/src/n-dhcp4-c-lease.c +++ b/src/n-dhcp4-c-lease.c @@ -44,10 +44,7 @@ static int n_dhcp4_incoming_get_timeouts(NDhcp4Incoming *message, uint64_t *t1p, } else if (u32 == UINT32_MAX) { lifetime = UINT64_MAX; } else { - if (u32 == UINT32_MAX) - lifetime = UINT64_MAX; - else - lifetime = u32 * (1000000000ULL); + lifetime = u32 * (1000000000ULL); } r = n_dhcp4_incoming_query_t2(message, &u32); @@ -206,6 +203,32 @@ _c_public_ void n_dhcp4_client_lease_get_yiaddr(NDhcp4ClientLease *lease, struct yiaddr->s_addr = header->yiaddr; } +/** + * n_dhcp4_client_lease_get_siaddr() - get the server IP address + * @lease: the lease to operate on + * @siaddr: return argument for the IP address + * + * Gets the server 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_siaddr(NDhcp4ClientLease *lease, struct in_addr *siaddr) { + NDhcp4Header *header = n_dhcp4_incoming_get_header(lease->message); + + siaddr->s_addr = header->siaddr; +} + +/** + * n_dhcp4_client_lease_get_basetime() - get the timestamp when the lease was received. + * @lease: the lease to operate on + * @ns_basetimep: return argument for the base time in nano seconds + * + * Gets the timestamp when the lease was received in CLOCK_BOOTTIME. This + * is also the base timestamp for the expiration of the lifetime and t1/t2. + */ +_c_public_ void n_dhcp4_client_lease_get_basetime(NDhcp4ClientLease *lease, uint64_t *ns_basetimep) { + *ns_basetimep = lease->message->userdata.base_time; +} + /** * n_dhcp4_client_lease_get_lifetime() - get the lifetime * @lease: the lease to operate on diff --git a/src/n-dhcp4-c-probe.c b/src/n-dhcp4-c-probe.c index 67fedafff..f3d4f265c 100644 --- a/src/n-dhcp4-c-probe.c +++ b/src/n-dhcp4-c-probe.c @@ -26,7 +26,7 @@ static int n_dhcp4_client_probe_option_new(NDhcp4ClientProbeOption **optionp, uint8_t n_data) { NDhcp4ClientProbeOption *op; - op = malloc(sizeof(op) + n_data); + op = malloc(sizeof(*op) + n_data); if (!op) return -ENOMEM; @@ -170,8 +170,6 @@ _c_public_ void n_dhcp4_client_probe_config_set_inform_only(NDhcp4ClientProbeCon * INIT-REBOOT path, as described by the DHCP specification. In most cases, you * do not want this. * - * XXX: This is currently not implemented, and setting the property has no effect. - * * Background: The INIT-REBOOT path allows a DHCP client to skip * server-discovery when rebooting/resuming their machine. The DHCP * client simply re-requests the lease it had acquired before. This @@ -431,18 +429,31 @@ int n_dhcp4_client_probe_new(NDhcp4ClientProbe **probep, */ n_dhcp4_client_probe_config_initialize_random_seed(probe->config); + /* The new probe keeps a reference on @client. So we are sure that &client->log_queue + * stays alive as long as we need it. */ + r = n_dhcp4_c_connection_init(&probe->connection, client->config, probe->config, + &client->log_queue, active ? client->fd_epoll : -1); if (r) return r; + if (probe->config->requested_ip.s_addr != INADDR_ANY) + probe->last_address = probe->config->requested_ip; + + if (probe->config->init_reboot && probe->last_address.s_addr != INADDR_ANY) + probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT; + else + probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT; + if (active) { /* * Defer the sending of DISCOVER by a random amount (by default up to 9 seconds). */ - probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL)); + if (probe->state == N_DHCP4_CLIENT_PROBE_STATE_INIT) + probe->ns_deferred = ns_now + (n_dhcp4_client_probe_config_get_random(probe->config) % (probe->config->ms_start_delay * 1000000ULL)); probe->client->current_probe = probe; } else { r = n_dhcp4_client_probe_raise(probe, @@ -483,6 +494,7 @@ _c_public_ NDhcp4ClientProbe *n_dhcp4_client_probe_free(NDhcp4ClientProbe *probe if (probe == probe->client->current_probe) probe->client->current_probe = NULL; + n_dhcp4_client_lease_unref(probe->current_lease); n_dhcp4_c_connection_deinit(&probe->connection); n_dhcp4_client_unref(probe->client); n_dhcp4_client_probe_config_free(probe->config); @@ -574,6 +586,14 @@ void n_dhcp4_client_probe_get_timeout(NDhcp4ClientProbe *probe, uint64_t *timeou n_dhcp4_c_connection_get_timeout(&probe->connection, &timeout); switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + /* send DHCP request immediately */ + timeout = 1; + break; + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + if (probe->ns_reinit && (!timeout || probe->ns_reinit < timeout)) + timeout = probe->ns_reinit; + break; case N_DHCP4_CLIENT_PROBE_STATE_INIT: if (probe->ns_deferred && (!timeout || probe->ns_deferred < timeout)) timeout = probe->ns_deferred; @@ -625,6 +645,52 @@ static int n_dhcp4_client_probe_outgoing_append_options(NDhcp4ClientProbe *probe return 0; } +static int n_dhcp4_client_probe_transition_reboot(NDhcp4ClientProbe *probe, uint64_t ns_now) { + _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; + int r; + + switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + r = n_dhcp4_c_connection_listen(&probe->connection); + if (r) + return r; + + r = n_dhcp4_c_connection_reboot_new(&probe->connection, &request, &probe->last_address); + if (r) + return r; + + r = n_dhcp4_client_probe_outgoing_append_options(probe, request); + if (r) + return r; + + r = n_dhcp4_c_connection_start_request(&probe->connection, request, ns_now); + if (r) + return r; + else + request = NULL; /* consumed */ + + probe->state = N_DHCP4_CLIENT_PROBE_STATE_REBOOTING; + probe->ns_reinit = ns_now + 2000000000ULL; + + break; + + case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: + case N_DHCP4_CLIENT_PROBE_STATE_INIT: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: + case N_DHCP4_CLIENT_PROBE_STATE_BOUND: + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: + default: + abort(); + break; + } + + return 0; +} + static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, uint64_t ns_now) { _c_cleanup_(n_dhcp4_outgoing_freep) NDhcp4Outgoing *request = NULL; int r; @@ -634,13 +700,15 @@ static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, ui r = n_dhcp4_c_connection_listen(&probe->connection); if (r) return r; + /* fall-through */ + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: r = n_dhcp4_c_connection_discover_new(&probe->connection, &request); if (r) return r; - if (probe->config->requested_ip.s_addr != INADDR_ANY) { - r = n_dhcp4_outgoing_append_requested_ip(request, probe->config->requested_ip); + if (probe->last_address.s_addr != INADDR_ANY) { + r = n_dhcp4_outgoing_append_requested_ip(request, probe->last_address); if (r) return r; } @@ -662,7 +730,6 @@ static int n_dhcp4_client_probe_transition_deferred(NDhcp4ClientProbe *probe, ui case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: - case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: case N_DHCP4_CLIENT_PROBE_STATE_BOUND: @@ -725,6 +792,10 @@ static int n_dhcp4_client_probe_transition_t2(NDhcp4ClientProbe *probe, uint64_t switch (probe->state) { case N_DHCP4_CLIENT_PROBE_STATE_BOUND: case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: + r = n_dhcp4_c_connection_listen(&probe->connection); + if (r) + return r; + r = n_dhcp4_c_connection_rebind_new(&probe->connection, &request); if (r) return r; @@ -777,11 +848,11 @@ static int n_dhcp4_client_probe_transition_lifetime(NDhcp4ClientProbe *probe) { return r; c_assert(probe->client->current_probe == probe); - probe->client->current_probe = NULL; - n_dhcp4_c_connection_close(&probe->connection); + probe->current_lease = n_dhcp4_client_lease_unref(probe->current_lease); - probe->state = N_DHCP4_CLIENT_PROBE_STATE_EXPIRED; + probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT; + probe->ns_deferred = n_dhcp4_gettime(CLOCK_BOOTTIME) + UINT64_C(1); break; @@ -799,7 +870,8 @@ static int n_dhcp4_client_probe_transition_lifetime(NDhcp4ClientProbe *probe) { return 0; } -static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) { +static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp4Incoming *message_take) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = message_take; _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL; NDhcp4CEventNode *node; int r; @@ -817,7 +889,7 @@ static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp if (r) return r; - /* message consumed, do not fail */ + message = NULL; /* consumed */ n_dhcp4_client_lease_link(lease, probe); @@ -842,14 +914,26 @@ static int n_dhcp4_client_probe_transition_offer(NDhcp4ClientProbe *probe, NDhcp return 0; } -static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4Incoming *message) { +static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4Incoming *message_take) { + _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *message = message_take; _c_cleanup_(n_dhcp4_client_lease_unrefp) NDhcp4ClientLease *lease = NULL; NDhcp4CEventNode *node; + struct in_addr client = {}; + struct in_addr server = {}; int r; switch (probe->state) { - case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: case N_DHCP4_CLIENT_PROBE_STATE_REBINDING: + n_dhcp4_incoming_get_yiaddr(message, &client); + + r = n_dhcp4_incoming_query_server_identifier(message, &server); + if (r) + return r; + r = n_dhcp4_c_connection_connect(&probe->connection, &client, &server); + if (r) + return r; + /* fall-through */ + case N_DHCP4_CLIENT_PROBE_STATE_RENEWING: r = n_dhcp4_client_probe_raise(probe, &node, @@ -861,7 +945,7 @@ static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4I if (r) return r; - /* message consumed, do not fail */ + message = NULL; /* consumed */ n_dhcp4_client_lease_link(lease, probe); @@ -869,10 +953,12 @@ static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4I n_dhcp4_client_lease_unref(probe->current_lease); probe->current_lease = n_dhcp4_client_lease_ref(lease); probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND; - + n_dhcp4_client_lease_get_yiaddr(lease, &probe->last_address); + probe->ns_nak_restart_delay = 0; break; case N_DHCP4_CLIENT_PROBE_STATE_REQUESTING: + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: r = n_dhcp4_client_probe_raise(probe, &node, @@ -884,20 +970,19 @@ static int n_dhcp4_client_probe_transition_ack(NDhcp4ClientProbe *probe, NDhcp4I if (r) return r; - /* message consumed, don to fail */ + message = NULL; /* consumed */ n_dhcp4_client_lease_link(lease, probe); node->event.granted.lease = n_dhcp4_client_lease_ref(lease); probe->current_lease = n_dhcp4_client_lease_ref(lease); probe->state = N_DHCP4_CLIENT_PROBE_STATE_GRANTED; - + probe->ns_nak_restart_delay = 0; break; case N_DHCP4_CLIENT_PROBE_STATE_INIT: case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: - case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: case N_DHCP4_CLIENT_PROBE_STATE_BOUND: case N_DHCP4_CLIENT_PROBE_STATE_GRANTED: case N_DHCP4_CLIENT_PROBE_STATE_EXPIRED: @@ -927,9 +1012,11 @@ static int n_dhcp4_client_probe_transition_nak(NDhcp4ClientProbe *probe) { return r; probe->state = N_DHCP4_CLIENT_PROBE_STATE_INIT; - + probe->ns_deferred = n_dhcp4_gettime(CLOCK_BOOTTIME) + probe->ns_nak_restart_delay; + probe->ns_nak_restart_delay = C_CLAMP(probe->ns_nak_restart_delay * 2u, + UINT64_C(2) * UINT64_C(1000000000), + UINT64_C(300) * UINT64_C(1000000000)); break; - case N_DHCP4_CLIENT_PROBE_STATE_SELECTING: case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: case N_DHCP4_CLIENT_PROBE_STATE_INIT: @@ -1008,7 +1095,7 @@ int n_dhcp4_client_probe_transition_accept(NDhcp4ClientProbe *probe, NDhcp4Incom probe->state = N_DHCP4_CLIENT_PROBE_STATE_BOUND; - /* XXX: trigger timers */ + n_dhcp4_client_arm_timer(probe->client); break; @@ -1076,6 +1163,19 @@ int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_no int r; switch (probe->state) { + case N_DHCP4_CLIENT_PROBE_STATE_INIT_REBOOT: + r = n_dhcp4_client_probe_transition_reboot(probe, ns_now); + if (r) + return r; + break; + case N_DHCP4_CLIENT_PROBE_STATE_REBOOTING: + if (ns_now >= probe->ns_reinit) { + r = n_dhcp4_client_probe_transition_deferred(probe, ns_now); + if (r) + return r; + } + + break; case N_DHCP4_CLIENT_PROBE_STATE_INIT: if (ns_now >= probe->ns_deferred) { r = n_dhcp4_client_probe_transition_deferred(probe, ns_now); @@ -1099,16 +1199,17 @@ int n_dhcp4_client_probe_dispatch_timer(NDhcp4ClientProbe *probe, uint64_t ns_no r = n_dhcp4_client_probe_transition_lifetime(probe); if (r) return r; - } else if (ns_now >= probe->current_lease->t2) { + } else if (ns_now >= probe->current_lease->t2 && + probe->state != N_DHCP4_CLIENT_PROBE_STATE_REBINDING) { r = n_dhcp4_client_probe_transition_t2(probe, ns_now); if (r) return r; - } else if (ns_now >= probe->current_lease->t1) { + } else if (ns_now >= probe->current_lease->t1 && + probe->state == N_DHCP4_CLIENT_PROBE_STATE_BOUND) { r = n_dhcp4_client_probe_transition_t1(probe, ns_now); if (r) return r; } - break; default: /* ignore */ @@ -1145,6 +1246,7 @@ int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events) return 0; } + abort(); return r; } @@ -1166,17 +1268,15 @@ int n_dhcp4_client_probe_dispatch_io(NDhcp4ClientProbe *probe, uint32_t events) switch (type) { case N_DHCP4_MESSAGE_OFFER: r = n_dhcp4_client_probe_transition_offer(probe, message); + message = NULL; /* consumed */ if (r) return r; - else - message = NULL; /* consumed */ break; case N_DHCP4_MESSAGE_ACK: r = n_dhcp4_client_probe_transition_ack(probe, message); + message = NULL; /* consumed */ if (r) return r; - else - message = NULL; /* consumed */ break; case N_DHCP4_MESSAGE_NAK: r = n_dhcp4_client_probe_transition_nak(probe); diff --git a/src/n-dhcp4-client.c b/src/n-dhcp4-client.c index a492a74c6..403a69324 100644 --- a/src/n-dhcp4-client.c +++ b/src/n-dhcp4-client.c @@ -183,7 +183,11 @@ _c_public_ void n_dhcp4_client_config_set_request_broadcast(NDhcp4ClientConfig * */ _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))); + + if (n_mac > sizeof(config->mac)) + n_mac = sizeof(config->mac); + + memcpy(config->mac, mac, n_mac); } /** @@ -209,37 +213,77 @@ _c_public_ void n_dhcp4_client_config_set_mac(NDhcp4ClientConfig *config, const */ _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))); + + if (n_mac > sizeof(config->mac)) + n_mac = sizeof(config->mac); + + memcpy(config->broadcast_mac, mac, n_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 + * @n_id: length of the client id in bytes. The length + * must be from 2 up to 255 bytes. Set it to 0 + * to unset the client-id. * * This sets the client-id property of @config. It copies the entire client-id * buffer into the configuration. + * See RFC 2132 (section 9.14) for the format of the Client Identifier. * * 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; + if (n_id == 0) { + config->client_id = c_free(config->client_id); + config->n_client_id = 0; + return 0; + } + + if (n_id < 2 || n_id > 255) + return -EINVAL; + t = malloc(n_id + 1); if (!t) return -ENOMEM; + memcpy(t, id, n_id); + t[n_id] = 0; /* safety 0 for debugging */ + 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_client_set_log_level() - set the logging level of the client + * @client: the client to operate on + * @level: the minimum syslog logging level that is + * still logged. For example, set to LOG_NOTICE + * to receive logging events with level LOG_NOTICE + * and higher. Set to -1 to disable generating + * logging events (which is also the default). + * + * By enabling logging, you can get N_DHCP4_CLIENT_EVENT_LOG events. + * + * From the logging event you may steal the message if (and only if) "allow_steal_message" + * is true. In that case, clear the message field and free the message yourself. + * + * If a logging event cannot be logged due to out of memory, one message + * gets logged that messages are missing. Until the event with that message + * gets dropped, no further logging events will be queued. + * + * You may change the logging level at any time, but it does not affect + * logging events that are already queued. + */ +_c_public_ void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level) { + client->log_queue.log_level = level; +} + /** * n_dhcp4_c_event_node_new() - allocate new event * @nodep: output argument for new event @@ -293,6 +337,16 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node) { case N_DHCP4_CLIENT_EVENT_EXTENDED: node->event.extended.lease = n_dhcp4_client_lease_unref(node->event.extended.lease); break; + case N_DHCP4_CLIENT_EVENT_LOG: + if (_c_unlikely_(!node->event.log.allow_steal_message)) { + /* @node is the static node "nomem_node". It must not be + * freed. */ + c_list_unlink(&node->client_link); + node->is_public = false; + return NULL; + } + node->event.log.message = c_free((char *)node->event.log.message); + break; default: break; } @@ -368,13 +422,18 @@ _c_public_ int n_dhcp4_client_new(NDhcp4Client **clientp, NDhcp4ClientConfig *co return -errno; client->fd_timer = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC | TFD_NONBLOCK); + if (client->fd_timer < 0 && errno == EINVAL) + client->fd_timer = timerfd_create(CLOCK_MONOTONIC, 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) + if (r < 0) { + close(client->fd_timer); + client->fd_timer = -1; return -errno; + } *clientp = client; client = NULL; @@ -464,6 +523,78 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne return 0; } +/** + * n_dhcp4_log_queue_fmt() - add a logging event. + * @client: the NDhcp4LogQueue to operate on + * @level: the syslog logging level + * @fmt: the format string for the message + * @... printf arguments for logging + * + * Appends a logging event to the event queue if logging is + * enabled and the logging level sufficiently high. + * + * Queuing a logging event might fail with out of memory. + * In that case, a static event will be queued that informs + * about lost messages. + */ +void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue, + int level, + const char *fmt, + ...) { + NDhcp4CEventNode *node; + char *message; + va_list ap; + int r; + + if (level > log_queue->log_level) + return; + + /* Currently the logging queue is only implemented for + * the client. Nobody would enable logging except a + * client instance. */ + c_assert(log_queue->is_client); + + if (!c_list_is_empty (&log_queue->nomem_node.client_link)) { + /* we have the nomem_node queued after a recent out + * of memory. This disables all logging messages until + * the event gets popped. + * + * The reason is that we can only queue the nomem_node once, + * so if we now try to append another event and succeed, the + * user wouldn't know which messages got dropped. Instead, + * just drop them all!! */ + return; + } + + r = n_dhcp4_c_event_node_new(&node); + if (r < 0) + goto handle_nomem; + + va_start(ap, fmt); + r = vasprintf(&message, fmt, ap); + va_end(ap); + + if (r < 0) { + n_dhcp4_c_event_node_free(node); + goto handle_nomem; + } + + node->event = (NDhcp4ClientEvent) { + .event = N_DHCP4_CLIENT_EVENT_LOG, + .log = { + .level = level, + .message = message, + .allow_steal_message = true, + }, + }; + + c_list_link_tail(log_queue->event_list, &node->client_link); + return; + +handle_nomem: + c_list_link_tail(log_queue->event_list, &log_queue->nomem_node.client_link); +} + /** * n_dhcp4_client_arm_timer() - update timer * @client: client to operate on @@ -472,19 +603,39 @@ int n_dhcp4_client_raise(NDhcp4Client *client, NDhcp4CEventNode **nodep, unsigne * must be called whenever a timeout on @client might have changed. */ void n_dhcp4_client_arm_timer(NDhcp4Client *client) { - uint64_t timeout = 0; + uint64_t now, offset, timeout = 0; int r; if (client->current_probe) n_dhcp4_client_probe_get_timeout(client->current_probe, &timeout); if (timeout != client->scheduled_timeout) { + /* + * Across our codebase, timeouts are specified as absolute + * timestamps on CLOCK_BOOTTIME. Unfortunately, there are + * systems with CLOCK_BOOTTIME support, but timerfd lacks it + * (in particular RHEL). Therefore, our timerfd might be on + * CLOCK_MONOTONIC. + * To account for this, we always schedule a relative timeout. + * We fetch the current time and then calculate the offset + * which we then schedule as relative timeout on the timerfd. + * This works regardless which clock the timerfd runs on. + * Once we no longer support CLOCK_MONOTONIC as fallback, we + * can simply switch to TFD_TIMER_ABSTIME here and specify + * `timeout` directly as value. + */ + now = n_dhcp4_gettime(CLOCK_BOOTTIME); + if (now >= timeout) + offset = 1; /* 0 would disarm the timerfd */ + else + offset = timeout - now; + r = timerfd_settime(client->fd_timer, - TFD_TIMER_ABSTIME, + 0, &(struct itimerspec){ .it_value = { - .tv_sec = timeout / UINT64_C(1000000000), - .tv_nsec = timeout % UINT64_C(1000000000), + .tv_sec = offset / UINT64_C(1000000000), + .tv_nsec = offset % UINT64_C(1000000000), }, }, NULL); @@ -639,7 +790,13 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) { /* continue normally */ } else if (r) { - c_assert(r < _N_DHCP4_E_INTERNAL); + if (r >= _N_DHCP4_E_INTERNAL) { + n_dhcp4_log(&client->log_queue, + LOG_ERR, + "invalid internal error code %d after dispatch", + r); + return N_DHCP4_E_INTERNAL; + } return r; } } @@ -706,6 +863,8 @@ _c_public_ int n_dhcp4_client_dispatch(NDhcp4Client *client) { * the client attempted several incompatible * probes in parallel, then the most recent * ones will be cancelled asynchronously. + * * N_DHCP4_CLIENT_EVENT_LOG: A logging event if n_dhcp4_client_set_log_level() + * is enabled. * * Return: 0 on success, negative error code on failure. */ diff --git a/src/n-dhcp4-incoming.c b/src/n-dhcp4-incoming.c index 255da4584..f739413b5 100644 --- a/src/n-dhcp4-incoming.c +++ b/src/n-dhcp4-incoming.c @@ -326,7 +326,7 @@ static int n_dhcp4_incoming_query_u8(NDhcp4Incoming *message, uint8_t option, ui r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; - else if (n_data != sizeof(*data)) + else if (n_data < sizeof(*data)) return N_DHCP4_E_MALFORMED; *u8p = *data; @@ -342,7 +342,7 @@ static int n_dhcp4_incoming_query_u16(NDhcp4Incoming *message, uint8_t option, u r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; - else if (n_data != sizeof(be16)) + else if (n_data < sizeof(be16)) return N_DHCP4_E_MALFORMED; memcpy(&be16, data, sizeof(be16)); @@ -360,15 +360,12 @@ static int n_dhcp4_incoming_query_u32(NDhcp4Incoming *message, uint8_t option, u r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; - else if (n_data != sizeof(be32)) + 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); + *u32p = ntohl(be32); return 0; } @@ -381,7 +378,7 @@ static int n_dhcp4_incoming_query_in_addr(NDhcp4Incoming *message, uint8_t optio r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; - else if (n_data != sizeof(be32)) + else if (n_data < sizeof(be32)) return N_DHCP4_E_MALFORMED; memcpy(&be32, data, sizeof(be32)); diff --git a/src/n-dhcp4-outgoing.c b/src/n-dhcp4-outgoing.c index bb33bbdbe..bcab407f2 100644 --- a/src/n-dhcp4-outgoing.c +++ b/src/n-dhcp4-outgoing.c @@ -54,8 +54,6 @@ 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. @@ -64,6 +62,8 @@ int n_dhcp4_outgoing_new(NDhcp4Outgoing **outgoingp, size_t max_size, uint8_t ov sizeof(NDhcp4Message) + 1, "Invalid minimum IP packet limit"); + c_assert(!(overload & ~(N_DHCP4_OVERLOAD_FILE | N_DHCP4_OVERLOAD_SNAME))); + outgoing = calloc(1, sizeof(*outgoing)); if (!outgoing) return -ENOMEM; @@ -220,8 +220,9 @@ int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, /* 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); + n = outgoing->n_message + n_data + 128; + if (n > outgoing->max_size) + n = outgoing->max_size; m = realloc(outgoing->message, n); if (!m) return -ENOMEM; @@ -276,6 +277,7 @@ int n_dhcp4_outgoing_append(NDhcp4Outgoing *outgoing, return 0; } + overload = outgoing->overload; if (overload & N_DHCP4_OVERLOAD_SNAME) outgoing->i_message = offsetof(NDhcp4Message, sname); else @@ -340,7 +342,7 @@ int n_dhcp4_outgoing_append_requested_ip(NDhcp4Outgoing *message, struct in_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) { +void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint16_t secs) { NDhcp4Header *header = n_dhcp4_outgoing_get_header(message); /* @@ -349,7 +351,7 @@ void n_dhcp4_outgoing_set_secs(NDhcp4Outgoing *message, uint32_t secs) { */ c_assert(secs); - header->secs = htonl(secs); + header->secs = htons(secs); } void n_dhcp4_outgoing_set_xid(NDhcp4Outgoing *message, uint32_t xid) { diff --git a/src/n-dhcp4-private.h b/src/n-dhcp4-private.h index b896a93dc..90f8f0c35 100644 --- a/src/n-dhcp4-private.h +++ b/src/n-dhcp4-private.h @@ -7,11 +7,11 @@ #include #include #include -#include #include #include #include #include +#include #include "n-dhcp4.h" typedef struct NDhcp4CConnection NDhcp4CConnection; @@ -24,6 +24,7 @@ typedef struct NDhcp4Outgoing NDhcp4Outgoing; typedef struct NDhcp4SConnection NDhcp4SConnection; typedef struct NDhcp4SConnectionIp NDhcp4SConnectionIp; typedef struct NDhcp4SEventNode NDhcp4SEventNode; +typedef struct NDhcp4LogQueue NDhcp4LogQueue; /* specs */ @@ -199,6 +200,8 @@ struct NDhcp4Outgoing { struct { uint8_t type; + uint8_t message_type; + uint32_t client_addr; uint64_t start_time; uint64_t base_time; uint64_t send_time; @@ -234,9 +237,9 @@ struct NDhcp4ClientConfig { int ifindex; unsigned int transport; bool request_broadcast; - uint8_t mac[MAX_ADDR_LEN]; + uint8_t mac[32]; /* MAX_ADDR_LEN */ size_t n_mac; - uint8_t broadcast_mac[MAX_ADDR_LEN]; + uint8_t broadcast_mac[32]; /* MAX_ADDR_LEN */ size_t n_broadcast_mac; uint8_t *client_id; size_t n_client_id; @@ -283,9 +286,42 @@ struct NDhcp4CEventNode { .probe_link = C_LIST_INIT((_x).probe_link), \ } +struct NDhcp4LogQueue { + CList *event_list; + NDhcp4CEventNode nomem_node; + int log_level; + bool is_client : 1; +}; + +#define N_DHCP4_LOG_QUEUE_NULL_DEFUNCT() { \ + .log_level = -1, \ + .is_client = false, \ + } + +#define N_DHCP4_LOG_QUEUE_NULL_CLIENT(client) { \ + .event_list = &((client).event_list), \ + .log_level = -1, \ + .is_client = true, \ + .nomem_node = { \ + .client_link = C_LIST_INIT((client).log_queue.nomem_node.client_link), \ + .probe_link = C_LIST_INIT((client).log_queue.nomem_node.probe_link), \ + .event = { \ + .event = N_DHCP4_CLIENT_EVENT_LOG, \ + .log = { \ + .level = LOG_CRIT, \ + .message = "one or more logging messages dropped due to out of memory", \ + .allow_steal_message = false, \ + }, \ + }, \ + .is_public = false, \ + }, \ + } + struct NDhcp4CConnection { NDhcp4ClientConfig *client_config; NDhcp4ClientProbeConfig *probe_config; + NDhcp4LogQueue *log_queue; + int fd_epoll; unsigned int state; /* current connection state */ @@ -317,6 +353,9 @@ struct NDhcp4Client { unsigned long n_refs; NDhcp4ClientConfig *config; CList event_list; + + NDhcp4LogQueue log_queue; + int fd_epoll; int fd_timer; @@ -332,6 +371,7 @@ struct NDhcp4Client { .event_list = C_LIST_INIT((_x).event_list), \ .fd_epoll = -1, \ .fd_timer = -1, \ + .log_queue = N_DHCP4_LOG_QUEUE_NULL_CLIENT(_x), \ } struct NDhcp4ClientProbe { @@ -342,7 +382,10 @@ struct NDhcp4ClientProbe { void *userdata; unsigned int state; /* current probe state */ + struct in_addr last_address; /* last address obtained */ uint64_t ns_deferred; /* timeout for deferred action */ + uint64_t ns_reinit; + uint64_t ns_nak_restart_delay; /* restart delay after a nak */ NDhcp4ClientLease *current_lease; /* current lease */ NDhcp4CConnection connection; /* client connection wrapper */ @@ -467,7 +510,7 @@ 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_secs(NDhcp4Outgoing *message, uint16_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); @@ -560,6 +603,7 @@ NDhcp4CEventNode *n_dhcp4_c_event_node_free(NDhcp4CEventNode *node); int n_dhcp4_c_connection_init(NDhcp4CConnection *connection, NDhcp4ClientConfig *client_config, NDhcp4ClientProbeConfig *probe_config, + NDhcp4LogQueue *log_queue, int fd_epoll); void n_dhcp4_c_connection_deinit(NDhcp4CConnection *connection); @@ -687,3 +731,29 @@ static inline uint64_t n_dhcp4_gettime(clockid_t clock) { return ts.tv_sec * 1000ULL * 1000ULL * 1000ULL + ts.tv_nsec; } + +void n_dhcp4_log_queue_fmt(NDhcp4LogQueue *log_queue, + int level, + const char *fmt, + ...) _c_printf_(3, 4); + +/** + * n_dhcp4_log() - append a logging event + * @x_log_queue: the logging event queue + * @x_level: the syslog logging level for the message. + * @...: the format string and arguments. + * + * Warning: this macro only evaluates the format arguments if the logging + * level is enabled. + */ +#define n_dhcp4_log(x_log_queue, x_level, ...) \ + do { \ + NDhcp4LogQueue *const _log_queue = (x_log_queue); \ + const int _level = (x_level); \ + \ + if (_level <= _log_queue->log_level) { \ + n_dhcp4_log_queue_fmt(_log_queue, \ + _level, \ + __VA_ARGS__); \ + } \ + } while (0) diff --git a/src/n-dhcp4-socket.c b/src/n-dhcp4-socket.c index 0bcc2419d..c7e897726 100644 --- a/src/n-dhcp4-socket.c +++ b/src/n-dhcp4-socket.c @@ -5,16 +5,17 @@ #include #include #include +#include /* needed by linux/if.h */ +#include #include +#include #include -#include #include #include #include #include #include #include -#include #include "n-dhcp4-private.h" #include "util/packet.h" #include "util/socket.h" @@ -194,6 +195,10 @@ int n_dhcp4_c_socket_udp_new(int *sockfdp, if (sockfd < 0) return -errno; + r = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (r < 0) + return -errno; + r = setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); if (r < 0) return -errno; diff --git a/src/n-dhcp4.h b/src/n-dhcp4.h index 58a3cb803..81452848d 100644 --- a/src/n-dhcp4.h +++ b/src/n-dhcp4.h @@ -61,6 +61,7 @@ enum { N_DHCP4_CLIENT_EVENT_EXTENDED, N_DHCP4_CLIENT_EVENT_EXPIRED, N_DHCP4_CLIENT_EVENT_CANCELLED, + N_DHCP4_CLIENT_EVENT_LOG, _N_DHCP4_CLIENT_EVENT_N, }; @@ -86,6 +87,14 @@ struct NDhcp4ClientEvent { struct { NDhcp4ClientProbe *probe; } retracted, expired, cancelled; + struct { + /* If allow_steal_message is true, then the user may steal the message when handling + * the event. In that case, set the message field to %NULL and free it yourself + * with free(). */ + const char *message; + int level; + bool allow_steal_message; + } log; }; }; @@ -137,6 +146,8 @@ 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); +void n_dhcp4_client_set_log_level(NDhcp4Client *client, int level); + int n_dhcp4_client_update_mtu(NDhcp4Client *client, uint16_t mtu); int n_dhcp4_client_probe(NDhcp4Client *client, @@ -156,6 +167,8 @@ 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_siaddr(NDhcp4ClientLease *lease, struct in_addr *siaddr); +void n_dhcp4_client_lease_get_basetime(NDhcp4ClientLease *lease, uint64_t *ns_basetimep); 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); diff --git a/src/test-api.c b/src/test-api.c index 9e7b6bcaa..be2d7b36a 100644 --- a/src/test-api.c +++ b/src/test-api.c @@ -104,6 +104,7 @@ static void test_api_functions(void) { (void *)n_dhcp4_client_lease_unrefp, (void *)n_dhcp4_client_lease_unrefv, (void *)n_dhcp4_client_lease_get_yiaddr, + (void *)n_dhcp4_client_lease_get_siaddr, (void *)n_dhcp4_client_lease_get_lifetime, (void *)n_dhcp4_client_lease_query, (void *)n_dhcp4_client_lease_select, diff --git a/src/test-connection.c b/src/test-connection.c index 360076e7b..98bd2aca0 100644 --- a/src/test-connection.c +++ b/src/test-connection.c @@ -322,6 +322,7 @@ static void test_connection(void) { NDhcp4CConnection connection_client = N_DHCP4_C_CONNECTION_NULL(connection_client); _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *offer = NULL; _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *ack = NULL; + NDhcp4LogQueue log_queue = N_DHCP4_LOG_QUEUE_NULL_DEFUNCT(); test_s_connection_init(ns_server, &connection_server, link_server.ifindex); n_dhcp4_s_connection_ip_init(&connection_server_ip, addr_server); @@ -351,6 +352,7 @@ static void test_connection(void) { r = n_dhcp4_c_connection_init(&connection_client, client_config, probe_config, + &log_queue, efd_client); c_assert(!r); test_c_connection_listen(ns_client, &connection_client); @@ -358,13 +360,13 @@ static void test_connection(void) { test_discover(&connection_server, &connection_client, &addr_server, &addr_client, &offer); test_select(&connection_server, &connection_client, offer, &addr_server, &addr_client); test_reboot(&connection_server, &connection_client, &addr_server, &addr_client, &ack); + test_rebind(&connection_server, &connection_client, &addr_server, &addr_client); test_decline(&connection_server, &connection_client, ack); link_add_ip4(&link_client, &addr_client, 8); test_c_connection_connect(ns_client, &connection_client, &addr_client, &addr_server); test_renew(&connection_server, &connection_client, &addr_server, &addr_client); - test_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); diff --git a/src/util/link.c b/src/util/link.c index 2a3ac7fe1..54d9a5c39 100644 --- a/src/util/link.c +++ b/src/util/link.c @@ -59,7 +59,7 @@ static void link_query(int netns, const char *name, int *ifindexp, struct ether_ s = socket(AF_INET, SOCK_DGRAM, 0); c_assert(s >= 0); - strncpy(ifr.ifr_name, name, n_name); + memcpy(ifr.ifr_name, name, n_name); r = ioctl(s, SIOCGIFHWADDR, &ifr); c_assert(r >= 0); diff --git a/src/util/packet.h b/src/util/packet.h index edbf2a3e4..98dabf7fb 100644 --- a/src/util/packet.h +++ b/src/util/packet.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +24,7 @@ struct packet_sockaddr_ll { unsigned short sll_hatype; unsigned char sll_pkttype; unsigned char sll_halen; - unsigned char sll_addr[MAX_ADDR_LEN]; + unsigned char sll_addr[32]; /* MAX_ADDR_LEN */ }; uint16_t packet_internet_checksum(const uint8_t *data, size_t len); diff --git a/subprojects/c-list b/subprojects/c-list index 2e4b605c6..6c53ef1c0 160000 --- a/subprojects/c-list +++ b/subprojects/c-list @@ -1 +1 @@ -Subproject commit 2e4b605c6217cd3c8a1ef773f82f5cc329ba650d +Subproject commit 6c53ef1c0066a3b0d82e9e181e90114eacb7c4aa diff --git a/subprojects/c-siphash b/subprojects/c-siphash index 7c42c5925..2d159c7da 160000 --- a/subprojects/c-siphash +++ b/subprojects/c-siphash @@ -1 +1 @@ -Subproject commit 7c42c592581906fef19458372b8db2b643278211 +Subproject commit 2d159c7da1d542f2b1fcbbefea6931bce242b943 diff --git a/subprojects/c-stdaux b/subprojects/c-stdaux index 11930d259..8b8f941c5 160000 --- a/subprojects/c-stdaux +++ b/subprojects/c-stdaux @@ -1 +1 @@ -Subproject commit 11930d259212605a15430523472ef54e0c7654ee +Subproject commit 8b8f941c57a790c277f49b099e73ed9f8ea141af