| /* |
| * Copyright (c) 2024 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/ztest.h> |
| #include <zephyr/net/dummy.h> |
| #include <zephyr/net/ethernet.h> |
| #include <zephyr/net/icmp.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/dhcpv4.h> |
| #include <zephyr/net/dhcpv4_server.h> |
| |
| #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(iface, &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(void) |
| { |
| client_send_discover(); |
| verify_lease_count(1, 0, 0); |
| get_reserved_address(&test_ctx.assigned_ip); |
| test_pkt_free(); |
| |
| client_send_request_solicit(); |
| 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, |
| 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_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); |
| verify_no_option(pkt, DHCPV4_OPTIONS_CLIENT_ID); |
| } |
| |
| 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)); |
| 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); |
| verify_no_option(pkt, DHCPV4_OPTIONS_CLIENT_ID); |
| } |
| |
| 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(); |
| |
| 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(); |
| |
| 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(); |
| |
| /* 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(); |
| |
| 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. |
| */ |
| ZTEST(dhcpv4_server_tests, test_decline) |
| { |
| client_get_lease(); |
| verify_lease_count(0, 1, 0); |
| |
| client_send_decline(); |
| verify_lease_count(0, 0, 1); |
| verify_declined_address(&test_ctx.assigned_ip); |
| } |
| |
| /* 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); |