Merge pull request #31492 from yuwata/icmp6-packet

sd-ndisc: introduce ICMP6Packet and relevant functions, and use them
This commit is contained in:
Yu Watanabe
2024-03-01 09:18:52 +09:00
committed by GitHub
11 changed files with 297 additions and 138 deletions
+121
View File
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/icmp6.h>
#include "icmp6-packet.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
#include "iovec-util.h"
#include "network-common.h"
#include "socket-util.h"
DEFINE_TRIVIAL_REF_UNREF_FUNC(ICMP6Packet, icmp6_packet, mfree);
static ICMP6Packet* icmp6_packet_new(size_t size) {
ICMP6Packet *p;
if (size > SIZE_MAX - offsetof(ICMP6Packet, raw_packet))
return NULL;
p = malloc0(offsetof(ICMP6Packet, raw_packet) + size);
if (!p)
return NULL;
p->raw_size = size;
p->n_ref = 1;
return p;
}
int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret) {
assert(p);
if (in6_addr_is_null(&p->sender_address))
return -ENODATA;
if (ret)
*ret = p->sender_address;
return 0;
}
int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret) {
assert(p);
assert(ret);
if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock) || !clock_supported(clock))
return -EOPNOTSUPP;
if (!triple_timestamp_is_set(&p->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&p->timestamp, clock);
return 0;
}
static const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
assert(p);
if (p->raw_size < sizeof(struct icmp6_hdr))
return NULL;
return (const struct icmp6_hdr*) p->raw_packet;
}
int icmp6_packet_get_type(ICMP6Packet *p) {
const struct icmp6_hdr *hdr = icmp6_packet_get_header(p);
if (!hdr)
return -EBADMSG;
return hdr->icmp6_type;
}
static int icmp6_packet_verify(ICMP6Packet *p) {
const struct icmp6_hdr *hdr = icmp6_packet_get_header(p);
if (!hdr)
return -EBADMSG;
if (hdr->icmp6_code != 0)
return -EBADMSG;
return 0;
}
int icmp6_packet_receive(int fd, ICMP6Packet **ret) {
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *p = NULL;
ssize_t len;
int r;
assert(fd >= 0);
assert(ret);
len = next_datagram_size_fd(fd);
if (len < 0)
return (int) len;
p = icmp6_packet_new(len);
if (!p)
return -ENOMEM;
r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->timestamp);
if (r < 0)
switch (r) {
case -EADDRNOTAVAIL:
return log_debug_errno(r, "ICMPv6: Received a packet from neither link-local nor null address.");
case -EMULTIHOP:
return log_debug_errno(r, "ICMPv6: Received a packet with an invalid hop limit.");
case -EPFNOSUPPORT:
return log_debug_errno(r, "ICMPv6: Received a packet with an invalid source address.");
default:
return log_debug_errno(r, "ICMPv6: Unexpected error while receiving a packet: %m");
}
r = icmp6_packet_verify(p);
if (r < 0)
return r;
*ret = TAKE_PTR(p);
return 0;
}
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <netinet/in.h>
#include "macro.h"
#include "time-util.h"
typedef struct ICMP6Pakcet {
unsigned n_ref;
struct in6_addr sender_address;
struct triple_timestamp timestamp;
size_t raw_size;
uint8_t raw_packet[];
} ICMP6Packet;
ICMP6Packet* icmp6_packet_ref(ICMP6Packet *p);
ICMP6Packet* icmp6_packet_unref(ICMP6Packet *p);
DEFINE_TRIVIAL_CLEANUP_FUNC(ICMP6Packet*, icmp6_packet_unref);
int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret);
int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret);
int icmp6_packet_get_type(ICMP6Packet *p);
int icmp6_packet_receive(int fd, ICMP6Packet **ret);
+1
View File
@@ -8,6 +8,7 @@ sources = files(
'dhcp6-network.c',
'dhcp6-option.c',
'dhcp6-protocol.c',
'icmp6-packet.c',
'icmp6-util.c',
'lldp-neighbor.c',
'lldp-network.c',
+1
View File
@@ -24,6 +24,7 @@ struct sd_ndisc {
sd_event *event;
int event_priority;
struct in6_addr link_local_addr;
struct ether_addr mac_addr;
sd_event_source *recv_event_source;
+38
View File
@@ -1,7 +1,45 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/icmp6.h>
#include "ndisc-protocol.h"
int ndisc_option_parse(
ICMP6Packet *p,
size_t offset,
uint8_t *ret_type,
size_t *ret_len,
const uint8_t **ret_opt) {
assert(p);
if (offset == p->raw_size)
return -ESPIPE; /* end of the packet */
if (offset > p->raw_size)
return -EBADMSG;
if (p->raw_size - offset < sizeof(struct nd_opt_hdr))
return -EBADMSG;
const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset);
if (hdr->nd_opt_len == 0)
return -EBADMSG;
size_t len = hdr->nd_opt_len * 8;
if (p->raw_size - offset < len)
return -EBADMSG;
if (ret_type)
*ret_type = hdr->nd_opt_type;
if (ret_len)
*ret_len = len;
if (ret_opt)
*ret_opt = p->raw_packet + offset;
return 0;
}
static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = {
[PREFIX_LENGTH_CODE_96] = 96,
[PREFIX_LENGTH_CODE_64] = 64,
+8
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "icmp6-packet.h"
#include "time-util.h"
/* RFC 8781: PREF64 or (NAT64 prefix) */
@@ -29,3 +30,10 @@ struct nd_opt_prefix64_info {
int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret);
int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret);
int ndisc_option_parse(
ICMP6Packet *p,
size_t offset,
uint8_t *ret_type,
size_t *ret_len,
const uint8_t **ret_opt);
@@ -7,16 +7,13 @@
#include "sd-ndisc.h"
#include "icmp6-packet.h"
#include "time-util.h"
struct sd_ndisc_router {
unsigned n_ref;
triple_timestamp timestamp;
struct in6_addr address;
/* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */
size_t raw_size;
ICMP6Packet *packet;
/* The current read index for the iterative option interface */
size_t rindex;
@@ -32,7 +29,7 @@ struct sd_ndisc_router {
};
static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) {
return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router));
return ASSERT_PTR(ASSERT_PTR(rt)->packet)->raw_packet;
}
static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) {
@@ -46,5 +43,5 @@ static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) {
return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8;
}
sd_ndisc_router *ndisc_router_new(size_t raw_size);
sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet);
int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt);
+47 -87
View File
@@ -18,46 +18,44 @@
#include "ndisc-router-internal.h"
#include "strv.h"
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, mfree);
sd_ndisc_router *ndisc_router_new(size_t raw_size) {
sd_ndisc_router *rt;
if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_ndisc_router)))
return NULL;
rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) {
if (!rt)
return NULL;
rt->raw_size = raw_size;
rt->n_ref = 1;
icmp6_packet_unref(rt->packet);
return mfree(rt);
}
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, ndisc_router_free);
sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) {
sd_ndisc_router *rt;
assert(packet);
rt = new(sd_ndisc_router, 1);
if (!rt)
return NULL;
*rt = (sd_ndisc_router) {
.n_ref = 1,
.packet = icmp6_packet_ref(packet),
};
return rt;
}
int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (in6_addr_is_null(&rt->address))
return -ENODATA;
*ret = rt->address;
return 0;
return icmp6_packet_get_sender_address(rt->packet, ret);
}
int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) {
assert_return(rt, -EINVAL);
assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP);
assert_return(clock_supported(clock), -EOPNOTSUPP);
assert_return(ret, -EINVAL);
if (!triple_timestamp_is_set(&rt->timestamp))
return -ENODATA;
*ret = triple_timestamp_by_clock(&rt->timestamp, clock);
return 0;
return icmp6_packet_get_timestamp(rt->packet, clock, ret);
}
#define DEFINE_GET_TIMESTAMP(name) \
@@ -98,7 +96,7 @@ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_s
assert_return(ret_size, -EINVAL);
*ret = NDISC_ROUTER_RAW(rt);
*ret_size = rt->raw_size;
*ret_size = rt->packet->raw_size;
return 0;
}
@@ -119,27 +117,20 @@ static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t le
}
int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
struct nd_router_advert *a;
const uint8_t *p;
const struct nd_router_advert *a;
bool has_mtu = false, has_flag_extension = false;
size_t left;
int r;
assert(rt);
assert(rt->packet);
if (rt->raw_size < sizeof(struct nd_router_advert))
if (rt->packet->raw_size < sizeof(struct nd_router_advert))
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Too small to be a router advertisement, ignoring.");
/* Router advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */
a = NDISC_ROUTER_RAW(rt);
if (a->nd_ra_type != ND_ROUTER_ADVERT)
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Received ND packet that is not a router advertisement, ignoring.");
if (a->nd_ra_code != 0)
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Received ND packet with wrong RA code, ignoring.");
a = (const struct nd_router_advert*) rt->packet->raw_packet;
assert(a->nd_ra_type == ND_ROUTER_ADVERT);
assert(a->nd_ra_code == 0);
rt->hop_limit = a->nd_ra_curhoplimit;
rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */
@@ -151,29 +142,13 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH))
rt->preference = SD_NDISC_PREFERENCE_MEDIUM;
p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert);
left = rt->raw_size - sizeof(struct nd_router_advert);
for (;;) {
for (size_t offset = sizeof(struct nd_router_advert), length; offset < rt->packet->raw_size; offset += length) {
uint8_t type;
size_t length;
const uint8_t *p;
if (left == 0)
break;
if (left < 2)
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Option lacks header, ignoring datagram.");
type = p[0];
length = p[1] * 8;
if (length == 0)
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Zero-length option, ignoring datagram.");
if (left < length)
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Option truncated, ignoring datagram.");
r = ndisc_option_parse(rt->packet, offset, &type, &length, &p);
if (r < 0)
return log_ndisc_errno(nd, r, "Failed to parse NDisc option header, ignoring: %m");
switch (type) {
@@ -261,8 +236,6 @@ int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
"PREF64 prefix has invalid prefix length.");
break;
}}
p += length, left -= length;
}
rt->rindex = sizeof(struct nd_router_advert);
@@ -332,43 +305,30 @@ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) {
int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) {
assert_return(rt, -EINVAL);
assert(rt->raw_size >= sizeof(struct nd_router_advert));
rt->rindex = sizeof(struct nd_router_advert);
assert(rt->packet);
assert(rt->packet->raw_size >= sizeof(struct nd_router_advert));
return rt->rindex < rt->raw_size;
rt->rindex = sizeof(struct nd_router_advert);
return rt->rindex < rt->packet->raw_size;
}
int sd_ndisc_router_option_next(sd_ndisc_router *rt) {
size_t length;
int r;
assert_return(rt, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
return -EBADMSG;
r = ndisc_option_parse(rt->packet, rt->rindex, NULL, &length, NULL);
if (r < 0)
return r;
rt->rindex += length;
return rt->rindex < rt->raw_size;
return rt->rindex < rt->packet->raw_size;
}
int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) {
assert_return(rt, -EINVAL);
assert_return(ret, -EINVAL);
if (rt->rindex == rt->raw_size) /* EOF */
return -ESPIPE;
if (rt->rindex + 2 > rt->raw_size) /* Truncated message */
return -EBADMSG;
*ret = NDISC_ROUTER_OPTION_TYPE(rt);
return 0;
return ndisc_option_parse(rt->packet, rt->rindex, ret, NULL, NULL);
}
int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) {
@@ -393,11 +353,11 @@ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t
/* Note that this returns the full option, including the option header */
if (rt->rindex + 2 > rt->raw_size)
if (rt->rindex + 2 > rt->packet->raw_size)
return -EBADMSG;
length = NDISC_ROUTER_OPTION_LENGTH(rt);
if (rt->rindex + length > rt->raw_size)
if (rt->rindex + length > rt->packet->raw_size)
return -EBADMSG;
*ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex;
+44 -37
View File
@@ -96,6 +96,18 @@ int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) {
return 0;
}
int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) {
assert_return(nd, -EINVAL);
assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL);
if (addr)
nd->link_local_addr = *addr;
else
zero(nd->link_local_addr);
return 0;
}
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
assert_return(nd, -EINVAL);
@@ -186,11 +198,16 @@ int sd_ndisc_new(sd_ndisc **ret) {
return 0;
}
static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
int r;
assert(nd);
assert(rt);
assert(packet);
rt = ndisc_router_new(packet);
if (!rt)
return -ENOMEM;
r = ndisc_router_parse(nd, rt);
if (r < 0)
@@ -208,56 +225,46 @@ static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
}
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
sd_ndisc *nd = ASSERT_PTR(userdata);
ssize_t buflen;
int r;
assert(s);
assert(nd->event);
buflen = next_datagram_size_fd(fd);
if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen))
return 0;
if (buflen < 0) {
log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m");
r = icmp6_packet_receive(fd, &packet);
if (r < 0) {
log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m");
return 0;
}
rt = ndisc_router_new(buflen);
if (!rt)
return -ENOMEM;
r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r))
return 0;
if (r < 0)
switch (r) {
case -EADDRNOTAVAIL:
log_ndisc(nd, "Received an ICMPv6 packet from neither link-local nor null address, ignoring.");
return 0;
case -EMULTIHOP:
log_ndisc(nd, "Received an ICMPv6 packet with an invalid hop limit, ignoring.");
return 0;
case -EPFNOSUPPORT:
log_ndisc(nd, "Received an ICMPv6 packet with an invalid source address, ignoring.");
return 0;
default:
log_ndisc_errno(nd, r, "Unexpected error while receiving an ICMPv6 packet, ignoring: %m");
return 0;
}
/* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states
* that hosts MUST discard messages with the null source address. */
if (in6_addr_is_null(&rt->address)) {
if (in6_addr_is_null(&packet->sender_address)) {
log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring.");
return 0;
}
(void) ndisc_handle_datagram(nd, rt);
if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) {
log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring.");
return 0;
}
r = icmp6_packet_get_type(packet);
if (r < 0) {
log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m");
return 0;
}
switch (r) {
case ND_ROUTER_ADVERT:
(void) ndisc_handle_router(nd, packet);
break;
default:
log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
}
return 0;
}
+4 -7
View File
@@ -456,13 +456,6 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) {
if (DEBUG_LOGGING)
log_link_debug(link, "No NDisc route added, gateway %s matches local address",
IN6_ADDR_TO_STRING(&gateway));
return 0;
}
r = sd_ndisc_router_get_preference(rt, &preference);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m");
@@ -1774,6 +1767,10 @@ int ndisc_start(Link *link) {
if (in6_addr_is_null(&link->ipv6ll_address))
return 0;
r = sd_ndisc_set_link_local_address(link->ndisc, &link->ipv6ll_address);
if (r < 0)
return r;
log_link_debug(link, "Discovering IPv6 routers");
r = sd_ndisc_start(link->ndisc);
+1
View File
@@ -62,6 +62,7 @@ int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata);
int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index);
int sd_ndisc_set_ifname(sd_ndisc *nd, const char *interface_name);
int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret);
int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr);
int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr);
_SD_END_DECLARATIONS;