]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-dhcp-client,-server: set timestamp based on the time when received a packet 29921/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 8 Nov 2023 04:49:03 +0000 (13:49 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 8 Nov 2023 08:58:51 +0000 (17:58 +0900)
It seems that RFC does not say anything about the timestamp of lease
we should use: time that the client sent a request or received a reply.
In DHCPv6 client and NDisc, we use a timestamp that we receive a packet,
rather than we sent something. So, let's consistently use the same
logic for DHCPv4 client.

By using the logic, we will hopefully not forget to set timestamp again,
which is fixed by 089362976c2a653a77f942bfeb3f61d0e180f078.

src/libsystemd-network/dhcp-lease-internal.h
src/libsystemd-network/dhcp-network.c
src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/fuzz-dhcp-client.c
src/libsystemd-network/fuzz-dhcp-server.c
src/libsystemd-network/sd-dhcp-client.c
src/libsystemd-network/sd-dhcp-lease.c
src/libsystemd-network/sd-dhcp-server.c
src/libsystemd-network/test-dhcp-server.c

index 0b72813733bf5a89cecbb116747696d287f472bd..a3d8bb45b397ae527e3d9ce29cfdb88f282394e0 100644 (file)
@@ -90,8 +90,8 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
 int dhcp_lease_parse_search_domains(const uint8_t *option, size_t len, char ***domains);
 int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len);
 
+void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp);
 int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease);
-
 int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len);
 
 #define dhcp_lease_unref_and_replace(a, b)                              \
index 3d3809c96b3ccbda09124382fcd431b78ec152ab..1f4ad095d5dedc147355daaf6a85747276ee4d27 100644 (file)
@@ -115,6 +115,10 @@ static int _bind_raw_socket(
         if (r < 0)
                 return -errno;
 
+        r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+        if (r < 0)
+                return r;
+
         if (so_priority_set) {
                 r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority);
                 if (r < 0)
@@ -206,6 +210,10 @@ int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int
         if (r < 0)
                 return r;
 
+        r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true);
+        if (r < 0)
+                return r;
+
         if (ifindex > 0) {
                 r = socket_bind_to_ifindex(s, ifindex);
                 if (r < 0)
index 8db517293f891722262fef228e4659916e744c12..da9e56b943f2d09c30faeec29284370c322de9f6 100644 (file)
@@ -110,12 +110,13 @@ typedef struct DHCPRequest {
         const uint8_t *parameter_request_list;
         size_t parameter_request_list_len;
         bool rapid_commit;
+        triple_timestamp timestamp;
 } DHCPRequest;
 
 extern const struct hash_ops dhcp_lease_hash_ops;
 
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
-                               size_t length);
+                               size_t length, const triple_timestamp *timestamp);
 int dhcp_server_send_packet(sd_dhcp_server *server,
                             DHCPRequest *req, DHCPPacket *packet,
                             int type, size_t optoffset);
index cbdeb42eff816b2a50e796c86111c4b16105cc27..384972f1a0adf4025611b828d78a31dfb99e28e1 100644 (file)
@@ -75,7 +75,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         client->xid = 2;
         client->state = DHCP_STATE_SELECTING;
 
-        (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size);
+        (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL);
 
         assert_se(sd_dhcp_client_stop(client) >= 0);
 
index 6be2f4b02ab58496c0102b4dd57a8c9c79da038b..fddb3a59ebef6137ac65828edaa2bbcdab02ed06 100644 (file)
@@ -96,7 +96,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         assert_se(add_static_lease(server, 3) >= 0);
         assert_se(add_static_lease(server, 4) >= 0);
 
-        (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size);
+        (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL);
 
         return 0;
 }
