blob: 49478c991d2c22fc4c75ce8980227e90fc53615d [file] [log] [blame]
/*
* 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_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);