/* * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include "dhcpv4/dhcpv4_internal.h" #include "icmpv4.h" #include "ipv4.h" #include "udp_internal.h" /* 00-00-5E-00-53-xx Documentation RFC 7042 */ static uint8_t server_mac_addr[] = { 0x00, 0x00, 0x5E, 0x00, 0x53, 0x01 }; static uint8_t client_mac_addr[] = { 0x00, 0x00, 0x5E, 0x00, 0x53, 0x02 }; static struct in_addr server_addr = { { { 192, 0, 2, 1 } } }; static struct in_addr netmask = { { { 255, 255, 255, 0 } } }; static struct in_addr test_base_addr = { { { 192, 0, 2, 10 } } }; /* Only to test Inform. */ static struct in_addr client_addr_static = { { { 192, 0, 2, 2 } } }; typedef void (*test_dhcpv4_server_fn_t)(struct net_if *iface, struct net_pkt *pkt); static struct test_dhcpv4_server_ctx { struct net_if *iface; struct k_sem test_proceed; struct net_pkt *pkt; struct in_addr assigned_ip; struct in_addr declined_ip; /* Request params */ const char *client_id; int lease_time; bool broadcast; bool send_echo_reply; } test_ctx; struct test_lease_count { int reserved; int allocated; int declined; }; #define CLIENT_ID_1 "client1" #define CLIENT_ID_2 "client2" #define NO_LEASE_TIME -1 #define TEST_XID 0x12345678 #define TEST_TIMEOUT K_MSEC(100) static void server_iface_init(struct net_if *iface) { net_if_set_link_addr(iface, server_mac_addr, sizeof(server_mac_addr), NET_LINK_ETHERNET); test_ctx.iface = iface; (void)net_if_ipv4_addr_add(iface, &server_addr, NET_ADDR_MANUAL, 0); (void)net_if_ipv4_set_netmask_by_addr(iface, &server_addr, &netmask); } static void send_icmp_echo_reply(struct net_pkt *pkt, struct net_ipv4_hdr *ipv4_hdr) { struct net_pkt *reply; size_t payload_len = net_pkt_get_len(pkt) - net_pkt_ip_hdr_len(pkt) - NET_ICMPH_LEN; reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), payload_len, AF_INET, IPPROTO_ICMP, K_FOREVER); zassert_not_null(reply, "Failed to allocate echo reply"); zassert_ok(net_ipv4_create(reply, (struct in_addr *)ipv4_hdr->dst, (struct in_addr *)ipv4_hdr->src), "Failed to create IPv4 header"); zassert_ok(net_icmpv4_create(reply, NET_ICMPV4_ECHO_REPLY, 0), "Failed to create ICMP header"); zassert_ok(net_pkt_copy(reply, pkt, payload_len), "Failed to copy payload"); net_pkt_cursor_init(reply); net_ipv4_finalize(reply, IPPROTO_ICMP); zassert_ok(net_recv_data(test_ctx.iface, reply), "Failed to receive data"); } static int server_send(const struct device *dev, struct net_pkt *pkt) { NET_PKT_DATA_ACCESS_DEFINE(ipv4_access, struct net_ipv4_hdr); struct net_ipv4_hdr *ipv4_hdr; ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access); zassert_not_null(ipv4_hdr, "Failed to access IPv4 header."); if (ipv4_hdr->proto == IPPROTO_ICMP) { if (test_ctx.send_echo_reply) { test_ctx.send_echo_reply = false; memcpy(&test_ctx.declined_ip, ipv4_hdr->dst, sizeof(struct in_addr)); send_icmp_echo_reply(pkt, ipv4_hdr); } return 0; } test_ctx.pkt = pkt; net_pkt_ref(pkt); k_sem_give(&test_ctx.test_proceed); return 0; } static struct dummy_api server_if_api = { .iface_api.init = server_iface_init, .send = server_send, }; NET_DEVICE_INIT(server_iface, "server_iface", NULL, NULL, NULL, NULL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &server_if_api, DUMMY_L2, NET_L2_GET_CTX_TYPE(DUMMY_L2), NET_IPV4_MTU); static const uint8_t cookie[4] = { 0x63, 0x82, 0x53, 0x63 }; static void test_pkt_free(void) { if (test_ctx.pkt != NULL) { net_pkt_unref(test_ctx.pkt); test_ctx.pkt = NULL; } } static void client_prepare_test_msg( const struct in_addr *src_addr, const struct in_addr *dst_addr, enum net_dhcpv4_msg_type type, const struct in_addr *server_id, const struct in_addr *requested_ip, const struct in_addr *ciaddr) { struct dhcp_msg msg = { 0 }; uint8_t empty_buf[SIZE_OF_FILE] = { 0 }; struct net_pkt *pkt; pkt = net_pkt_alloc_with_buffer(test_ctx.iface, NET_IPV4_MTU, AF_INET, IPPROTO_UDP, K_FOREVER); zassert_not_null(pkt, "Failed to allocate packet"); net_pkt_set_ipv4_ttl(pkt, 1); zassert_ok(net_ipv4_create(pkt, src_addr, dst_addr), "Failed to create IPv4 header"); zassert_ok(net_udp_create(pkt, htons(DHCPV4_CLIENT_PORT), htons(DHCPV4_SERVER_PORT)), "Failed to create UDP header"); msg.op = DHCPV4_MSG_BOOT_REQUEST; msg.htype = HARDWARE_ETHERNET_TYPE; msg.hlen = sizeof(client_mac_addr); msg.xid = htonl(TEST_XID); if (test_ctx.broadcast) { msg.flags = htons(DHCPV4_MSG_BROADCAST); } if (ciaddr) { memcpy(msg.ciaddr, ciaddr, sizeof(*ciaddr)); } else { memset(msg.ciaddr, 0, sizeof(msg.ciaddr)); } memset(msg.yiaddr, 0, sizeof(msg.ciaddr)); memset(msg.siaddr, 0, sizeof(msg.siaddr)); memset(msg.giaddr, 0, sizeof(msg.giaddr)); memcpy(msg.chaddr, client_mac_addr, sizeof(client_mac_addr)); net_pkt_write(pkt, &msg, sizeof(msg)); net_pkt_write(pkt, empty_buf, SIZE_OF_SNAME); net_pkt_write(pkt, empty_buf, SIZE_OF_FILE); net_pkt_write(pkt, cookie, SIZE_OF_MAGIC_COOKIE); /* Options */ net_pkt_write_u8(pkt, DHCPV4_OPTIONS_MSG_TYPE); net_pkt_write_u8(pkt, 1); net_pkt_write_u8(pkt, type); if (requested_ip) { net_pkt_write_u8(pkt, DHCPV4_OPTIONS_REQ_IPADDR); net_pkt_write_u8(pkt, sizeof(*requested_ip)); net_pkt_write(pkt, requested_ip, sizeof(*requested_ip)); } if (server_id) { net_pkt_write_u8(pkt, DHCPV4_OPTIONS_SERVER_ID); net_pkt_write_u8(pkt, sizeof(*server_id)); net_pkt_write(pkt, server_id, sizeof(*server_id)); } net_pkt_write_u8(pkt, DHCPV4_OPTIONS_REQ_LIST); net_pkt_write_u8(pkt, 1); net_pkt_write_u8(pkt, DHCPV4_OPTIONS_SUBNET_MASK); if (test_ctx.client_id) { net_pkt_write_u8(pkt, DHCPV4_OPTIONS_CLIENT_ID); net_pkt_write_u8(pkt, strlen(test_ctx.client_id)); net_pkt_write(pkt, test_ctx.client_id, strlen(test_ctx.client_id)); } if (test_ctx.lease_time != NO_LEASE_TIME) { net_pkt_write_u8(pkt, DHCPV4_OPTIONS_LEASE_TIME); net_pkt_write_u8(pkt, 4); net_pkt_write_be32(pkt, test_ctx.lease_time); } net_pkt_write_u8(pkt, DHCPV4_OPTIONS_END); net_pkt_cursor_init(pkt); net_ipv4_finalize(pkt, IPPROTO_UDP); zassert_ok(net_recv_data(test_ctx.iface, pkt), "Failed to receive data"); } static void client_send_discover(void) { int ret; client_prepare_test_msg( net_ipv4_unspecified_address(), net_ipv4_broadcast_address(), NET_DHCPV4_MSG_TYPE_DISCOVER, NULL, NULL, NULL); /* Wait for reply */ ret = k_sem_take(&test_ctx.test_proceed, TEST_TIMEOUT); zassert_ok(ret, "Exchange not completed in required time"); } static void client_send_request_solicit(void) { int ret; client_prepare_test_msg( net_ipv4_unspecified_address(), net_ipv4_broadcast_address(), NET_DHCPV4_MSG_TYPE_REQUEST, &server_addr, &test_ctx.assigned_ip, NULL); /* Wait for reply */ ret = k_sem_take(&test_ctx.test_proceed, TEST_TIMEOUT); zassert_ok(ret, "Exchange not completed in required time"); } static void client_send_request_renew(void) { int ret; client_prepare_test_msg( &test_ctx.assigned_ip, &server_addr, NET_DHCPV4_MSG_TYPE_REQUEST, NULL, NULL, &test_ctx.assigned_ip); /* Wait for reply */ ret = k_sem_take(&test_ctx.test_proceed, TEST_TIMEOUT); zassert_ok(ret, "Exchange not completed in required time"); } static void client_send_request_rebind(void) { int ret; client_prepare_test_msg( &test_ctx.assigned_ip, net_ipv4_broadcast_address(), NET_DHCPV4_MSG_TYPE_REQUEST, NULL, NULL, &test_ctx.assigned_ip); /* Wait for reply */ ret = k_sem_take(&test_ctx.test_proceed, TEST_TIMEOUT); zassert_ok(ret, "Exchange not completed in required time"); } static void client_send_release(void) { client_prepare_test_msg( &test_ctx.assigned_ip, &server_addr, NET_DHCPV4_MSG_TYPE_RELEASE, &server_addr, NULL, &test_ctx.assigned_ip); /* Small delay to let the DHCP server process the packet */ k_msleep(10); } static void client_send_decline(void) { client_prepare_test_msg( net_ipv4_unspecified_address(), net_ipv4_broadcast_address(), NET_DHCPV4_MSG_TYPE_DECLINE, &server_addr, &test_ctx.assigned_ip, NULL); /* Small delay to let the DHCP server process the packet */ k_msleep(10); } static void client_send_inform(void) { int ret; client_prepare_test_msg( &client_addr_static, net_ipv4_broadcast_address(), NET_DHCPV4_MSG_TYPE_INFORM, NULL, NULL, &client_addr_static); /* Wait for reply */ ret = k_sem_take(&test_ctx.test_proceed, TEST_TIMEOUT); zassert_ok(ret, "Exchange not completed in required time"); } static void lease_count_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { struct test_lease_count *count = user_data; switch (lease->state) { case DHCPV4_SERVER_ADDR_RESERVED: count->reserved++; break; case DHCPV4_SERVER_ADDR_ALLOCATED: count->allocated++; break; case DHCPV4_SERVER_ADDR_DECLINED: count->declined++; break; default: break; } } static void test_get_lease_count(struct test_lease_count *count) { int ret; memset(count, 0, sizeof(*count)); ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, lease_count_cb, count); zassert_ok(ret, "Failed to obtain lease count"); } static void verify_lease_count(int reserved, int allocated, int declined) { struct test_lease_count count; test_get_lease_count(&count); zassert_equal(count.reserved, reserved, "Incorrect %s count, expected %d got %d", "reserved", reserved, count.reserved); zassert_equal(count.allocated, allocated, "Incorrect %s count, expected %d got %d", "allocated", allocated, count.allocated); zassert_equal(count.declined, declined, "Incorrect %s count, expected %d got %d", "declined", declined, count.declined); } static void get_reserved_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { struct in_addr *reserved = user_data; if (lease->state == DHCPV4_SERVER_ADDR_RESERVED) { reserved->s_addr = lease->addr.s_addr; } } static void get_reserved_address(struct in_addr *reserved) { int ret; ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, get_reserved_cb, reserved); zassert_ok(ret, "Failed to obtain reserved address"); } static void client_get_lease(bool verify) { client_send_discover(); if (verify) { verify_lease_count(1, 0, 0); } get_reserved_address(&test_ctx.assigned_ip); test_pkt_free(); client_send_request_solicit(); if (verify) { verify_lease_count(0, 1, 0); } test_pkt_free(); } static void verify_no_option(struct net_pkt *pkt, uint8_t opt_type) { struct net_pkt_cursor cursor; net_pkt_cursor_backup(pkt, &cursor); while (true) { uint8_t type; uint8_t len; if (net_pkt_read_u8(pkt, &type) < 0) { break; } if (net_pkt_read_u8(pkt, &len) < 0) { break; } zassert_not_equal(type, opt_type, "Option %d should not be present", opt_type); (void)net_pkt_skip(pkt, len); } net_pkt_cursor_restore(pkt, &cursor); } static void verify_option(struct net_pkt *pkt, uint8_t opt_type, const void *optval, uint8_t optlen) { struct net_pkt_cursor cursor; net_pkt_cursor_backup(pkt, &cursor); while (true) { static uint8_t buf[255]; uint8_t type; uint8_t len; if (net_pkt_read_u8(pkt, &type) < 0) { break; } if (net_pkt_read_u8(pkt, &len) < 0) { break; } if (net_pkt_read(pkt, buf, len)) { break; } if (type == opt_type) { zassert_equal(len, optlen, "Invalid option length"); zassert_mem_equal(buf, optval, optlen, "Invalid option value"); net_pkt_cursor_restore(pkt, &cursor); return; } } zassert_true(false, "Option %d not found", opt_type); } static void verify_option_uint32(struct net_pkt *pkt, uint8_t opt_type, uint32_t optval) { optval = htonl(optval); verify_option(pkt, opt_type, &optval, sizeof(optval)); } static void verify_option_uint8(struct net_pkt *pkt, uint8_t opt_type, uint8_t optval) { verify_option(pkt, opt_type, &optval, sizeof(optval)); } static void verify_offer(bool broadcast) { NET_PKT_DATA_ACCESS_DEFINE(ipv4_access, struct net_ipv4_hdr); NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr); NET_PKT_DATA_ACCESS_DEFINE(dhcp_access, struct dhcp_msg); uint8_t cookie_buf[SIZE_OF_MAGIC_COOKIE]; struct net_pkt *pkt = test_ctx.pkt; struct net_ipv4_hdr *ipv4_hdr; struct net_udp_hdr *udp_hdr; struct dhcp_msg *msg; int ret; ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access); zassert_not_null(ipv4_hdr, "Failed to access IPv4 header."); net_pkt_acknowledge_data(pkt, &ipv4_access); udp_hdr = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access); zassert_not_null(udp_hdr, "Failed to access UDP header."); net_pkt_acknowledge_data(pkt, &udp_access); msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access); zassert_not_null(msg, "Failed to access DHCP data."); net_pkt_acknowledge_data(pkt, &dhcp_access); /* IPv4 */ zassert_mem_equal(ipv4_hdr->src, server_addr.s4_addr, sizeof(struct in_addr), "Incorrect source address"); if (broadcast) { zassert_mem_equal(ipv4_hdr->dst, net_ipv4_broadcast_address(), sizeof(struct in_addr), "Destination should be broadcast"); } else { zassert_mem_equal(ipv4_hdr->dst, msg->yiaddr, sizeof(struct in_addr), "Destination should match address lease"); } zassert_equal(ipv4_hdr->proto, IPPROTO_UDP, "Wrong protocol"); /* UDP */ zassert_equal(udp_hdr->src_port, htons(DHCPV4_SERVER_PORT), "Wrong source port"); zassert_equal(udp_hdr->dst_port, htons(DHCPV4_CLIENT_PORT), "Wrong client port"); /* DHCPv4 */ zassert_equal(msg->op, DHCPV4_MSG_BOOT_REPLY, "Incorrect %s value", "op"); zassert_equal(msg->htype, HARDWARE_ETHERNET_TYPE, "Incorrect %s value", "htype"); zassert_equal(msg->hlen, sizeof(client_mac_addr), "Incorrect %s value", "hlen"); zassert_equal(msg->hops, 0, "Incorrect %s value", "hops"); zassert_equal(msg->xid, htonl(TEST_XID), "Incorrect %s value", "xid"); zassert_equal(msg->secs, 0, "Incorrect %s value", "secs"); zassert_equal(sys_get_be32(msg->ciaddr), 0, "Incorrect %s value", "ciaddr"); zassert_true((sys_get_be32(msg->yiaddr) >= sys_get_be32(test_base_addr.s4_addr)) && (sys_get_be32(msg->yiaddr) < sys_get_be32(test_base_addr.s4_addr) + CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT), "Assigned DHCP address outside of address pool"); zassert_equal(sys_get_be32(msg->siaddr), 0, "Incorrect %s value", "siaddr"); if (broadcast) { zassert_equal(msg->flags, htons(DHCPV4_MSG_BROADCAST), "Incorrect %s value", "flags"); } else { zassert_equal(msg->flags, 0, "Incorrect %s value", "flags"); } zassert_equal(sys_get_be32(msg->giaddr), 0, "Incorrect %s value", "giaddr"); zassert_mem_equal(msg->chaddr, client_mac_addr, sizeof(client_mac_addr), "Incorrect %s value", "chaddr"); memcpy(&test_ctx.assigned_ip, msg->yiaddr, sizeof(struct in_addr)); ret = net_pkt_skip(pkt, SIZE_OF_SNAME + SIZE_OF_FILE); zassert_ok(ret, "DHCP Offer too short"); ret = net_pkt_read(pkt, cookie_buf, SIZE_OF_MAGIC_COOKIE); zassert_ok(ret, "DHCP Offer too short"); zassert_mem_equal(cookie_buf, cookie, SIZE_OF_MAGIC_COOKIE, "Incorrect cookie value"); verify_option_uint32(pkt, DHCPV4_OPTIONS_LEASE_TIME, CONFIG_NET_DHCPV4_SERVER_ADDR_LEASE_TIME); verify_option_uint8(pkt, DHCPV4_OPTIONS_MSG_TYPE, NET_DHCPV4_MSG_TYPE_OFFER); verify_option(pkt, DHCPV4_OPTIONS_SERVER_ID, server_addr.s4_addr, sizeof(struct in_addr)); verify_option(pkt, DHCPV4_OPTIONS_CLIENT_ID, test_ctx.client_id, strlen(test_ctx.client_id)); verify_option(pkt, DHCPV4_OPTIONS_SUBNET_MASK, netmask.s4_addr, sizeof(struct in_addr)); verify_no_option(pkt, DHCPV4_OPTIONS_REQ_IPADDR); verify_no_option(pkt, DHCPV4_OPTIONS_REQ_LIST); } static void reserved_address_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { struct in_addr *reserved = user_data; zassert_equal(lease->state, DHCPV4_SERVER_ADDR_RESERVED, "Wrong lease state"); zassert_equal(reserved->s_addr, lease->addr.s_addr, "Reserved wrong address"); } static void verify_reserved_address(struct in_addr *reserved) { int ret; ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, reserved_address_cb, reserved); zassert_ok(ret, "Failed to verify reserved address"); } /* Verify that the DHCP server replies with Offer for a Discover message. */ ZTEST(dhcpv4_server_tests, test_discover) { client_send_discover(); verify_offer(false); test_pkt_free(); verify_lease_count(1, 0, 0); verify_reserved_address(&test_ctx.assigned_ip); } /* Verify that the DHCP server offers the same IP address for repeated Discover * message. */ ZTEST(dhcpv4_server_tests, test_discover_repeat) { struct in_addr first_addr; client_send_discover(); verify_offer(false); test_pkt_free(); first_addr = test_ctx.assigned_ip; verify_lease_count(1, 0, 0); /* Repeat Discover with the same client ID */ client_send_discover(); verify_offer(false); test_pkt_free(); verify_lease_count(1, 0, 0); zassert_equal(first_addr.s_addr, test_ctx.assigned_ip.s_addr, "Received different address for the same client ID"); /* Send Discover with a different client ID */ test_ctx.client_id = CLIENT_ID_2; client_send_discover(); verify_offer(false); test_pkt_free(); verify_lease_count(2, 0, 0); zassert_not_equal(first_addr.s_addr, test_ctx.assigned_ip.s_addr, "Received same address for the different client ID"); } /* Verify that the DHCP server replies to broadcast address if broadcast flag * is set. */ ZTEST(dhcpv4_server_tests, test_discover_with_broadcast) { test_ctx.broadcast = true; client_send_discover(); verify_offer(true); verify_lease_count(1, 0, 0); test_pkt_free(); } static void verify_ack(bool inform, bool renew) { NET_PKT_DATA_ACCESS_DEFINE(ipv4_access, struct net_ipv4_hdr); NET_PKT_DATA_ACCESS_DEFINE(udp_access, struct net_udp_hdr); NET_PKT_DATA_ACCESS_DEFINE(dhcp_access, struct dhcp_msg); uint8_t cookie_buf[SIZE_OF_MAGIC_COOKIE]; struct net_pkt *pkt = test_ctx.pkt; struct net_ipv4_hdr *ipv4_hdr; struct net_udp_hdr *udp_hdr; struct dhcp_msg *msg; int ret; ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access); zassert_not_null(ipv4_hdr, "Failed to access IPv4 header."); net_pkt_acknowledge_data(pkt, &ipv4_access); udp_hdr = (struct net_udp_hdr *)net_pkt_get_data(pkt, &udp_access); zassert_not_null(udp_hdr, "Failed to access UDP header."); net_pkt_acknowledge_data(pkt, &udp_access); msg = (struct dhcp_msg *)net_pkt_get_data(pkt, &dhcp_access); zassert_not_null(msg, "Failed to access DHCP data."); net_pkt_acknowledge_data(pkt, &dhcp_access); /* IPv4 */ zassert_mem_equal(ipv4_hdr->src, server_addr.s4_addr, sizeof(struct in_addr), "Incorrect source address"); if (inform || renew) { zassert_mem_equal(ipv4_hdr->dst, msg->ciaddr, sizeof(struct in_addr), "Destination should match client address"); } else { zassert_mem_equal(ipv4_hdr->dst, msg->yiaddr, sizeof(struct in_addr), "Destination should match client address"); } zassert_equal(ipv4_hdr->proto, IPPROTO_UDP, "Wrong protocol"); /* UDP */ zassert_equal(udp_hdr->src_port, htons(DHCPV4_SERVER_PORT), "Wrong source port"); zassert_equal(udp_hdr->dst_port, htons(DHCPV4_CLIENT_PORT), "Wrong client port"); /* DHCPv4 */ zassert_equal(msg->op, DHCPV4_MSG_BOOT_REPLY, "Incorrect %s value", "op"); zassert_equal(msg->htype, HARDWARE_ETHERNET_TYPE, "Incorrect %s value", "htype"); zassert_equal(msg->hlen, sizeof(client_mac_addr), "Incorrect %s value", "hlen"); zassert_equal(msg->hops, 0, "Incorrect %s value", "hops"); zassert_equal(msg->xid, htonl(TEST_XID), "Incorrect %s value", "xid"); zassert_equal(msg->secs, 0, "Incorrect %s value", "secs"); if (inform) { zassert_mem_equal(msg->ciaddr, client_addr_static.s4_addr, sizeof(struct in_addr), "Incorrect %s value", "ciaddr"); } else if (renew) { zassert_mem_equal(msg->ciaddr, test_ctx.assigned_ip.s4_addr, sizeof(struct in_addr), "Incorrect %s value", "ciaddr"); } else { zassert_equal(sys_get_be32(msg->ciaddr), 0, "Incorrect %s value", "ciaddr"); } if (inform) { zassert_equal(sys_get_be32(msg->yiaddr), 0, "Incorrect %s value", "yiaddr"); } else { zassert_mem_equal(msg->yiaddr, test_ctx.assigned_ip.s4_addr, sizeof(struct in_addr), "Incorrect %s value", "yiaddr"); } zassert_equal(sys_get_be32(msg->siaddr), 0, "Incorrect %s value", "siaddr"); zassert_equal(msg->flags, 0, "Incorrect %s value", "flags"); zassert_equal(sys_get_be32(msg->giaddr), 0, "Incorrect %s value", "giaddr"); zassert_mem_equal(msg->chaddr, client_mac_addr, sizeof(client_mac_addr), "Incorrect %s value", "chaddr"); if (!inform) { memcpy(&test_ctx.assigned_ip, msg->yiaddr, sizeof(struct in_addr)); } ret = net_pkt_skip(pkt, SIZE_OF_SNAME + SIZE_OF_FILE); zassert_ok(ret, "DHCP Offer too short"); ret = net_pkt_read(pkt, cookie_buf, SIZE_OF_MAGIC_COOKIE); zassert_ok(ret, "DHCP Offer too short"); zassert_mem_equal(cookie_buf, cookie, SIZE_OF_MAGIC_COOKIE, "Incorrect cookie value"); if (inform) { verify_no_option(pkt, DHCPV4_OPTIONS_LEASE_TIME); } else { verify_option_uint32(pkt, DHCPV4_OPTIONS_LEASE_TIME, CONFIG_NET_DHCPV4_SERVER_ADDR_LEASE_TIME); } verify_option_uint8(pkt, DHCPV4_OPTIONS_MSG_TYPE, NET_DHCPV4_MSG_TYPE_ACK); verify_option(pkt, DHCPV4_OPTIONS_SERVER_ID, server_addr.s4_addr, sizeof(struct in_addr)); if (inform) { verify_no_option(pkt, DHCPV4_OPTIONS_CLIENT_ID); } else { verify_option(pkt, DHCPV4_OPTIONS_CLIENT_ID, test_ctx.client_id, strlen(test_ctx.client_id)); } verify_option(pkt, DHCPV4_OPTIONS_SUBNET_MASK, netmask.s4_addr, sizeof(struct in_addr)); verify_no_option(pkt, DHCPV4_OPTIONS_REQ_IPADDR); verify_no_option(pkt, DHCPV4_OPTIONS_REQ_LIST); } static void allocated_address_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { struct in_addr *allocated = user_data; zassert_equal(lease->state, DHCPV4_SERVER_ADDR_ALLOCATED, "Wrong lease state"); zassert_equal(allocated->s_addr, lease->addr.s_addr, "Reserved wrong address"); } static void verify_allocated_address(struct in_addr *allocated) { int ret; ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, allocated_address_cb, allocated); zassert_ok(ret, "Failed to verify allocated address"); } /* Verify that the DHCP server replies with ACK for a Request message. */ ZTEST(dhcpv4_server_tests, test_request) { client_send_discover(); verify_offer(false); verify_lease_count(1, 0, 0); test_pkt_free(); client_send_request_solicit(); verify_ack(false, false); verify_lease_count(0, 1, 0); verify_allocated_address(&test_ctx.assigned_ip); test_pkt_free(); } /* Verify that the DHCP server replies with ACK for a Request message * (renewing). */ ZTEST(dhcpv4_server_tests, test_renew) { client_get_lease(true); client_send_request_renew(); verify_ack(false, true); verify_lease_count(0, 1, 0); test_pkt_free(); } /* Verify that the DHCP server replies with ACK for a Request message * (rebinding). */ ZTEST(dhcpv4_server_tests, test_rebind) { client_get_lease(true); client_send_request_rebind(); verify_ack(false, true); verify_lease_count(0, 1, 0); test_pkt_free(); } /* Verify that the DHCP server lease expires after the lease timeout. */ ZTEST(dhcpv4_server_tests, test_expiry) { test_ctx.lease_time = 1; client_get_lease(true); /* Add extra 10ms to avoid race. */ k_msleep(1000 + 10); verify_lease_count(0, 0, 0); } /* Verify that the DHCP server releases the lease after receiving Release * message. */ ZTEST(dhcpv4_server_tests, test_release) { client_get_lease(true); client_send_release(); verify_lease_count(0, 0, 0); } static void declined_address_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { struct in_addr *declined = user_data; zassert_equal(lease->state, DHCPV4_SERVER_ADDR_DECLINED, "Wrong lease state"); zassert_equal(declined->s_addr, lease->addr.s_addr, "Declined wrong address"); } static void verify_declined_address(struct in_addr *declined) { int ret; ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, declined_address_cb, declined); zassert_ok(ret, "Failed to verify declined address"); } /* Verify that the DHCP server blocks the address after receiving Decline * message, and gets released after configured time. */ ZTEST(dhcpv4_server_tests, test_decline) { client_get_lease(true); verify_lease_count(0, 1, 0); client_send_decline(); verify_lease_count(0, 0, 1); verify_declined_address(&test_ctx.assigned_ip); /* Add extra 10ms to avoid race. */ k_msleep(1000 + 10); verify_lease_count(0, 0, 0); } /* Verify that if all of the address leases get blocked (due to conflict), the * server will try to reuse the oldest blocked entry on Discovery. */ ZTEST(dhcpv4_server_tests, test_declined_reuse) { struct in_addr oldest_addr; for (int i = 0; i < CONFIG_NET_DHCPV4_SERVER_ADDR_COUNT; i++) { client_get_lease(false); if (i == 0) { oldest_addr = test_ctx.assigned_ip; } client_send_decline(); k_msleep(10); } verify_lease_count(0, 0, 4); client_send_discover(); verify_offer(false); verify_lease_count(1, 0, 3); test_pkt_free(); client_send_request_solicit(); verify_ack(false, false); verify_lease_count(0, 1, 3); test_pkt_free(); zassert_equal(oldest_addr.s_addr, test_ctx.assigned_ip.s_addr, "Should've reassing oldest declined address"); } /* Verify that the DHCP server replies with ACK for a Inform message, w/o * address assignment. */ ZTEST(dhcpv4_server_tests, test_inform) { client_send_inform(); verify_ack(true, false); verify_lease_count(0, 0, 0); } static void after_probe_address_cb(struct net_if *iface, struct dhcpv4_addr_slot *lease, void *user_data) { if (lease->state == DHCPV4_SERVER_ADDR_DECLINED) { zassert_equal(test_ctx.declined_ip.s_addr, lease->addr.s_addr, "Declined wrong address"); } if (lease->state == DHCPV4_SERVER_ADDR_RESERVED) { zassert_equal(test_ctx.assigned_ip.s_addr, lease->addr.s_addr, "Reserved wrong address"); } } static void verify_address_after_probe(void) { int ret; ret = net_dhcpv4_server_foreach_lease(test_ctx.iface, after_probe_address_cb, NULL); zassert_ok(ret, "Failed to verify address after probe"); } /* Verify that if the server detects conflict with ICMP probe, it assigns * different address. */ ZTEST(dhcpv4_server_tests, test_icmp_probe) { if (CONFIG_NET_DHCPV4_SERVER_ICMP_PROBE_TIMEOUT == 0) { ztest_test_skip(); } test_ctx.send_echo_reply = true; client_send_discover(); verify_offer(false); test_pkt_free(); verify_lease_count(1, 0, 1); zassert_not_equal(test_ctx.assigned_ip.s_addr, test_ctx.declined_ip.s_addr, "DHCPv4 srever offered conflicted address"); verify_address_after_probe(); } /* Verify that the DHCP server can start and validate input properly. */ ZTEST(dhcpv4_server_tests_no_init, test_initialization) { struct in_addr base_addr_wrong_subnet = { { { 192, 0, 3, 10 } } }; struct in_addr base_addr_overlap = { { { 192, 0, 2, 1 } } }; int ret; ret = net_dhcpv4_server_start(test_ctx.iface, &base_addr_wrong_subnet); zassert_equal(ret, -EINVAL, "Started server for wrong subnet"); ret = net_dhcpv4_server_start(test_ctx.iface, &base_addr_overlap); zassert_equal(ret, -EINVAL, "Started server for overlapping address"); ret = net_dhcpv4_server_start(test_ctx.iface, &test_base_addr); zassert_ok(ret, "Failed to start server for valid address range"); net_dhcpv4_server_stop(test_ctx.iface); } static void dhcpv4_server_tests_before(void *fixture) { ARG_UNUSED(fixture); k_sem_init(&test_ctx.test_proceed, 0, 1); test_ctx.client_id = CLIENT_ID_1; test_ctx.broadcast = false; test_ctx.pkt = NULL; test_ctx.lease_time = NO_LEASE_TIME; test_ctx.send_echo_reply = false; memset(&test_ctx.assigned_ip, 0, sizeof(test_ctx.assigned_ip)); net_dhcpv4_server_start(test_ctx.iface, &test_base_addr); } static void dhcpv4_server_tests_after(void *fixture) { ARG_UNUSED(fixture); test_pkt_free(); net_dhcpv4_server_stop(test_ctx.iface); } ZTEST_SUITE(dhcpv4_server_tests, NULL, NULL, dhcpv4_server_tests_before, dhcpv4_server_tests_after, NULL); ZTEST_SUITE(dhcpv4_server_tests_no_init, NULL, NULL, NULL, NULL, NULL);