index 2a0d7bf853ba12a9eae0ef099ddd18086e92bdec..bf19226d4ff7f4859aa8975962d9108addb23673 100644 (file)
@@ -116,7 +116,6 @@ struct sd_dhcp_client {
         uint64_t max_attempts;
         OrderedHashmap *extra_options;
         OrderedHashmap *vendor_options;
-        usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
         sd_event_source *timeout_expire;
@@ -1366,16 +1365,12 @@ static int client_timeout_resend(
                         client->attempt = 0;
                 } else if (client->attempt >= client->max_attempts)
                         goto error;
-
-                client->request_sent = time_now;
                 break;
 
         case DHCP_STATE_SELECTING:
                 r = client_send_discover(client);
                 if (r < 0 && client->attempt >= client->max_attempts)
                         goto error;
-
-                client->request_sent = time_now;
                 break;
 
         case DHCP_STATE_INIT_REBOOT:
@@ -1388,8 +1383,6 @@ static int client_timeout_resend(
 
                 if (client->state == DHCP_STATE_INIT_REBOOT)
                         client_set_state(client, DHCP_STATE_REBOOTING);
-
-                client->request_sent = time_now;
                 break;
 
         case DHCP_STATE_REBOOTING:
@@ -1672,7 +1665,7 @@ static int client_parse_message(
         return 0;
 }
 
-static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
+static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
         _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
         int r;
 
@@ -1683,6 +1676,8 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage
         if (r < 0)
                 return r;
 
+        dhcp_lease_set_timestamp(lease, timestamp);
+
         dhcp_lease_unref_and_replace(client->lease, lease);
 
         if (client->lease->rapid_commit) {
@@ -1786,7 +1781,7 @@ static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
         return true;
 }
 
-static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
+static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
         _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
         int r;
 
@@ -1797,6 +1792,8 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *message, size_
         if (r < 0)
                 return r;
 
+        dhcp_lease_set_timestamp(lease, timestamp);
+
         if (!client->lease)
                 r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
         else if (lease_equal(client->lease, lease))
@@ -1818,8 +1815,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         assert(client->event);
         assert(client->lease);
         assert(client->lease->lifetime > 0);
-
-        triple_timestamp_from_boottime(&client->lease->timestamp, client->request_sent);
+        assert(triple_timestamp_is_set(&client->lease->timestamp));
 
         /* don't set timers for infinite leases */
         if (client->lease->lifetime == USEC_INFINITY) {
@@ -1833,7 +1829,6 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         r = sd_event_now(client->event, CLOCK_BOOTTIME, &time_now);
         if (r < 0)
                 return r;
-        assert(client->request_sent <= time_now);
 
         /* verify that 0 < t2 < lifetime */
         if (client->lease->t2 == 0 || client->lease->t2 >= client->lease->lifetime)
@@ -1851,9 +1846,15 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
         assert(client->lease->t1 < client->lease->t2);
         assert(client->lease->t2 < client->lease->lifetime);
 
-        client->expire_time = usec_add(client->request_sent, client->lease->lifetime);
-        client->t1_time = usec_add(client->request_sent, client->lease->t1);
-        client->t2_time = usec_add(client->request_sent, client->lease->t2);
+        r = sd_dhcp_lease_get_lifetime_timestamp(client->lease, CLOCK_BOOTTIME, &client->expire_time);
+        if (r < 0)
+                return r;
+        r = sd_dhcp_lease_get_t1_timestamp(client->lease, CLOCK_BOOTTIME, &client->t1_time);
+        if (r < 0)
+                return r;
+        r = sd_dhcp_lease_get_t2_timestamp(client->lease, CLOCK_BOOTTIME, &client->t2_time);
+        if (r < 0)
+                return r;
 
         /* RFC2131 section 4.4.5:
          * Times T1 and T2 SHOULD be chosen with some random "fuzz".
@@ -2060,7 +2061,7 @@ static int client_verify_message_header(sd_dhcp_client *client, DHCPMessage *mes
         return 0;
 }
 
-static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
+static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, size_t len, const triple_timestamp *timestamp) {
         DHCP_CLIENT_DONT_DESTROY(client);
         int r;
 
@@ -2073,7 +2074,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s
         switch (client->state) {
         case DHCP_STATE_SELECTING:
 
-                r = client_handle_offer_or_rapid_ack(client, message, len);
+                r = client_handle_offer_or_rapid_ack(client, message, len, timestamp);
                 if (ERRNO_IS_NEG_RESOURCE(r))
                         return r;
                 if (r == -EADDRNOTAVAIL)
@@ -2093,7 +2094,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s
         case DHCP_STATE_RENEWING:
         case DHCP_STATE_REBINDING:
 
-                r = client_handle_ack(client, message, len);
+                r = client_handle_ack(client, message, len, timestamp);
                 if (ERRNO_IS_NEG_RESOURCE(r))
                         return r;
                 if (r == -EADDRNOTAVAIL)
@@ -2134,6 +2135,16 @@ static int client_receive_message_udp(
         sd_dhcp_client *client = ASSERT_PTR(userdata);
         _cleanup_free_ DHCPMessage *message = NULL;
         ssize_t len, buflen;
+        /* This needs to be initialized with zero. See #20741. */
+        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL) control = {};
+        struct iovec iov;
+        struct msghdr msg = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = &control,
+                .msg_controllen = sizeof(control),
+        };
+        triple_timestamp t = {};
         int r;
 
         assert(s);
@@ -2150,17 +2161,22 @@ static int client_receive_message_udp(
         if (!message)
                 return -ENOMEM;
 
-        len = recv(fd, message, buflen, 0);
-        if (len < 0) {
-                if (ERRNO_IS_TRANSIENT(errno) || ERRNO_IS_DISCONNECT(errno))
-                        return 0;
+        iov = IOVEC_MAKE(message, buflen);
 
-                log_dhcp_client_errno(client, errno, "Could not receive message from UDP socket, ignoring: %m");
+        len = recvmsg_safe(fd, &msg, MSG_DONTWAIT);
+        if (ERRNO_IS_NEG_TRANSIENT(len) || ERRNO_IS_NEG_DISCONNECT(len))
+                return 0;
+        if (len < 0) {
+                log_dhcp_client_errno(client, len, "Could not receive message from UDP socket, ignoring: %m");
                 return 0;
         }
 
+        struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+        if (tv)
+                triple_timestamp_from_realtime(&t, timeval_load(tv));
+
         log_dhcp_client(client, "Received message from UDP socket, processing.");
-        r = client_handle_message(client, message, len);
+        r = client_handle_message(client, message, len, &t);
         if (r < 0)
                 client_stop(client, r);
 
@@ -2175,7 +2191,9 @@ static int client_receive_message_raw(
 
         sd_dhcp_client *client = ASSERT_PTR(userdata);
         _cleanup_free_ DHCPPacket *packet = NULL;
-        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct tpacket_auxdata))) control;
+        /* This needs to be initialized with zero. See #20741. */
+        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
+                         CMSG_SPACE(sizeof(struct tpacket_auxdata))) control = {};
         struct iovec iov = {};
         struct msghdr msg = {
                 .msg_iov = &iov,
@@ -2185,6 +2203,7 @@ static int client_receive_message_raw(
         };
         struct cmsghdr *cmsg;
         bool checksum = true;
+        triple_timestamp t = {};
         ssize_t buflen, len;
         int r;
 
@@ -2212,11 +2231,15 @@ static int client_receive_message_raw(
                 return 0;
         }
 
-        cmsg = cmsg_find(&msg, SOL_PACKET, PACKET_AUXDATA, CMSG_LEN(sizeof(struct tpacket_auxdata)));
-        if (cmsg) {
-                struct tpacket_auxdata *aux = CMSG_TYPED_DATA(cmsg, struct tpacket_auxdata);
-                checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
-        }
+        CMSG_FOREACH(cmsg, &msg)
+                if (cmsg->cmsg_level == SOL_PACKET && cmsg->cmsg_type == PACKET_AUXDATA) {
+                        struct tpacket_auxdata *aux = CMSG_TYPED_DATA(cmsg, struct tpacket_auxdata);
+                        checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY);
+
+                } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
+                        struct timeval *tv = CMSG_TYPED_DATA(cmsg, struct timeval);
+                        triple_timestamp_from_realtime(&t, timeval_load(tv));
+                }
 
         if (dhcp_packet_verify_headers(packet, len, checksum, client->port) < 0)
                 return 0;
