mirror of
https://github.com/systemd/systemd.git
synced 2025-08-25 13:49:55 +03:00
Merge pull request #31807 from yuwata/sd-ndisc-send
sd-ndisc: introduce sd_ndisc_send()
This commit is contained in:
@ -9,22 +9,19 @@
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fuzz.h"
|
||||
#include "icmp6-packet.h"
|
||||
#include "icmp6-util-unix.h"
|
||||
#include "ndisc-internal.h"
|
||||
#include "ndisc-option.h"
|
||||
#include "socket-util.h"
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
static void test_with_sd_ndisc(const uint8_t *data, size_t size) {
|
||||
struct ether_addr mac_addr = {
|
||||
.ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
|
||||
};
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
|
||||
|
||||
if (outside_size_range(size, 0, 2048))
|
||||
return 0;
|
||||
|
||||
fuzz_setup_logging();
|
||||
|
||||
assert_se(sd_event_new(&e) >= 0);
|
||||
assert_se(sd_ndisc_new(&nd) >= 0);
|
||||
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
|
||||
@ -35,6 +32,46 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
(void) sd_event_run(e, UINT64_MAX);
|
||||
assert_se(sd_ndisc_stop(nd) >= 0);
|
||||
close(test_fd[1]);
|
||||
}
|
||||
|
||||
static void test_with_icmp6_packet(const uint8_t *data, size_t size) {
|
||||
static const struct sockaddr_in6 dst = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
|
||||
};
|
||||
|
||||
_cleanup_close_pair_ int fd_pair[2] = EBADF_PAIR;
|
||||
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
|
||||
_cleanup_set_free_ Set *options = NULL;
|
||||
|
||||
assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd_pair) >= 0);
|
||||
assert_se(write(fd_pair[1], data, size) == (ssize_t) size);
|
||||
|
||||
if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
|
||||
return;
|
||||
|
||||
if (ndisc_parse_options(packet, &options) < 0)
|
||||
return;
|
||||
|
||||
if (ndisc_send(fd_pair[1], &dst, icmp6_packet_get_header(packet), options) < 0)
|
||||
return;
|
||||
|
||||
packet = icmp6_packet_unref(packet);
|
||||
options = set_free(options);
|
||||
|
||||
if (icmp6_packet_receive(fd_pair[0], &packet) < 0)
|
||||
return;
|
||||
|
||||
(void) ndisc_parse_options(packet, &options);
|
||||
}
|
||||
|
||||
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
if (outside_size_range(size, 0, 2048))
|
||||
return 0;
|
||||
|
||||
fuzz_setup_logging();
|
||||
|
||||
test_with_sd_ndisc(data, size);
|
||||
test_with_icmp6_packet(data, size);
|
||||
return 0;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
|
||||
const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) {
|
||||
assert(p);
|
||||
|
||||
if (p->raw_size < sizeof(struct icmp6_hdr))
|
||||
|
@ -23,6 +23,7 @@ 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);
|
||||
const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p);
|
||||
int icmp6_packet_get_type(ICMP6Packet *p);
|
||||
|
||||
int icmp6_packet_receive(int fd, ICMP6Packet **ret);
|
||||
|
@ -1,12 +1,12 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "fd-util.h"
|
||||
#include "icmp6-util-unix.h"
|
||||
|
||||
send_ra_t send_ra_function = NULL;
|
||||
int test_fd[2] = EBADF_PAIR;
|
||||
|
||||
static struct in6_addr dummy_link_local = {
|
||||
@ -23,11 +23,8 @@ int icmp6_bind(int ifindex, bool is_router) {
|
||||
return test_fd[is_router];
|
||||
}
|
||||
|
||||
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
|
||||
if (!send_ra_function)
|
||||
return 0;
|
||||
|
||||
return send_ra_function(0);
|
||||
int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) {
|
||||
return writev(fd, iov, n_iov);
|
||||
}
|
||||
|
||||
int icmp6_receive(
|
||||
|
@ -3,7 +3,4 @@
|
||||
|
||||
#include "icmp6-util.h"
|
||||
|
||||
typedef int (*send_ra_t)(uint8_t flags);
|
||||
|
||||
extern send_ra_t send_ra_function;
|
||||
extern int test_fd[2];
|
||||
|
@ -88,37 +88,15 @@ int icmp6_bind(int ifindex, bool is_router) {
|
||||
return TAKE_FD(s);
|
||||
}
|
||||
|
||||
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
|
||||
struct sockaddr_in6 dst = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
|
||||
};
|
||||
struct {
|
||||
struct nd_router_solicit rs;
|
||||
struct nd_opt_hdr rs_opt;
|
||||
struct ether_addr rs_opt_mac;
|
||||
} _packed_ rs = {
|
||||
.rs.nd_rs_type = ND_ROUTER_SOLICIT,
|
||||
.rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR,
|
||||
.rs_opt.nd_opt_len = 1,
|
||||
};
|
||||
struct iovec iov = {
|
||||
.iov_base = &rs,
|
||||
.iov_len = sizeof(rs),
|
||||
};
|
||||
int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov) {
|
||||
struct msghdr msg = {
|
||||
.msg_name = &dst,
|
||||
.msg_namelen = sizeof(dst),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_name = (struct sockaddr_in6*) dst,
|
||||
.msg_namelen = sizeof(struct sockaddr_in6),
|
||||
.msg_iov = (struct iovec*) iov,
|
||||
.msg_iovlen = n_iov,
|
||||
};
|
||||
|
||||
assert(s >= 0);
|
||||
assert(ether_addr);
|
||||
|
||||
rs.rs_opt_mac = *ether_addr;
|
||||
|
||||
if (sendmsg(s, &msg, 0) < 0)
|
||||
if (sendmsg(fd, &msg, 0) < 0)
|
||||
return -errno;
|
||||
|
||||
return 0;
|
||||
|
@ -6,7 +6,9 @@
|
||||
***/
|
||||
|
||||
#include <net/ethernet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "time-util.h"
|
||||
|
||||
@ -19,7 +21,7 @@
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
|
||||
|
||||
int icmp6_bind(int ifindex, bool is_router);
|
||||
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
|
||||
int icmp6_send(int fd, const struct sockaddr_in6 *dst, const struct iovec *iov, size_t n_iov);
|
||||
int icmp6_receive(
|
||||
int fd,
|
||||
void *buffer,
|
||||
|
@ -5,7 +5,9 @@
|
||||
#include "dns-domain.h"
|
||||
#include "ether-addr-util.h"
|
||||
#include "hostname-util.h"
|
||||
#include "icmp6-util.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "missing_network.h"
|
||||
#include "ndisc-option.h"
|
||||
#include "network-common.h"
|
||||
@ -65,6 +67,13 @@ static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) {
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ndisc_raw_done(sd_ndisc_raw *raw) {
|
||||
if (!raw)
|
||||
return;
|
||||
|
||||
free(raw->bytes);
|
||||
}
|
||||
|
||||
static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) {
|
||||
if (!rdnss)
|
||||
return;
|
||||
@ -84,6 +93,10 @@ sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
|
||||
return NULL;
|
||||
|
||||
switch (option->type) {
|
||||
case 0:
|
||||
ndisc_raw_done(&option->raw);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_RDNSS:
|
||||
ndisc_rdnss_done(&option->rdnss);
|
||||
break;
|
||||
@ -111,6 +124,9 @@ static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_op
|
||||
return r;
|
||||
|
||||
switch (x->type) {
|
||||
case 0:
|
||||
return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length);
|
||||
|
||||
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_REDIRECTED_HEADER:
|
||||
@ -155,6 +171,10 @@ static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash
|
||||
siphash24_compress_typesafe(option->type, state);
|
||||
|
||||
switch (option->type) {
|
||||
case 0:
|
||||
siphash24_compress(option->raw.bytes, option->raw.length, state);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_REDIRECTED_HEADER:
|
||||
@ -199,6 +219,44 @@ static int ndisc_option_consume(Set **options, sd_ndisc_option *p) {
|
||||
return set_ensure_consume(options, &ndisc_option_hash_ops, p);
|
||||
}
|
||||
|
||||
int ndisc_option_add_raw(Set **options, size_t offset, size_t length, const uint8_t *bytes) {
|
||||
_cleanup_free_ uint8_t *copy = NULL;
|
||||
|
||||
assert(options);
|
||||
assert(bytes);
|
||||
|
||||
if (length == 0)
|
||||
return -EINVAL;
|
||||
|
||||
copy = newdup(uint8_t, bytes, length);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
sd_ndisc_option *p = ndisc_option_new(/* type = */ 0, offset);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
p->raw = (sd_ndisc_raw) {
|
||||
.bytes = TAKE_PTR(copy),
|
||||
.length = length,
|
||||
};
|
||||
|
||||
return ndisc_option_consume(options, p);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == 0);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_link_layer_address(Set **options, uint8_t opt, size_t offset, const struct ether_addr *mac) {
|
||||
assert(options);
|
||||
assert(IN_SET(opt, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
|
||||
@ -232,6 +290,25 @@ static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, s
|
||||
return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS));
|
||||
assert(ret);
|
||||
|
||||
assert_cc(2 + sizeof(struct ether_addr) == 8);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr));
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = option->type;
|
||||
buf[1] = 1;
|
||||
memcpy(buf + 2, &option->mac, sizeof(struct ether_addr));
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_prefix(
|
||||
Set **options,
|
||||
size_t offset,
|
||||
@ -292,6 +369,31 @@ static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, c
|
||||
return ndisc_option_add_prefix(options, offset, flags, pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, valid, pref);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_prefix(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION);
|
||||
assert(ret);
|
||||
|
||||
assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0);
|
||||
|
||||
_cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
*buf = (struct nd_opt_prefix_info) {
|
||||
.nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION,
|
||||
.nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8,
|
||||
.nd_opt_pi_prefix_len = option->prefix.prefixlen,
|
||||
.nd_opt_pi_flags_reserved = option->prefix.flags,
|
||||
.nd_opt_pi_valid_time = usec_to_be32_sec(option->prefix.valid_lifetime),
|
||||
.nd_opt_pi_preferred_time = usec_to_be32_sec(option->prefix.preferred_lifetime),
|
||||
.nd_opt_pi_prefix = option->prefix.address,
|
||||
};
|
||||
|
||||
*ret = (uint8_t*) TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) {
|
||||
assert(options);
|
||||
assert(hdr);
|
||||
@ -319,6 +421,32 @@ static int ndisc_option_parse_redirected_header(Set **options, size_t offset, si
|
||||
return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr)));
|
||||
}
|
||||
|
||||
static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER);
|
||||
assert(ret);
|
||||
|
||||
assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0);
|
||||
|
||||
size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
uint8_t *p;
|
||||
p = mempcpy(buf,
|
||||
&(const struct nd_opt_rd_hdr) {
|
||||
.nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER,
|
||||
.nd_opt_rh_len = len,
|
||||
},
|
||||
sizeof(struct nd_opt_rd_hdr));
|
||||
memcpy(p, &option->hdr, sizeof(struct ip6_hdr));
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) {
|
||||
assert(options);
|
||||
|
||||
@ -348,6 +476,27 @@ static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, cons
|
||||
return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu));
|
||||
}
|
||||
|
||||
static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_MTU);
|
||||
assert(ret);
|
||||
|
||||
assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0);
|
||||
|
||||
_cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
*buf = (struct nd_opt_mtu) {
|
||||
.nd_opt_mtu_type = SD_NDISC_OPTION_MTU,
|
||||
.nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8,
|
||||
.nd_opt_mtu_mtu = htobe32(option->mtu),
|
||||
};
|
||||
|
||||
*ret = (uint8_t*) TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_route(
|
||||
Set **options,
|
||||
size_t offset,
|
||||
@ -413,6 +562,30 @@ static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, co
|
||||
return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_route(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION);
|
||||
assert(option->route.prefixlen <= 128);
|
||||
assert(ret);
|
||||
|
||||
size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64);
|
||||
be32_t lifetime = usec_to_be32_sec(option->route.lifetime);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION;
|
||||
buf[1] = len;
|
||||
buf[2] = option->route.prefixlen;
|
||||
buf[3] = option->route.preference << 3;
|
||||
memcpy(buf + 4, &lifetime, sizeof(be32_t));
|
||||
memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8);
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_rdnss(
|
||||
Set **options,
|
||||
size_t offset,
|
||||
@ -459,6 +632,29 @@ static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, co
|
||||
return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_rdnss(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_RDNSS);
|
||||
assert(ret);
|
||||
|
||||
size_t len = option->rdnss.n_addresses * 2 + 1;
|
||||
be32_t lifetime = usec_to_be32_sec(option->rdnss.lifetime);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = SD_NDISC_OPTION_RDNSS;
|
||||
buf[1] = len;
|
||||
buf[2] = 0;
|
||||
buf[3] = 0;
|
||||
memcpy(buf + 4, &lifetime, sizeof(be32_t));
|
||||
memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses);
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) {
|
||||
assert(options);
|
||||
|
||||
@ -488,6 +684,23 @@ static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size
|
||||
return ndisc_option_add_flags_extension(options, offset, flags);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, 8);
|
||||
if (!buf)
|
||||
return 0;
|
||||
|
||||
unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8);
|
||||
buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION;
|
||||
buf[1] = 1;
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_dnssl(Set **options, size_t offset, char * const *domains, usec_t lifetime) {
|
||||
int r;
|
||||
|
||||
@ -592,6 +805,50 @@ static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, co
|
||||
return ndisc_option_add_dnssl(options, offset, l, lifetime);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_dnssl(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
int r;
|
||||
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_DNSSL);
|
||||
assert(ret);
|
||||
|
||||
size_t len = 8;
|
||||
STRV_FOREACH(s, option->dnssl.domains)
|
||||
len += strlen(*s) + 2;
|
||||
len = DIV_ROUND_UP(len, 8);
|
||||
|
||||
be32_t lifetime = usec_to_be32_sec(option->dnssl.lifetime);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = SD_NDISC_OPTION_DNSSL;
|
||||
buf[1] = len;
|
||||
buf[2] = 0;
|
||||
buf[3] = 0;
|
||||
memcpy(buf + 4, &lifetime, sizeof(be32_t));
|
||||
|
||||
size_t remaining = len * 8 - 8;
|
||||
uint8_t *p = buf + 8;
|
||||
|
||||
STRV_FOREACH(s, option->dnssl.domains) {
|
||||
r = dns_name_to_wire_format(*s, p, remaining, /* canonical = */ false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(remaining >= (size_t) r);
|
||||
p += r;
|
||||
remaining -= r;
|
||||
}
|
||||
|
||||
if (remaining > 0)
|
||||
memset(p, 0, remaining);
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) {
|
||||
assert(options);
|
||||
|
||||
@ -640,6 +897,30 @@ static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_
|
||||
return ndisc_option_add_captive_portal(options, offset, portal);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL);
|
||||
assert(ret);
|
||||
|
||||
size_t len_portal = strlen(option->captive_portal);
|
||||
size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8);
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, len * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL;
|
||||
buf[1] = len;
|
||||
|
||||
uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal);
|
||||
size_t remaining = len * 8 - 2 - len_portal;
|
||||
if (remaining > 0)
|
||||
memset(p, 0, remaining);
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
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,
|
||||
@ -731,6 +1012,37 @@ static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len,
|
||||
return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime);
|
||||
}
|
||||
|
||||
static int ndisc_option_build_prefix64(const sd_ndisc_option *option, uint8_t **ret) {
|
||||
int r;
|
||||
|
||||
assert(option);
|
||||
assert(option->type == SD_NDISC_OPTION_PREF64);
|
||||
assert(ret);
|
||||
|
||||
uint8_t code;
|
||||
r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
uint16_t lifetime;
|
||||
if (option->prefix64.lifetime >= PREF64_SCALED_LIFETIME_MASK * USEC_PER_SEC)
|
||||
lifetime = PREF64_SCALED_LIFETIME_MASK;
|
||||
else
|
||||
lifetime = (uint16_t) DIV_ROUND_UP(option->prefix64.lifetime, USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK;
|
||||
|
||||
_cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
buf[0] = SD_NDISC_OPTION_PREF64;
|
||||
buf[1] = 2;
|
||||
unaligned_write_be16(buf + 2, lifetime | code);
|
||||
memcpy(buf + 4, &option->prefix64.prefix, 12);
|
||||
|
||||
*ret = TAKE_PTR(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
|
||||
assert(options);
|
||||
assert(opt);
|
||||
@ -788,6 +1100,10 @@ int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) {
|
||||
return log_debug_errno(r, "Failed to parse NDisc option header: %m");
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
r = -EBADMSG;
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
|
||||
r = ndisc_option_parse_link_layer_address(&options, offset, length, opt);
|
||||
@ -853,3 +1169,94 @@ int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) {
|
||||
*ret = p->mac;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options) {
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(dst);
|
||||
assert(hdr);
|
||||
|
||||
struct iovec *iov = NULL;
|
||||
size_t n_iov = 0;
|
||||
CLEANUP_ARRAY(iov, n_iov, iovec_array_free);
|
||||
|
||||
iov = new(struct iovec, 1 + set_size(options));
|
||||
if (!iov)
|
||||
return -ENOMEM;
|
||||
|
||||
r = ndisc_header_size(hdr->icmp6_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
size_t hdr_size = r;
|
||||
|
||||
_cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size);
|
||||
|
||||
const sd_ndisc_option *option;
|
||||
SET_FOREACH(option, options) {
|
||||
_cleanup_free_ uint8_t *buf = NULL;
|
||||
|
||||
switch (option->type) {
|
||||
case 0:
|
||||
r = ndisc_option_build_raw(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_SOURCE_LL_ADDRESS:
|
||||
case SD_NDISC_OPTION_TARGET_LL_ADDRESS:
|
||||
r = ndisc_option_build_link_layer_address(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_PREFIX_INFORMATION:
|
||||
r = ndisc_option_build_prefix(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_REDIRECTED_HEADER:
|
||||
r = ndisc_option_build_redirected_header(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_MTU:
|
||||
r = ndisc_option_build_mtu(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_ROUTE_INFORMATION:
|
||||
r = ndisc_option_build_route(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_RDNSS:
|
||||
r = ndisc_option_build_rdnss(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_FLAGS_EXTENSION:
|
||||
r = ndisc_option_build_flags_extension(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_DNSSL:
|
||||
r = ndisc_option_build_dnssl(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_CAPTIVE_PORTAL:
|
||||
r = ndisc_option_build_captive_portal(option, &buf);
|
||||
break;
|
||||
|
||||
case SD_NDISC_OPTION_PREF64:
|
||||
r = ndisc_option_build_prefix64(option, &buf);
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (r == -ENOMEM)
|
||||
return log_oom_debug();
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type);
|
||||
|
||||
iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8);
|
||||
TAKE_PTR(buf);
|
||||
}
|
||||
|
||||
return icmp6_send(fd, dst, iov, n_iov);
|
||||
}
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <net/ethernet.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "sd-ndisc-protocol.h"
|
||||
|
||||
@ -13,6 +15,11 @@
|
||||
#include "set.h"
|
||||
#include "time-util.h"
|
||||
|
||||
typedef struct sd_ndisc_raw {
|
||||
uint8_t *bytes;
|
||||
size_t length;
|
||||
} sd_ndisc_raw;
|
||||
|
||||
/* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */
|
||||
typedef struct sd_ndisc_prefix {
|
||||
uint8_t flags;
|
||||
@ -51,6 +58,7 @@ typedef struct sd_ndisc_option {
|
||||
size_t offset;
|
||||
|
||||
union {
|
||||
sd_ndisc_raw raw; /* for testing or unsupported options */
|
||||
struct ether_addr mac; /* SD_NDISC_OPTION_SOURCE_LL_ADDRESS or SD_NDISC_OPTION_TARGET_LL_ADDRESS */
|
||||
sd_ndisc_prefix prefix; /* SD_NDISC_OPTION_PREFIX_INFORMATION */
|
||||
struct ip6_hdr hdr; /* SD_NDISC_OPTION_REDIRECTED_HEADER */
|
||||
@ -107,6 +115,11 @@ static inline sd_ndisc_option* ndisc_option_get(Set *options, uint8_t type) {
|
||||
|
||||
int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret);
|
||||
|
||||
int ndisc_option_add_raw(
|
||||
Set **options,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
const uint8_t *bytes);
|
||||
int ndisc_option_add_link_layer_address(
|
||||
Set **options,
|
||||
uint8_t opt,
|
||||
@ -160,3 +173,5 @@ int ndisc_option_add_prefix64(
|
||||
uint8_t prefixlen,
|
||||
const struct in6_addr *prefix,
|
||||
usec_t lifetime);
|
||||
|
||||
int ndisc_send(int fd, const struct sockaddr_in6 *dst, const struct icmp6_hdr *hdr, Set *options);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "sd-ndisc.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ether-addr-util.h"
|
||||
#include "event-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "icmp6-util.h"
|
||||
@ -268,6 +269,29 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ndisc_send_router_solicitation(sd_ndisc *nd) {
|
||||
static const struct sockaddr_in6 dst = {
|
||||
.sin6_family = AF_INET6,
|
||||
.sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
|
||||
};
|
||||
static const struct nd_router_solicit header = {
|
||||
.nd_rs_type = ND_ROUTER_SOLICIT,
|
||||
};
|
||||
|
||||
_cleanup_set_free_ Set *options = NULL;
|
||||
int r;
|
||||
|
||||
assert(nd);
|
||||
|
||||
if (!ether_addr_is_null(&nd->mac_addr)) {
|
||||
r = ndisc_option_add_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, 0, &nd->mac_addr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return ndisc_send(nd->fd, &dst, &header.nd_rs_hdr, options);
|
||||
}
|
||||
|
||||
static usec_t ndisc_timeout_compute_random(usec_t val) {
|
||||
/* compute a time that is random within ±10% of the given value */
|
||||
return val - val / 10 +
|
||||
@ -301,7 +325,7 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
|
||||
r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
|
||||
r = ndisc_send_router_solicitation(nd);
|
||||
if (r < 0)
|
||||
log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m",
|
||||
FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC));
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "icmp6-packet.h"
|
||||
#include "icmp6-util-unix.h"
|
||||
#include "socket-util.h"
|
||||
#include "strv.h"
|
||||
@ -23,7 +24,6 @@ static struct ether_addr mac_addr = {
|
||||
};
|
||||
|
||||
static bool verbose = false;
|
||||
static sd_ndisc *test_timeout_nd;
|
||||
|
||||
static void router_dump(sd_ndisc_router *rt) {
|
||||
struct in6_addr addr;
|
||||
@ -232,12 +232,18 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, void *message, v
|
||||
sd_event_exit(e, 0);
|
||||
}
|
||||
|
||||
static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
|
||||
assert_se(icmp6_packet_receive(fd, &packet) >= 0);
|
||||
|
||||
return send_ra(0);
|
||||
}
|
||||
|
||||
TEST(rs) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
|
||||
|
||||
send_ra_function = send_ra;
|
||||
|
||||
assert_se(sd_event_new(&e) >= 0);
|
||||
|
||||
assert_se(sd_ndisc_new(&nd) >= 0);
|
||||
@ -261,9 +267,12 @@ TEST(rs) {
|
||||
|
||||
assert_se(sd_ndisc_start(nd) >= 0);
|
||||
|
||||
assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs, nd) >= 0);
|
||||
assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
|
||||
|
||||
assert_se(sd_event_loop(e) >= 0);
|
||||
|
||||
test_fd[1] = safe_close(test_fd[1]);
|
||||
test_fd[1] = -EBADF;
|
||||
}
|
||||
|
||||
static int send_ra_invalid_domain(uint8_t flags) {
|
||||
@ -312,12 +321,18 @@ static int send_ra_invalid_domain(uint8_t flags) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_recv_rs_invalid_domain(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
|
||||
assert_se(icmp6_packet_receive(fd, &packet) >= 0);
|
||||
|
||||
return send_ra_invalid_domain(0);
|
||||
}
|
||||
|
||||
TEST(invalid_domain) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
|
||||
|
||||
send_ra_function = send_ra_invalid_domain;
|
||||
|
||||
assert_se(sd_event_new(&e) >= 0);
|
||||
|
||||
assert_se(sd_ndisc_new(&nd) >= 0);
|
||||
@ -335,19 +350,22 @@ TEST(invalid_domain) {
|
||||
|
||||
assert_se(sd_ndisc_start(nd) >= 0);
|
||||
|
||||
assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_invalid_domain, nd) >= 0);
|
||||
assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
|
||||
|
||||
assert_se(sd_event_loop(e) >= 0);
|
||||
|
||||
test_fd[1] = safe_close(test_fd[1]);
|
||||
test_fd[1] = -EBADF;
|
||||
}
|
||||
|
||||
static int test_timeout_value(uint8_t flags) {
|
||||
static int on_recv_rs_timeout(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
||||
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
|
||||
sd_ndisc *nd = ASSERT_PTR(userdata);
|
||||
static int count = 0;
|
||||
static usec_t last = 0;
|
||||
sd_ndisc *nd = test_timeout_nd;
|
||||
usec_t min, max;
|
||||
|
||||
assert_se(nd);
|
||||
assert_se(nd->event);
|
||||
assert_se(icmp6_packet_receive(fd, &packet) >= 0);
|
||||
|
||||
if (++count >= 20)
|
||||
sd_event_exit(nd->event, 0);
|
||||
@ -391,17 +409,14 @@ static int test_timeout_value(uint8_t flags) {
|
||||
|
||||
TEST(timeout) {
|
||||
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
|
||||
_cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
|
||||
|
||||
send_ra_function = test_timeout_value;
|
||||
|
||||
assert_se(sd_event_new(&e) >= 0);
|
||||
|
||||
assert_se(sd_ndisc_new(&nd) >= 0);
|
||||
assert_se(nd);
|
||||
|
||||
test_timeout_nd = nd;
|
||||
|
||||
assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0);
|
||||
|
||||
assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0);
|
||||
@ -413,9 +428,12 @@ TEST(timeout) {
|
||||
|
||||
assert_se(sd_ndisc_start(nd) >= 0);
|
||||
|
||||
assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_timeout, nd) >= 0);
|
||||
assert_se(sd_event_source_set_io_fd_own(s, true) >= 0);
|
||||
|
||||
assert_se(sd_event_loop(e) >= 0);
|
||||
|
||||
test_fd[1] = safe_close(test_fd[1]);
|
||||
test_fd[1] = -EBADF;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
Reference in New Issue
Block a user