Client ID option is now expected in Request/Ack responses so tests need to be updated accordingly. Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
1030 lines
28 KiB
C
1030 lines
28 KiB
C
/*
|
|
* 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);
|