#include "ndisc-router-internal.h"
#include "strv.h"
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, mfree);
+static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) {
+ if (!rt)
+ return NULL;
+
+ 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(size_t raw_size) {
+sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) {
sd_ndisc_router *rt;
- if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_ndisc_router)))
- return NULL;
+ assert(packet);
- rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size);
+ rt = new(sd_ndisc_router, 1);
if (!rt)
return NULL;
- rt->raw_size = raw_size;
- rt->n_ref = 1;
+ *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) \
assert_return(ret_size, -EINVAL);
*ret = NDISC_ROUTER_RAW(rt);
- *ret_size = rt->raw_size;
+ *ret_size = rt->packet->raw_size;
return 0;
}
}
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 */
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) {
"PREF64 prefix has invalid prefix length.");
break;
}}
-
- p += length, left -= length;
}
rt->rindex = sizeof(struct nd_router_advert);
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) {
/* 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;
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)
}
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);
+ 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;
}