@@ -2224,7 +2247,7 @@ static int client_receive_message_raw(
         len -= DHCP_IP_UDP_SIZE;
 
         log_dhcp_client(client, "Received message from RAW socket, processing.");
-        r = client_handle_message(client, &packet->dhcp, len);
+        r = client_handle_message(client, &packet->dhcp, len, &t);
         if (r < 0)
                 client_stop(client, r);
 
index a8e5c683b88c06a810381b0b8d44dbb52aa37809..a6e78dd0719740ed8c9414cff7a80ba282b8dc2e 100644 (file)
 #include "tmpfile-util.h"
 #include "unaligned.h"
 
+void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp) {
+        assert(lease);
+
+        if (timestamp && triple_timestamp_is_set(timestamp))
+                lease->timestamp = *timestamp;
+        else
+                triple_timestamp_get(&lease->timestamp);
+}
+
 int sd_dhcp_lease_get_timestamp(sd_dhcp_lease *lease, clockid_t clock, uint64_t *ret) {
         assert_return(lease, -EINVAL);
         assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
index 437028800d0eff7f121435a0f2feb25e555a2691..45c0f14d2cf9ca982eb4ad41b51ada15abefedca 100644 (file)
@@ -897,6 +897,31 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes
         return 0;
 }
 
+static void request_set_timestamp(DHCPRequest *req, const triple_timestamp *timestamp) {
+        assert(req);
+
+        if (timestamp && triple_timestamp_is_set(timestamp))
+                req->timestamp = *timestamp;
+        else
+                triple_timestamp_get(&req->timestamp);
+}
+
+static int request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_t *ret) {
+        assert(req);
+        assert(TRIPLE_TIMESTAMP_HAS_CLOCK(clock));
+        assert(clock_supported(clock));
+        assert(ret);
+
+        if (req->lifetime <= 0)
+                return -ENODATA;
+
+        if (!triple_timestamp_is_set(&req->timestamp))
+                return -ENODATA;
+
+        *ret = usec_add(triple_timestamp_by_clock(&req->timestamp, clock), req->lifetime);
+        return 0;
+}
+
 static bool address_is_in_pool(sd_dhcp_server *server, be32_t address) {
         assert(server);
 
@@ -1031,19 +1056,17 @@ static int prepare_new_lease(DHCPLease **ret_lease, be32_t address, DHCPRequest
 }
 
 static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLease *existing_lease, be32_t address) {
-        usec_t time_now, expiration;
+        usec_t expiration;
         int r;
 
         assert(server);
         assert(req);
         assert(address != 0);
 
-        r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now);
+        r = request_get_lifetime_timestamp(req, CLOCK_BOOTTIME, &expiration);
         if (r < 0)
                 return r;
 
