blob: ace0a0128c2ff54932b88590a9aaf2fb24631f04 [file] [log] [blame]
/*
* Copyright (c) 2023 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/net_if.h>
#include "../../../subsys/net/lib/dhcpv6/dhcpv6.c"
static struct in6_addr test_addr = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0x1 } } };
static struct in6_addr test_prefix = { { { 0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 } } };
static uint8_t test_prefix_len = 64;
static uint8_t test_preference;
static struct net_dhcpv6_duid_storage test_serverid;
typedef void (*test_dhcpv6_pkt_fn_t)(struct net_if *iface,
struct net_pkt *pkt);
typedef int (*test_dhcpv6_options_fn_t)(struct net_if *iface,
struct net_pkt *pkt,
enum dhcpv6_msg_type msg_type);
struct test_dhcpv6_context {
uint8_t mac[sizeof(struct net_eth_addr)];
struct net_if *iface;
test_dhcpv6_pkt_fn_t test_fn;
struct k_sem tx_sem;
struct k_sem exchange_complete_sem;
bool reset_dhcpv6;
};
struct test_dhcpv6_context test_ctx;
static void test_iface_init(struct net_if *iface)
{
struct test_dhcpv6_context *ctx = net_if_get_device(iface)->data;
/* Generate and assign MAC. */
/* 00-00-5E-00-53-xx Documentation RFC 7042 */
ctx->mac[0] = 0x00;
ctx->mac[1] = 0x00;
ctx->mac[2] = 0x5E;
ctx->mac[3] = 0x00;
ctx->mac[4] = 0x53;
ctx->mac[5] = 0x00;
net_if_set_link_addr(iface, ctx->mac, sizeof(ctx->mac), NET_LINK_ETHERNET);
}
static int test_send(const struct device *dev, struct net_pkt *pkt)
{
struct test_dhcpv6_context *ctx = dev->data;
if (ctx->test_fn != NULL) {
ctx->test_fn(net_pkt_iface(pkt), pkt);
}
k_sem_give(&ctx->tx_sem);
return 0;
}
static struct dummy_api test_if_api = {
.iface_api.init = test_iface_init,
.send = test_send,
};
NET_DEVICE_INIT(test_dhcpv6, "test_dhcpv6", NULL, NULL, &test_ctx, NULL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &test_if_api,
DUMMY_L2, NET_L2_GET_CTX_TYPE(DUMMY_L2), NET_IPV6_MTU);
static void set_dhcpv6_test_fn(test_dhcpv6_pkt_fn_t test_fn)
{
test_ctx.test_fn = test_fn;
}
static void set_test_addr_on_iface(struct net_if *iface)
{
memcpy(&test_ctx.iface->config.dhcpv6.addr, &test_addr,
sizeof(test_ctx.iface->config.dhcpv6.addr));
memcpy(&test_ctx.iface->config.dhcpv6.prefix, &test_prefix,
sizeof(test_ctx.iface->config.dhcpv6.prefix));
test_ctx.iface->config.dhcpv6.prefix_len = test_prefix_len;
}
static void clear_test_addr_on_iface(struct net_if *iface)
{
memset(&test_ctx.iface->config.dhcpv6.addr, 0,
sizeof(test_ctx.iface->config.dhcpv6.addr));
memset(&test_ctx.iface->config.dhcpv6.prefix, 0,
sizeof(test_ctx.iface->config.dhcpv6.prefix));
test_ctx.iface->config.dhcpv6.prefix_len = 0;
}
static void generate_fake_server_duid(void)
{
struct net_dhcpv6_duid_storage *serverid = &test_serverid;
struct dhcpv6_duid_ll *duid_ll =
(struct dhcpv6_duid_ll *)&serverid->duid.buf;
uint8_t fake_mac[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
memset(serverid, 0, sizeof(*serverid));
UNALIGNED_PUT(htons(DHCPV6_DUID_TYPE_LL), &serverid->duid.type);
UNALIGNED_PUT(htons(DHCPV6_HARDWARE_ETHERNET_TYPE), &duid_ll->hw_type);
memcpy(duid_ll->ll_addr, fake_mac, sizeof(fake_mac));
serverid->length = DHCPV6_DUID_LL_HEADER_SIZE + sizeof(fake_mac);
}
static void set_fake_server_duid(struct net_if *iface)
{
memcpy(&iface->config.dhcpv6.serverid, &test_serverid,
sizeof(test_serverid));
}
#define TEST_MSG_SIZE 256
static struct net_pkt *test_dhcpv6_create_message(
struct net_if *iface, enum dhcpv6_msg_type msg_type,
test_dhcpv6_options_fn_t set_options_fn)
{
struct in6_addr *local_addr;
struct in6_addr peer_addr;
struct net_pkt *pkt;
local_addr = net_if_ipv6_get_ll(iface, NET_ADDR_ANY_STATE);
if (local_addr == NULL) {
return NULL;
}
/* Create a peer address from my address but invert the last byte
* so that the address is not the same. This is needed as we drop
* the packet if source address is our own address.
*/
memcpy(&peer_addr, local_addr, sizeof(peer_addr));
peer_addr.s6_addr[15] = ~peer_addr.s6_addr[15];
pkt = net_pkt_alloc_with_buffer(iface, TEST_MSG_SIZE, AF_INET6,
IPPROTO_UDP, K_FOREVER);
if (pkt == NULL) {
return NULL;
}
if (net_ipv6_create(pkt, &peer_addr, local_addr) < 0 ||
net_udp_create(pkt, htons(DHCPV6_SERVER_PORT),
htons(DHCPV6_CLIENT_PORT)) < 0) {
goto fail;
}
dhcpv6_generate_tid(iface);
if (dhcpv6_add_header(pkt, msg_type, iface->config.dhcpv6.tid) < 0) {
goto fail;
}
if (set_options_fn(iface, pkt, msg_type) < 0) {
goto fail;
}
net_pkt_cursor_init(pkt);
net_ipv6_finalize(pkt, IPPROTO_UDP);
net_pkt_cursor_init(pkt);
return pkt;
fail:
net_pkt_unref(pkt);
return NULL;
}
static void *dhcpv6_tests_setup(void)
{
struct in6_addr lladdr;
test_ctx.iface = net_if_get_first_by_type(&NET_L2_GET_NAME(DUMMY));
net_ipv6_addr_create_iid(&lladdr, net_if_get_link_addr(test_ctx.iface));
(void)net_if_ipv6_addr_add(test_ctx.iface, &lladdr, NET_ADDR_AUTOCONF, 0);
k_sem_init(&test_ctx.tx_sem, 0, 1);
k_sem_init(&test_ctx.exchange_complete_sem, 0, 1);
generate_fake_server_duid();
return NULL;
}
static void dhcpv6_tests_before(void *fixture)
{
ARG_UNUSED(fixture);
test_ctx.reset_dhcpv6 = false;
set_dhcpv6_test_fn(NULL);
k_sem_reset(&test_ctx.tx_sem);
k_sem_reset(&test_ctx.exchange_complete_sem);
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
dhcpv6_generate_client_duid(test_ctx.iface);
test_ctx.iface->config.dhcpv6.state = NET_DHCPV6_DISABLED;
test_ctx.iface->config.dhcpv6.addr_iaid = 10;
test_ctx.iface->config.dhcpv6.prefix_iaid = 20;
test_ctx.iface->config.dhcpv6.exchange_start = k_uptime_get();
test_ctx.iface->config.dhcpv6.params = (struct net_dhcpv6_params){
.request_addr = true,
.request_prefix = true,
};
test_preference = 100;
net_if_ipv6_addr_rm(test_ctx.iface, &test_addr);
net_if_ipv6_prefix_rm(test_ctx.iface, &test_prefix, test_prefix_len);
}
static void dhcpv6_tests_after(void *fixture)
{
ARG_UNUSED(fixture);
set_dhcpv6_test_fn(NULL);
if (test_ctx.reset_dhcpv6) {
net_dhcpv6_stop(test_ctx.iface);
}
}
static void verify_dhcpv6_header(struct net_if *iface, struct net_pkt *pkt,
enum dhcpv6_msg_type msg_type)
{
uint8_t tid[DHCPV6_TID_SIZE];
uint8_t type;
int ret;
(void)net_pkt_skip(pkt, NET_IPV6UDPH_LEN);
ret = net_pkt_read_u8(pkt, &type);
zassert_ok(ret, "DHCPv6 header incomplete (type)");
zassert_equal(type, msg_type, "Invalid message type");
ret = net_pkt_read(pkt, tid, sizeof(tid));
zassert_ok(ret, "DHCPv6 header incomplete (tid)");
zassert_mem_equal(tid, iface->config.dhcpv6.tid, sizeof(tid),
"Transaction ID doesn't match ID of the current exchange");
}
static void verify_dhcpv6_clientid(struct net_if *iface, struct net_pkt *pkt)
{
struct net_dhcpv6_duid_storage duid;
int ret;
ret = dhcpv6_find_clientid(pkt, &duid);
zassert_ok(ret, "Missing Client ID option");
zassert_equal(duid.length, iface->config.dhcpv6.clientid.length,
"Invalid Client ID length");
zassert_mem_equal(&duid.duid, &iface->config.dhcpv6.clientid.duid,
duid.length, "Invalid Client ID value");
}
static void verify_dhcpv6_serverid(struct net_if *iface, struct net_pkt *pkt)
{
struct net_dhcpv6_duid_storage duid;
int ret;
ret = dhcpv6_find_serverid(pkt, &duid);
zassert_ok(ret, "Missing Server ID option");
zassert_equal(duid.length, iface->config.dhcpv6.serverid.length,
"Invalid Server ID length");
zassert_mem_equal(&duid.duid, &iface->config.dhcpv6.serverid.duid,
duid.length, "Invalid Server ID value");
}
static void verify_dhcpv6_no_serverid(struct net_if *iface, struct net_pkt *pkt)
{
struct net_dhcpv6_duid_storage duid;
int ret;
ret = dhcpv6_find_serverid(pkt, &duid);
zassert_not_equal(ret, 0, "Server ID option should not be present");
}
static void verify_dhcpv6_elapsed_time(struct net_if *iface, struct net_pkt *pkt,
uint16_t min_accepted, uint16_t max_accepted)
{
struct net_pkt_cursor backup;
uint16_t elapsed_time;
uint16_t length;
int ret;
net_pkt_cursor_backup(pkt, &backup);
ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_ELAPSED_TIME, &length);
zassert_ok(ret, "Missing Elapsed time option");
zassert_equal(length, sizeof(uint16_t), "Invalid Elapsed time length");
ret = net_pkt_read_be16(pkt, &elapsed_time);
zassert_ok(ret, "Failed to read Elapsed time option");
zassert_between_inclusive(elapsed_time, min_accepted, max_accepted,
"Elapsed time not in accepted range");
net_pkt_cursor_restore(pkt, &backup);
}
static void verify_dhcpv6_ia_na(struct net_if *iface, struct net_pkt *pkt,
struct in6_addr *addr)
{
struct dhcpv6_ia_na ia_na;
int ret;
ret = dhcpv6_find_ia_na(pkt, &ia_na);
zassert_ok(ret, "Missing IA NA option");
zassert_equal(ia_na.iaid, iface->config.dhcpv6.addr_iaid,
"Incorrect IA NA IAID");
zassert_equal(ia_na.t1, 0, "T1 should be set to 0 by the client");
zassert_equal(ia_na.t2, 0, "T2 should be set to 0 by the client");
if (addr == NULL) {
zassert_equal(ia_na.iaaddr.status, DHCPV6_STATUS_NO_ADDR_AVAIL,
"Adddress should not be present");
return;
}
zassert_equal(ia_na.iaaddr.status, DHCPV6_STATUS_SUCCESS, "Invalid status");
zassert_equal(ia_na.iaaddr.preferred_lifetime, 0,
"Preferred lifetime should be set to 0 by the client");
zassert_equal(ia_na.iaaddr.valid_lifetime, 0,
"Valid lifetime should be set to 0 by the client");
zassert_mem_equal(&ia_na.iaaddr.addr, addr, sizeof(ia_na.iaaddr.addr),
"Incorrect address");
}
static void verify_dhcpv6_ia_pd(struct net_if *iface, struct net_pkt *pkt,
struct in6_addr *prefix, uint8_t prefix_len)
{
struct dhcpv6_ia_pd ia_pd;
int ret;
ret = dhcpv6_find_ia_pd(pkt, &ia_pd);
zassert_ok(ret, "Missing IA PD option");
zassert_equal(ia_pd.iaid, iface->config.dhcpv6.prefix_iaid,
"Incorrect IA PD IAID");
zassert_equal(ia_pd.t1, 0, "T1 should be set to 0 by the client");
zassert_equal(ia_pd.t2, 0, "T2 should be set to 0 by the client");
if (prefix == NULL) {
zassert_equal(ia_pd.iaprefix.status, DHCPV6_STATUS_NO_PREFIX_AVAIL,
"Prefix should not be present");
return;
}
zassert_equal(ia_pd.iaprefix.status, DHCPV6_STATUS_SUCCESS, "Invalid status");
zassert_equal(ia_pd.iaprefix.preferred_lifetime, 0,
"Preferred lifetime should be set to 0 by the client");
zassert_equal(ia_pd.iaprefix.valid_lifetime, 0,
"Valid lifetime should be set to 0 by the client");
zassert_equal(ia_pd.iaprefix.prefix_len, prefix_len,
"Incorrect prefix length");
zassert_mem_equal(&ia_pd.iaprefix.prefix, prefix,
sizeof(ia_pd.iaprefix.prefix), "Incorrect prefix");
}
static void verify_dhcpv6_no_reconfigure_accept(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt_cursor backup;
uint16_t length;
int ret;
net_pkt_cursor_backup(pkt, &backup);
ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_RECONF_ACCEPT, &length);
zassert_not_equal(ret, 0, "Reconfigure accept option should not be present");
net_pkt_cursor_restore(pkt, &backup);
}
static void verify_dhcpv6_oro_sol_max_rt(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt_cursor backup;
uint16_t length;
uint16_t oro;
int ret;
net_pkt_cursor_backup(pkt, &backup);
ret = dhcpv6_find_option(pkt, DHCPV6_OPTION_CODE_ORO, &length);
zassert_ok(ret, 0, "ORO option not found");
zassert_true(length >= sizeof(uint16_t) && length % sizeof(uint16_t) == 0,
"Invalid ORO length");
while (length >= sizeof(uint16_t)) {
ret = net_pkt_read_be16(pkt, &oro);
zassert_ok(ret, 0, "ORO read error");
length -= sizeof(uint16_t);
if (oro == DHCPV6_OPTION_CODE_SOL_MAX_RT) {
break;
}
}
zassert_equal(oro, DHCPV6_OPTION_CODE_SOL_MAX_RT,
"No SOL_MAX_RT option request present");
net_pkt_cursor_restore(pkt, &backup);
}
static void verify_solicit_message(struct net_if *iface, struct net_pkt *pkt)
{
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_SOLICIT);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_no_serverid(iface, pkt);
verify_dhcpv6_elapsed_time(iface, pkt, 0, 10);
verify_dhcpv6_ia_na(iface, pkt, NULL);
verify_dhcpv6_ia_pd(iface, pkt, NULL, 0);
verify_dhcpv6_no_reconfigure_accept(iface, pkt);
verify_dhcpv6_oro_sol_max_rt(iface, pkt);
}
/* Verify that outgoing DHCPv6 Solicit has a valid format and includes all
* mandatory options.
*/
ZTEST(dhcpv6_tests, test_solicit_message_format)
{
int ret;
set_dhcpv6_test_fn(verify_solicit_message);
ret = dhcpv6_send_solicit(test_ctx.iface);
zassert_ok(ret, "dhcpv6_send_solicit failed");
ret = k_sem_take(&test_ctx.tx_sem, K_SECONDS(1));
zassert_ok(ret, "Packet not transmitted");
}
static void verify_request_message(struct net_if *iface, struct net_pkt *pkt)
{
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_REQUEST);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_serverid(iface, pkt);
verify_dhcpv6_elapsed_time(iface, pkt, 0, 10);
verify_dhcpv6_ia_na(iface, pkt, NULL);
verify_dhcpv6_ia_pd(iface, pkt, NULL, 0);
verify_dhcpv6_no_reconfigure_accept(iface, pkt);
verify_dhcpv6_oro_sol_max_rt(iface, pkt);
}
/* Verify that outgoing DHCPv6 Request has a valid format and includes all
* mandatory options.
*/
ZTEST(dhcpv6_tests, test_request_message_format)
{
int ret;
set_fake_server_duid(test_ctx.iface);
set_dhcpv6_test_fn(verify_request_message);
ret = dhcpv6_send_request(test_ctx.iface);
zassert_ok(ret, "dhcpv6_send_request failed");
ret = k_sem_take(&test_ctx.tx_sem, K_SECONDS(1));
zassert_ok(ret, "Packet not transmitted");
}
static void verify_confirm_message(struct net_if *iface, struct net_pkt *pkt)
{
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_CONFIRM);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_no_serverid(iface, pkt);
verify_dhcpv6_elapsed_time(iface, pkt, 0, 10);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
}
/* Verify that outgoing DHCPv6 Confirm has a valid format and includes all
* mandatory options.
*/
ZTEST(dhcpv6_tests, test_confirm_message_format)
{
int ret;
set_test_addr_on_iface(test_ctx.iface);
set_dhcpv6_test_fn(verify_confirm_message);
ret = dhcpv6_send_confirm(test_ctx.iface);
zassert_ok(ret, "dhcpv6_send_confirm failed");
ret = k_sem_take(&test_ctx.tx_sem, K_SECONDS(1));
zassert_ok(ret, "Packet not transmitted");
}
void verify_renew_message(struct net_if *iface, struct net_pkt *pkt)
{
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_RENEW);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_serverid(iface, pkt);
verify_dhcpv6_elapsed_time(iface, pkt, 0, 10);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
verify_dhcpv6_ia_pd(iface, pkt, &test_prefix, test_prefix_len);
verify_dhcpv6_oro_sol_max_rt(iface, pkt);
}
/* Verify that outgoing DHCPv6 Renew has a valid format and includes all
* mandatory options.
*/
ZTEST(dhcpv6_tests, test_renew_message_format)
{
int ret;
set_test_addr_on_iface(test_ctx.iface);
set_fake_server_duid(test_ctx.iface);
set_dhcpv6_test_fn(verify_renew_message);
ret = dhcpv6_send_renew(test_ctx.iface);
zassert_ok(ret, "dhcpv6_send_renew failed");
ret = k_sem_take(&test_ctx.tx_sem, K_SECONDS(1));
zassert_ok(ret, "Packet not transmitted");
}
static void verify_rebind_message(struct net_if *iface, struct net_pkt *pkt)
{
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_REBIND);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_no_serverid(iface, pkt);
verify_dhcpv6_elapsed_time(iface, pkt, 0, 10);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
verify_dhcpv6_ia_pd(iface, pkt, &test_prefix, test_prefix_len);
verify_dhcpv6_oro_sol_max_rt(iface, pkt);
}
/* Verify that outgoing DHCPv6 Rebind has a valid format and includes all
* mandatory options.
*/
ZTEST(dhcpv6_tests, test_rebind_message_format)
{
int ret;
set_test_addr_on_iface(test_ctx.iface);
set_dhcpv6_test_fn(verify_rebind_message);
ret = dhcpv6_send_rebind(test_ctx.iface);
zassert_ok(ret, "dhcpv6_send_rebind failed");
ret = k_sem_take(&test_ctx.tx_sem, K_SECONDS(1));
zassert_ok(ret, "Packet not transmitted");
}
static int set_generic_client_options(struct net_if *iface, struct net_pkt *pkt,
enum dhcpv6_msg_type msg_type)
{
int ret;
/* Simulate a minimum subset of valid options */
ret = dhcpv6_add_option_clientid(pkt, &iface->config.dhcpv6.clientid);
if (ret < 0) {
return ret;
}
if (msg_type == DHCPV6_MSG_TYPE_REQUEST ||
msg_type == DHCPV6_MSG_TYPE_RENEW ||
msg_type == DHCPV6_MSG_TYPE_RELEASE ||
msg_type == DHCPV6_MSG_TYPE_DECLINE) {
ret = dhcpv6_add_option_serverid(pkt, &test_serverid);
if (ret < 0) {
return ret;
}
}
return 0;
}
/* Verify that DHCPv6 client rejects all messages other than Advertise, Reply
* and Reconfigure.
*/
ZTEST(dhcpv6_tests, test_input_reject_client_initiated_messages)
{
enum dhcpv6_msg_type type;
enum net_verdict result;
struct net_pkt *pkt;
test_ctx.iface->config.dhcpv6.state = NET_DHCPV6_INIT;
for (type = DHCPV6_MSG_TYPE_SOLICIT;
type <= DHCPV6_MSG_TYPE_RELAY_REPL; type++) {
if (type == DHCPV6_MSG_TYPE_ADVERTISE ||
type == DHCPV6_MSG_TYPE_REPLY ||
type == DHCPV6_MSG_TYPE_RECONFIGURE) {
continue;
}
pkt = test_dhcpv6_create_message(test_ctx.iface, type,
set_generic_client_options);
zassert_not_null(pkt, "Failed to create fake pkt");
result = net_ipv6_input(pkt, false);
zassert_equal(result, NET_DROP, "Should've drop the message");
net_pkt_unref(pkt);
}
}
static int set_advertise_options(struct net_if *iface, struct net_pkt *pkt,
enum dhcpv6_msg_type msg_type)
{
struct dhcpv6_ia_na test_ia_na = {
.iaid = iface->config.dhcpv6.addr_iaid,
.t1 = 60,
.t2 = 120,
.iaaddr.addr = test_addr,
.iaaddr.preferred_lifetime = 120,
.iaaddr.valid_lifetime = 240,
};
struct dhcpv6_ia_pd test_ia_pd = {
.iaid = iface->config.dhcpv6.prefix_iaid,
.t1 = 60,
.t2 = 120,
.iaprefix.prefix = test_prefix,
.iaprefix.prefix_len = test_prefix_len,
.iaprefix.preferred_lifetime = 120,
.iaprefix.valid_lifetime = 240,
};
int ret;
ret = dhcpv6_add_option_clientid(pkt, &iface->config.dhcpv6.clientid);
if (ret < 0) {
return ret;
}
ret = dhcpv6_add_option_serverid(pkt, &test_serverid);
if (ret < 0) {
return ret;
}
if (test_ctx.iface->config.dhcpv6.params.request_addr) {
ret = dhcpv6_add_option_ia_na(pkt, &test_ia_na, true);
if (ret < 0) {
return ret;
}
}
if (test_ctx.iface->config.dhcpv6.params.request_prefix) {
ret = dhcpv6_add_option_ia_pd(pkt, &test_ia_pd, true);
if (ret < 0) {
return ret;
}
}
/* Server specific options */
ret = dhcpv6_add_option_header(pkt, DHCPV6_OPTION_CODE_PREFERENCE,
DHCPV6_OPTION_PREFERENCE_SIZE);
if (ret < 0) {
return ret;
}
ret = net_pkt_write_u8(pkt, test_preference);
if (ret < 0) {
return ret;
}
return 0;
}
/* Verify that DHCPv6 client only accepts Advertise messages in Soliciting state */
ZTEST(dhcpv6_tests, test_input_advertise)
{
enum net_verdict result;
struct net_pkt *pkt;
enum net_dhcpv6_state state;
for (state = NET_DHCPV6_DISABLED; state <= NET_DHCPV6_BOUND; state++) {
test_ctx.iface->config.dhcpv6.state = state;
pkt = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_ADVERTISE,
set_advertise_options);
zassert_not_null(pkt, "Failed to create pkt");
result = net_ipv6_input(pkt, false);
switch (state) {
case NET_DHCPV6_SOLICITING:
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify that Advertise actually updated DHPCv6 context. */
zassert_equal(test_ctx.iface->config.dhcpv6.server_preference,
test_preference, "Preference not set");
zassert_equal(test_ctx.iface->config.dhcpv6.serverid.length,
test_serverid.length, "Invalid Server ID length");
zassert_mem_equal(&test_ctx.iface->config.dhcpv6.serverid.duid,
&test_serverid.duid, test_serverid.length,
"Invalid Server ID value");
break;
default:
zassert_equal(result, NET_DROP, "Should've drop the message");
break;
}
net_pkt_unref(pkt);
}
}
static int set_reply_options(struct net_if *iface, struct net_pkt *pkt,
enum dhcpv6_msg_type msg_type)
{
struct dhcpv6_ia_na test_ia_na = {
.iaid = iface->config.dhcpv6.addr_iaid,
.t1 = 60,
.t2 = 120,
.iaaddr.addr = test_addr,
.iaaddr.preferred_lifetime = 120,
.iaaddr.valid_lifetime = 240,
};
struct dhcpv6_ia_pd test_ia_pd = {
.iaid = iface->config.dhcpv6.prefix_iaid,
.t1 = 60,
.t2 = 120,
.iaprefix.prefix = test_prefix,
.iaprefix.prefix_len = test_prefix_len,
.iaprefix.preferred_lifetime = 120,
.iaprefix.valid_lifetime = 240,
};
int ret;
ret = dhcpv6_add_option_clientid(pkt, &iface->config.dhcpv6.clientid);
if (ret < 0) {
return ret;
}
ret = dhcpv6_add_option_serverid(pkt, &test_serverid);
if (ret < 0) {
return ret;
}
if (iface->config.dhcpv6.state == NET_DHCPV6_CONFIRMING) {
ret = dhcpv6_add_option_header(
pkt, DHCPV6_OPTION_CODE_STATUS_CODE,
DHCPV6_OPTION_STATUS_CODE_HEADER_SIZE);
if (ret < 0) {
return ret;
}
ret = net_pkt_write_be16(pkt, DHCPV6_STATUS_SUCCESS);
if (ret < 0) {
return ret;
}
return 0;
}
ret = dhcpv6_add_option_ia_na(pkt, &test_ia_na, true);
if (ret < 0) {
return ret;
}
ret = dhcpv6_add_option_ia_pd(pkt, &test_ia_pd, true);
if (ret < 0) {
return ret;
}
return 0;
}
/* Verify that DHCPv6 client accepts Reply messages in Requesting, Confirming,
* Renewing and Rebinding states
*/
ZTEST(dhcpv6_tests, test_input_reply)
{
enum net_verdict result;
struct net_pkt *pkt;
enum net_dhcpv6_state state;
for (state = NET_DHCPV6_DISABLED; state <= NET_DHCPV6_BOUND; state++) {
test_ctx.iface->config.dhcpv6.state = state;
set_fake_server_duid(test_ctx.iface);
clear_test_addr_on_iface(test_ctx.iface);
pkt = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(pkt, "Failed to create pkt");
result = net_ipv6_input(pkt, false);
switch (state) {
case NET_DHCPV6_CONFIRMING:
case NET_DHCPV6_REQUESTING:
case NET_DHCPV6_RENEWING:
case NET_DHCPV6_REBINDING:
zassert_equal(result, NET_OK, "Message should've been processed");
/* Confirm is an exception, as it does not update
* address on an interface (only status OK is expected).
*/
if (state == NET_DHCPV6_CONFIRMING) {
break;
}
/* Verify that Reply actually updated DHPCv6 context. */
zassert_mem_equal(&test_ctx.iface->config.dhcpv6.addr,
&test_addr, sizeof(test_addr),
"Invalid address (state %s)",
net_dhcpv6_state_name(state));
zassert_mem_equal(&test_ctx.iface->config.dhcpv6.prefix,
&test_prefix, sizeof(test_prefix),
"Invalid prefix (state %s)",
net_dhcpv6_state_name(state));
zassert_equal(test_ctx.iface->config.dhcpv6.prefix_len,
test_prefix_len, "Invalid prefix len (state %s)",
net_dhcpv6_state_name(state));
break;
default:
zassert_equal(result, NET_DROP, "Should've drop the message");
break;
}
net_pkt_unref(pkt);
}
}
static void test_solicit_expect_request_send_reply(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_REQUEST);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_serverid(iface, pkt);
verify_dhcpv6_ia_na(iface, pkt, NULL);
verify_dhcpv6_ia_pd(iface, pkt, NULL, 0);
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_REQUESTING,
"Invalid state");
/* Reply with Reply message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_BOUND,
"Invalid state");
zassert_mem_equal(&test_ctx.iface->config.dhcpv6.addr,
&test_addr, sizeof(test_addr), "Invalid address");
zassert_mem_equal(&test_ctx.iface->config.dhcpv6.prefix,
&test_prefix, sizeof(test_prefix), "Invalid prefix");
zassert_equal(test_ctx.iface->config.dhcpv6.prefix_len,
test_prefix_len, "Invalid prefix len");
k_sem_give(&test_ctx.exchange_complete_sem);
}
static void test_solicit_expect_solicit_send_advertise(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_SOLICIT);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_ia_na(iface, pkt, NULL);
verify_dhcpv6_ia_pd(iface, pkt, NULL, 0);
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_SOLICITING,
"Invalid state");
zassert_equal(iface->config.dhcpv6.server_preference, -1,
"Invalid initial preference");
/* Update next expected packet handler */
set_dhcpv6_test_fn(test_solicit_expect_request_send_reply);
/* Reply with Advertise message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_ADVERTISE,
set_advertise_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_SOLICITING,
"Invalid state");
zassert_equal(iface->config.dhcpv6.server_preference, test_preference,
"Invalid initial preference");
zassert_equal(test_serverid.length, iface->config.dhcpv6.serverid.length,
"Invalid Server ID length");
zassert_mem_equal(&test_serverid.duid, &iface->config.dhcpv6.serverid.duid,
test_serverid.length, "Invalid Server ID value");
}
/* Verify that DHCPv6 client can handle standard exchange (Solicit/Request) */
ZTEST(dhcpv6_tests, test_solicit_exchange)
{
struct net_dhcpv6_params params = {
.request_addr = true,
.request_prefix = true,
};
struct net_if_ipv6_prefix *prefix;
struct net_if_addr *addr;
int ret;
test_ctx.reset_dhcpv6 = true;
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
set_dhcpv6_test_fn(test_solicit_expect_solicit_send_advertise);
net_dhcpv6_start(test_ctx.iface, &params);
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
addr = net_if_ipv6_addr_lookup_by_iface(test_ctx.iface, &test_addr);
prefix = net_if_ipv6_prefix_lookup(test_ctx.iface, &test_prefix,
test_prefix_len);
zassert_not_null(addr, "Address not configured on the interface");
zassert_not_null(prefix, "Prefix not configured on the interface");
}
static void expect_request_send_reply(struct net_if *iface, struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_REQUEST);
set_dhcpv6_test_fn(NULL);
/* Reply with Reply message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
k_sem_give(&test_ctx.exchange_complete_sem);
}
static void expect_solicit_send_advertise(struct net_if *iface, struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_SOLICIT);
set_dhcpv6_test_fn(expect_request_send_reply);
/* Reply with Advertise message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_ADVERTISE,
set_advertise_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
}
static void test_dhcpv6_start_and_enter_bound(struct net_dhcpv6_params *params)
{
int ret;
/* Set maximum preference to speed up the process. */
test_preference = DHCPV6_MAX_SERVER_PREFERENCE;
set_dhcpv6_test_fn(expect_solicit_send_advertise);
net_dhcpv6_start(test_ctx.iface, params);
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
zassert_equal(test_ctx.iface->config.dhcpv6.state, NET_DHCPV6_BOUND,
"Invalid state");
}
static void test_confirm_expect_confirm_send_reply(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_CONFIRM);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_CONFIRMING,
"Invalid state");
set_dhcpv6_test_fn(NULL);
/* Reply with Advertise message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_BOUND,
"Invalid state");
zassert_equal(test_serverid.length, iface->config.dhcpv6.serverid.length,
"Invalid Server ID length");
zassert_mem_equal(&test_serverid.duid, &iface->config.dhcpv6.serverid.duid,
test_serverid.length, "Invalid Server ID value");
k_sem_give(&test_ctx.exchange_complete_sem);
}
/* Verify that DHCPv6 client starts with Confirm when interface goes down and
* up again (no prefix).
*/
ZTEST(dhcpv6_tests, test_confirm_exchange_after_iface_down)
{
struct net_dhcpv6_params params = {
.request_addr = true,
.request_prefix = false,
};
struct net_if_addr *addr;
int ret;
test_ctx.reset_dhcpv6 = true;
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
test_dhcpv6_start_and_enter_bound(&params);
set_dhcpv6_test_fn(test_confirm_expect_confirm_send_reply);
net_if_down(test_ctx.iface);
net_if_up(test_ctx.iface);
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
addr = net_if_ipv6_addr_lookup_by_iface(test_ctx.iface, &test_addr);
zassert_not_null(addr, "Address not configured on the interface");
}
static void test_rebind_expect_rebind_send_reply(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_REBIND);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
verify_dhcpv6_ia_pd(iface, pkt, &test_prefix, test_prefix_len);
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_REBINDING,
"Invalid state");
set_dhcpv6_test_fn(NULL);
/* Reply with Advertise message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_BOUND,
"Invalid state");
zassert_equal(test_serverid.length, iface->config.dhcpv6.serverid.length,
"Invalid Server ID length");
zassert_mem_equal(&test_serverid.duid, &iface->config.dhcpv6.serverid.duid,
test_serverid.length, "Invalid Server ID value");
k_sem_give(&test_ctx.exchange_complete_sem);
}
/* Verify that DHCPv6 client starts with Rebind when interface goes down and
* up again (w/ prefix).
*/
ZTEST(dhcpv6_tests, test_rebind_exchange_after_iface_down)
{
struct net_dhcpv6_params params = {
.request_addr = true,
.request_prefix = true,
};
struct net_if_ipv6_prefix *prefix;
struct net_if_addr *addr;
int ret;
test_ctx.reset_dhcpv6 = true;
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
test_dhcpv6_start_and_enter_bound(&params);
set_dhcpv6_test_fn(test_rebind_expect_rebind_send_reply);
net_if_down(test_ctx.iface);
net_if_up(test_ctx.iface);
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
addr = net_if_ipv6_addr_lookup_by_iface(test_ctx.iface, &test_addr);
prefix = net_if_ipv6_prefix_lookup(test_ctx.iface, &test_prefix,
test_prefix_len);
zassert_not_null(addr, "Address not configured on the interface");
zassert_not_null(prefix, "Prefix not configured on the interface");
}
static void test_renew_expect_renew_send_reply(struct net_if *iface,
struct net_pkt *pkt)
{
struct net_pkt *reply;
int result;
/* Verify header */
verify_dhcpv6_header(iface, pkt, DHCPV6_MSG_TYPE_RENEW);
/* Verify options */
verify_dhcpv6_clientid(iface, pkt);
verify_dhcpv6_serverid(iface, pkt);
verify_dhcpv6_ia_na(iface, pkt, &test_addr);
verify_dhcpv6_ia_pd(iface, pkt, &test_prefix, test_prefix_len);
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_RENEWING,
"Invalid state");
set_dhcpv6_test_fn(NULL);
/* Reply with Advertise message */
reply = test_dhcpv6_create_message(test_ctx.iface,
DHCPV6_MSG_TYPE_REPLY,
set_reply_options);
zassert_not_null(reply, "Failed to create pkt");
result = net_ipv6_input(reply, false);
zassert_equal(result, NET_OK, "Message should've been processed");
/* Verify client state */
zassert_equal(iface->config.dhcpv6.state, NET_DHCPV6_BOUND,
"Invalid state");
zassert_equal(test_serverid.length, iface->config.dhcpv6.serverid.length,
"Invalid Server ID length");
zassert_mem_equal(&test_serverid.duid, &iface->config.dhcpv6.serverid.duid,
test_serverid.length, "Invalid Server ID value");
k_sem_give(&test_ctx.exchange_complete_sem);
}
/* Verify that DHCPv6 client proceeds with Renew when T1 timeout expires. */
ZTEST(dhcpv6_tests, test_renew_exchange_after_t1)
{
struct net_dhcpv6_params params = {
.request_addr = true,
.request_prefix = true,
};
struct net_if_ipv6_prefix *prefix;
struct net_if_addr *addr;
int ret;
test_ctx.reset_dhcpv6 = true;
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
test_dhcpv6_start_and_enter_bound(&params);
set_dhcpv6_test_fn(test_renew_expect_renew_send_reply);
/* Simulate T1 timeout */
test_ctx.iface->config.dhcpv6.t1 = k_uptime_get();
test_ctx.iface->config.dhcpv6.timeout = test_ctx.iface->config.dhcpv6.t1;
dhcpv6_reschedule();
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
addr = net_if_ipv6_addr_lookup_by_iface(test_ctx.iface, &test_addr);
prefix = net_if_ipv6_prefix_lookup(test_ctx.iface, &test_prefix,
test_prefix_len);
zassert_not_null(addr, "Address not configured on the interface");
zassert_not_null(prefix, "Prefix not configured on the interface");
}
/* Verify that DHCPv6 client proceeds with Rebind when T2 timeout expires. */
ZTEST(dhcpv6_tests, test_rebind_exchange_after_t2)
{
struct net_dhcpv6_params params = {
.request_addr = true,
.request_prefix = true,
};
struct net_if_ipv6_prefix *prefix;
struct net_if_addr *addr;
int ret;
test_ctx.reset_dhcpv6 = true;
memset(&test_ctx.iface->config.dhcpv6, 0,
sizeof(test_ctx.iface->config.dhcpv6));
test_dhcpv6_start_and_enter_bound(&params);
set_dhcpv6_test_fn(NULL);
/* Simulate T1 timeout */
test_ctx.iface->config.dhcpv6.t1 = k_uptime_get();
test_ctx.iface->config.dhcpv6.timeout = test_ctx.iface->config.dhcpv6.t1;
dhcpv6_reschedule();
/* Give a state machine a chance to run, we ignore Renew message. */
k_msleep(10);
set_dhcpv6_test_fn(test_rebind_expect_rebind_send_reply);
/* Simulate T2 timeout */
test_ctx.iface->config.dhcpv6.t2 = k_uptime_get();
test_ctx.iface->config.dhcpv6.timeout = test_ctx.iface->config.dhcpv6.t2;
dhcpv6_reschedule();
ret = k_sem_take(&test_ctx.exchange_complete_sem, K_SECONDS(2));
zassert_ok(ret, "Exchange not completed in required time");
addr = net_if_ipv6_addr_lookup_by_iface(test_ctx.iface, &test_addr);
prefix = net_if_ipv6_prefix_lookup(test_ctx.iface, &test_prefix,
test_prefix_len);
zassert_not_null(addr, "Address not configured on the interface");
zassert_not_null(prefix, "Prefix not configured on the interface");
}
ZTEST_SUITE(dhcpv6_tests, NULL, dhcpv6_tests_setup, dhcpv6_tests_before,
dhcpv6_tests_after, NULL);