mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
416 lines
13 KiB
C
416 lines
13 KiB
C
/*
|
|
* 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, 0,
|
|
_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);
|
|
}
|
|
}
|
|
|
|
/** @} */
|