Merge pull request #16731 from JKRhb/dhcp-stateless
sys/net/dhcpv6: Add stateless DHCPv6
This commit is contained in:
commit
96eb2c5050
@ -80,7 +80,9 @@ extern "C" {
|
|||||||
#define DHCPV6_OPT_IA_PD (25U) /**< identity association for prefix
|
#define DHCPV6_OPT_IA_PD (25U) /**< identity association for prefix
|
||||||
* delegation (IA_PD) option */
|
* delegation (IA_PD) option */
|
||||||
#define DHCPV6_OPT_IAPFX (26U) /**< IA prefix option */
|
#define DHCPV6_OPT_IAPFX (26U) /**< IA prefix option */
|
||||||
|
#define DHCPV6_OPT_IRT (32U) /**< Information Refresh Time Option */
|
||||||
#define DHCPV6_OPT_SMR (82U) /**< SOL_MAX_RT option */
|
#define DHCPV6_OPT_SMR (82U) /**< SOL_MAX_RT option */
|
||||||
|
#define DHCPV6_OPT_IMR (83U) /**< INF_MAX_RT option */
|
||||||
#define DHCPV6_OPT_MUD_URL (112U) /**< MUD URL option (see RFC 8520) */
|
#define DHCPV6_OPT_MUD_URL (112U) /**< MUD URL option (see RFC 8520) */
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
|
|||||||
@ -280,6 +280,29 @@ static inline uint32_t dhcpv6_client_addr_valid_until(unsigned netif,
|
|||||||
*/
|
*/
|
||||||
#define MAX_MUD_URL_LENGTH (0xFF - sizeof(dhcpv6_opt_mud_url_t))
|
#define MAX_MUD_URL_LENGTH (0xFF - sizeof(dhcpv6_opt_mud_url_t))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Definition of DHCPv6 client configuration modes.
|
||||||
|
*/
|
||||||
|
enum {
|
||||||
|
DHCPV6_CLIENT_CONF_MODE_INACTIVE,
|
||||||
|
DHCPV6_CLIENT_CONF_MODE_STATEFUL,
|
||||||
|
DHCPV6_CLIENT_CONF_MODE_STATELESS,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Changes the DHCPv6 client's configuration mode.
|
||||||
|
*
|
||||||
|
* @param[in] configuration_mode The new configuration mode.
|
||||||
|
*/
|
||||||
|
void dhcpv6_client_set_conf_mode(uint8_t configuration_mode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the DHCPv6 client's current configuration mode.
|
||||||
|
*
|
||||||
|
* @return The current configuration mode.
|
||||||
|
*/
|
||||||
|
uint8_t dhcpv6_client_get_conf_mode(void);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@ -67,6 +67,14 @@ extern "C" {
|
|||||||
|
|
||||||
#define DHCPV6_REB_TIMEOUT (10U) /**< REB_TIMEOUT (in sec) */
|
#define DHCPV6_REB_TIMEOUT (10U) /**< REB_TIMEOUT (in sec) */
|
||||||
#define DHCPV6_REB_MAX_RT (600U) /**< REB_MAX_RT (in sec) */
|
#define DHCPV6_REB_MAX_RT (600U) /**< REB_MAX_RT (in sec) */
|
||||||
|
|
||||||
|
#define DHCPV6_INF_MAX_DELAY (1U) /**< INF_MAX_DELAY (in sec) */
|
||||||
|
#define DHCPV6_INF_TIMEOUT (1U) /**< INF_TIMEOUT (in sec) */
|
||||||
|
#define DHCPV6_INF_MAX_RT (3600U) /**< INF_MAX_RT (in sec) */
|
||||||
|
|
||||||
|
#define DHCPV6_IRT_DEFAULT (86400U) /**< IRT_DEFAULT (in sec) */
|
||||||
|
#define DHCPV6_IRT_MINIMUM (600U) /**< IRT_MINIMUM (in sec) */
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
#define DHCPV6_DUID_MAX_LEN (128U) /**< maximum length of DUID */
|
#define DHCPV6_DUID_MAX_LEN (128U) /**< maximum length of DUID */
|
||||||
@ -286,6 +294,18 @@ typedef struct __attribute__((packed)) {
|
|||||||
uint8_t opts[]; /**< IAprefix options */
|
uint8_t opts[]; /**< IAprefix options */
|
||||||
} dhcpv6_opt_iapfx_t;
|
} dhcpv6_opt_iapfx_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DHCPv6 Information Refresh Time option format
|
||||||
|
* @see [RFC 8415, section 21.23]
|
||||||
|
* (https://tools.ietf.org/html/rfc8415#section-21.23)
|
||||||
|
*/
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
network_uint16_t type; /**< @ref DHCPV6_OPT_IRT */
|
||||||
|
network_uint16_t len; /**< always 4 */
|
||||||
|
network_uint32_t value; /**< Time duration relative to the
|
||||||
|
current time (in sec) */
|
||||||
|
} dhcpv6_opt_irt_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief DHCPv6 SOL_MAX_RT option format
|
* @brief DHCPv6 SOL_MAX_RT option format
|
||||||
* @see [RFC 8415, section 21.24]
|
* @see [RFC 8415, section 21.24]
|
||||||
@ -297,6 +317,17 @@ typedef struct __attribute__((packed)) {
|
|||||||
network_uint32_t value; /**< overriding value for SOL_MAX_RT (in sec) */
|
network_uint32_t value; /**< overriding value for SOL_MAX_RT (in sec) */
|
||||||
} dhcpv6_opt_smr_t;
|
} dhcpv6_opt_smr_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DHCPv6 INF_MAX_RT option format
|
||||||
|
* @see [RFC 8415, section 21.25]
|
||||||
|
* (https://tools.ietf.org/html/rfc8415#section-21.25)
|
||||||
|
*/
|
||||||
|
typedef struct __attribute__((packed)) {
|
||||||
|
network_uint16_t type; /**< @ref DHCPV6_OPT_IMR */
|
||||||
|
network_uint16_t len; /**< always 4 */
|
||||||
|
network_uint32_t value; /**< overriding value for INF_MAX_RT (in sec) */
|
||||||
|
} dhcpv6_opt_imr_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief MUD URL DHCPv6 option format
|
* @brief MUD URL DHCPv6 option format
|
||||||
* @see [RFC 8520, section 10]
|
* @see [RFC 8520, section 10]
|
||||||
|
|||||||
@ -88,7 +88,7 @@ static uint8_t duid[DHCPV6_CLIENT_DUID_LEN];
|
|||||||
static addr_lease_t addr_leases[CONFIG_DHCPV6_CLIENT_ADDR_LEASE_MAX];
|
static addr_lease_t addr_leases[CONFIG_DHCPV6_CLIENT_ADDR_LEASE_MAX];
|
||||||
static pfx_lease_t pfx_leases[CONFIG_DHCPV6_CLIENT_PFX_LEASE_MAX];
|
static pfx_lease_t pfx_leases[CONFIG_DHCPV6_CLIENT_PFX_LEASE_MAX];
|
||||||
static server_t server;
|
static server_t server;
|
||||||
static event_timeout_t solicit_renew_timeout, rebind_timeout;
|
static event_timeout_t solicit_renew_timeout, information_refresh_timeout, rebind_timeout;
|
||||||
static event_queue_t *event_queue;
|
static event_queue_t *event_queue;
|
||||||
static sock_udp_t sock;
|
static sock_udp_t sock;
|
||||||
static sock_udp_ep_t local = { .family = AF_INET6, .port = DHCPV6_CLIENT_PORT };
|
static sock_udp_ep_t local = { .family = AF_INET6, .port = DHCPV6_CLIENT_PORT };
|
||||||
@ -97,14 +97,17 @@ static sock_udp_ep_t remote = { .family = AF_INET6, .port = DHCPV6_SERVER_PORT,
|
|||||||
.ipv6 = DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS
|
.ipv6 = DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS
|
||||||
} };
|
} };
|
||||||
static uint32_t sol_max_rt = DHCPV6_SOL_MAX_RT;
|
static uint32_t sol_max_rt = DHCPV6_SOL_MAX_RT;
|
||||||
|
static uint32_t inf_max_rt = DHCPV6_INF_MAX_RT;
|
||||||
static uint32_t t2, rebind_time;
|
static uint32_t t2, rebind_time;
|
||||||
static uint32_t transaction_start;
|
static uint32_t transaction_start;
|
||||||
static uint32_t transaction_id;
|
static uint32_t transaction_id;
|
||||||
static uint8_t duid_len = sizeof(dhcpv6_duid_l2_t);
|
static uint8_t duid_len = sizeof(dhcpv6_duid_l2_t);
|
||||||
|
static uint8_t configuration_mode = DHCPV6_CLIENT_CONF_MODE_INACTIVE;
|
||||||
|
|
||||||
static const char mud_url[] = CONFIG_DHCPV6_CLIENT_MUD_URL;
|
static const char mud_url[] = CONFIG_DHCPV6_CLIENT_MUD_URL;
|
||||||
|
|
||||||
static void _post_solicit_servers(void);
|
static void _post_solicit_servers(void);
|
||||||
|
static void _refresh_information(event_t *event);
|
||||||
static void _solicit_servers(event_t *event);
|
static void _solicit_servers(event_t *event);
|
||||||
static void _request(event_t *event);
|
static void _request(event_t *event);
|
||||||
static void _renew(event_t *event);
|
static void _renew(event_t *event);
|
||||||
@ -116,6 +119,7 @@ static void _set_event_timeout_sec(event_timeout_t *timeout, event_t *event,
|
|||||||
uint32_t delay_sec);
|
uint32_t delay_sec);
|
||||||
static void _clear_event_timeout(event_timeout_t *timeout);
|
static void _clear_event_timeout(event_timeout_t *timeout);
|
||||||
|
|
||||||
|
static event_t refresh_information = { .handler = _refresh_information };
|
||||||
static event_t solicit_servers = { .handler = _solicit_servers };
|
static event_t solicit_servers = { .handler = _solicit_servers };
|
||||||
static event_t request = { .handler = _request };
|
static event_t request = { .handler = _request };
|
||||||
static event_t renew = { .handler = _renew };
|
static event_t renew = { .handler = _renew };
|
||||||
@ -202,16 +206,53 @@ void dhcpv6_client_init(event_queue_t *eq, uint16_t netif)
|
|||||||
remote.netif = netif;
|
remote.netif = netif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _restart(void)
|
||||||
|
{
|
||||||
|
|
||||||
|
_clear_event_timeout(&solicit_renew_timeout);
|
||||||
|
_clear_event_timeout(&rebind_timeout);
|
||||||
|
_clear_event_timeout(&information_refresh_timeout);
|
||||||
|
|
||||||
|
switch (configuration_mode)
|
||||||
|
{
|
||||||
|
case DHCPV6_CLIENT_CONF_MODE_INACTIVE:
|
||||||
|
return;
|
||||||
|
case DHCPV6_CLIENT_CONF_MODE_STATEFUL: {
|
||||||
|
uint32_t delay = random_uint32_range(0, DHCPV6_SOL_MAX_DELAY * MS_PER_SEC);
|
||||||
|
_set_event_timeout_ms(&solicit_renew_timeout, &solicit_servers, delay);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DHCPV6_CLIENT_CONF_MODE_STATELESS: {
|
||||||
|
uint32_t delay = random_uint32_range(0, DHCPV6_INF_MAX_DELAY * MS_PER_SEC);
|
||||||
|
_set_event_timeout_ms(&information_refresh_timeout, &refresh_information, delay);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
DEBUG("DHCPv6 Client: Invalid configuration mode!");
|
||||||
|
assert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dhcpv6_client_set_conf_mode(uint8_t _configuration_mode) {
|
||||||
|
if (configuration_mode != _configuration_mode) {
|
||||||
|
configuration_mode = _configuration_mode;
|
||||||
|
_restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t dhcpv6_client_get_conf_mode(void) {
|
||||||
|
return configuration_mode;
|
||||||
|
}
|
||||||
|
|
||||||
void dhcpv6_client_start(void)
|
void dhcpv6_client_start(void)
|
||||||
{
|
{
|
||||||
duid_len = dhcpv6_client_get_duid_l2(local.netif,
|
duid_len = dhcpv6_client_get_duid_l2(local.netif,
|
||||||
(dhcpv6_duid_l2_t *)&duid);
|
(dhcpv6_duid_l2_t *)&duid);
|
||||||
assert(event_queue != NULL);
|
assert(event_queue != NULL);
|
||||||
if (duid_len > 0) {
|
if (duid_len > 0) {
|
||||||
uint32_t delay = random_uint32_range(0, DHCPV6_SOL_MAX_DELAY * MS_PER_SEC);
|
|
||||||
|
|
||||||
sock_udp_create(&sock, &local, NULL, 0);
|
sock_udp_create(&sock, &local, NULL, 0);
|
||||||
_set_event_timeout_ms(&solicit_renew_timeout, &solicit_servers, delay);
|
_restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +287,8 @@ int dhcpv6_client_req_ia_na(unsigned netif)
|
|||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dhcpv6_client_set_conf_mode(DHCPV6_CLIENT_CONF_MODE_STATEFUL);
|
||||||
|
|
||||||
addr_lease_t *lease = NULL;
|
addr_lease_t *lease = NULL;
|
||||||
|
|
||||||
for (unsigned i = 0; i < CONFIG_DHCPV6_CLIENT_ADDR_LEASE_MAX; i++) {
|
for (unsigned i = 0; i < CONFIG_DHCPV6_CLIENT_ADDR_LEASE_MAX; i++) {
|
||||||
@ -532,6 +575,10 @@ static bool _check_cid_opt(dhcpv6_opt_duid_t *cid)
|
|||||||
|
|
||||||
static bool _check_sid_opt(dhcpv6_opt_duid_t *sid)
|
static bool _check_sid_opt(dhcpv6_opt_duid_t *sid)
|
||||||
{
|
{
|
||||||
|
if (configuration_mode == DHCPV6_CLIENT_CONF_MODE_STATELESS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
if (IS_ACTIVE(ENABLE_DEBUG)) {
|
||||||
if ((byteorder_ntohs(sid->len) != server.duid_len) ||
|
if ((byteorder_ntohs(sid->len) != server.duid_len) ||
|
||||||
(memcmp(sid->duid, server.duid.u8, server.duid_len) != 0)) {
|
(memcmp(sid->duid, server.duid.u8, server.duid_len) != 0)) {
|
||||||
@ -932,13 +979,15 @@ static bool _parse_ia_na_option(dhcpv6_opt_ia_na_t *ia_na)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _parse_reply(uint8_t *rep, size_t len)
|
static bool _parse_reply(uint8_t *rep, size_t len, uint8_t request_type)
|
||||||
{
|
{
|
||||||
dhcpv6_opt_duid_t *cid = NULL, *sid = NULL;
|
dhcpv6_opt_duid_t *cid = NULL, *sid = NULL;
|
||||||
dhcpv6_opt_ia_pd_t *ia_pd = NULL;
|
dhcpv6_opt_ia_pd_t *ia_pd = NULL;
|
||||||
dhcpv6_opt_ia_na_t *ia_na = NULL;
|
dhcpv6_opt_ia_na_t *ia_na = NULL;
|
||||||
dhcpv6_opt_status_t *status = NULL;
|
dhcpv6_opt_status_t *status = NULL;
|
||||||
dhcpv6_opt_smr_t *smr = NULL;
|
dhcpv6_opt_smr_t *smr = NULL;
|
||||||
|
dhcpv6_opt_imr_t *imr = NULL;
|
||||||
|
dhcpv6_opt_irt_t *irt = NULL;
|
||||||
size_t orig_len = len;
|
size_t orig_len = len;
|
||||||
|
|
||||||
DEBUG("DHCPv6 client: received REPLY\n");
|
DEBUG("DHCPv6 client: received REPLY\n");
|
||||||
@ -975,6 +1024,12 @@ static bool _parse_reply(uint8_t *rep, size_t len)
|
|||||||
case DHCPV6_OPT_SMR:
|
case DHCPV6_OPT_SMR:
|
||||||
smr = (dhcpv6_opt_smr_t *)opt;
|
smr = (dhcpv6_opt_smr_t *)opt;
|
||||||
break;
|
break;
|
||||||
|
case DHCPV6_OPT_IMR:
|
||||||
|
imr = (dhcpv6_opt_imr_t *)opt;
|
||||||
|
break;
|
||||||
|
case DHCPV6_OPT_IRT:
|
||||||
|
irt = (dhcpv6_opt_irt_t *)opt;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -992,6 +1047,21 @@ static bool _parse_reply(uint8_t *rep, size_t len)
|
|||||||
if (smr != NULL) {
|
if (smr != NULL) {
|
||||||
sol_max_rt = byteorder_ntohl(smr->value);
|
sol_max_rt = byteorder_ntohl(smr->value);
|
||||||
}
|
}
|
||||||
|
if (imr != NULL) {
|
||||||
|
inf_max_rt = byteorder_ntohl(imr->value);
|
||||||
|
}
|
||||||
|
if (request_type == DHCPV6_INFO_REQUEST){
|
||||||
|
uint32_t refresh_time;
|
||||||
|
if (irt != NULL) {
|
||||||
|
refresh_time = byteorder_ntohl(irt->value);
|
||||||
|
if (refresh_time < DHCPV6_IRT_MINIMUM) {
|
||||||
|
refresh_time = DHCPV6_IRT_MINIMUM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refresh_time = DHCPV6_IRT_DEFAULT;
|
||||||
|
}
|
||||||
|
_set_event_timeout_sec(&information_refresh_timeout, &refresh_information, refresh_time);
|
||||||
|
}
|
||||||
if (!_check_status_opt(status)) {
|
if (!_check_status_opt(status)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1028,19 +1098,17 @@ static bool _parse_reply(uint8_t *rep, size_t len)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t _compose_message(dhcpv6_msg_t *msg, uint8_t type)
|
static size_t _compose_message(dhcpv6_msg_t *msg, uint8_t type, bool reconfigure)
|
||||||
{
|
{
|
||||||
msg->type = type;
|
msg->type = type;
|
||||||
_generate_tid();
|
_generate_tid();
|
||||||
_set_tid(msg->tid);
|
_set_tid(msg->tid);
|
||||||
uint16_t oro_opts[] = { DHCPV6_OPT_SMR };
|
|
||||||
size_t msg_len = sizeof(dhcpv6_msg_t);
|
size_t msg_len = sizeof(dhcpv6_msg_t);
|
||||||
|
|
||||||
transaction_start = _now_cs();
|
transaction_start = _now_cs();
|
||||||
|
|
||||||
msg_len += _compose_cid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
msg_len += _compose_cid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
||||||
|
|
||||||
if (type != DHCPV6_REBIND && type != DHCPV6_SOLICIT) {
|
if (type != DHCPV6_REBIND && type != DHCPV6_SOLICIT &&
|
||||||
|
(type != DHCPV6_INFO_REQUEST && !reconfigure)) {
|
||||||
/* See RFC 8415, Appendix B */
|
/* See RFC 8415, Appendix B */
|
||||||
msg_len += _compose_sid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
msg_len += _compose_sid_opt((dhcpv6_opt_duid_t *)&send_buf[msg_len]);
|
||||||
}
|
}
|
||||||
@ -1048,8 +1116,19 @@ static size_t _compose_message(dhcpv6_msg_t *msg, uint8_t type)
|
|||||||
msg_len += _compose_mud_url_opt((dhcpv6_opt_mud_url_t *)&send_buf[msg_len],
|
msg_len += _compose_mud_url_opt((dhcpv6_opt_mud_url_t *)&send_buf[msg_len],
|
||||||
sizeof(send_buf) - msg_len);
|
sizeof(send_buf) - msg_len);
|
||||||
|
|
||||||
msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], oro_opts,
|
if (type == DHCPV6_INFO_REQUEST) {
|
||||||
ARRAY_SIZE(oro_opts));
|
uint16_t info_req_oro_opts[] = {
|
||||||
|
DHCPV6_OPT_SMR,
|
||||||
|
DHCPV6_OPT_IRT,
|
||||||
|
DHCPV6_OPT_IMR,
|
||||||
|
};
|
||||||
|
msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], info_req_oro_opts,
|
||||||
|
ARRAY_SIZE(info_req_oro_opts));
|
||||||
|
} else {
|
||||||
|
uint16_t general_oro_opts[] = {DHCPV6_OPT_SMR};
|
||||||
|
msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], general_oro_opts,
|
||||||
|
ARRAY_SIZE(general_oro_opts));
|
||||||
|
}
|
||||||
|
|
||||||
msg_len += _add_ia_na(&send_buf[msg_len], sizeof(send_buf) - msg_len);
|
msg_len += _add_ia_na(&send_buf[msg_len], sizeof(send_buf) - msg_len);
|
||||||
msg_len += _add_ia_pd_from_config(&send_buf[msg_len], sizeof(send_buf) - msg_len);
|
msg_len += _add_ia_pd_from_config(&send_buf[msg_len], sizeof(send_buf) - msg_len);
|
||||||
@ -1069,7 +1148,7 @@ static void _solicit_servers(event_t *event)
|
|||||||
|
|
||||||
(void)event;
|
(void)event;
|
||||||
|
|
||||||
msg_len = _compose_message(msg, DHCPV6_SOLICIT);
|
msg_len = _compose_message(msg, DHCPV6_SOLICIT, false);
|
||||||
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
||||||
msg_len += _compose_elapsed_time_opt(time);
|
msg_len += _compose_elapsed_time_opt(time);
|
||||||
|
|
||||||
@ -1151,7 +1230,7 @@ static uint32_t _calculate_mrd_from_leases(void)
|
|||||||
return mrd;
|
return mrd;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _request_renew_rebind(uint8_t type)
|
static void _request_renew_rebind(uint8_t type, bool reconfigure)
|
||||||
{
|
{
|
||||||
dhcpv6_msg_t *msg = (dhcpv6_msg_t *)&send_buf[0];
|
dhcpv6_msg_t *msg = (dhcpv6_msg_t *)&send_buf[0];
|
||||||
dhcpv6_opt_elapsed_time_t *time;
|
dhcpv6_opt_elapsed_time_t *time;
|
||||||
@ -1165,6 +1244,10 @@ static void _request_renew_rebind(uint8_t type)
|
|||||||
uint32_t mrd = 0;
|
uint32_t mrd = 0;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case DHCPV6_INFO_REQUEST:
|
||||||
|
irt = DHCPV6_INF_TIMEOUT;
|
||||||
|
mrt = DHCPV6_OPT_IMR;
|
||||||
|
break;
|
||||||
case DHCPV6_REQUEST:
|
case DHCPV6_REQUEST:
|
||||||
irt = DHCPV6_REQ_TIMEOUT;
|
irt = DHCPV6_REQ_TIMEOUT;
|
||||||
mrt = DHCPV6_REQ_MAX_RT;
|
mrt = DHCPV6_REQ_MAX_RT;
|
||||||
@ -1190,8 +1273,14 @@ static void _request_renew_rebind(uint8_t type)
|
|||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == DHCPV6_INFO_REQUEST) {
|
||||||
|
retrans_timeout = _irt_ms(inf_max_rt, false);
|
||||||
|
} else {
|
||||||
retrans_timeout = _irt_ms(irt, false);
|
retrans_timeout = _irt_ms(irt, false);
|
||||||
msg_len = _compose_message(msg, type);
|
}
|
||||||
|
|
||||||
|
msg_len = _compose_message(msg, type, reconfigure);
|
||||||
|
|
||||||
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
time = (dhcpv6_opt_elapsed_time_t *)&send_buf[msg_len];
|
||||||
msg_len += _compose_elapsed_time_opt(time);
|
msg_len += _compose_elapsed_time_opt(time);
|
||||||
@ -1215,7 +1304,7 @@ static void _request_renew_rebind(uint8_t type)
|
|||||||
sock_udp_send(&sock, send_buf, msg_len, &remote);
|
sock_udp_send(&sock, send_buf, msg_len, &remote);
|
||||||
}
|
}
|
||||||
if ((res > 0) && (recv_buf[0] == DHCPV6_REPLY)) {
|
if ((res > 0) && (recv_buf[0] == DHCPV6_REPLY)) {
|
||||||
if (!_parse_reply(recv_buf, res)) {
|
if (!_parse_reply(recv_buf, res, type)) {
|
||||||
/* try again */
|
/* try again */
|
||||||
event_post(event_queue, &request);
|
event_post(event_queue, &request);
|
||||||
}
|
}
|
||||||
@ -1229,21 +1318,28 @@ static void _request(event_t *event)
|
|||||||
{
|
{
|
||||||
(void)event;
|
(void)event;
|
||||||
DEBUG("DHCPv6 client: send REQUEST\n");
|
DEBUG("DHCPv6 client: send REQUEST\n");
|
||||||
_request_renew_rebind(DHCPV6_REQUEST);
|
_request_renew_rebind(DHCPV6_REQUEST, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _renew(event_t *event)
|
static void _renew(event_t *event)
|
||||||
{
|
{
|
||||||
(void)event;
|
(void)event;
|
||||||
DEBUG("DHCPv6 client: send RENEW\n");
|
DEBUG("DHCPv6 client: send RENEW\n");
|
||||||
_request_renew_rebind(DHCPV6_RENEW);
|
_request_renew_rebind(DHCPV6_RENEW, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _rebind(event_t *event)
|
static void _rebind(event_t *event)
|
||||||
{
|
{
|
||||||
(void)event;
|
(void)event;
|
||||||
DEBUG("DHCPv6 client: send REBIND\n");
|
DEBUG("DHCPv6 client: send REBIND\n");
|
||||||
_request_renew_rebind(DHCPV6_REBIND);
|
_request_renew_rebind(DHCPV6_REBIND, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _refresh_information(event_t *event)
|
||||||
|
{
|
||||||
|
(void)event;
|
||||||
|
DEBUG("DHCPv6 client: send INFORMATION REQUEST\n");
|
||||||
|
_request_renew_rebind(DHCPV6_INFO_REQUEST, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _set_event_timeout_ms(event_timeout_t *timeout, event_t *event,
|
static void _set_event_timeout_ms(event_timeout_t *timeout, event_t *event,
|
||||||
|
|||||||
@ -133,6 +133,8 @@ static void *_dhcpv6_cl_simple_pd_thread(void *args)
|
|||||||
dhcpv6_client_init(&event_queue, upstream_netif->pid);
|
dhcpv6_client_init(&event_queue, upstream_netif->pid);
|
||||||
/* configure client to request prefix delegation for WPAN interfaces */
|
/* configure client to request prefix delegation for WPAN interfaces */
|
||||||
_configure_dhcpv6_client();
|
_configure_dhcpv6_client();
|
||||||
|
/* set client configuration mode to stateful */
|
||||||
|
dhcpv6_client_set_conf_mode(DHCPV6_CLIENT_CONF_MODE_STATEFUL);
|
||||||
/* start DHCPv6 client */
|
/* start DHCPv6 client */
|
||||||
dhcpv6_client_start();
|
dhcpv6_client_start();
|
||||||
/* start event loop of DHCPv6 client */
|
/* start event loop of DHCPv6 client */
|
||||||
|
|||||||
@ -32,6 +32,9 @@
|
|||||||
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_DNS)
|
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_DNS)
|
||||||
#include "net/sock/dns.h"
|
#include "net/sock/dns.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if IS_ACTIVE(MODULE_DHCPV6_CLIENT)
|
||||||
|
#include "net/dhcpv6/client.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "_nib-internal.h"
|
#include "_nib-internal.h"
|
||||||
#include "_nib-arsm.h"
|
#include "_nib-arsm.h"
|
||||||
@ -794,6 +797,20 @@ static void _handle_rtr_adv(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6,
|
|||||||
if (dr == NULL) {
|
if (dr == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#if IS_ACTIVE(MODULE_DHCPV6_CLIENT)
|
||||||
|
uint8_t current_conf_mode = dhcpv6_client_get_conf_mode();
|
||||||
|
if (rtr_adv->flags & NDP_RTR_ADV_FLAGS_M) {
|
||||||
|
if (IS_USED(MODULE_DHCPV6_CLIENT_IA_NA)) {
|
||||||
|
netif->ipv6.aac_mode |= GNRC_NETIF_AAC_DHCP;
|
||||||
|
dhcpv6_client_req_ia_na(netif->pid);
|
||||||
|
} else {
|
||||||
|
dhcpv6_client_set_conf_mode(DHCPV6_CLIENT_CONF_MODE_STATEFUL);
|
||||||
|
}
|
||||||
|
} else if ((rtr_adv->flags & NDP_RTR_ADV_FLAGS_O) &&
|
||||||
|
(current_conf_mode != DHCPV6_CLIENT_CONF_MODE_STATEFUL)) {
|
||||||
|
dhcpv6_client_set_conf_mode(DHCPV6_CLIENT_CONF_MODE_STATELESS);
|
||||||
|
}
|
||||||
|
#endif /* MODULE_DHCPV6_CLIENT */
|
||||||
|
|
||||||
/* stop sending router solicitations
|
/* stop sending router solicitations
|
||||||
* see https://tools.ietf.org/html/rfc4861#section-6.3.7 */
|
* see https://tools.ietf.org/html/rfc4861#section-6.3.7 */
|
||||||
|
|||||||
@ -44,6 +44,8 @@ void *_dhcpv6_client_thread(void *args)
|
|||||||
/* configure client to request prefix delegation of /64 subnet
|
/* configure client to request prefix delegation of /64 subnet
|
||||||
* interface netif */
|
* interface netif */
|
||||||
dhcpv6_client_req_ia_pd(netif->pid, 64U);
|
dhcpv6_client_req_ia_pd(netif->pid, 64U);
|
||||||
|
/* set client configuration mode to stateful */
|
||||||
|
dhcpv6_client_set_conf_mode(DHCPV6_CLIENT_CONF_MODE_STATEFUL);
|
||||||
/* start DHCPv6 client */
|
/* start DHCPv6 client */
|
||||||
dhcpv6_client_start();
|
dhcpv6_client_start();
|
||||||
/* start event loop of DHCPv6 client */
|
/* start event loop of DHCPv6 client */
|
||||||
|
|||||||
48
tests/gnrc_dhcpv6_client_stateless/Makefile
Normal file
48
tests/gnrc_dhcpv6_client_stateless/Makefile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
DEVELHELP := 1
|
||||||
|
include $(CURDIR)/../Makefile.tests_common
|
||||||
|
|
||||||
|
export TAP ?= tap0
|
||||||
|
|
||||||
|
USEMODULE += auto_init_gnrc_netif
|
||||||
|
USEMODULE += dhcpv6_client_mud_url
|
||||||
|
USEMODULE += gnrc_dhcpv6_client
|
||||||
|
USEMODULE += gnrc_ipv6_default
|
||||||
|
USEMODULE += auto_init_dhcpv6_client
|
||||||
|
USEMODULE += gnrc_netdev_default
|
||||||
|
USEMODULE += gnrc_pktdump
|
||||||
|
USEMODULE += ps
|
||||||
|
USEMODULE += shell
|
||||||
|
USEMODULE += shell_commands
|
||||||
|
|
||||||
|
# use Ethernet as link-layer protocol
|
||||||
|
ifeq (native,$(BOARD))
|
||||||
|
# Has to be provided here and not in Makefile.dep, so TERMFLAGS are properly
|
||||||
|
# configured
|
||||||
|
USEMODULE += netdev_default
|
||||||
|
IFACE ?= tapbr0
|
||||||
|
else
|
||||||
|
IFACE ?= tap0
|
||||||
|
ETHOS_BAUDRATE ?= 115200
|
||||||
|
CFLAGS += -DETHOS_BAUDRATE=$(ETHOS_BAUDRATE)
|
||||||
|
TERMPROG ?= sudo $(RIOTBASE)/dist/tools/ethos/ethos
|
||||||
|
TERMFLAGS ?= $(IFACE) $(PORT) $(ETHOS_BAUDRATE)
|
||||||
|
TERMDEPS += ethos
|
||||||
|
endif
|
||||||
|
|
||||||
|
# The test requires some setup and to be run as root
|
||||||
|
# So it cannot currently be run on CI
|
||||||
|
TEST_ON_CI_BLACKLIST += all
|
||||||
|
|
||||||
|
# As there is an 'app.config' we want to explicitly disable Kconfig by setting
|
||||||
|
# the variable to empty
|
||||||
|
SHOULD_RUN_KCONFIG ?=
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
|
|
||||||
|
|
||||||
|
ifeq (,$(filter native,$(BOARD)))
|
||||||
|
.PHONY: ethos
|
||||||
|
|
||||||
|
ethos:
|
||||||
|
$(Q)env -u CC -u CFLAGS $(MAKE) -C $(RIOTBASE)/dist/tools/ethos
|
||||||
|
endif
|
||||||
4
tests/gnrc_dhcpv6_client_stateless/Makefile.board.dep
Normal file
4
tests/gnrc_dhcpv6_client_stateless/Makefile.board.dep
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Put board specific dependencies here
|
||||||
|
ifneq (native,$(BOARD))
|
||||||
|
USEMODULE += stdio_ethos
|
||||||
|
endif
|
||||||
51
tests/gnrc_dhcpv6_client_stateless/Makefile.ci
Normal file
51
tests/gnrc_dhcpv6_client_stateless/Makefile.ci
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
BOARD_INSUFFICIENT_MEMORY := \
|
||||||
|
airfy-beacon \
|
||||||
|
arduino-duemilanove \
|
||||||
|
arduino-leonardo \
|
||||||
|
arduino-mega2560 \
|
||||||
|
arduino-nano \
|
||||||
|
arduino-uno \
|
||||||
|
atmega1284p \
|
||||||
|
atmega328p \
|
||||||
|
atmega328p-xplained-mini \
|
||||||
|
atxmega-a1u-xpro \
|
||||||
|
atxmega-a3bu-xplained \
|
||||||
|
bluepill-stm32f030c8 \
|
||||||
|
b-l072z-lrwan1 \
|
||||||
|
derfmega128 \
|
||||||
|
hifive1 \
|
||||||
|
hifive1b \
|
||||||
|
i-nucleo-lrwan1 \
|
||||||
|
im880b \
|
||||||
|
lsn50 \
|
||||||
|
mega-xplained \
|
||||||
|
microbit \
|
||||||
|
microduino-corerf \
|
||||||
|
msb-430 \
|
||||||
|
msb-430h \
|
||||||
|
nrf51dongle \
|
||||||
|
nrf6310 \
|
||||||
|
nucleo-f030r8 \
|
||||||
|
nucleo-f031k6 \
|
||||||
|
nucleo-f042k6 \
|
||||||
|
nucleo-f070rb \
|
||||||
|
nucleo-f072rb \
|
||||||
|
nucleo-f303k8 \
|
||||||
|
nucleo-f334r8 \
|
||||||
|
nucleo-l011k4 \
|
||||||
|
nucleo-l031k6 \
|
||||||
|
nucleo-l053r8 \
|
||||||
|
samd10-xmini \
|
||||||
|
saml10-xpro \
|
||||||
|
saml11-xpro \
|
||||||
|
slstk3400a \
|
||||||
|
stk3200 \
|
||||||
|
stm32f030f4-demo \
|
||||||
|
stm32f0discovery \
|
||||||
|
stm32l0538-disco \
|
||||||
|
telosb \
|
||||||
|
waspmote-pro \
|
||||||
|
yunjia-nrf51822 \
|
||||||
|
z1 \
|
||||||
|
zigduino \
|
||||||
|
#
|
||||||
43
tests/gnrc_dhcpv6_client_stateless/README.md
Normal file
43
tests/gnrc_dhcpv6_client_stateless/README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# `gnrc_dhcpv6_client_stateless` test
|
||||||
|
|
||||||
|
This test utilizes [scapy] to test the DHCPv6 client configuration for
|
||||||
|
stateless DHCP.
|
||||||
|
|
||||||
|
The protocol procedure is modelled using a scapy [Automaton] by first waiting for an
|
||||||
|
NDP Router Solicitation (RS) which is replied to with an NDP Router Advertisement (RA).
|
||||||
|
The RA contains a set O-bit which indicates that additional information can be acquired by sending
|
||||||
|
a DHCPv6 Information Request (IR). The Automaton now waits for an IR, expects it to contain a
|
||||||
|
number of options, and sends a DHCPv6 Reply back to the client.
|
||||||
|
After this procedure is completed, a check for a correctly assigned global IP address (from the RA
|
||||||
|
using SLAAC) is performed. If this final assertion is correct, the test succeeds.
|
||||||
|
|
||||||
|
The procedure is visualized in the following graph:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To test, compile and flash the application to any board of your liking (since
|
||||||
|
`ethos` is used to communicate with non-native boards it really doesn't matter
|
||||||
|
as long as the application fits).
|
||||||
|
|
||||||
|
```
|
||||||
|
make flash
|
||||||
|
```
|
||||||
|
|
||||||
|
And run the tests using
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo make test-as-root
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that root privileges are required since `scapy` needs to construct Ethernet
|
||||||
|
frames to properly communicate over the TAP interface.
|
||||||
|
|
||||||
|
The test succeeds if you see the string `SUCCESS`.
|
||||||
|
|
||||||
|
If any problems are encountered (i.e. if the test prints the string `FAILED`),
|
||||||
|
set the echo parameter in the `run()` function at the bottom of the test script
|
||||||
|
(tests-as-root/01-run.py) to `True`. The test script will then offer a more detailed
|
||||||
|
output.
|
||||||
|
|
||||||
|
[scapy]: https://scapy.readthedocs.io/en/latest/
|
||||||
|
[Automaton]: https://scapy.readthedocs.io/en/latest/api/scapy.automaton.html
|
||||||
3
tests/gnrc_dhcpv6_client_stateless/app.config
Normal file
3
tests/gnrc_dhcpv6_client_stateless/app.config
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
CONFIG_KCONFIG_USEMODULE_GNRC_IPV6_NIB=y
|
||||||
|
CONFIG_GNRC_IPV6_NIB_ARSM=y
|
||||||
|
CONFIG_GNRC_IPV6_NIB_SLAAC=y
|
||||||
27
tests/gnrc_dhcpv6_client_stateless/main.c
Normal file
27
tests/gnrc_dhcpv6_client_stateless/main.c
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Freie Universität Berlin
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "shell.h"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
char line_buf[SHELL_DEFAULT_BUFSIZE];
|
||||||
|
shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE);
|
||||||
|
|
||||||
|
/* should be never reached */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @} */
|
||||||
47
tests/gnrc_dhcpv6_client_stateless/test-graph.svg
Normal file
47
tests/gnrc_dhcpv6_client_stateless/test-graph.svg
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||||
|
-->
|
||||||
|
<!-- Title: Automaton_metaclass Pages: 1 -->
|
||||||
|
<svg width="233pt" height="248pt"
|
||||||
|
viewBox="0.00 0.00 232.88 248.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 244)">
|
||||||
|
<title>Automaton_metaclass</title>
|
||||||
|
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-244 228.88,-244 228.88,4 -4,4"/>
|
||||||
|
<!-- WAITING_FOR_NDP_RS -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>WAITING_FOR_NDP_RS</title>
|
||||||
|
<polygon fill="#0000ff" stroke="#000000" points="195.94,-240 28.94,-240 28.94,-204 195.94,-204 195.94,-240"/>
|
||||||
|
<text text-anchor="middle" x="112.44" y="-218.3" font-family="Times,serif" font-size="14.00" fill="#000000">WAITING_FOR_NDP_RS</text>
|
||||||
|
</g>
|
||||||
|
<!-- WAITING_FOR_DHCP_IR -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>WAITING_FOR_DHCP_IR</title>
|
||||||
|
<ellipse fill="none" stroke="#000000" cx="112.44" cy="-120" rx="112.3801" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="112.44" y="-116.3" font-family="Times,serif" font-size="14.00" fill="#000000">WAITING_FOR_DHCP_IR</text>
|
||||||
|
</g>
|
||||||
|
<!-- WAITING_FOR_NDP_RS->WAITING_FOR_DHCP_IR -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>WAITING_FOR_NDP_RS->WAITING_FOR_DHCP_IR</title>
|
||||||
|
<path fill="none" stroke="#ff0000" d="M112.44,-203.7644C112.44,-188.317 112.44,-165.9149 112.44,-148.164"/>
|
||||||
|
<polygon fill="#ff0000" stroke="#ff0000" points="115.9401,-148.0776 112.44,-138.0777 108.9401,-148.0777 115.9401,-148.0776"/>
|
||||||
|
<text text-anchor="start" x="112.44" y="-174.8" font-family="Times,serif" font-size="14.00" fill="#000000">received_ICMP</text>
|
||||||
|
<text text-anchor="middle" x="156.44" y="-159.8" font-family="Times,serif" font-size="14.00" fill="#000000">>[on_NDP_RS]</text>
|
||||||
|
</g>
|
||||||
|
<!-- END -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>END</title>
|
||||||
|
<polygon fill="#00ff00" stroke="#000000" points="143.3474,-10.5442 143.3474,-25.4558 125.2423,-36 99.6378,-36 81.5326,-25.4558 81.5326,-10.5442 99.6378,0 125.2423,0 143.3474,-10.5442"/>
|
||||||
|
<text text-anchor="middle" x="112.44" y="-14.3" font-family="Times,serif" font-size="14.00" fill="#000000">END</text>
|
||||||
|
</g>
|
||||||
|
<!-- WAITING_FOR_DHCP_IR->END -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>WAITING_FOR_DHCP_IR->END</title>
|
||||||
|
<path fill="none" stroke="#ff0000" d="M112.44,-101.7644C112.44,-86.317 112.44,-63.9149 112.44,-46.164"/>
|
||||||
|
<polygon fill="#ff0000" stroke="#ff0000" points="115.9401,-46.0776 112.44,-36.0777 108.9401,-46.0777 115.9401,-46.0776"/>
|
||||||
|
<text text-anchor="start" x="112.44" y="-72.8" font-family="Times,serif" font-size="14.00" fill="#000000">received_DHCPv6</text>
|
||||||
|
<text text-anchor="middle" x="166.44" y="-57.8" font-family="Times,serif" font-size="14.00" fill="#000000">>[on_DHCPv6_IR]</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
250
tests/gnrc_dhcpv6_client_stateless/tests-as-root/01-run.py
Executable file
250
tests/gnrc_dhcpv6_client_stateless/tests-as-root/01-run.py
Executable file
@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (C) 2018 Freie Universität Berlin
|
||||||
|
# 2021 Jan Romann <jan.romann@uni-bremen.de>
|
||||||
|
#
|
||||||
|
# This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
# General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
# directory for more details.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pexpect
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from scapy.all import (
|
||||||
|
DHCP6_Reply,
|
||||||
|
DHCP6_InfoRequest,
|
||||||
|
ICMPv6NDOptSrcLLAddr,
|
||||||
|
ICMPv6NDOptMTU,
|
||||||
|
sendp,
|
||||||
|
Ether,
|
||||||
|
IPv6,
|
||||||
|
UDP,
|
||||||
|
ICMPv6ND_RS,
|
||||||
|
ICMPv6ND_RA,
|
||||||
|
DHCP6OptClientId,
|
||||||
|
DHCP6OptServerId,
|
||||||
|
ICMPv6NDOptPrefixInfo,
|
||||||
|
DUID_LL,
|
||||||
|
Automaton,
|
||||||
|
ATMT,
|
||||||
|
)
|
||||||
|
from testrunner import run
|
||||||
|
|
||||||
|
try:
|
||||||
|
from scapy.all import DHCP6OptMudUrl
|
||||||
|
|
||||||
|
mud_option_loaded = True
|
||||||
|
except ImportError:
|
||||||
|
from scapy.all import DHCP6OptUnknown
|
||||||
|
|
||||||
|
DHCP6OptMudUrl = DHCP6OptUnknown
|
||||||
|
mud_option_loaded = False
|
||||||
|
|
||||||
|
TIMEOUT = 1
|
||||||
|
|
||||||
|
MUD_OPTION_CODE = 112
|
||||||
|
MUD_TEST_URL = b"https://example.org"
|
||||||
|
|
||||||
|
|
||||||
|
class StatelessDHCPv6Test(Automaton):
|
||||||
|
"""
|
||||||
|
Scapy Automaton used for performing stateless DHCPv6 tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parse_args(self, child, **kwargs):
|
||||||
|
"""
|
||||||
|
Initializes the Automaton.
|
||||||
|
|
||||||
|
Receives the TAP interface that is being
|
||||||
|
used as a keyword argument (`iface`).
|
||||||
|
Also generates a randomized prefix for
|
||||||
|
SLAAC testing.
|
||||||
|
"""
|
||||||
|
super().parse_args(**kwargs)
|
||||||
|
self.child = child
|
||||||
|
self.iface = kwargs["iface"]
|
||||||
|
self.prefix = "2001:db8:{:x}:{:x}::".format(
|
||||||
|
random.randint(0, 0xFFFF), random.randint(0, 0xFFFF)
|
||||||
|
)
|
||||||
|
|
||||||
|
@ATMT.state(initial=1)
|
||||||
|
def WAITING_FOR_NDP_RS(self):
|
||||||
|
"""
|
||||||
|
The initial state.
|
||||||
|
|
||||||
|
The Automaton waits for an NDP Router Solication.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@ATMT.receive_condition(WAITING_FOR_NDP_RS, prio=1)
|
||||||
|
def received_ICMP(self, pkt):
|
||||||
|
"""
|
||||||
|
Checks if an incoming packet contains an NDP Router Solicitaion (RS).
|
||||||
|
|
||||||
|
If an RS has been received, `on_NDP_RS` will be called with the packet
|
||||||
|
as an argument and the Automaton's state will change to
|
||||||
|
`WAITING_FOR_DHCP_IR`.
|
||||||
|
"""
|
||||||
|
if ICMPv6ND_RS in pkt:
|
||||||
|
raise self.WAITING_FOR_DHCP_IR().action_parameters(pkt)
|
||||||
|
|
||||||
|
@ATMT.action(received_ICMP)
|
||||||
|
def on_NDP_RS(self, pkt):
|
||||||
|
"""
|
||||||
|
Called when an NDP Router Solicitation has been received.
|
||||||
|
|
||||||
|
Calls `send_RA` to send an NDP Router Advertisement to all IPv6 nodes.
|
||||||
|
"""
|
||||||
|
self.send_RA()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_router_advertise_header():
|
||||||
|
"""
|
||||||
|
Builds Ethernet and IPv6 headers for sending a packet to all IPv6 nodes.
|
||||||
|
"""
|
||||||
|
return Ether() / IPv6(dst="ff02::1")
|
||||||
|
|
||||||
|
def send_RA(self):
|
||||||
|
"""
|
||||||
|
Composes and sends an NDP Router Advertisement (RA).
|
||||||
|
|
||||||
|
The RA contains a prefix which will be used by the RIOT
|
||||||
|
application for configuring a global IPv6 addresses using
|
||||||
|
Stateless Address Autoconfiguration (SLAAC).
|
||||||
|
"""
|
||||||
|
header = self.build_router_advertise_header()
|
||||||
|
ra = ICMPv6ND_RA(M=0, O=1)
|
||||||
|
src_ll_addr = ICMPv6NDOptSrcLLAddr(lladdr=header[Ether].src)
|
||||||
|
mtu = ICMPv6NDOptMTU()
|
||||||
|
prefix_info = ICMPv6NDOptPrefixInfo(prefix=self.prefix, prefixlen=64)
|
||||||
|
sendp(
|
||||||
|
header / ra / src_ll_addr / mtu / prefix_info,
|
||||||
|
iface=self.iface,
|
||||||
|
verbose=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@ATMT.state()
|
||||||
|
def WAITING_FOR_DHCP_IR(self):
|
||||||
|
"""
|
||||||
|
The second state.
|
||||||
|
|
||||||
|
The Automaton waits for a DHCPv6 Information Request.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@ATMT.receive_condition(WAITING_FOR_DHCP_IR, prio=1)
|
||||||
|
def received_DHCPv6(self, pkt):
|
||||||
|
"""
|
||||||
|
Checks if an expected DHCPv6 Information Request (IR) was received.
|
||||||
|
|
||||||
|
The method asserts that the expected options are contained in the IR,
|
||||||
|
triggers `on_DHCPv6_IR` if this is the case, and lets the Automaton
|
||||||
|
switch to the final state `END`.
|
||||||
|
"""
|
||||||
|
if DHCP6_InfoRequest in pkt:
|
||||||
|
hwaddrs = get_hwaddrs(self.child)
|
||||||
|
|
||||||
|
assert DHCP6OptClientId in pkt and DUID_LL in pkt[DHCP6OptClientId].duid
|
||||||
|
assert pkt[DHCP6OptClientId].duid[DUID_LL].lladdr in hwaddrs
|
||||||
|
|
||||||
|
# The information-request contained a MUD URL option
|
||||||
|
assert DHCP6OptMudUrl in pkt
|
||||||
|
mud_option = pkt[DHCP6OptMudUrl]
|
||||||
|
assert mud_option.optlen == len(MUD_TEST_URL)
|
||||||
|
|
||||||
|
if mud_option_loaded:
|
||||||
|
assert mud_option.mudstring == MUD_TEST_URL
|
||||||
|
else:
|
||||||
|
assert mud_option.optcode == MUD_OPTION_CODE
|
||||||
|
assert mud_option.data == MUD_TEST_URL
|
||||||
|
|
||||||
|
raise self.END().action_parameters(pkt)
|
||||||
|
|
||||||
|
@ATMT.action(received_DHCPv6)
|
||||||
|
def on_DHCPv6_IR(self, pkt):
|
||||||
|
"""
|
||||||
|
Calls `send_DHCPv6_Reply` for sending a DHCPv6 Reply message.
|
||||||
|
"""
|
||||||
|
self.send_DHCPv6_Reply(pkt)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def build_reply_headers(pkt):
|
||||||
|
"""
|
||||||
|
Constructs the Ethernet, IPv6, and UDP headers for the DHCPv6 Reply.
|
||||||
|
|
||||||
|
Uses the received packet for inserting the correct addresses and ports.
|
||||||
|
"""
|
||||||
|
src_ether = pkt[Ether].src
|
||||||
|
src_ip = pkt[IPv6].src
|
||||||
|
sport = pkt[UDP].sport
|
||||||
|
dport = pkt[UDP].dport
|
||||||
|
return Ether(dst=src_ether) / IPv6(dst=src_ip) / UDP(sport=dport, dport=sport)
|
||||||
|
|
||||||
|
def send_DHCPv6_Reply(self, pkt):
|
||||||
|
"""
|
||||||
|
Sends out the DHCPv6 Reply message.
|
||||||
|
"""
|
||||||
|
header = self.build_reply_headers(pkt)
|
||||||
|
trid = pkt[DHCP6_InfoRequest].trid
|
||||||
|
srv_duid = header[Ether].src
|
||||||
|
cli_id = DHCP6OptClientId(duid=pkt[DHCP6OptClientId].duid)
|
||||||
|
srv_id = DHCP6OptServerId(duid=DUID_LL(lladdr=srv_duid))
|
||||||
|
sendp(
|
||||||
|
header / DHCP6_Reply(trid=trid) / cli_id / srv_id,
|
||||||
|
iface=self.iface,
|
||||||
|
verbose=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@ATMT.timeout(WAITING_FOR_NDP_RS, 10.0)
|
||||||
|
@ATMT.timeout(WAITING_FOR_DHCP_IR, 10.0)
|
||||||
|
def waiting_timeout(self):
|
||||||
|
"""
|
||||||
|
Defines a timeout of 10 seconds for both the first and second state.
|
||||||
|
"""
|
||||||
|
raise self.ERROR_TIMEOUT()
|
||||||
|
|
||||||
|
@ATMT.state(final=1)
|
||||||
|
def END(self):
|
||||||
|
"""
|
||||||
|
The final state.
|
||||||
|
|
||||||
|
Checks if the global IPv6 address has been configured correctly and
|
||||||
|
terminates the test.
|
||||||
|
"""
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# check if global address was configured
|
||||||
|
self.child.sendline("ifconfig")
|
||||||
|
# remove one trailing ':' from prefix just to be safe ;-)
|
||||||
|
self.child.expect(r"inet6 addr:\s+{}[0-9a-fA-F:]+\s".format(self.prefix[:-1]))
|
||||||
|
print("SUCCESS")
|
||||||
|
|
||||||
|
|
||||||
|
def get_hwaddrs(child):
|
||||||
|
"""
|
||||||
|
Extracts the RIOT device's MAC Address from the command line for assertions.
|
||||||
|
"""
|
||||||
|
hwaddrs = []
|
||||||
|
child.sendline("ifconfig")
|
||||||
|
child.expect(r"HWaddr:\s+(([A-Fa-f0-9]{2}:?)+)\s")
|
||||||
|
hwaddrs.append(child.match.group(1).lower())
|
||||||
|
if len(hwaddrs[0]) == 5: # short address
|
||||||
|
res = child.expect([pexpect.TIMEOUT, r"Long HWaddr:\s+(([A-Fa-f0-9]{2}:?)+)\s"])
|
||||||
|
if res > 0:
|
||||||
|
hwaddrs.append(child.match.group(1).lower())
|
||||||
|
return hwaddrs
|
||||||
|
|
||||||
|
|
||||||
|
def testfunc(child):
|
||||||
|
"""
|
||||||
|
The test function that is called by the test runner.
|
||||||
|
"""
|
||||||
|
iface = os.environ["TAP"]
|
||||||
|
StatelessDHCPv6Test(child, iface=iface).run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run(testfunc, timeout=TIMEOUT, echo=True))
|
||||||
Loading…
x
Reference in New Issue
Block a user