mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-25 06:23:53 +01:00
Merge pull request #16606 from miri64/dhcpv6_relay/feat/init
dhcpv6_relay: initial import of a lightweight DHCPv6 relay agent
This commit is contained in:
commit
057aba8f61
@ -27,6 +27,7 @@ PSEUDOMODULES += dhcpv6_%
|
||||
PSEUDOMODULES += dhcpv6_client_dns
|
||||
PSEUDOMODULES += dhcpv6_client_ia_pd
|
||||
PSEUDOMODULES += dhcpv6_client_mud_url
|
||||
PSEUDOMODULES += dhcpv6_relay
|
||||
PSEUDOMODULES += dns_msg
|
||||
PSEUDOMODULES += ecc_%
|
||||
PSEUDOMODULES += event_%
|
||||
|
||||
@ -110,6 +110,16 @@ ifneq (,$(filter dhcpv6_client,$(USEMODULE)))
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dhcpv6_relay,$(USEMODULE)))
|
||||
USEMODULE += event
|
||||
USEMODULE += sock_async_event
|
||||
USEMODULE += sock_udp
|
||||
endif
|
||||
|
||||
ifneq (,$(filter auto_init_dhcpv6_relay,$(USEMODULE)))
|
||||
USEMODULE += dhcpv6_relay
|
||||
endif
|
||||
|
||||
ifneq (,$(filter dns_%,$(USEMODULE)))
|
||||
USEMODULE += dns
|
||||
endif
|
||||
|
||||
@ -267,6 +267,12 @@ void auto_init(void)
|
||||
dhcpv6_client_auto_init();
|
||||
}
|
||||
|
||||
if (IS_USED(MODULE_AUTO_INIT_DHCPV6_RELAY)) {
|
||||
LOG_DEBUG("Auto init DHCPv6 relay agent.\n");
|
||||
extern void dhcpv6_relay_auto_init(void);
|
||||
dhcpv6_relay_auto_init();
|
||||
}
|
||||
|
||||
if (IS_USED(MODULE_GNRC_DHCPV6_CLIENT_SIMPLE_PD)) {
|
||||
LOG_DEBUG("Auto init DHCPv6 client for simple prefix delegation\n");
|
||||
extern void gnrc_dhcpv6_client_simple_pd_init(void);
|
||||
|
||||
@ -47,9 +47,15 @@ extern "C" {
|
||||
#define DHCPV6_SOLICIT (1U) /**< SOLICIT */
|
||||
#define DHCPV6_ADVERTISE (2U) /**< ADVERTISE */
|
||||
#define DHCPV6_REQUEST (3U) /**< REQUEST */
|
||||
#define DHCPV6_CONFIRM (4U) /**< CONFIRM */
|
||||
#define DHCPV6_RENEW (5U) /**< RENEW */
|
||||
#define DHCPV6_REBIND (6U) /**< REBIND */
|
||||
#define DHCPV6_REPLY (7U) /**< REPLY */
|
||||
#define DHCPV6_RELEASE (8U) /**< RELEASE */
|
||||
#define DHCPV6_DECLINE (9U) /**< DECLINE */
|
||||
#define DHCPV6_INFO_REQUEST (11U) /**< INFORMATION-REQUEST */
|
||||
#define DHCPV6_RELAY_FORW (12U) /**< RELAY-FORW */
|
||||
#define DHCPV6_RELAY_REPL (13U) /**< RELAY-REPL */
|
||||
/** @ } */
|
||||
|
||||
/**
|
||||
@ -64,7 +70,9 @@ extern "C" {
|
||||
#define DHCPV6_OPT_ORO (6U) /**< option request option */
|
||||
#define DHCPV6_OPT_PREF (7U) /**< preference option */
|
||||
#define DHCPV6_OPT_ELAPSED_TIME (8U) /**< elapsed time option */
|
||||
#define DHCPV6_OPT_RELAY_MSG (9U) /**< relay message option */
|
||||
#define DHCPV6_OPT_STATUS (13U) /**< status code option */
|
||||
#define DHCPV6_OPT_IID (18U) /**< interface-id option */
|
||||
#define DHCPV6_OPT_DNS_RNS (23U) /**< DNS recursive name server option */
|
||||
#define DHCPV6_OPT_IA_PD (25U) /**< identity association for prefix
|
||||
* delegation (IA_PD) option */
|
||||
|
||||
@ -48,7 +48,7 @@ extern "C" {
|
||||
#define DHCPV6_CLIENT_BUFLEN (256) /**< default length for send and receive buffer */
|
||||
|
||||
/**
|
||||
* @defgroup net_dhcpv6_conf DHCPv6 client compile configurations
|
||||
* @defgroup net_dhcpv6_conf DHCPv6 compile configurations
|
||||
* @ingroup config
|
||||
* @{
|
||||
*/
|
||||
|
||||
79
sys/include/net/dhcpv6/relay.h
Normal file
79
sys/include/net/dhcpv6/relay.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup net_dhcpv6_relay DHCPv6 relay agent
|
||||
* @ingroup net_dhcpv6
|
||||
* @brief DHCPv6 relay agent implementation
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief DHCPv6 client definitions
|
||||
*
|
||||
* @author Martine Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
#ifndef NET_DHCPV6_RELAY_H
|
||||
#define NET_DHCPV6_RELAY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "event.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @addtogroup net_dhcpv6_conf
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Maximum hop count in a relay-forward message (HOP_COUNT_LIMIT)
|
||||
*
|
||||
* @see [RFC 8415, section 7.6](https://tools.ietf.org/html/rfc8415#section-7.6)
|
||||
*/
|
||||
#ifndef CONFIG_DHCPV6_RELAY_HOP_LIMIT
|
||||
#define CONFIG_DHCPV6_RELAY_HOP_LIMIT (8U)
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DHCPV6_RELAY_BUFLEN
|
||||
#define CONFIG_DHCPV6_RELAY_BUFLEN (256U) /**< default length for send and receive buffer */
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Auto-initializes the relay agent in its own thread or event thread
|
||||
* when available
|
||||
*
|
||||
* @note Only used with `auto_init_dhcpv6_relay`.
|
||||
*/
|
||||
void dhcpv6_relay_auto_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initializes the relay agent
|
||||
*
|
||||
* @pre `event_queue->waiter != NULL` (event queue is initialized)
|
||||
*
|
||||
* @param[in] event_queue Event queue to use with the relay agent. Needs to
|
||||
* be initialized in the handler thread.
|
||||
* @param[in] listen_netif The network interface the relay agent listens on for
|
||||
* incoming client or relay forward messages from other
|
||||
* relay agents.
|
||||
* @param[in] fwd_netif The network interface the relay agent relays
|
||||
* messages upstreams and listens for relay replies
|
||||
* on.
|
||||
*/
|
||||
void dhcpv6_relay_init(event_queue_t *event_queue, uint16_t listen_netif,
|
||||
uint16_t fwd_netif);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NET_DHCPV6_RELAY_H */
|
||||
/** @} */
|
||||
@ -4,13 +4,21 @@
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
#
|
||||
|
||||
menuconfig KCONFIG_USEMODULE_DHCPV6
|
||||
bool "Configure DHCPv6"
|
||||
depends on USEMODULE_DHCPV6
|
||||
help
|
||||
Configure DHCPv6 client using Kconfig.
|
||||
Configure DHCPv6 using Kconfig.
|
||||
|
||||
if KCONFIG_USEMODULE_DHCPV6
|
||||
menuconfig KCONFIG_USEMODULE_DHCPV6_CLIENT
|
||||
bool "Configure DHCPv6 client"
|
||||
depends on USEMODULE_DHCPV6_CLIENT
|
||||
help
|
||||
Configure DHCPv6 client using Kconfig.
|
||||
|
||||
if KCONFIG_USEMODULE_DHCPV6_CLIENT
|
||||
|
||||
config DHCPV6_CLIENT_PFX_LEASE_MAX
|
||||
int "Maximum number of prefix leases to be stored"
|
||||
@ -31,4 +39,23 @@ config DHCPV6_CLIENT_MUD_URL
|
||||
string "URL pointing to a Manufacturer Usage Description file"
|
||||
|
||||
endif # KCONFIG_USEMODULE_DHCPV6_CLIENT_MUD_URL
|
||||
endif # KCONFIG_USEMODULE_DHCPv6
|
||||
endif # KCONFIG_USEMODULE_DHCPV6_CLIENT
|
||||
|
||||
menuconfig KCONFIG_USEMODULE_DHCPV6_RELAY
|
||||
bool "Configure DHCPv6 relay agent"
|
||||
depends on USEMODULE_DHCPV6_RELAY
|
||||
help
|
||||
Configure DHCPv6 relay agent using Kconfig.
|
||||
|
||||
if KCONFIG_USEMODULE_DHCPV6_RELAY
|
||||
|
||||
config DHCPV6_RELAY_HOP_LIMIT
|
||||
int "Maximum hop count in relay-forward message (HOP_COUNT_LIMIT)"
|
||||
default 8
|
||||
|
||||
config DHCPV6_RELAY_BUFLEN
|
||||
int "Default length of relay agent send and receive buffer"
|
||||
default 256
|
||||
|
||||
endif # KCONFIG_USEMODULE_DHCPV6_RELAY
|
||||
endif # KCONFIG_USEMODULE_DHCPV6
|
||||
|
||||
@ -84,6 +84,26 @@ typedef struct __attribute__((packed)) {
|
||||
uint8_t type; /**< message type (see [DHCPv6 messeg types ](@ref net_dhcp6_msg_types)) */
|
||||
uint8_t tid[3]; /**< transaction ID */
|
||||
} dhcpv6_msg_t;
|
||||
|
||||
/**
|
||||
* @brief Relay Agents/Server message format
|
||||
* @see [RFC 8415, section 9]
|
||||
* (https://tools.ietf.org/html/rfc8415#section-9)
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t type; /**< message type (see [DHCPv6 messeg types ](@ref net_dhcp6_msg_types)) */
|
||||
uint8_t hop_count; /**< number of relays that have already relayed the message */
|
||||
/**
|
||||
* @brief optional address to identify the link on which the client is
|
||||
* located.
|
||||
*/
|
||||
ipv6_addr_t link_address;
|
||||
/**
|
||||
* @brief The address of the client or relay agent from which the message
|
||||
* to be relayed was received.
|
||||
*/
|
||||
ipv6_addr_t peer_address;
|
||||
} dhcpv6_relay_msg_t;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@ -160,6 +180,17 @@ typedef struct __attribute__((packed)) {
|
||||
network_uint16_t elapsed_time;
|
||||
} dhcpv6_opt_elapsed_time_t;
|
||||
|
||||
/**
|
||||
* @brief DHCPv6 relay message option
|
||||
* @see [RFC 8415, section 21.10]
|
||||
* (https://tools.ietf.org/html/rfc8415#section-21.10)
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
network_uint16_t type; /**< @ref DHCPV6_OPT_RELAY_MSG */
|
||||
network_uint16_t len; /**< length of dhcpv6_opt_iid_t::msg in byte */
|
||||
uint16_t msg[]; /**< the relayed message */
|
||||
} dhcpv6_opt_relay_msg_t;
|
||||
|
||||
/**
|
||||
* @brief DHCPv6 status code option format
|
||||
* @see [RFC 8415, section 21.13]
|
||||
@ -172,6 +203,17 @@ typedef struct __attribute__((packed)) {
|
||||
char msg[]; /**< UTF-8 encoded text string (not 0-terminated!) */
|
||||
} dhcpv6_opt_status_t;
|
||||
|
||||
/**
|
||||
* @brief DHCPv6 interface-id option
|
||||
* @see [RFC 8415, section 21.18]
|
||||
* (https://tools.ietf.org/html/rfc8415#section-21.18)
|
||||
*/
|
||||
typedef struct __attribute__((packed)) {
|
||||
network_uint16_t type; /**< @ref DHCPV6_OPT_IID */
|
||||
network_uint16_t len; /**< length of dhcpv6_opt_iid_t::iid in byte */
|
||||
uint8_t iid[]; /**< opaque interface identifier */
|
||||
} dhcpv6_opt_iid_t;
|
||||
|
||||
/**
|
||||
* @brief DHCPv6 DNS recursive name server option
|
||||
* @see [RFC 3646, section 3]
|
||||
|
||||
415
sys/net/application_layer/dhcpv6/relay.c
Normal file
415
sys/net/application_layer/dhcpv6/relay.c
Normal file
@ -0,0 +1,415 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <assert.h>
|
||||
|
||||
#include "event.h"
|
||||
#include "event/thread.h"
|
||||
#include "log.h"
|
||||
#include "net/dhcpv6.h"
|
||||
#include "net/dhcpv6/client.h" /* required for dhcpv6_duid_l2_t in _dhcpv6.h */
|
||||
#include "net/dhcpv6/relay.h"
|
||||
#include "net/ipv6/addr.h"
|
||||
#include "net/netif.h"
|
||||
#include "net/sock/async/event.h"
|
||||
#include "net/sock/udp.h"
|
||||
#include "net/sock/util.h"
|
||||
#include "thread.h"
|
||||
|
||||
#include "_dhcpv6.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
#define AUTO_INIT_PRIO (THREAD_PRIORITY_MAIN - 1)
|
||||
|
||||
static char _auto_init_stack[THREAD_STACKSIZE_DEFAULT];
|
||||
static struct {
|
||||
uint8_t inbuf[CONFIG_DHCPV6_RELAY_BUFLEN];
|
||||
uint8_t outbuf[CONFIG_DHCPV6_RELAY_BUFLEN];
|
||||
sock_udp_t *listen_sock;
|
||||
sock_udp_t *fwd_sock;
|
||||
uint16_t fwd_netif;
|
||||
} _relay_state;
|
||||
|
||||
static void *_dhcpv6_relay_auto_init_thread(void *);
|
||||
static void _udp_handler(sock_udp_t *sock, sock_async_flags_t type,
|
||||
void *arg);
|
||||
static void _dhcpv6_handler(const sock_udp_ep_t *remote, const uint8_t *msg,
|
||||
size_t msg_size);
|
||||
static void _forward_msg(const sock_udp_ep_t *remote, const uint8_t *msg, size_t
|
||||
msg_size, bool is_client_msg);
|
||||
static void _forward_reply(const uint8_t *in_msg, size_t in_msg_size);
|
||||
|
||||
static int16_t _only_one_netif(void)
|
||||
{
|
||||
if (IS_USED(MODULE_NETIF)) {
|
||||
netif_t *netif = netif_iter(NULL);
|
||||
|
||||
return (netif_iter(netif) == NULL) ? netif_get_id(netif) : -1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void dhcpv6_relay_auto_init(void)
|
||||
{
|
||||
if (IS_USED(MODULE_AUTO_INIT_DHCPV6_RELAY)) {
|
||||
int16_t netif = _only_one_netif();
|
||||
if (netif > 0) {
|
||||
if (IS_USED(MODULE_EVENT_THREAD)) {
|
||||
dhcpv6_relay_init(EVENT_PRIO_LOWEST, netif, netif);
|
||||
}
|
||||
else {
|
||||
thread_create(_auto_init_stack, ARRAY_SIZE(_auto_init_stack),
|
||||
AUTO_INIT_PRIO, THREAD_CREATE_STACKTEST,
|
||||
_dhcpv6_relay_auto_init_thread,
|
||||
(void *)(intptr_t)netif, "dhcpv6_relay");
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG_WARNING("DHCPv6 relay: auto init failed, more than 1 interface\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int _join_all_relays_and_server(uint16_t netif_id)
|
||||
{
|
||||
netif_t *netif = netif_get_by_id(netif_id);
|
||||
ipv6_addr_t all_relays_and_server = {
|
||||
.u8 = DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS
|
||||
};
|
||||
assert(netif != NULL);
|
||||
|
||||
return netif_set_opt(netif, NETOPT_IPV6_GROUP, 0, &all_relays_and_server,
|
||||
sizeof(all_relays_and_server));
|
||||
}
|
||||
|
||||
void dhcpv6_relay_init(event_queue_t *eq, uint16_t listen_netif,
|
||||
uint16_t fwd_netif)
|
||||
{
|
||||
sock_udp_ep_t local = { .family = AF_INET6, .port = DHCPV6_SERVER_PORT,
|
||||
.netif = listen_netif };
|
||||
static sock_udp_t listen_sock;
|
||||
int res;
|
||||
|
||||
assert(eq->waiter != NULL);
|
||||
memset(&listen_sock, 0, sizeof(listen_sock));
|
||||
_relay_state.fwd_netif = fwd_netif;
|
||||
res = _join_all_relays_and_server(listen_netif);
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: unable to join All_DHCP_Relay_Agents_and_Servers: "
|
||||
"%d\n", -res);
|
||||
return;
|
||||
}
|
||||
/* initialize client-listening sock */
|
||||
res = sock_udp_create(&listen_sock, &local, NULL, 0);
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: unable to open listen sock: %d\n", -res);
|
||||
return;
|
||||
}
|
||||
_relay_state.listen_sock = &listen_sock;
|
||||
sock_udp_event_init(_relay_state.listen_sock, eq, _udp_handler, NULL);
|
||||
|
||||
if (listen_netif != fwd_netif) {
|
||||
static sock_udp_t fwd_sock;
|
||||
|
||||
memset(&fwd_sock, 0, sizeof(fwd_sock));
|
||||
/* initialize forwarding / reply-listening sock */
|
||||
local.netif = fwd_netif;
|
||||
res = sock_udp_create(&fwd_sock, &local, NULL, 0);
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: unable to open fwd sock: %d\n", -res);
|
||||
return;
|
||||
}
|
||||
_relay_state.fwd_sock = &fwd_sock;
|
||||
sock_udp_event_init(_relay_state.fwd_sock, eq, _udp_handler, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void *_dhcpv6_relay_auto_init_thread(void *args)
|
||||
{
|
||||
event_queue_t queue;
|
||||
int16_t netif = (intptr_t)args;
|
||||
|
||||
event_queue_init(&queue);
|
||||
dhcpv6_relay_init(&queue, netif, netif);
|
||||
event_loop(&queue);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _udp_handler(sock_udp_t *sock, sock_async_flags_t type,
|
||||
void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
if (type == SOCK_ASYNC_MSG_RECV) {
|
||||
sock_udp_ep_t remote = { .family = AF_INET6 };
|
||||
ssize_t res = sock_udp_recv(sock, _relay_state.inbuf,
|
||||
sizeof(_relay_state.inbuf), 0, &remote);
|
||||
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: Error receiving UDP message: %d\n", (int)-res);
|
||||
return;
|
||||
}
|
||||
_dhcpv6_handler(&remote, _relay_state.inbuf, res);
|
||||
}
|
||||
}
|
||||
|
||||
static void _dhcpv6_handler(const sock_udp_ep_t *remote, const uint8_t *msg,
|
||||
size_t msg_size)
|
||||
{
|
||||
bool is_client_msg = false;
|
||||
|
||||
if (msg_size == 0) {
|
||||
DEBUG("DHCPv6 relay: incoming message size 0\n");
|
||||
return;
|
||||
}
|
||||
if (remote->family != AF_INET6) {
|
||||
DEBUG("DHCPv6 relay: incoming message source not an IPv6 address\n");
|
||||
return;
|
||||
}
|
||||
switch (msg[0]) {
|
||||
case DHCPV6_SOLICIT:
|
||||
case DHCPV6_REQUEST:
|
||||
case DHCPV6_CONFIRM:
|
||||
case DHCPV6_RENEW:
|
||||
case DHCPV6_REBIND:
|
||||
case DHCPV6_RELEASE:
|
||||
case DHCPV6_DECLINE:
|
||||
case DHCPV6_INFO_REQUEST:
|
||||
is_client_msg = true;
|
||||
/* intentionally falls through */
|
||||
case DHCPV6_RELAY_FORW:
|
||||
_forward_msg(remote, msg, msg_size, is_client_msg);
|
||||
break;
|
||||
case DHCPV6_RELAY_REPL:
|
||||
_forward_reply(msg, msg_size);
|
||||
break;
|
||||
default:
|
||||
DEBUG("DHCPv6 relay: unexpected incoming message type %u\n",
|
||||
msg[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t _compose_iid_opt(dhcpv6_opt_iid_t *opt,
|
||||
const sock_udp_ep_t *remote)
|
||||
{
|
||||
opt->type = byteorder_htons(DHCPV6_OPT_IID);
|
||||
opt->len = byteorder_htons(sizeof(remote->netif));
|
||||
memcpy(opt->iid, &remote->netif, sizeof(remote->netif));
|
||||
return sizeof(remote->netif) + sizeof(dhcpv6_opt_iid_t);
|
||||
}
|
||||
|
||||
static uint16_t _compose_relay_msg_opt(dhcpv6_opt_relay_msg_t *opt,
|
||||
const uint8_t *in_msg,
|
||||
size_t in_msg_size)
|
||||
{
|
||||
opt->type = byteorder_htons(DHCPV6_OPT_RELAY_MSG);
|
||||
opt->len = byteorder_htons((uint16_t)in_msg_size);
|
||||
memcpy(opt->msg, in_msg, in_msg_size);
|
||||
return (uint16_t)in_msg_size + sizeof(dhcpv6_opt_relay_msg_t);
|
||||
}
|
||||
|
||||
static bool _addr_unspec(const uint8_t *addr, size_t addr_len)
|
||||
{
|
||||
for (unsigned i = 0; i < addr_len; i++) {
|
||||
if (addr[i] != 0U) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _remote_unspec(const sock_udp_ep_t *remote)
|
||||
{
|
||||
switch (remote->family) {
|
||||
case AF_INET6:
|
||||
return _addr_unspec(remote->addr.ipv6, sizeof(remote->addr.ipv6));
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static void _forward_msg(const sock_udp_ep_t *remote, const uint8_t *in_msg,
|
||||
size_t in_msg_size, bool is_client_msg)
|
||||
{
|
||||
dhcpv6_relay_msg_t *out_fwd = (dhcpv6_relay_msg_t *)_relay_state.outbuf;
|
||||
int res;
|
||||
sock_udp_ep_t send_remote = {
|
||||
.family = AF_INET6,
|
||||
.netif = _relay_state.fwd_netif,
|
||||
.addr = { .ipv6 = DHCPV6_ALL_RELAY_AGENTS_AND_SERVERS },
|
||||
.port = DHCPV6_SERVER_PORT,
|
||||
};
|
||||
uint16_t out_fwd_len = sizeof(dhcpv6_relay_msg_t);
|
||||
|
||||
assert(in_msg_size <= UINT16_MAX);
|
||||
|
||||
if (in_msg_size < sizeof(dhcpv6_msg_t)) {
|
||||
DEBUG("DHCPv6 relay: incoming message too small\n");
|
||||
return;
|
||||
}
|
||||
if (_remote_unspec(remote)) {
|
||||
DEBUG("DHCPv6 relay: incoming message came from unspecified remote\n");
|
||||
return;
|
||||
}
|
||||
if (is_client_msg) {
|
||||
out_fwd->hop_count = 0;
|
||||
}
|
||||
else {
|
||||
const dhcpv6_relay_msg_t *in_fwd = (dhcpv6_relay_msg_t *)in_msg;
|
||||
|
||||
if (in_fwd->hop_count > CONFIG_DHCPV6_RELAY_HOP_LIMIT) {
|
||||
DEBUG("DHCPv6 relay: incoming message exceeded hop limit\n");
|
||||
return;
|
||||
}
|
||||
if (in_msg_size < sizeof(dhcpv6_relay_msg_t)) {
|
||||
DEBUG("DHCPv6 relay: incoming forward message too small\n");
|
||||
return;
|
||||
}
|
||||
/* TODO: check if peer-address is myself to prevent network spam when
|
||||
* fwd_netif == listen_netif */
|
||||
out_fwd->hop_count = in_fwd->hop_count + 1;
|
||||
}
|
||||
|
||||
out_fwd->type = DHCPV6_RELAY_FORW;
|
||||
/* set link-address to unspecified address, we will provide an Interface-ID
|
||||
* option instead */
|
||||
memset(&out_fwd->link_address, 0, sizeof(out_fwd->link_address));
|
||||
assert(sizeof(out_fwd->peer_address) == sizeof(remote->addr.ipv6));
|
||||
memcpy(&out_fwd->peer_address, &remote->addr.ipv6,
|
||||
sizeof(out_fwd->peer_address));
|
||||
|
||||
/* set mandatory options */
|
||||
out_fwd_len += _compose_iid_opt(
|
||||
(dhcpv6_opt_iid_t *)&_relay_state.outbuf[out_fwd_len], remote
|
||||
);
|
||||
if ((out_fwd_len + in_msg_size + sizeof(dhcpv6_opt_relay_msg_t)) >
|
||||
sizeof(_relay_state.outbuf)) {
|
||||
DEBUG("DHCPv6 relay: output buffer too small to relay message\n");
|
||||
return;
|
||||
}
|
||||
out_fwd_len += _compose_relay_msg_opt(
|
||||
(dhcpv6_opt_relay_msg_t *)&_relay_state.outbuf[out_fwd_len],
|
||||
in_msg, in_msg_size
|
||||
);
|
||||
res = sock_udp_send(_relay_state.fwd_sock, out_fwd, out_fwd_len,
|
||||
&send_remote);
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: sending forward message failed: %d\n", -res);
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t _get_iid(dhcpv6_opt_iid_t *opt)
|
||||
{
|
||||
return (opt->iid[1] << 8) | (opt->iid[0] & 0xff);
|
||||
}
|
||||
|
||||
static inline size_t _opt_len(dhcpv6_opt_t *opt)
|
||||
{
|
||||
return sizeof(dhcpv6_opt_t) + byteorder_ntohs(opt->len);
|
||||
}
|
||||
|
||||
static inline dhcpv6_opt_t *_opt_next(dhcpv6_opt_t *opt)
|
||||
{
|
||||
return (dhcpv6_opt_t *)(((uint8_t *)opt) + _opt_len(opt));
|
||||
}
|
||||
|
||||
static void _forward_reply(const uint8_t *in_msg, size_t in_msg_size)
|
||||
{
|
||||
const dhcpv6_relay_msg_t *in_reply = (const dhcpv6_relay_msg_t *)in_msg;
|
||||
const uint8_t *out_msg = NULL;
|
||||
size_t out_msg_len = 0;
|
||||
int res;
|
||||
sock_udp_ep_t target = { .family = AF_INET6 };
|
||||
|
||||
if (in_msg_size < sizeof(dhcpv6_relay_msg_t)) {
|
||||
DEBUG("DHCPv6 relay: incoming reply message too small\n");
|
||||
return;
|
||||
}
|
||||
if (_addr_unspec(in_reply->peer_address.u8,
|
||||
sizeof(in_reply->peer_address.u8))) {
|
||||
DEBUG("DHCPv6 relay: incoming reply message has unspecified peer "
|
||||
"address\n");
|
||||
return;
|
||||
}
|
||||
in_msg_size -= sizeof(dhcpv6_relay_msg_t);
|
||||
for (dhcpv6_opt_t *opt = (dhcpv6_opt_t *)(&in_msg[sizeof(dhcpv6_relay_msg_t)]);
|
||||
in_msg_size > 0; in_msg_size -= _opt_len(opt), opt = _opt_next(opt)) {
|
||||
|
||||
uint16_t opt_len = byteorder_ntohs(opt->len);
|
||||
|
||||
if (opt_len > in_msg_size) {
|
||||
DEBUG("DHCPv6 relay: invalid option size\n");
|
||||
return;
|
||||
}
|
||||
switch (byteorder_ntohs(opt->type)) {
|
||||
case DHCPV6_OPT_IID:
|
||||
if (opt_len != sizeof(uint16_t)) {
|
||||
DEBUG("DHCPv6 relay: unexpected interface-ID length\n");
|
||||
return;
|
||||
}
|
||||
target.netif = _get_iid((dhcpv6_opt_iid_t *)opt);
|
||||
break;
|
||||
case DHCPV6_OPT_RELAY_MSG: {
|
||||
out_msg = ((uint8_t *)opt) + sizeof(dhcpv6_opt_relay_msg_t);
|
||||
out_msg_len = opt_len;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG("DHCPv6 relay: ignoring unknown option %u\n",
|
||||
byteorder_ntohs(opt->type));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.netif == 0) {
|
||||
DEBUG("DHCPv6 relay: no interface ID option found\n");
|
||||
return;
|
||||
}
|
||||
if ((out_msg == NULL) || (out_msg_len == 0)) {
|
||||
DEBUG("DHCPv6 relay: no reply to forward found\n");
|
||||
return;
|
||||
}
|
||||
if (out_msg[0] == DHCPV6_RELAY_REPL) {
|
||||
/* out message is heading for the next relay */
|
||||
target.port = DHCPV6_SERVER_PORT;
|
||||
}
|
||||
else {
|
||||
/* out message is heading for the client it is destined to */
|
||||
target.port = DHCPV6_CLIENT_PORT;
|
||||
}
|
||||
assert(sizeof(in_reply->peer_address) == sizeof(target.addr.ipv6));
|
||||
|
||||
memcpy(&target.addr.ipv6, &in_reply->peer_address, sizeof(target.addr.ipv6));
|
||||
if (IS_USED(MODULE_SOCK_UTIL) && ENABLE_DEBUG) {
|
||||
static char addr_str[IPV6_ADDR_MAX_STR_LEN];
|
||||
uint16_t port;
|
||||
|
||||
if (sock_udp_ep_fmt(&target, addr_str, &port) > 0) {
|
||||
DEBUG("DHCPv6 relay: forwarding reply towards target [%s]:%u\n",
|
||||
addr_str, port);
|
||||
}
|
||||
}
|
||||
res = sock_udp_send(NULL, out_msg, out_msg_len, &target);
|
||||
if (res < 0) {
|
||||
DEBUG("DHCPv6 relay: forwarding reply towards target failed: %d\n",
|
||||
-res);
|
||||
}
|
||||
}
|
||||
|
||||
/** @} */
|
||||
49
tests/gnrc_dhcpv6_relay/Makefile
Normal file
49
tests/gnrc_dhcpv6_relay/Makefile
Normal file
@ -0,0 +1,49 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
RIOTBASE ?= $(CURDIR)/../..
|
||||
|
||||
export TAP ?= tap0
|
||||
|
||||
USEMODULE += auto_init_dhcpv6_relay
|
||||
USEMODULE += event_thread
|
||||
USEMODULE += gnrc_ipv6_default
|
||||
USEMODULE += gnrc_netif_single # Only one interface used and it makes
|
||||
# shell commands easier
|
||||
|
||||
# use Ethernet as link-layer protocol
|
||||
ifeq (native,$(BOARD))
|
||||
TERMFLAGS ?= $(TAP)
|
||||
else
|
||||
ETHOS_BAUDRATE ?= 115200
|
||||
CFLAGS += -DETHOS_BAUDRATE=$(ETHOS_BAUDRATE)
|
||||
TERMDEPS += ethos
|
||||
TERMPROG ?= sudo $(RIOTTOOLS)/ethos/ethos
|
||||
TERMFLAGS ?= $(TAP) $(PORT) $(ETHOS_BAUDRATE)
|
||||
endif
|
||||
USEMODULE += auto_init_gnrc_netif
|
||||
|
||||
USEMODULE += ps
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_commands
|
||||
|
||||
# The test requires some setup and to be run as root
|
||||
# So it cannot currently be run
|
||||
TEST_ON_CI_BLACKLIST += all
|
||||
|
||||
.PHONY: ethos
|
||||
|
||||
ethos:
|
||||
$(Q)env -u CC -u CFLAGS $(MAKE) -C $(RIOTTOOLS)/ethos
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
ifndef CONFIG_DHCPV6_RELAY_HOP_LIMIT
|
||||
CONFIG_DHCPV6_RELAY_HOP_LIMIT=8
|
||||
CFLAGS += -DCONFIG_DHCPV6_RELAY_HOP_LIMIT=$(CONFIG_DHCPV6_RELAY_HOP_LIMIT)
|
||||
endif
|
||||
export CONFIG_DHCPV6_RELAY_HOP_LIMIT
|
||||
ifndef CONFIG_DHCPV6_RELAY_BUFLEN
|
||||
CONFIG_DHCPV6_RELAY_BUFLEN=256
|
||||
CFLAGS += -DCONFIG_DHCPV6_RELAY_BUFLEN=$(CONFIG_DHCPV6_RELAY_BUFLEN)
|
||||
endif
|
||||
export CONFIG_DHCPV6_RELAY_BUFLEN
|
||||
6
tests/gnrc_dhcpv6_relay/Makefile.board.dep
Normal file
6
tests/gnrc_dhcpv6_relay/Makefile.board.dep
Normal file
@ -0,0 +1,6 @@
|
||||
# Put board specific dependencies here
|
||||
ifeq (native,$(BOARD))
|
||||
USEMODULE += netdev_tap
|
||||
else
|
||||
USEMODULE += stdio_ethos
|
||||
endif
|
||||
41
tests/gnrc_dhcpv6_relay/Makefile.ci
Normal file
41
tests/gnrc_dhcpv6_relay/Makefile.ci
Normal file
@ -0,0 +1,41 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
arduino-duemilanove \
|
||||
arduino-leonardo \
|
||||
arduino-mega2560 \
|
||||
arduino-nano \
|
||||
arduino-uno \
|
||||
atmega1284p \
|
||||
atmega328p-xplained-mini \
|
||||
atmega328p \
|
||||
atxmega-a1u-xpro \
|
||||
atxmega-a3bu-xplained \
|
||||
bluepill-stm32f030c8 \
|
||||
derfmega128 \
|
||||
i-nucleo-lrwan1 \
|
||||
mega-xplained \
|
||||
microduino-corerf \
|
||||
msb-430 \
|
||||
msb-430h \
|
||||
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 \
|
||||
z1 \
|
||||
zigduino \
|
||||
#
|
||||
27
tests/gnrc_dhcpv6_relay/main.c
Normal file
27
tests/gnrc_dhcpv6_relay/main.c
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 shell_buffer[SHELL_DEFAULT_BUFSIZE];
|
||||
|
||||
/* start shell */
|
||||
shell_run(NULL, shell_buffer, sizeof(shell_buffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
421
tests/gnrc_dhcpv6_relay/tests-as-root/01-run.py
Executable file
421
tests/gnrc_dhcpv6_relay/tests-as-root/01-run.py
Executable file
@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 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.
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from scapy.all import Ether, IPv6, ICMPv6DestUnreach, UDP, DHCP6, \
|
||||
DHCP6_Solicit, DHCP6_Advertise, DHCP6_Request, DHCP6_Confirm, \
|
||||
DHCP6_Renew, DHCP6_Rebind, DHCP6_Reply, DHCP6_Release, DHCP6_Decline, \
|
||||
DHCP6_Reconf, DHCP6_InfoRequest, DHCP6_RelayForward, DHCP6_RelayReply, \
|
||||
DHCP6OptIfaceId, DHCP6OptRelayMsg, DHCP6OptClientId, DHCP6OptUnknown, \
|
||||
raw, sendp, AsyncSniffer
|
||||
from testrunner.unittest import PexpectTestCase
|
||||
|
||||
|
||||
class TestDHCPv6RelayAgent(PexpectTestCase):
|
||||
CLIENT_PORT = 546
|
||||
SERVER_PORT = 547
|
||||
BOARD = os.environ["BOARD"]
|
||||
CONFIG_DHCPV6_RELAY_BUFLEN = int(os.environ["CONFIG_DHCPV6_RELAY_BUFLEN"])
|
||||
CONFIG_DHCPV6_RELAY_HOP_LIMIT = \
|
||||
int(os.environ["CONFIG_DHCPV6_RELAY_HOP_LIMIT"])
|
||||
LOGFILE = None
|
||||
tap = os.environ["TAP"]
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._get_bridge()
|
||||
cls._get_tap_lladdr()
|
||||
if cls.BOARD == "native":
|
||||
cls.pre_sniffer_wait = cls.TIMEOUT / 20
|
||||
cls.post_sniffer_wait = cls.TIMEOUT / 20
|
||||
else:
|
||||
cls.pre_sniffer_wait = cls.TIMEOUT / 20
|
||||
cls.post_sniffer_wait = cls.TIMEOUT / 20
|
||||
cls._get_node_ifaceid()
|
||||
cls.spawn.sendline('ifconfig')
|
||||
# check if node joined All_DHCP_Relay_Agents_and_Servers
|
||||
cls.spawn.expect_exact('inet6 group: ff02::1:2')
|
||||
|
||||
def setUp(self):
|
||||
self.test_trid = 0xc0ffee
|
||||
|
||||
@staticmethod
|
||||
def _check_and_search_output(cmd, pattern, res_group, *args, **kwargs):
|
||||
output = subprocess.check_output(cmd, *args, **kwargs).decode()
|
||||
for line in output.splitlines():
|
||||
match = re.search(pattern, line)
|
||||
if match is not None:
|
||||
return match.group(res_group)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _get_bridge(cls):
|
||||
res = cls._check_and_search_output(
|
||||
["bridge", "link"],
|
||||
r"{}.+master\s+(?P<master>[^\s]+)".format(cls.tap),
|
||||
"master"
|
||||
)
|
||||
cls.tap = cls.tap if res is None else res
|
||||
|
||||
@classmethod
|
||||
def _get_tap_lladdr(cls):
|
||||
res = cls._check_and_search_output(
|
||||
["ip", "addr", "show", "dev", cls.tap, "scope", "link"],
|
||||
r"inet6\s+(?P<lladdr>[0-9A-Fa-f:]+)/\d+",
|
||||
"lladdr"
|
||||
)
|
||||
if res is None:
|
||||
raise AssertionError(
|
||||
"Can't find host link-local address on interface {}".format(
|
||||
cls.tap
|
||||
)
|
||||
)
|
||||
cls.tap_lladdr = res
|
||||
|
||||
@classmethod
|
||||
def _get_node_ifaceid(cls):
|
||||
pkt = DHCP6_Solicit()
|
||||
result = cls.send_and_exp_pkts(pkt, DHCP6_RelayForward)
|
||||
assert len(result) > 0
|
||||
cls.node_ifaceid = result[0][DHCP6OptIfaceId].ifaceid
|
||||
|
||||
@staticmethod
|
||||
def _contains_dhcp(pkt):
|
||||
# just using DHCP6 in pkt does not work
|
||||
def expand(pkt):
|
||||
yield pkt
|
||||
while pkt.payload:
|
||||
pkt = pkt.payload
|
||||
yield pkt
|
||||
return any(isinstance(layer,
|
||||
(DHCP6, DHCP6_RelayForward, DHCP6_RelayReply))
|
||||
for layer in expand(pkt))
|
||||
|
||||
@classmethod
|
||||
def _udp(cls, client=True):
|
||||
if client:
|
||||
return UDP(sport=cls.CLIENT_PORT, dport=cls.SERVER_PORT)
|
||||
else:
|
||||
return UDP(sport=cls.SERVER_PORT, dport=cls.SERVER_PORT)
|
||||
|
||||
@staticmethod
|
||||
def _lower_headers(ipv6_src=None):
|
||||
return Ether(dst="33:33:00:01:00:02") / \
|
||||
IPv6(src=ipv6_src, dst="ff02::1:2")
|
||||
|
||||
@classmethod
|
||||
def _sendp(cls, pkt, client=True, ipv6_src=None):
|
||||
sendp(cls._lower_headers(ipv6_src=ipv6_src) / cls._udp(client=client) /
|
||||
pkt, iface=cls.tap, verbose=0 if cls.LOGFILE is None else 2)
|
||||
|
||||
@classmethod
|
||||
def send_and_exp_pkts(cls, send_pkt, exp_type, client=True):
|
||||
sniffer = AsyncSniffer(iface=cls.tap)
|
||||
sniffer.start()
|
||||
time.sleep(cls.pre_sniffer_wait)
|
||||
cls._sendp(send_pkt, client=client)
|
||||
time.sleep(cls.post_sniffer_wait)
|
||||
return [pkt for pkt in sniffer.stop() if exp_type in pkt and
|
||||
# filter out sent packet
|
||||
(UDP not in pkt or
|
||||
raw(pkt[UDP].payload) != raw(send_pkt)) and
|
||||
# filter out ICMPv6 since error notifications can also contain
|
||||
# the sent packets and we are not interested in error
|
||||
# notifications
|
||||
ICMPv6DestUnreach not in pkt]
|
||||
|
||||
@classmethod
|
||||
def send_and_exp_any_dhcp6(cls, send_pkt, client=True, ipv6_src=None):
|
||||
sniffer = AsyncSniffer(iface=cls.tap)
|
||||
sniffer.start()
|
||||
time.sleep(cls.pre_sniffer_wait)
|
||||
cls._sendp(send_pkt, client=client, ipv6_src=ipv6_src)
|
||||
time.sleep(cls.post_sniffer_wait)
|
||||
return [pkt for pkt in sniffer.stop() if cls._contains_dhcp(pkt) and
|
||||
# filter out sent packet
|
||||
(UDP not in pkt or
|
||||
raw(pkt[UDP].payload) != raw(send_pkt)) and
|
||||
# filter out ICMPv6 since error notifications can also contain
|
||||
# the sent packets and we are not interested in error
|
||||
# notifications
|
||||
ICMPv6DestUnreach not in pkt]
|
||||
|
||||
def assert_node_responsive(self):
|
||||
self.spawn.sendline("")
|
||||
self.spawn.expect(r"> ")
|
||||
|
||||
def assert_legal_relay_forward(self, pkt, exp_hopcount):
|
||||
self.assertEqual(pkt[UDP].dport, self.SERVER_PORT)
|
||||
self.assertEqual(pkt[DHCP6_RelayForward].hopcount, exp_hopcount)
|
||||
# either linkaddr is set or there is an Interface-ID option in
|
||||
# the message
|
||||
if pkt[DHCP6_RelayForward].linkaddr == "::":
|
||||
self.assertIn(DHCP6OptIfaceId, pkt)
|
||||
# assure ifaceid for later tests
|
||||
self.assertEqual(pkt[DHCP6OptIfaceId].ifaceid,
|
||||
self.node_ifaceid)
|
||||
self.assertIn(DHCP6OptRelayMsg, pkt)
|
||||
|
||||
def assert_pkt_ignored(self, pkt, client_pkt=True, ipv6_src=None):
|
||||
result = self.send_and_exp_any_dhcp6(pkt, client=client_pkt,
|
||||
ipv6_src=ipv6_src)
|
||||
self.assert_empty(result)
|
||||
self.assert_node_responsive()
|
||||
|
||||
def assert_len(self, collection, exp_len):
|
||||
assert len(collection) == exp_len, \
|
||||
f'{collection} is not of length {exp_len}'
|
||||
|
||||
def assert_empty(self, collection):
|
||||
assert len(collection) == 0, f'{collection} is not empty'
|
||||
|
||||
def assert_not_empty(self, collection):
|
||||
assert len(collection) > 0, f'{collection} is empty'
|
||||
|
||||
def assert_relayed_reply_pkt_w_trid(self, pkt, exp_rpkt):
|
||||
self.assertNotIn(DHCP6_RelayReply, pkt)
|
||||
self.assertNotIn(DHCP6OptIfaceId, pkt)
|
||||
self.assertNotIn(DHCP6OptRelayMsg, pkt)
|
||||
self.assertIn(exp_rpkt, pkt)
|
||||
self.assertEqual(pkt[exp_rpkt].trid, self.test_trid)
|
||||
|
||||
def test_dhcpv6_client_msgs(self):
|
||||
for msg_type in [DHCP6_Solicit, DHCP6_Request, DHCP6_Confirm,
|
||||
DHCP6_Renew, DHCP6_Rebind, DHCP6_Release,
|
||||
DHCP6_Decline, DHCP6_InfoRequest]:
|
||||
pkt = msg_type(trid=self.test_trid)
|
||||
result = self.send_and_exp_pkts(pkt, DHCP6_RelayForward)
|
||||
self.assert_len(result, 1)
|
||||
pkt = result[0]
|
||||
self.assert_legal_relay_forward(pkt, 0)
|
||||
self.assertIn(msg_type, pkt[DHCP6OptRelayMsg].message)
|
||||
self.assertEqual(pkt[DHCP6OptRelayMsg][msg_type].trid,
|
||||
self.test_trid)
|
||||
self.assert_node_responsive()
|
||||
|
||||
def test_dhcpv6_server_msgs(self):
|
||||
for msg_type in [DHCP6_Advertise, DHCP6_Reply, DHCP6_Reconf]:
|
||||
pkt = msg_type(trid=self.test_trid)
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_client_msg_too_long(self):
|
||||
pkt = DHCP6_Solicit() / \
|
||||
DHCP6OptUnknown(optcode=1,
|
||||
optlen=self.CONFIG_DHCPV6_RELAY_BUFLEN) / \
|
||||
(b"x" * self.CONFIG_DHCPV6_RELAY_BUFLEN)
|
||||
self.assert_pkt_ignored(pkt)
|
||||
|
||||
def test_dhcpv6_client_msg_too_small(self):
|
||||
for dhcp6_len in range(len(DHCP6())):
|
||||
with self.subTest(dhcp6_len=dhcp6_len):
|
||||
pkt = (b"\x01" * dhcp6_len)
|
||||
self.assert_pkt_ignored(pkt)
|
||||
|
||||
def test_dhcpv6_client_msg_from_unspec(self):
|
||||
self.assert_pkt_ignored(DHCP6_Confirm(), ipv6_src="::")
|
||||
|
||||
def test_dhcpv6_client_msg_too_long_for_fwd(self):
|
||||
buflen = self.CONFIG_DHCPV6_RELAY_BUFLEN
|
||||
buflen -= len(DHCP6_Solicit()) # remove SOLICIT header
|
||||
buflen -= len(DHCP6OptUnknown()) # remove option header of SOLICIT
|
||||
buflen -= len(DHCP6_RelayForward()) # remove RELAY-FORWARD header
|
||||
# remove Interface-ID option of RELAY-FORWARD
|
||||
buflen -= len(DHCP6OptIfaceId(ifaceid=self.node_ifaceid))
|
||||
# remove Relay-Message option header of RELAY-FORWARD
|
||||
buflen -= len(DHCP6OptUnknown(optlen=0))
|
||||
|
||||
pkt = DHCP6_Solicit() / \
|
||||
DHCP6OptUnknown(optcode=1, optlen=buflen) / (b"x" * buflen)
|
||||
result = self.send_and_exp_pkts(pkt, DHCP6_RelayForward)
|
||||
# should just fit
|
||||
self.assert_len(result, 1)
|
||||
self.assert_legal_relay_forward(result[0], 0)
|
||||
self.assert_node_responsive()
|
||||
buflen += 1
|
||||
pkt = DHCP6_Solicit() / \
|
||||
DHCP6OptUnknown(optcode=1, optlen=buflen) / (b"x" * buflen)
|
||||
# SOLICIT should be too long
|
||||
self.assert_pkt_ignored(pkt)
|
||||
|
||||
def _test_dhcpv6_relay_forward(self, pkt, hopcount, exp_peeraddr,
|
||||
exp_ifaceid):
|
||||
result = self.send_and_exp_pkts(pkt, DHCP6_RelayForward, client=False)
|
||||
self.assert_len(result, 1)
|
||||
pkt = result[0]
|
||||
self.assert_legal_relay_forward(pkt, hopcount + 1)
|
||||
rpkt = pkt[DHCP6OptRelayMsg].message[DHCP6_RelayForward]
|
||||
self.assertEqual(rpkt.peeraddr, exp_peeraddr)
|
||||
self.assertEqual(rpkt[DHCP6OptIfaceId].ifaceid, exp_ifaceid)
|
||||
self.assert_node_responsive()
|
||||
return rpkt
|
||||
|
||||
def test_dhcpv6_relay_forward(self):
|
||||
hopcount = self.CONFIG_DHCPV6_RELAY_HOP_LIMIT - 1
|
||||
peeraddr = "fe80::f00:1337"
|
||||
ifaceid = b"ab"
|
||||
self.assertGreater(hopcount, 0)
|
||||
pkt = DHCP6_RelayForward(peeraddr=peeraddr, hopcount=hopcount) / \
|
||||
DHCP6OptIfaceId(ifaceid=ifaceid) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Rebind(trid=self.test_trid))
|
||||
rpkt = self._test_dhcpv6_relay_forward(pkt, hopcount, peeraddr,
|
||||
ifaceid)
|
||||
self.assertIn(DHCP6_Rebind, rpkt[DHCP6OptRelayMsg].message)
|
||||
self.assertEqual(rpkt[DHCP6OptRelayMsg].message[DHCP6_Rebind].trid,
|
||||
self.test_trid)
|
||||
|
||||
def test_dhcpv6_relay_forward_options_reversed(self):
|
||||
hopcount = self.CONFIG_DHCPV6_RELAY_HOP_LIMIT - 1
|
||||
peeraddr = "fe80::f00:1337"
|
||||
ifaceid = b"ab"
|
||||
self.assertGreater(hopcount, 0)
|
||||
pkt = DHCP6_RelayForward(peeraddr=peeraddr, hopcount=hopcount) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Rebind(trid=self.test_trid)) / \
|
||||
DHCP6OptIfaceId(ifaceid=ifaceid)
|
||||
rpkt = self._test_dhcpv6_relay_forward(pkt, hopcount, peeraddr,
|
||||
ifaceid)
|
||||
self.assertIn(DHCP6_Rebind, rpkt[DHCP6OptRelayMsg].message)
|
||||
self.assertEqual(rpkt[DHCP6OptRelayMsg].message[DHCP6_Rebind].trid,
|
||||
self.test_trid)
|
||||
|
||||
def test_dhcpv6_relay_forward_hop_limit_exceeded(self):
|
||||
test_trid = 0xc0ffee
|
||||
hoplimit = self.CONFIG_DHCPV6_RELAY_HOP_LIMIT
|
||||
pkt = DHCP6_RelayForward(hopcount=hoplimit + 1,
|
||||
peeraddr="fe80::f00:1337") / \
|
||||
DHCP6OptIfaceId(ifaceid=b"ab") / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Rebind(trid=test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_forward_too_small(self):
|
||||
for i in range(len(DHCP6()), len(DHCP6_RelayForward())):
|
||||
pkt = (chr(12) + ("\0" * (i - 1))).encode()
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_forward_from_unspec(self):
|
||||
hopcount = self.CONFIG_DHCPV6_RELAY_HOP_LIMIT - 1
|
||||
peeraddr = "fe80::f00:1337"
|
||||
ifaceid = b"ab"
|
||||
self.assertGreater(hopcount, 0)
|
||||
pkt = DHCP6_RelayForward(peeraddr=peeraddr, hopcount=hopcount) / \
|
||||
DHCP6OptIfaceId(ifaceid=ifaceid) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Rebind(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, ipv6_src="::")
|
||||
|
||||
def _test_dhcpv6_simple_relay_reply(self, pkt, exp_rpkt):
|
||||
result = self.send_and_exp_pkts(pkt, exp_rpkt, client=False)
|
||||
self.assert_len(result, 1)
|
||||
pkt = result[0]
|
||||
self.assert_relayed_reply_pkt_w_trid(pkt, exp_rpkt)
|
||||
self.assert_node_responsive()
|
||||
|
||||
def test_dhcpv6_simple_relay_reply(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Advertise(trid=self.test_trid))
|
||||
self._test_dhcpv6_simple_relay_reply(pkt, DHCP6_Advertise)
|
||||
|
||||
def test_dhcpv6_simple_relay_reply_options_reversed(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid)) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid)
|
||||
self._test_dhcpv6_simple_relay_reply(pkt, DHCP6_Reply)
|
||||
|
||||
def test_dhcpv6_simple_relay_reply_foreign_option(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptClientId() / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid))
|
||||
result = self.send_and_exp_pkts(pkt, DHCP6_Reply, client=False)
|
||||
self.assert_len(result, 1)
|
||||
pkt = result[0]
|
||||
self.assert_relayed_reply_pkt_w_trid(pkt, DHCP6_Reply)
|
||||
self.assert_node_responsive()
|
||||
|
||||
def test_dhcpv6_simple_relay_reply_foreign_option_w_bogus_optlen(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid)) / \
|
||||
DHCP6OptClientId(optlen=32)
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_nested_relay_reply(self):
|
||||
peeraddr = "fe80::f00:affe"
|
||||
ifaceid = b"abcd"
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg(
|
||||
message=DHCP6_RelayReply(peeraddr=peeraddr) /
|
||||
# noqa: E131 (easier to read with this indentation)
|
||||
DHCP6OptIfaceId(ifaceid=ifaceid) /
|
||||
DHCP6OptRelayMsg(
|
||||
message=DHCP6_Reconf(trid=self.test_trid)
|
||||
)
|
||||
)
|
||||
result = self.send_and_exp_pkts(pkt, DHCP6_Reconf, client=False)
|
||||
self.assert_len(result, 1)
|
||||
pkt = result[0]
|
||||
self.assertIn(DHCP6_RelayReply, pkt)
|
||||
self.assertNotIn(DHCP6_RelayReply, pkt[DHCP6_RelayReply].message)
|
||||
self.assertEqual(pkt[DHCP6_RelayReply].peeraddr, peeraddr)
|
||||
self.assertIn(DHCP6OptIfaceId, pkt)
|
||||
self.assertEqual(pkt[DHCP6OptIfaceId].ifaceid, ifaceid)
|
||||
self.assertIn(DHCP6_Reconf, pkt[DHCP6_RelayReply].message)
|
||||
self.assertEqual(pkt[DHCP6_Reconf].trid, self.test_trid)
|
||||
self.assert_node_responsive()
|
||||
|
||||
def test_dhcpv6_relay_reply_too_small(self):
|
||||
for dhcp6_len in range(len(DHCP6()), len(DHCP6_RelayReply())):
|
||||
with self.subTest(dhcp6_len=dhcp6_len):
|
||||
pkt = (chr(13) + ("\0" * (dhcp6_len - 1))).encode()
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_unexpeted_ifaceid_len(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid="hello!") / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_unexpected_ifaceid(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=b"ab") / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_unexpected_peeraddr(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr="fe80::abcd:f00:1337") / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_invalid_optlen(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg(optlen=32,
|
||||
message=DHCP6_Reply(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_no_ifaceid_no_linkaddr(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptRelayMsg(message=DHCP6_Reply(trid=self.test_trid))
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
def test_dhcpv6_relay_reply_empty(self):
|
||||
pkt = DHCP6_RelayReply(peeraddr=self.tap_lladdr) / \
|
||||
DHCP6OptIfaceId(ifaceid=self.node_ifaceid) / \
|
||||
DHCP6OptRelayMsg()
|
||||
self.assert_pkt_ignored(pkt, client_pkt=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
x
Reference in New Issue
Block a user