-        expiration = usec_add(req->lifetime, time_now);
-
         if (existing_lease) {
                 assert(existing_lease->server);
                 assert(existing_lease->address == address);
@@ -1148,7 +1171,7 @@ static int server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *re
 
 #define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30)
 
-int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) {
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp) {
         _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL;
         _cleanup_free_ char *error_message = NULL;
         DHCPLease *existing_lease, *static_lease;
@@ -1172,6 +1195,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz
         if (r < 0)
                 return r;
 
+        request_set_timestamp(req, timestamp);
+
         r = dhcp_server_cleanup_expired_leases(server);
         if (r < 0)
                 return r;
@@ -1354,7 +1379,9 @@ static size_t relay_agent_information_length(const char* agent_circuit_id, const
 static int server_receive_message(sd_event_source *s, int fd,
                                   uint32_t revents, void *userdata) {
         _cleanup_free_ DHCPMessage *message = NULL;
-        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control;
+        /* This needs to be initialized with zero. See #20741. */
+        CMSG_BUFFER_TYPE(CMSG_SPACE_TIMEVAL +
+                         CMSG_SPACE(sizeof(struct in_pktinfo))) control = {};
         sd_dhcp_server *server = ASSERT_PTR(userdata);
         struct iovec iov = {};
         struct msghdr msg = {
@@ -1364,6 +1391,8 @@ static int server_receive_message(sd_event_source *s, int fd,
                 .msg_controllen = sizeof(control),
         };
         ssize_t datagram_size, len;
+        struct cmsghdr *cmsg;
+        triple_timestamp t = {};
         int r;
 
         datagram_size = next_datagram_size_fd(fd);
@@ -1397,16 +1426,22 @@ static int server_receive_message(sd_event_source *s, int fd,
                 return 0;
 
         /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
-        struct in_pktinfo *info = CMSG_FIND_DATA(&msg, IPPROTO_IP, IP_PKTINFO, struct in_pktinfo);
-        if (info && info->ipi_ifindex != server->ifindex)
-                return 0;
+        CMSG_FOREACH(cmsg, &msg)
+                if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
+                        struct in_pktinfo *info = CMSG_TYPED_DATA(cmsg, struct in_pktinfo);
+                        if (info->ipi_ifindex != server->ifindex)
+                                return 0;
+                } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMP) {
+                        struct timeval *tv = CMSG_TYPED_DATA(cmsg, struct timeval);
+                        triple_timestamp_from_realtime(&t, timeval_load(tv));
+                }
 
         if (sd_dhcp_server_is_in_relay_mode(server)) {
                 r = dhcp_server_relay_message(server, message, len - sizeof(DHCPMessage), buflen);
                 if (r < 0)
                         log_dhcp_server_errno(server, r, "Couldn't relay message, ignoring: %m");
         } else {
-                r = dhcp_server_handle_message(server, message, (size_t) len);
+                r = dhcp_server_handle_message(server, message, (size_t) len, &t);
                 if (r < 0)
                         log_dhcp_server_errno(server, r, "Couldn't process incoming message, ignoring: %m");
         }
index a2b46b996271ec60e86ebce8e0a29f570739c113..b2e6034b42010a99ac250ed91b1d76da840df4e4 100644 (file)
@@ -135,58 +135,58 @@ static void test_message_handler(void) {
         assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
         assert_se(sd_dhcp_server_start(server) >= 0);
 
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.end = 0;
         /* TODO, shouldn't this fail? */
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
         test.end = SD_DHCP_OPTION_END;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.option_type.code = 0;
         test.option_type.length = 0;
         test.option_type.type = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == -ENOMSG);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == -ENOMSG);
         test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE;
         test.option_type.length = 1;
         test.option_type.type = DHCP_DISCOVER;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.message.op = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
         test.message.op = BOOTREQUEST;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.message.htype = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
         test.message.htype = ARPHRD_ETHER;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.message.hlen = 0;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == -EBADMSG);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == -EBADMSG);
         test.message.hlen = ETHER_ADDR_LEN;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_OFFER);
 
         test.option_type.type = DHCP_REQUEST;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
         test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS;
         test.option_requested_ip.length = 4;
         test.option_requested_ip.address = htobe32(0x12345678);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_NAK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_NAK);
         test.option_server_id.code = SD_DHCP_OPTION_SERVER_IDENTIFIER;
         test.option_server_id.length = 4;
         test.option_server_id.address = htobe32(INADDR_LOOPBACK);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
 
         test.option_server_id.address = htobe32(0x12345678);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
         test.option_server_id.address = htobe32(INADDR_LOOPBACK);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
 
         test.option_client_id.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER;
         test.option_client_id.length = 7;
@@ -197,30 +197,30 @@ static void test_message_handler(void) {
         test.option_client_id.id[4] = 'D';
         test.option_client_id.id[5] = 'E';
         test.option_client_id.id[6] = 'F';
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
 
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
 
         /* request address reserved for static lease (unmatching client ID) */
         test.option_client_id.id[6] = 'H';
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
 
         /* request unmatching address */
         test.option_client_id.id[6] = 'G';
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 41);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0);
 
         /* request matching address */
         test.option_client_id.id[6] = 'G';
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
 
         /* try again */
         test.option_client_id.id[6] = 'G';
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 42);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK);
 }
 
 static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZE]) {