diff --git a/src/libsystemd-network/dhcp-server-request.c b/src/libsystemd-network/dhcp-server-request.c index 9aab83d0dd4..a553fc43b62 100644 --- a/src/libsystemd-network/dhcp-server-request.c +++ b/src/libsystemd-network/dhcp-server-request.c @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-event.h" #include "alloc-util.h" -#include "dhcp-protocol.h" +#include "dhcp-message.h" #include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-request.h" @@ -12,17 +14,18 @@ #include "fd-util.h" #include "hashmap.h" #include "iovec-util.h" -#include "memory-util.h" +#include "ip-util.h" +#include "set.h" #include "siphash24.h" #include "socket-util.h" #include "string-util.h" -#include "unaligned.h" static DHCPRequest* dhcp_request_free(DHCPRequest *req) { if (!req) return NULL; - free(req->hostname); + sd_dhcp_message_unref(req->message); + set_free(req->parameter_request_list); return mfree(req); } @@ -53,27 +56,22 @@ int dhcp_request_get_lifetime_timestamp(DHCPRequest *req, clockid_t clock, usec_ return 0; } -static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { +static int dhcp_request_set_client_id(DHCPRequest *req) { assert(req); - assert(message); + assert(req->message); - req->message = message; - - if (message->hlen > sizeof(message->chaddr)) - return -EBADMSG; - - req->hw_addr.length = req->message->hlen; - memcpy_safe(req->hw_addr.bytes, message->chaddr, message->hlen); + /* Genuine client ID from Client Identifier option. The option may not be set. */ + (void) dhcp_message_get_option_client_id(req->message, &req->client_id); /* Fake client ID generated from the DHCP header. * The client ID type 0 and 255 are special. So do not use if htype is 0 or 255. * Note, Some hardware type (e.g. Infiniband) may not set chaddr field. */ - if (!IN_SET(req->message->htype, 0, UINT8_MAX)) + if (!IN_SET(req->message->header.htype, 0, UINT8_MAX)) (void) sd_dhcp_client_id_set( &req->client_id_by_header, - req->message->htype, - req->message->chaddr, - req->message->hlen); + req->message->header.htype, + req->message->header.chaddr, + req->message->header.hlen); /* If Client Identifier option is unspecified, use the generated one. */ if (!sd_dhcp_client_id_is_set(&req->client_id)) @@ -83,100 +81,140 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes if (!sd_dhcp_client_id_is_set(&req->client_id)) return -EBADMSG; - if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) - req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + return 0; +} +static int dhcp_request_set_server_identifier(DHCPRequest *req) { + int r; + + assert(req); + assert(req->message); + + bool mandatory = IN_SET(req->type, DHCP_RELEASE, DHCP_DECLINE); + + be32_t a; + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_SERVER_IDENTIFIER, &a); + if (r < 0) + return mandatory ? r : 0; + + req->server_address = a; + return 0; +} + +static int dhcp_request_set_maximum_message_size(DHCPRequest *req) { + int r; + + assert(req); + assert(req->message); + + uint16_t sz; + r = dhcp_message_get_option_u16(req->message, SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, &sz); + if (r < 0) + return r; + + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + if (sz < IPV4_MIN_REASSEMBLY_SIZE) + return -EBADMSG; + + req->max_message_size = sz; + return 0; +} + +static int dhcp_request_set_lifetime(DHCPRequest *req, sd_dhcp_server *server) { + assert(req); + assert(req->message); + assert(server); + + (void) dhcp_message_get_option_sec( + req->message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + /* max_as_infinity= */ true, + &req->lifetime); + + /* If unset (or zero is specified...), use the default lease time. */ if (req->lifetime <= 0) - req->lifetime = MAX(USEC_PER_SEC, server->default_lease_time); + req->lifetime = MAX(30 * USEC_PER_SEC, server->default_lease_time); + /* If the requested lifetime is too long, then cap it with the maximum lease time. */ if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) req->lifetime = server->max_lease_time; return 0; } -static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { - DHCPRequest *req = ASSERT_PTR(userdata); - int r; - - switch (code) { - case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - if (len == 4) - req->lifetime = unaligned_be32_sec_to_usec(option, /* max_as_infinity= */ true); - - break; - case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: - if (len == 4) - memcpy(&req->requested_ip, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - if (len == 4) - memcpy(&req->server_address, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_CLIENT_IDENTIFIER: - if (client_id_size_is_valid(len)) - (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); - - break; - case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: - - if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) - req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); - - break; - case SD_DHCP_OPTION_RELAY_AGENT_INFORMATION: - req->agent_info_option = (uint8_t*)option - 2; - - break; - case SD_DHCP_OPTION_HOST_NAME: { - _cleanup_free_ char *p = NULL; - - r = dhcp_option_parse_hostname(option, len, &p); - if (r < 0) - log_debug_errno(r, "Failed to parse hostname, ignoring: %m"); - else - free_and_replace(req->hostname, p); - break; - } - case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: - req->parameter_request_list = option; - req->parameter_request_list_len = len; - break; - - case SD_DHCP_OPTION_RAPID_COMMIT: - req->rapid_commit = true; - break; - } - - return 0; -} - -static int dhcp_server_parse_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, DHCPRequest **ret, char **ret_error_message) { +static int dhcp_server_parse_message(sd_dhcp_server *server, const struct iovec *iov, DHCPRequest **ret) { int r; assert(server); - assert(message); + assert(iov); assert(ret); - assert(ret_error_message); - _cleanup_(dhcp_request_freep) DHCPRequest *req = new0(DHCPRequest, 1); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_parse( + iov, + BOOTREQUEST, + /* xid= */ NULL, + ARPHRD_NONE, + /* hw_addr= */ NULL, + &message); + if (r < 0) + return r; + + /* A DHCP relay agent is running on the interface with the same address?? + * Should be malicious message. */ + if (message->header.giaddr == server->address) + return -EBADMSG; + + _cleanup_(dhcp_request_freep) DHCPRequest *req = new(DHCPRequest, 1); if (!req) return -ENOMEM; - _cleanup_free_ char *error_message = NULL; - r = dhcp_option_parse(message, length, parse_request, req, &error_message); - if (r < 0) - return r; - req->type = r; + *req = (DHCPRequest) { + .message = sd_dhcp_message_ref(message), - r = ensure_sane_request(server, req, message); + /* RFC 2132 section 9.10: + * The minimum legal value is 576 octets. */ + .max_message_size = IPV4_MIN_REASSEMBLY_SIZE, + }; + + /* client hardware address + * Note, hlen and chaddr may not be set for non-ethernet interface. + * See RFC2131 section 4.1. */ + r = dhcp_message_get_hw_addr(message, &req->hw_addr); if (r < 0) return r; + /* Message Type: mandatory */ + r = dhcp_message_get_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, &req->type); + if (r < 0) + return r; + + /* Client Identifier: Mandatory. If not set, fall back to use chaddr. */ + r = dhcp_request_set_client_id(req); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_request_set_server_identifier(req); + if (r < 0) + return r; + + /* Maximum Message Size: optional */ + (void) dhcp_request_set_maximum_message_size(req); + + if (req->max_message_size >= sizeof(DHCPPacket)) + req->max_optlen = req->max_message_size - sizeof(DHCPPacket); + else + req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + + /* Lifetime: optional */ + (void) dhcp_request_set_lifetime(req, server); + + /* Parameter Request List: optional */ + (void) dhcp_message_get_option_parameter_request_list(message, &req->parameter_request_list); + *ret = TAKE_PTR(req); - *ret_error_message = TAKE_PTR(error_message); return 0; } @@ -189,22 +227,17 @@ static int dhcp_server_ack(sd_dhcp_server *server, DHCPRequest *req) { r = dhcp_server_set_lease(server, req); if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); + return r; - r = server_send_offer_or_ack(server, req, DHCP_ACK); + r = dhcp_server_send_reply(server, req, DHCP_ACK); if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send ACK: %m"); - - log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); + return r; dhcp_server_on_lease_change(server); - - return DHCP_ACK; + return r; } static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req) { - int r; - assert(server); assert(req); @@ -212,7 +245,7 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req *existing_lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id), *static_lease = dhcp_server_get_static_lease(server, req); - log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "DISCOVER (0x%x)", be32toh(req->message->header.xid)); /* for now pick a random free address from the pool */ if (static_lease) { @@ -226,7 +259,6 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req req->address = static_lease->address; } else if (existing_lease && dhcp_server_address_is_in_pool(server, existing_lease->address)) - /* If we previously assigned an address to the host, then reuse it. */ req->address = existing_lease->address; @@ -257,19 +289,16 @@ static int dhcp_server_process_discover(sd_dhcp_server *server, DHCPRequest *req /* no free addresses left */ return 0; - if (server->rapid_commit && req->rapid_commit) + if (server->rapid_commit && + dhcp_message_get_option_flag(req->message, SD_DHCP_OPTION_RAPID_COMMIT) >= 0) return dhcp_server_ack(server, req); - r = server_send_offer_or_ack(server, req, DHCP_OFFER); - if (r < 0) - /* this only fails on critical errors */ - return log_dhcp_server_errno(server, r, "Could not send offer: %m"); - - log_dhcp_server(server, "OFFER (0x%x)", be32toh(req->message->xid)); - return DHCP_OFFER; + return dhcp_server_send_reply(server, req, DHCP_OFFER); } static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) { + int r; + assert(server); assert(req); @@ -281,67 +310,69 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) bool init_reboot = false; /* see RFC 2131, section 4.3.2 */ - if (req->server_address != INADDR_ANY) { log_dhcp_server(server, "REQUEST (selecting) (0x%x)", - be32toh(req->message->xid)); + be32toh(req->message->header.xid)); /* SELECTING */ if (req->server_address != server->address) - /* client did not pick us */ + return 0; /* The message is not for us. Let's silently ignore the packet. */ + + if (req->message->header.ciaddr != INADDR_ANY) /* this MUST be zero */ return 0; - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; + /* this must be filled in with the yiaddr from the chosen OFFER */ + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; - if (req->requested_ip == 0) - /* this must be filled in with the yiaddr - from the chosen OFFER */ - return 0; + if (address == INADDR_ANY) + return -EBADMSG; - address = req->requested_ip; - } else if (req->requested_ip != 0) { - log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", - be32toh(req->message->xid)); - - /* INIT-REBOOT */ - if (req->message->ciaddr != 0) - /* this MUST be zero */ - return 0; - - /* TODO: check more carefully if IP is correct */ - address = req->requested_ip; - init_reboot = true; - } else { + } else if (req->message->header.ciaddr != INADDR_ANY) { log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", - be32toh(req->message->xid)); + be32toh(req->message->header.xid)); /* REBINDING / RENEWING */ - if (req->message->ciaddr == 0) - /* this MUST be filled in with clients IP address */ - return 0; - address = req->message->ciaddr; + /* this must NOT be filled */ + if (dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, /* ret= */ NULL) >= 0) + return -EBADMSG; + + address = req->message->header.ciaddr; + + } else { + log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", + be32toh(req->message->header.xid)); + + /* INIT-REBOOT */ + r = dhcp_message_get_option_be32(req->message, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, &address); + if (r < 0) + return r; + + if (address == INADDR_ANY) + return -EBADMSG; + + init_reboot = true; } - /* Silently ignore Rapid Commit option in REQUEST message. */ - req->rapid_commit = false; - + /* Check if the request is consistent with the static lease. */ if (static_lease) { + /* Found a static lease for the client ID. In this case, the server is explicitly configured + * to manage the host. Hence, send NAK when the request is invalid. */ + if (static_lease->address != address) /* The client requested an address which is different from the static lease. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); + return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; sd_dhcp_server_lease *l = hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address)); if (l && l != existing_lease) /* The requested address is already assigned to another host. Refusing. */ - return server_send_nak_or_ignore(server, init_reboot, req); + return init_reboot ? dhcp_server_send_reply(server, req, DHCP_NAK) : 0; req->static_lease = static_lease; req->address = address; - /* Found a static lease for the client ID. */ return dhcp_server_ack(server, req); } @@ -353,10 +384,13 @@ static int dhcp_server_process_request(sd_dhcp_server *server, DHCPRequest *req) } /* Refuse otherwise. */ - return server_send_nak_or_ignore(server, init_reboot, req); + if (init_reboot) + return dhcp_server_send_reply(server, req, DHCP_NAK); + + return 0; } -static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, const char *error_message) { +static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req) { assert(server); assert(req); @@ -365,7 +399,9 @@ static int dhcp_server_process_decline(sd_dhcp_server *server, DHCPRequest *req, /* TODO: make sure we don't offer this address again for a while. */ - log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); + _cleanup_free_ char *e = NULL; + (void) dhcp_message_get_option_string(req->message, SD_DHCP_OPTION_ERROR_MESSAGE, &e); + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->header.xid), strna(e)); return 0; } @@ -380,13 +416,13 @@ static int dhcp_server_process_release(sd_dhcp_server *server, DHCPRequest *req) if (!existing_lease) return 0; - if (existing_lease->address != req->message->ciaddr) + if (existing_lease->address != req->message->header.ciaddr) return -EBADMSG; sd_dhcp_server_lease_unref(existing_lease); dhcp_server_on_lease_change(server); - log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->xid)); + log_dhcp_server(server, "RELEASE (0x%x)", be32toh(req->message->header.xid)); return 0; } @@ -396,15 +432,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz assert(server); assert(message); - if (length < sizeof(DHCPMessage)) - return 0; - - if (message->op != BOOTREQUEST) - return 0; - _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; - _cleanup_free_ char *error_message = NULL; - r = dhcp_server_parse_message(server, message, length, &req, &error_message); + r = dhcp_server_parse_message(server, &IOVEC_MAKE(message, length), &req); if (r < 0) return r; @@ -420,7 +449,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz case DHCP_REQUEST: return dhcp_server_process_request(server, req); case DHCP_DECLINE: - return dhcp_server_process_decline(server, req, error_message); + return dhcp_server_process_decline(server, req); case DHCP_RELEASE: return dhcp_server_process_release(server, req); default: diff --git a/src/libsystemd-network/dhcp-server-request.h b/src/libsystemd-network/dhcp-server-request.h index e5738978a2d..f37d81bd145 100644 --- a/src/libsystemd-network/dhcp-server-request.h +++ b/src/libsystemd-network/dhcp-server-request.h @@ -4,6 +4,7 @@ #include "sd-dhcp-server-lease.h" #include "dhcp-client-id-internal.h" +#include "dhcp-message.h" #include "dhcp-protocol.h" #include "ether-addr-util.h" #include "sd-forward.h" @@ -12,7 +13,7 @@ typedef struct DHCPRequest { /* received message */ - DHCPMessage *message; + sd_dhcp_message *message; /* sender hardware address, may not be set for non-ethernet interface */ struct hw_addr_data hw_addr; triple_timestamp timestamp; @@ -22,14 +23,10 @@ typedef struct DHCPRequest { sd_dhcp_client_id client_id; sd_dhcp_client_id client_id_by_header; size_t max_optlen; + size_t max_message_size; /* maximum message size, including IP and UDP headers */ be32_t server_address; - be32_t requested_ip; usec_t lifetime; - const uint8_t *agent_info_option; - char *hostname; - const uint8_t *parameter_request_list; - size_t parameter_request_list_len; - bool rapid_commit; + Set *parameter_request_list; /* acquired data */ sd_dhcp_server_lease *static_lease; diff --git a/src/libsystemd-network/dhcp-server-send.c b/src/libsystemd-network/dhcp-server-send.c index 9f52ff4cc8d..cc0f9d667b0 100644 --- a/src/libsystemd-network/dhcp-server-send.c +++ b/src/libsystemd-network/dhcp-server-send.c @@ -2,18 +2,14 @@ #include "sd-event.h" -#include "alloc-util.h" -#include "dhcp-option.h" -#include "dhcp-packet.h" +#include "dhcp-server-internal.h" #include "dhcp-server-lease-internal.h" #include "dhcp-server-send.h" -#include "dns-domain.h" #include "errno-util.h" #include "fd-util.h" -#include "hashmap.h" #include "in-addr-util.h" -#include "iovec-util.h" -#include "iovec-wrapper.h" +#include "random-util.h" +#include "set.h" #include "socket-util.h" static int server_acquire_raw_socket(sd_dhcp_server *server) { @@ -45,124 +41,72 @@ static int server_acquire_raw_socket(sd_dhcp_server *server) { static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, const struct hw_addr_data *hw_addr, - DHCPPacket *packet, - size_t len) { - - int r; + sd_dhcp_message *message) { assert(server); assert(server->ifindex > 0); assert(server->address != 0); assert(hw_addr); - assert(packet); - assert(len > sizeof(DHCPPacket)); - - if (len > UINT16_MAX) - return -EOVERFLOW; + assert(message); int fd = server_acquire_raw_socket(server); if (fd < 0) return fd; - r = dhcp_packet_append_ip_headers( - packet, + return dhcp_message_send_raw( + message, + fd, + server->ifindex, server->address, DHCP_PORT_SERVER, - packet->dhcp.yiaddr, + hw_addr, + message->header.yiaddr, DHCP_PORT_CLIENT, - len, server->ip_service_type); - if (r < 0) - return r; - - union sockaddr_union sa = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_IP), - .ll.sll_ifindex = server->ifindex, - .ll.sll_halen = hw_addr->length, - }; - - memcpy_safe(sa.ll.sll_addr, hw_addr->bytes, hw_addr->length); - - struct msghdr mh = { - .msg_name = &sa.sa, - .msg_namelen = sockaddr_ll_len(&sa.ll), - .msg_iov = &IOVEC_MAKE(packet, len), - .msg_iovlen = 1, - }; - - if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; } -static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, - uint16_t destination_port, - DHCPMessage *message, size_t len) { +static int dhcp_server_send_udp( + sd_dhcp_server *server, + be32_t address, + uint16_t port, + sd_dhcp_message *message) { assert(server); assert(message); - assert(len >= sizeof(DHCPMessage)); int fd = sd_event_source_get_io_fd(server->io_event_source); if (fd < 0) return fd; - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(destination_port), - .in.sin_addr.s_addr = destination, - }; - CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct in_pktinfo))) control = {}; - struct msghdr msg = { - .msg_name = &sa, - .msg_namelen = sizeof(sa.in), - .msg_iov = &IOVEC_MAKE(message, len), - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - - struct cmsghdr *cmsg = ASSERT_PTR(CMSG_FIRSTHDR(&msg)); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - - struct in_pktinfo *pktinfo = ASSERT_PTR(CMSG_TYPED_DATA(cmsg, struct in_pktinfo)); - pktinfo->ipi_ifindex = server->ifindex; - pktinfo->ipi_spec_dst.s_addr = server->address; - - if (sendmsg(fd, &msg, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; + return dhcp_message_send_udp( + message, + fd, + server->address, + address, + port); } static int dhcp_server_send_message( sd_dhcp_server *server, - DHCPRequest *req, uint8_t type, - DHCPPacket *packet, - size_t optoffset) { + sd_dhcp_message *message) { + + int r; assert(server); - assert(req); - assert(req->message); - assert(packet); + assert(message); /* RFC 2131 Section 4.1 */ /* 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 (req->message->giaddr != INADDR_ANY) + if (message->header.giaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->giaddr, + message->header.giaddr, DHCP_PORT_SERVER, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* when ’giaddr’ is zero, the server broadcasts any DHCPNAK messages to 0xffffffff. */ if (type == DHCP_NAK) @@ -170,18 +114,16 @@ static int dhcp_server_send_message( server, INADDR_BROADCAST, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* 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 (req->message->ciaddr != INADDR_ANY) + if (message->header.ciaddr != INADDR_ANY) return dhcp_server_send_udp( server, - req->message->ciaddr, + message->header.ciaddr, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is set, then the server * broadcasts DHCPOFFER and DHCPACK messages to 0xffffffff. @@ -189,166 +131,137 @@ static int dhcp_server_send_message( * (Note, even the broadcast flag is unset, we may not know the client hardware address, e.g. * InfiniBand. In that case, we cannot unicast in the below, so need to broadcast. Also, broadcast * the message if 'yiaddr' is zero.) */ - if (FLAGS_SET(be16toh(req->message->flags), 0x8000) || - hw_addr_is_null(&req->hw_addr) || - packet->dhcp.yiaddr == INADDR_ANY) + struct hw_addr_data hw_addr = {}; + if (!dhcp_message_has_broadcast_flag(message) && + message->header.yiaddr != INADDR_ANY) { + r = dhcp_message_get_hw_addr(message, &hw_addr); + if (r < 0) + return r; + } + + if (hw_addr_is_null(&hw_addr)) return dhcp_server_send_udp( server, INADDR_BROADCAST, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + message); /* 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. */ return dhcp_server_send_unicast_raw( server, - &req->hw_addr, - packet, - sizeof(DHCPPacket) + optoffset); + &hw_addr, + message); } -static int dhcp_server_send_packet(sd_dhcp_server *server, - DHCPRequest *req, DHCPPacket *packet, - int type, size_t optoffset) { - int r; - - assert(server); - assert(req); - assert(req->max_optlen > 0); - assert(req->message); - assert(optoffset <= req->max_optlen); - assert(packet); - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_SERVER_IDENTIFIER, - 4, &server->address); - if (r < 0) - return r; - - if (req->agent_info_option) { - size_t opt_full_length = *(req->agent_info_option + 1) + 2; - /* there must be space left for SD_DHCP_OPTION_END */ - if (optoffset + opt_full_length < req->max_optlen) { - memcpy(packet->dhcp.options + optoffset, req->agent_info_option, opt_full_length); - optoffset += opt_full_length; - } - } - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - SD_DHCP_OPTION_END, 0, NULL); - if (r < 0) - return r; - - return dhcp_server_send_message(server, req, type, packet, optoffset); -} - -static int server_message_init( - sd_dhcp_server *server, - DHCPPacket **ret, - uint8_t type, - size_t *ret_optoffset, - DHCPRequest *req) { - - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; - int r; - - assert(server); - assert(ret); - assert(ret_optoffset); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); - assert(req); - - packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, - be32toh(req->message->xid), - req->message->htype, req->hw_addr.length, req->hw_addr.bytes, - type, req->max_optlen, &optoffset); - if (r < 0) - return r; - - packet->dhcp.flags = req->message->flags; - packet->dhcp.giaddr = req->message->giaddr; - - *ret_optoffset = optoffset; - *ret = TAKE_PTR(packet); - - return 0; -} - -static int dhcp_server_append_static_hostname( - sd_dhcp_server *server, - DHCPPacket *packet, - size_t *offset, - DHCPRequest *req) { - - int r; - - assert(server); - assert(packet); - assert(offset); - assert(req); - - if (!req->static_lease || !req->static_lease->hostname) - return 0; - - if (dns_name_is_single_label(req->static_lease->hostname)) - /* Option 12 */ - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_HOST_NAME, - strlen(req->static_lease->hostname), - req->static_lease->hostname); - - - /* Option 81 */ - uint8_t buffer[DHCP_MAX_FQDN_LENGTH + 3]; - - /* Flags: S=0 (will not update RR), O=1 (are overriding client), - * E=1 (using DNS wire format), N=1 (will not update DNS) */ - buffer[0] = DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_E | DHCP_FQDN_FLAG_N; - - /* RFC 4702: A server SHOULD set these to 255 when sending the option and MUST ignore them on - * receipt. */ - buffer[1] = 255; - buffer[2] = 255; - - r = dns_name_to_wire_format(req->static_lease->hostname, buffer + 3, sizeof(buffer) - 3, false); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to encode FQDN for static lease: %m"); - if (r > DHCP_MAX_FQDN_LENGTH) - return log_dhcp_server_errno(server, SYNTHETIC_ERRNO(EINVAL), "FQDN for static lease too long"); - - return dhcp_option_append( - &packet->dhcp, - req->max_optlen, - offset, - /* overload= */ 0, - SD_DHCP_OPTION_FQDN, - 3 + r, - buffer); -} - -static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { - assert(req); - - if (!req->parameter_request_list) - return false; - - return memchr(req->parameter_request_list, option, req->parameter_request_list_len); -} - -int server_send_offer_or_ack( +static int dhcp_server_new_reply( sd_dhcp_server *server, DHCPRequest *req, - uint8_t type) { + uint8_t type, + sd_dhcp_message **ret) { + + int r; + + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + assert(ret); + + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); + if (r < 0) + return r; + + r = dhcp_message_init_header( + message, + BOOTREPLY, + be32toh(req->message->header.xid), + req->message->header.htype, + &req->hw_addr); + if (r < 0) + return r; + + message->header.giaddr = req->message->header.giaddr; + + /* RFC 2131 Section 4.3.2 + * + * If ’giaddr’ is set in the DHCPREQUEST message, the client is on a different subnet. The server + * MUST set the broadcast bit in the DHCPNAK, so that the relay agent will broadcast the DHCPNAK to + * the client, because the client may not have a correct network address or subnet mask, and the + * client may not be answering ARP requests. */ + dhcp_message_set_broadcast_flag( + message, + dhcp_message_has_broadcast_flag(req->message) || + req->message->header.giaddr != INADDR_ANY || + type == DHCP_NAK); + + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, type); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); + if (r < 0) + return r; + + if (type == DHCP_NAK) { + *ret = TAKE_PTR(message); + return 0; + } + + assert(req->address != INADDR_ANY); + message->header.yiaddr = req->address; + message->header.siaddr = server->boot_server_address.s_addr; + + if (type == DHCP_ACK) + message->header.ciaddr = req->message->header.ciaddr; + + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + usec_to_be32_sec(req->lifetime)); + if (r < 0) + return r; + + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SUBNET_MASK, + server->netmask); + if (r < 0) + return r; + + if (server->emit_router) { + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_ROUTER, + in4_addr_is_set(&server->router_address) ? + server->router_address.s_addr : + server->address); + if (r < 0) + return r; + } + + if (server->boot_server_name) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_SERVER_NAME, + server->boot_server_name); + if (r < 0) + return r; + } + + if (server->boot_filename) { + r = dhcp_message_append_option_string( + message, + SD_DHCP_OPTION_BOOT_FILENAME, + server->boot_filename); + if (r < 0) + return r; + } static const uint8_t option_map[_SD_DHCP_LEASE_SERVER_TYPE_MAX] = { [SD_DHCP_LEASE_DNS] = SD_DHCP_OPTION_DOMAIN_NAME_SERVER, @@ -359,87 +272,33 @@ int server_send_offer_or_ack( [SD_DHCP_LEASE_LPR] = SD_DHCP_OPTION_LPR_SERVER, }; - _cleanup_free_ DHCPPacket *packet = NULL; - be32_t lease_time; - size_t offset; - int r; - - assert(server); - assert(req); - assert(IN_SET(type, DHCP_OFFER, DHCP_ACK)); - - r = server_message_init(server, &packet, type, &offset, req); - if (r < 0) - return r; - - packet->dhcp.yiaddr = req->address; - packet->dhcp.siaddr = server->boot_server_address.s_addr; - - lease_time = usec_to_be32_sec(req->lifetime); - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, - &lease_time); - if (r < 0) - return r; - - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); - if (r < 0) - return r; - - if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, - in4_addr_is_set(&server->router_address) ? - &server->router_address.s_addr : - &server->address); - if (r < 0) - return r; - } - - if (server->boot_server_name) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_SERVER_NAME, - strlen(server->boot_server_name), server->boot_server_name); - if (r < 0) - return r; - } - - if (server->boot_filename) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_BOOT_FILENAME, - strlen(server->boot_filename), server->boot_filename); - if (r < 0) - return r; - } - for (sd_dhcp_lease_server_type_t k = 0; k < _SD_DHCP_LEASE_SERVER_TYPE_MAX; k++) { if (server->servers[k].size <= 0) continue; - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_addresses( + message, option_map[k], - sizeof(struct in_addr) * server->servers[k].size, + server->servers[k].size, server->servers[k].addr); if (r < 0) return r; } if (server->timezone) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_string( + message, SD_DHCP_OPTION_TZDB_TIMEZONE, - strlen(server->timezone), server->timezone); + server->timezone); if (r < 0) return r; } if (server->domain_name) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_string( + message, SD_DHCP_OPTION_DOMAIN_NAME, - strlen(server->domain_name), server->domain_name); + server->domain_name); if (r < 0) return r; } @@ -447,116 +306,138 @@ int server_send_offer_or_ack( /* RFC 8925 section 3.3. DHCPv4 Server Behavior * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if * the option was not present in the Parameter Request List sent by the client. */ - if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && + if (set_contains(req->parameter_request_list, UINT_TO_PTR(SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) && server->ipv6_only_preferred_usec > 0) { - be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, + r = dhcp_message_append_option_be32( + message, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, - sizeof(sec), &sec); + usec_to_be32_sec(server->ipv6_only_preferred_usec)); if (r < 0) return r; } - if (server->extra_options) { - void *key; - struct iovec_wrapper *iovw; - HASHMAP_FOREACH_KEY(iovw, key, server->extra_options->entries) { - uint32_t tag = PTR_TO_UINT32(key); - - FOREACH_ARRAY(iov, iovw->iovec, iovw->count) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - tag, iov->iov_len, iov->iov_base); - if (r < 0) - return r; - } - } - } - - if (!tlv_isempty(server->vendor_options)) { - _cleanup_(iovec_done) struct iovec iov = {}; - r = tlv_build(server->vendor_options, &iov); - if (r < 0) - return r; - - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, - iov.iov_len, iov.iov_base); - if (r < 0) - return r; - } - - if (server->rapid_commit && req->rapid_commit && type == DHCP_ACK) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_RAPID_COMMIT, - 0, NULL); - if (r < 0) - return r; - } - - r = dhcp_server_append_static_hostname(server, packet, &offset, req); + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_VENDOR_SPECIFIC_INFORMATION, + server->vendor_options); if (r < 0) return r; - return dhcp_server_send_packet(server, req, packet, type, offset); + if (req->static_lease) { + /* Hostname (12) or FQDN (81) + * Flags: S=0 (will not update RR), O=1 (are overriding client), N=1 (will not update DNS) */ + r = dhcp_message_append_option_hostname( + message, + DHCP_FQDN_FLAG_O | DHCP_FQDN_FLAG_N, + /* is_client= */ false, + req->static_lease->hostname); + if (r < 0) + return r; + } + + _cleanup_(tlv_unrefp) TLV *agent_info = NULL; + r = dhcp_message_get_option_sub_tlv( + req->message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + TLV_DHCP4_SUBOPTION, + &agent_info); + if (r < 0 && r != -ENODATA) + log_dhcp_server_errno(server, r, "Failed to parse %s option, ignoring: %m", + dhcp_option_code_to_string(SD_DHCP_OPTION_RELAY_AGENT_INFORMATION)); + + if (agent_info) { + r = dhcp_message_append_option_sub_tlv( + message, + SD_DHCP_OPTION_RELAY_AGENT_INFORMATION, + agent_info); + if (r < 0) + return r; + } + + if (type == DHCP_ACK && req->type == DHCP_DISCOVER) { + assert(server->rapid_commit); + r = dhcp_message_append_option_flag(message, SD_DHCP_OPTION_RAPID_COMMIT); + if (r < 0) + return r; + } + + r = dhcp_message_append_option_tlv(message, server->extra_options); + if (r < 0) + return r; + + *ret = TAKE_PTR(message); + return 0; } -int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t offset; +int dhcp_server_send_reply( + sd_dhcp_server *server, + DHCPRequest *req, + uint8_t type) { + int r; - /* When a request is refused, RFC 2131, section 4.3.2 mentioned we should send NAK when the - * client is in INITREBOOT. If the client is in other state, there is nothing mentioned in the - * RFC whether we should send NAK or not. Hence, let's silently ignore the request. */ + assert(server); + assert(req); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); - if (!init_reboot) - return 0; - - r = server_message_init(server, &packet, DHCP_NAK, &offset, req); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_server_new_reply(server, req, type, &message); if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create NAK message: %m"); + return r; - r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); + if (dhcp_message_packet_size(message) > req->max_message_size) + return -E2BIG; + + r = dhcp_server_send_message(server, type, message); if (r < 0) - return log_dhcp_server_errno(server, r, "Could not send NAK message: %m"); + return r; - log_dhcp_server(server, "NAK (0x%x)", be32toh(req->message->xid)); - return DHCP_NAK; + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(type), be32toh(message->header.xid)); + return type; /* Return the sent message type. To make the test easier. */ } static int dhcp_server_send_forcerenew( sd_dhcp_server *server, sd_dhcp_server_lease *lease) { - _cleanup_free_ DHCPPacket *packet = NULL; - size_t optoffset = 0; int r; assert(server); assert(lease); - packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); - if (!packet) - return -ENOMEM; - - r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, - lease->htype, lease->hw_addr.length, lease->hw_addr.bytes, DHCP_FORCERENEW, - DHCP_MIN_OPTIONS_SIZE, &optoffset); + _cleanup_(sd_dhcp_message_unrefp) sd_dhcp_message *message = NULL; + r = dhcp_message_new(&message); if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); + r = dhcp_message_init_header( + message, + BOOTREPLY, + random_u32(), + lease->htype, + &lease->hw_addr); if (r < 0) return r; - return dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, - &packet->dhcp, - sizeof(DHCPMessage) + optoffset); + /* DHCP Message Type (53): Mandatory. */ + r = dhcp_message_append_option_u8(message, SD_DHCP_OPTION_MESSAGE_TYPE, DHCP_FORCERENEW); + if (r < 0) + return r; + + /* Server Identifier */ + r = dhcp_message_append_option_be32( + message, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + server->address); + if (r < 0) + return r; + + r = dhcp_server_send_udp(server, lease->address, DHCP_PORT_CLIENT, message); + if (r < 0) + return r; + + log_dhcp_server(server, "%s (0x%x)", dhcp_message_type_to_string(DHCP_FORCERENEW), be32toh(message->header.xid)); + return 0; } int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { @@ -565,8 +446,6 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { assert_return(server, -EINVAL); - log_dhcp_server(server, "FORCERENEW"); - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) RET_GATHER(r, dhcp_server_send_forcerenew(server, lease)); return r; diff --git a/src/libsystemd-network/dhcp-server-send.h b/src/libsystemd-network/dhcp-server-send.h index 27801b64ec2..2dde23b2c2c 100644 --- a/src/libsystemd-network/dhcp-server-send.h +++ b/src/libsystemd-network/dhcp-server-send.h @@ -5,9 +5,7 @@ #include "dhcp-server-request.h" -int server_send_offer_or_ack( +int dhcp_server_send_reply( sd_dhcp_server *server, DHCPRequest *req, uint8_t type); - -int server_send_nak_or_ignore(sd_dhcp_server *server, bool init_reboot, DHCPRequest *req); diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c index 4ab78972640..3fe40537585 100644 --- a/src/libsystemd-network/sd-dhcp-server-lease.c +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -70,6 +70,7 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { assert(server); assert(req); + assert(req->message); assert(req->address != INADDR_ANY); usec_t expiration; @@ -105,18 +106,16 @@ int dhcp_server_set_lease(sd_dhcp_server *server, DHCPRequest *req) { .n_ref = 1, .address = req->address, .client_id = req->client_id, - .htype = req->message->htype, - .gateway = req->message->giaddr, + .htype = req->message->header.htype, + .gateway = req->message->header.giaddr, .expiration = expiration, }; lease->hw_addr = req->hw_addr; - if (req->hostname) { - lease->hostname = strdup(req->hostname); - if (!lease->hostname) - return -ENOMEM; - } + char *hostname; + if (dhcp_message_get_option_hostname(req->message, &hostname) >= 0) + free_and_replace(lease->hostname, hostname); r = dhcp_server_put_lease(server, lease, /* is_static= */ false); if (r < 0) diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index a4f025bd412..a112c0632ad 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -95,6 +95,7 @@ TEST(dhcp_server_handle_message) { .header.hlen = ETHER_ADDR_LEN, .header.xid = htobe32(0x12345678), .header.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + .header.magic = htobe32(DHCP_MAGIC_COOKIE), .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, @@ -141,14 +142,14 @@ TEST(dhcp_server_handle_message) { test.option_type.code = 0; test.option_type.length = 0; test.option_type.type = 0; - ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENOMSG); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA); test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE; test.option_type.length = 1; test.option_type.type = DHCP_DISCOVER; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); test.header.op = 0; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), EBADMSG); test.header.op = BOOTREQUEST; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); @@ -163,8 +164,10 @@ TEST(dhcp_server_handle_message) { test.header.hlen = ETHER_ADDR_LEN; ASSERT_OK_EQ(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), DHCP_OFFER); + /* DHCPREQUEST (init-boot) without requested IP */ test.option_type.type = DHCP_REQUEST; - ASSERT_OK_ZERO(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL)); + ASSERT_ERROR(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL), ENODATA); + test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS; test.option_requested_ip.length = 4; test.option_requested_ip.address = htobe32(0x12345678);