1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 04:52:59 +01:00

dhcpv6_relay: initial import of a lightweight DHCPv6 relay agent

This commit is contained in:
Martine Lenders 2021-07-02 17:47:01 +02:00
parent 6da2f0fab3
commit 4afc65688f
No known key found for this signature in database
GPG Key ID: 2134D77A5336DD80
7 changed files with 535 additions and 0 deletions

View File

@ -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_%

View File

@ -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

View File

@ -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);

View File

@ -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 */

View File

@ -0,0 +1,53 @@
/*
* 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
/**
* @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
void dhcpv6_relay_auto_init(void);
void dhcpv6_relay_init(event_queue_t *eq, uint16_t listen_netif,
uint16_t fwd_netif);
#ifdef __cplusplus
}
#endif
#endif /* NET_DHCPV6_RELAY_H */
/** @} */

View File

@ -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]

View 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);
}
}
/** @} */