Merge pull request #32104 from yuwata/network-ndisc-redirect

network/ndisc: add support for Redirect message
This commit is contained in:
Luca Boccassi 2024-04-08 20:03:32 +01:00 committed by GitHub
commit 0f0d001254
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1200 additions and 0 deletions

View file

@ -3191,6 +3191,16 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
with the <varname>IPv6AcceptRA=</varname> setting described above:</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>UseRedirect=</varname></term>
<listitem>
<para>When true (the default), Redirect message sent by the current first-hop router will be
accepted, and configures routes to redirected nodes will be configured.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Token=</varname></term>
<listitem>

View file

@ -43,6 +43,7 @@ int icmp6_bind(int ifindex, bool is_router) {
};
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
ICMP6_FILTER_SETPASS(ND_REDIRECT, &filter);
}
s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6);

View file

@ -29,6 +29,7 @@ sources = files(
'sd-lldp-tx.c',
'sd-ndisc.c',
'sd-ndisc-neighbor.c',
'sd-ndisc-redirect.c',
'sd-ndisc-router.c',
'sd-radv.c',
)
@ -98,6 +99,10 @@ executables += [
'icmp6-util-unix.c',
),
},
network_test_template + {
'sources' : files('test-ndisc-send.c'),
'type' : 'manual',
},
network_test_template + {
'sources' : files('test-sd-dhcp-lease.c'),
},

View file

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-ndisc.h"
#include "icmp6-packet.h"
#include "set.h"
struct sd_ndisc_redirect {
unsigned n_ref;
ICMP6Packet *packet;
struct in6_addr target_address;
struct in6_addr destination_address;
Set *options;
};
sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet);
int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd);

View file

@ -0,0 +1,123 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/icmp6.h>
#include "sd-ndisc.h"
#include "alloc-util.h"
#include "in-addr-util.h"
#include "ndisc-internal.h"
#include "ndisc-option.h"
#include "ndisc-redirect-internal.h"
static sd_ndisc_redirect* ndisc_redirect_free(sd_ndisc_redirect *rd) {
if (!rd)
return NULL;
icmp6_packet_unref(rd->packet);
set_free(rd->options);
return mfree(rd);
}
DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_redirect, sd_ndisc_redirect, ndisc_redirect_free);
sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet) {
sd_ndisc_redirect *rd;
assert(packet);
rd = new(sd_ndisc_redirect, 1);
if (!rd)
return NULL;
*rd = (sd_ndisc_redirect) {
.n_ref = 1,
.packet = icmp6_packet_ref(packet),
};
return rd;
}
int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd) {
int r;
assert(rd);
assert(rd->packet);
if (rd->packet->raw_size < sizeof(struct nd_redirect))
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Too small to be a redirect message, ignoring.");
const struct nd_redirect *a = (const struct nd_redirect*) rd->packet->raw_packet;
assert(a->nd_rd_type == ND_REDIRECT);
assert(a->nd_rd_code == 0);
rd->target_address = a->nd_rd_target;
rd->destination_address = a->nd_rd_dst;
if (in6_addr_is_null(&rd->target_address) || in6_addr_is_multicast(&rd->target_address))
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Received Redirect message with an invalid target address, ignoring datagram: %m");
if (in6_addr_is_null(&rd->destination_address) || in6_addr_is_multicast(&rd->destination_address))
return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG),
"Received Redirect message with an invalid destination address, ignoring datagram: %m");
r = ndisc_parse_options(rd->packet, &rd->options);
if (r < 0)
return log_ndisc_errno(nd, r, "Failed to parse NDisc options in Redirect message, ignoring datagram: %m");
return 0;
}
int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr) {
assert_return(rd, -EINVAL);
return icmp6_packet_set_sender_address(rd->packet, addr);
}
int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
assert_return(rd, -EINVAL);
return icmp6_packet_get_sender_address(rd->packet, ret);
}
int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
assert_return(rd, -EINVAL);
if (in6_addr_is_null(&rd->target_address))
return -ENODATA;
if (ret)
*ret = rd->target_address;
return 0;
}
int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *rd, struct in6_addr *ret) {
assert_return(rd, -EINVAL);
if (in6_addr_is_null(&rd->destination_address))
return -ENODATA;
if (ret)
*ret = rd->destination_address;
return 0;
}
int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *rd, struct ether_addr *ret) {
assert_return(rd, -EINVAL);
return ndisc_option_get_mac(rd->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret);
}
int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *rd, struct ip6_hdr *ret) {
assert_return(rd, -EINVAL);
sd_ndisc_option *p = ndisc_option_get_by_type(rd->options, SD_NDISC_OPTION_REDIRECTED_HEADER);
if (!p)
return -ENODATA;
if (ret)
*ret = p->hdr;
return 0;
}

View file

@ -17,6 +17,7 @@
#include "memory-util.h"
#include "ndisc-internal.h"
#include "ndisc-neighbor-internal.h"
#include "ndisc-redirect-internal.h"
#include "ndisc-router-internal.h"
#include "network-common.h"
#include "random-util.h"
@ -30,6 +31,7 @@ static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
[SD_NDISC_EVENT_TIMEOUT] = "timeout",
[SD_NDISC_EVENT_ROUTER] = "router",
[SD_NDISC_EVENT_NEIGHBOR] = "neighbor",
[SD_NDISC_EVENT_REDIRECT] = "redirect",
};
DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t);
@ -257,6 +259,35 @@ static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) {
return 0;
}
static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) {
_cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL;
struct in6_addr a;
int r;
assert(nd);
assert(packet);
rd = ndisc_redirect_new(packet);
if (!rd)
return -ENOMEM;
r = ndisc_redirect_parse(nd, rd);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_sender_address(rd, &a);
if (r < 0)
return r;
log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s",
IN6_ADDR_TO_STRING(&a),
IN6_ADDR_TO_STRING(&rd->target_address),
IN6_ADDR_TO_STRING(&rd->destination_address));
ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd);
return 0;
}
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL;
sd_ndisc *nd = ASSERT_PTR(userdata);
@ -298,6 +329,10 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
(void) ndisc_handle_neighbor(nd, packet);
break;
case ND_REDIRECT:
(void) ndisc_handle_redirect(nd, packet);
break;
default:
log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r);
}

View file

@ -0,0 +1,454 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "build.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "hexdecoct.h"
#include "icmp6-util.h"
#include "in-addr-util.h"
#include "main-func.h"
#include "ndisc-option.h"
#include "netlink-util.h"
#include "network-common.h"
#include "parse-util.h"
#include "socket-util.h"
#include "strv.h"
#include "time-util.h"
static int arg_ifindex = 0;
static int arg_icmp6_type = 0;
static union in_addr_union arg_dest = IN_ADDR_NULL;
static uint8_t arg_hop_limit = 0;
static uint8_t arg_ra_flags = 0;
static uint8_t arg_preference = false;
static usec_t arg_lifetime = 0;
static usec_t arg_reachable = 0;
static usec_t arg_retransmit = 0;
static uint32_t arg_na_flags = 0;
static union in_addr_union arg_target_address = IN_ADDR_NULL;
static union in_addr_union arg_redirect_destination = IN_ADDR_NULL;
static bool arg_set_source_mac = false;
static struct ether_addr arg_source_mac = {};
static bool arg_set_target_mac = false;
static struct ether_addr arg_target_mac = {};
static struct ip6_hdr *arg_redirected_header = NULL;
static bool arg_set_mtu = false;
static uint32_t arg_mtu = 0;
STATIC_DESTRUCTOR_REGISTER(arg_redirected_header, freep);
static int parse_icmp6_type(const char *str) {
if (STR_IN_SET(str, "router-solicit", "rs", "RS"))
return ND_ROUTER_SOLICIT;
if (STR_IN_SET(str, "router-advertisement", "ra", "RA"))
return ND_ROUTER_ADVERT;
if (STR_IN_SET(str, "neighbor-solicit", "ns", "NS"))
return ND_NEIGHBOR_SOLICIT;
if (STR_IN_SET(str, "neighbor-advertisement", "na", "NA"))
return ND_NEIGHBOR_ADVERT;
if (STR_IN_SET(str, "redirect", "rd", "RD"))
return ND_REDIRECT;
return -EINVAL;
}
static int parse_preference(const char *str) {
if (streq(str, "low"))
return SD_NDISC_PREFERENCE_LOW;
if (streq(str, "medium"))
return SD_NDISC_PREFERENCE_MEDIUM;
if (streq(str, "high"))
return SD_NDISC_PREFERENCE_HIGH;
if (streq(str, "reserved"))
return SD_NDISC_PREFERENCE_RESERVED;
return -EINVAL;
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_RA_HOP_LIMIT,
ARG_RA_MANAGED,
ARG_RA_OTHER,
ARG_RA_HOME_AGENT,
ARG_RA_PREFERENCE,
ARG_RA_LIFETIME,
ARG_RA_REACHABLE,
ARG_RA_RETRANSMIT,
ARG_NA_ROUTER,
ARG_NA_SOLICITED,
ARG_NA_OVERRIDE,
ARG_TARGET_ADDRESS,
ARG_REDIRECT_DESTINATION,
ARG_OPTION_SOURCE_LL,
ARG_OPTION_TARGET_LL,
ARG_OPTION_REDIRECTED_HEADER,
ARG_OPTION_MTU,
};
static const struct option options[] = {
{ "version", no_argument, NULL, ARG_VERSION },
{ "interface", required_argument, NULL, 'i' },
{ "type", required_argument, NULL, 't' },
{ "dest", required_argument, NULL, 'd' },
/* For Router Advertisement */
{ "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT },
{ "managed", required_argument, NULL, ARG_RA_MANAGED },
{ "other", required_argument, NULL, ARG_RA_OTHER },
{ "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT },
{ "preference", required_argument, NULL, ARG_RA_PREFERENCE },
{ "lifetime", required_argument, NULL, ARG_RA_LIFETIME },
{ "reachable-time", required_argument, NULL, ARG_RA_REACHABLE },
{ "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT },
/* For Neighbor Advertisement */
{ "is-router", required_argument, NULL, ARG_NA_ROUTER },
{ "is-solicited", required_argument, NULL, ARG_NA_SOLICITED },
{ "is-override", required_argument, NULL, ARG_NA_OVERRIDE },
/* For Neighbor Solicit, Neighbor Advertisement, and Redirect */
{ "target-address", required_argument, NULL, ARG_TARGET_ADDRESS },
/* For Redirect */
{ "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION },
/* Options */
{ "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL },
{ "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL },
{ "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER },
{ "mtu", required_argument, NULL, ARG_OPTION_MTU },
{}
};
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) {
switch (c) {
case ARG_VERSION:
return version();
case 'i':
r = rtnl_resolve_interface_or_warn(&rtnl, optarg);
if (r < 0)
return r;
arg_ifindex = r;
break;
case 't':
r = parse_icmp6_type(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse message type: %m");
arg_icmp6_type = r;
break;
case 'd':
r = in_addr_from_string(AF_INET6, optarg, &arg_dest);
if (r < 0)
return log_error_errno(r, "Failed to parse destination address: %m");
if (!in6_addr_is_link_local(&arg_dest.in6))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The destination address %s is not a link-local address.", optarg);
break;
case ARG_RA_HOP_LIMIT:
r = safe_atou8(optarg, &arg_hop_limit);
if (r < 0)
return log_error_errno(r, "Failed to parse hop limit: %m");
break;
case ARG_RA_MANAGED:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse managed flag: %m");
SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r);
break;
case ARG_RA_OTHER:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse other flag: %m");
SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r);
break;
case ARG_RA_HOME_AGENT:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse home-agent flag: %m");
SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r);
break;
case ARG_RA_PREFERENCE:
r = parse_preference(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse preference: %m");
arg_preference = r;
break;
case ARG_RA_LIFETIME:
r = parse_sec(optarg, &arg_lifetime);
if (r < 0)
return log_error_errno(r, "Failed to parse lifetime: %m");
break;
case ARG_RA_REACHABLE:
r = parse_sec(optarg, &arg_reachable);
if (r < 0)
return log_error_errno(r, "Failed to parse reachable time: %m");
break;
case ARG_RA_RETRANSMIT:
r = parse_sec(optarg, &arg_retransmit);
if (r < 0)
return log_error_errno(r, "Failed to parse retransmit timer: %m");
break;
case ARG_NA_ROUTER:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse is-router flag: %m");
SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r);
break;
case ARG_NA_SOLICITED:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse is-solicited flag: %m");
SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r);
break;
case ARG_NA_OVERRIDE:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse is-override flag: %m");
SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r);
break;
case ARG_TARGET_ADDRESS:
r = in_addr_from_string(AF_INET6, optarg, &arg_target_address);
if (r < 0)
return log_error_errno(r, "Failed to parse target address: %m");
break;
case ARG_REDIRECT_DESTINATION:
r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination);
if (r < 0)
return log_error_errno(r, "Failed to parse destination address: %m");
break;
case ARG_OPTION_SOURCE_LL:
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse source LL address option: %m");
arg_set_source_mac = r;
break;
case ARG_OPTION_TARGET_LL:
r = parse_ether_addr(optarg, &arg_target_mac);
if (r < 0)
return log_error_errno(r, "Failed to parse target LL address option: %m");
arg_set_target_mac = true;
break;
case ARG_OPTION_REDIRECTED_HEADER: {
_cleanup_free_ void *p = NULL;
size_t len;
r = unbase64mem(optarg, &p, &len);
if (r < 0)
return log_error_errno(r, "Failed to parse redirected header: %m");
if (len < sizeof(struct ip6_hdr))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid redirected header.");
arg_redirected_header = TAKE_PTR(p);
break;
}
case ARG_OPTION_MTU:
r = safe_atou32(optarg, &arg_mtu);
if (r < 0)
return log_error_errno(r, "Failed to parse MTU: %m");
arg_set_mtu = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
if (arg_ifindex <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory.");
if (arg_icmp6_type <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--type/-t option is mandatory.");
if (in6_addr_is_null(&arg_dest.in6)) {
if (IN_SET(arg_icmp6_type, ND_ROUTER_ADVERT, ND_NEIGHBOR_ADVERT, ND_REDIRECT))
arg_dest.in6 = (struct in6_addr) IN6ADDR_ALL_NODES_MULTICAST_INIT;
else
arg_dest.in6 = (struct in6_addr) IN6ADDR_ALL_ROUTERS_MULTICAST_INIT;
}
if (arg_set_source_mac) {
struct hw_addr_data hw_addr;
r = rtnl_get_link_info(&rtnl, arg_ifindex,
/* ret_iftype = */ NULL,
/* ret_flags = */ NULL,
/* ret_kind = */ NULL,
&hw_addr,
/* ret_permanent_hw_addr = */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to get the source link-layer address: %m");
if (hw_addr.length != sizeof(struct ether_addr))
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Unsupported hardware address length %zu: %m",
hw_addr.length);
arg_source_mac = hw_addr.ether;
}
return 1;
}
static int send_icmp6(int fd, const struct icmp6_hdr *hdr) {
_cleanup_set_free_ Set *options = NULL;
int r;
assert(fd >= 0);
assert(hdr);
if (arg_set_source_mac) {
r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &arg_source_mac);
if (r < 0)
return r;
}
if (arg_set_target_mac) {
r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_TARGET_LL_ADDRESS, &arg_target_mac);
if (r < 0)
return r;
}
if (arg_redirected_header) {
r = ndisc_option_add_redirected_header(&options, 0, arg_redirected_header);
if (r < 0)
return r;
}
if (arg_set_mtu) {
r = ndisc_option_add_mtu(&options, 0, arg_mtu);
if (r < 0)
return r;
}
struct sockaddr_in6 dst_sockaddr = {
.sin6_family = AF_INET6,
.sin6_addr = arg_dest.in6,
};
return ndisc_send(fd, &dst_sockaddr, hdr, options, now(CLOCK_BOOTTIME));
}
static int send_router_solicit(int fd) {
struct nd_router_solicit hdr = {
.nd_rs_type = ND_ROUTER_SOLICIT,
};
assert(fd >= 0);
return send_icmp6(fd, &hdr.nd_rs_hdr);
}
static int send_router_advertisement(int fd) {
struct nd_router_advert hdr = {
.nd_ra_type = ND_ROUTER_ADVERT,
.nd_ra_router_lifetime = usec_to_be16_sec(arg_lifetime),
.nd_ra_reachable = usec_to_be32_msec(arg_reachable),
.nd_ra_retransmit = usec_to_be32_msec(arg_retransmit),
};
assert(fd >= 0);
/* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime
* simultaneously in the structured initializer in the above. */
hdr.nd_ra_curhoplimit = arg_hop_limit;
hdr.nd_ra_flags_reserved = arg_ra_flags;
return send_icmp6(fd, &hdr.nd_ra_hdr);
}
static int send_neighbor_solicit(int fd) {
struct nd_neighbor_solicit hdr = {
.nd_ns_type = ND_NEIGHBOR_SOLICIT,
.nd_ns_target = arg_target_address.in6,
};
assert(fd >= 0);
return send_icmp6(fd, &hdr.nd_ns_hdr);
}
static int send_neighbor_advertisement(int fd) {
struct nd_neighbor_advert hdr = {
.nd_na_type = ND_NEIGHBOR_ADVERT,
.nd_na_flags_reserved = arg_na_flags,
.nd_na_target = arg_target_address.in6,
};
assert(fd >= 0);
return send_icmp6(fd, &hdr.nd_na_hdr);
}
static int send_redirect(int fd) {
struct nd_redirect hdr = {
.nd_rd_type = ND_REDIRECT,
.nd_rd_target = arg_target_address.in6,
.nd_rd_dst = arg_redirect_destination.in6,
};
assert(fd >= 0);
return send_icmp6(fd, &hdr.nd_rd_hdr);
}
static int run(int argc, char *argv[]) {
_cleanup_close_ int fd = -EBADF;
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
fd = icmp6_bind(arg_ifindex, /* is_router = */ false);
if (fd < 0)
return log_error_errno(fd, "Failed to bind socket to interface: %m");
switch (arg_icmp6_type) {
case ND_ROUTER_SOLICIT:
return send_router_solicit(fd);
case ND_ROUTER_ADVERT:
return send_router_advertisement(fd);
case ND_NEIGHBOR_SOLICIT:
return send_neighbor_solicit(fd);
case ND_NEIGHBOR_ADVERT:
return send_neighbor_advertisement(fd);
case ND_REDIRECT:
return send_redirect(fd);
default:
assert_not_reached();
}
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View file

@ -160,11 +160,13 @@ typedef struct Link {
sd_dhcp_server *dhcp_server;
sd_ndisc *ndisc;
sd_ndisc_router *ndisc_default_router;
sd_event_source *ndisc_expire;
Set *ndisc_rdnss;
Set *ndisc_dnssl;
Set *ndisc_captive_portals;
Set *ndisc_pref64;
Set *ndisc_redirects;
unsigned ndisc_messages;
bool ndisc_configured:1;

View file

@ -378,6 +378,292 @@ static int ndisc_request_address(Address *address, Link *link, sd_ndisc_router *
return 0;
}
static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) {
_cleanup_(route_unrefp) Route *route = NULL;
struct in6_addr gateway, destination;
int r;
assert(rd);
assert(ret);
r = sd_ndisc_redirect_get_target_address(rd, &gateway);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_destination_address(rd, &destination);
if (r < 0)
return r;
r = route_new(&route);
if (r < 0)
return r;
route->family = AF_INET6;
if (!in6_addr_equal(&gateway, &destination)) {
route->nexthop.gw.in6 = gateway;
route->nexthop.family = AF_INET6;
}
route->dst.in6 = destination;
route->dst_prefixlen = 128;
*ret = TAKE_PTR(route);
return 0;
}
static int ndisc_request_redirect_route(Link *link, sd_ndisc_redirect *rd) {
struct in6_addr router, sender;
int r;
assert(link);
assert(link->ndisc_default_router);
assert(rd);
r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &router);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_sender_address(rd, &sender);
if (r < 0)
return r;
if (!in6_addr_equal(&sender, &router))
return 0;
_cleanup_(route_unrefp) Route *route = NULL;
r = ndisc_redirect_route_new(rd, &route);
if (r < 0)
return r;
route->protocol = RTPROT_REDIRECT;
route->protocol_set = true; /* To make ndisc_request_route() not override the protocol. */
/* Redirect message does not have the lifetime, let's use the lifetime of the default router, and
* update the lifetime of the redirect route every time when we receive RA. */
return ndisc_request_route(route, link, link->ndisc_default_router);
}
static int ndisc_remove_redirect_route(Link *link, sd_ndisc_redirect *rd) {
_cleanup_(route_unrefp) Route *route = NULL;
int r;
assert(link);
assert(rd);
r = ndisc_redirect_route_new(rd, &route);
if (r < 0)
return r;
return ndisc_remove_route(route, link);
}
static void ndisc_redirect_hash_func(const sd_ndisc_redirect *x, struct siphash *state) {
struct in6_addr dest = {};
assert(x);
assert(state);
(void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest);
siphash24_compress_typesafe(dest, state);
}
static int ndisc_redirect_compare_func(const sd_ndisc_redirect *x, const sd_ndisc_redirect *y) {
struct in6_addr dest_x = {}, dest_y = {};
assert(x);
assert(y);
(void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest_x);
(void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) y, &dest_y);
return memcmp(&dest_x, &dest_y, sizeof(dest_x));
}
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
ndisc_redirect_hash_ops,
sd_ndisc_redirect,
ndisc_redirect_hash_func,
ndisc_redirect_compare_func,
sd_ndisc_redirect_unref);
static int ndisc_redirect_equal(sd_ndisc_redirect *x, sd_ndisc_redirect *y) {
struct in6_addr a, b;
int r;
assert(x);
assert(y);
r = sd_ndisc_redirect_get_destination_address(x, &a);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_destination_address(y, &b);
if (r < 0)
return r;
if (!in6_addr_equal(&a, &b))
return false;
r = sd_ndisc_redirect_get_target_address(x, &a);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_target_address(y, &b);
if (r < 0)
return r;
return in6_addr_equal(&a, &b);
}
static int ndisc_redirect_drop_conflict(Link *link, sd_ndisc_redirect *rd) {
_cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *existing = NULL;
int r;
assert(link);
assert(rd);
existing = set_remove(link->ndisc_redirects, rd);
if (!existing)
return 0;
r = ndisc_redirect_equal(rd, existing);
if (r != 0)
return r;
return ndisc_remove_redirect_route(link, existing);
}
static int ndisc_redirect_handler(Link *link, sd_ndisc_redirect *rd) {
struct in6_addr router, sender;
usec_t lifetime_usec, now_usec;
int r;
assert(link);
assert(link->network);
assert(rd);
if (!link->network->ndisc_use_redirect)
return 0;
/* Ignore all Redirect messages from non-default router. */
if (!link->ndisc_default_router)
return 0;
r = sd_ndisc_router_get_lifetime_timestamp(link->ndisc_default_router, CLOCK_BOOTTIME, &lifetime_usec);
if (r < 0)
return r;
r = sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec);
if (r < 0)
return r;
if (lifetime_usec <= now_usec)
return 0; /* The default router is outdated. Ignore the redirect message. */
r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &router);
if (r < 0)
return r;
r = sd_ndisc_redirect_get_sender_address(rd, &sender);
if (r < 0)
return r;
if (!in6_addr_equal(&sender, &router))
return 0; /* The redirect message is sent from a non-default router. */
/* OK, the Redirect message is sent from the current default router. */
r = ndisc_redirect_drop_conflict(link, rd);
if (r < 0)
return r;
r = set_ensure_put(&link->ndisc_redirects, &ndisc_redirect_hash_ops, rd);
if (r < 0)
return r;
sd_ndisc_redirect_ref(rd);
return ndisc_request_redirect_route(link, rd);
}
static int ndisc_router_update_redirect(Link *link) {
int r, ret = 0;
assert(link);
/* Reconfigure redirect routes to update their lifetime. */
sd_ndisc_redirect *rd;
SET_FOREACH(rd, link->ndisc_redirects) {
r = ndisc_request_redirect_route(link, rd);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to update lifetime of the Redirect route: %m"));
}
return ret;
}
static int ndisc_drop_redirect(Link *link, const struct in6_addr *router, bool remove) {
int r;
assert(link);
/* If the router is purged, then drop the redirect routes configured with the Redirect message sent
* by the router. */
if (!router)
return 0;
sd_ndisc_redirect *rd;
SET_FOREACH(rd, link->ndisc_redirects) {
struct in6_addr sender;
r = sd_ndisc_redirect_get_sender_address(rd, &sender);
if (r < 0)
return r;
if (!in6_addr_equal(&sender, router))
continue;
if (remove) {
r = ndisc_remove_redirect_route(link, rd);
if (r < 0)
return r;
}
sd_ndisc_redirect_unref(set_remove(link->ndisc_redirects, rd));
}
return 0;
}
static int ndisc_update_redirect_sender(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) {
int r;
assert(link);
assert(original_address);
assert(current_address);
sd_ndisc_redirect *rd;
SET_FOREACH(rd, link->ndisc_redirects) {
struct in6_addr sender;
r = sd_ndisc_redirect_get_sender_address(rd, &sender);
if (r < 0)
return r;
if (!in6_addr_equal(&sender, original_address))
continue;
r = sd_ndisc_redirect_set_sender_address(rd, current_address);
if (r < 0)
return r;
}
return 0;
}
static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_unrefp) Route *route = NULL;
struct in6_addr gateway;
@ -505,6 +791,126 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
return 0;
}
static int update_default_router_address(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) {
struct in6_addr a;
int r;
assert(link);
assert(original_address);
assert(current_address);
if (!link->ndisc_default_router)
return 0;
r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &a);
if (r < 0)
return r;
if (!in6_addr_equal(&a, original_address))
return 0;
return sd_ndisc_router_set_sender_address(link->ndisc_default_router, current_address);
}
static int drop_default_router(Link *link, const struct in6_addr *router, usec_t timestamp_usec) {
usec_t lifetime_usec;
int r;
assert(link);
if (!link->ndisc_default_router)
return 0;
if (router) {
struct in6_addr a;
r = sd_ndisc_router_get_sender_address(link->ndisc_default_router, &a);
if (r < 0)
return r;
if (!in6_addr_equal(&a, router))
return 0;
}
r = sd_ndisc_router_get_lifetime_timestamp(link->ndisc_default_router, CLOCK_BOOTTIME, &lifetime_usec);
if (r < 0)
return r;
if (lifetime_usec > timestamp_usec)
return 0;
link->ndisc_default_router = sd_ndisc_router_unref(link->ndisc_default_router);
return 0;
}
static int accept_default_router(sd_ndisc_router *new_router, sd_ndisc_router *existing_router) {
usec_t lifetime_usec;
struct in6_addr a, b;
uint8_t p, q;
int r;
assert(new_router);
r = sd_ndisc_router_get_lifetime(new_router, &lifetime_usec);
if (r < 0)
return r;
if (lifetime_usec == 0)
return false; /* Received a new RA about revoking the router, ignoring. */
if (!existing_router)
return true;
/* lifetime of the existing router is already checked in ndisc_drop_outdated(). */
r = sd_ndisc_router_get_sender_address(new_router, &a);
if (r < 0)
return r;
r = sd_ndisc_router_get_sender_address(existing_router, &b);
if (r < 0)
return r;
if (in6_addr_equal(&a, &b))
return true; /* Received a new RA from the remembered router. Replace the remembered RA. */
r = sd_ndisc_router_get_preference(new_router, &p);
if (r < 0)
return r;
r = sd_ndisc_router_get_preference(existing_router, &q);
if (r < 0)
return r;
if (p == q)
return true;
if (p == SD_NDISC_PREFERENCE_HIGH)
return true;
if (p == SD_NDISC_PREFERENCE_MEDIUM && q == SD_NDISC_PREFERENCE_LOW)
return true;
return false;
}
static int ndisc_remember_default_router(Link *link, sd_ndisc_router *rt) {
int r;
assert(link);
assert(rt);
r = accept_default_router(rt, link->ndisc_default_router);
if (r <= 0)
return r;
sd_ndisc_router_ref(rt);
sd_ndisc_router_unref(link->ndisc_default_router);
link->ndisc_default_router = rt;
return 1; /* The received router advertisement is from the default router. */
}
static int ndisc_router_process_reachable_time(Link *link, sd_ndisc_router *rt) {
usec_t reachable_time, msec;
int r;
@ -1410,6 +1816,14 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
* valid lifetimes to improve the reaction of SLAAC to renumbering events.
* See draft-ietf-6man-slaac-renum-02, section 4.2. */
r = drop_default_router(link, router, timestamp_usec);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated default router, ignoring: %m"));
r = ndisc_drop_redirect(link, router, /* remove = */ false);
if (r < 0)
RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated Redirect messages, ignoring: %m"));
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_NDISC)
continue;
@ -1620,6 +2034,7 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) {
static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
struct in6_addr router;
usec_t timestamp_usec;
bool is_default;
int r;
assert(link);
@ -1657,6 +2072,11 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return r;
r = ndisc_remember_default_router(link, rt);
if (r < 0)
return r;
is_default = r;
r = ndisc_start_dhcp6_client(link, rt);
if (r < 0)
return r;
@ -1685,6 +2105,17 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return r;
if (is_default) {
r = ndisc_router_update_redirect(link);
if (r < 0)
return r;
} else if (sd_ndisc_router_get_lifetime(rt, NULL) <= 0) {
r = ndisc_drop_redirect(link, &router, /* remove = */ true);
if (r < 0)
return r;
}
if (link->ndisc_messages == 0)
link->ndisc_configured = true;
else
@ -1743,6 +2174,14 @@ static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *n
if (in6_addr_equal(&current_address, &original_address))
return 0; /* the router address is not changed */
r = update_default_router_address(link, &original_address, &current_address);
if (r < 0)
return r;
r = ndisc_update_redirect_sender(link, &original_address, &current_address);
if (r < 0)
return r;
Route *route;
SET_FOREACH(route, link->manager->routes) {
if (route->source != NETWORK_CONFIG_SOURCE_NDISC)
@ -1847,6 +2286,15 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, v
}
break;
case SD_NDISC_EVENT_REDIRECT:
r = ndisc_redirect_handler(link, ASSERT_PTR(message));
if (r < 0 && r != -EBADMSG) {
log_link_warning_errno(link, r, "Failed to process Redirect message: %m");
link_enter_failed(link);
return;
}
break;
case SD_NDISC_EVENT_TIMEOUT:
log_link_debug(link, "NDisc handler get timeout event");
if (link->ndisc_messages == 0) {
@ -1982,6 +2430,7 @@ void ndisc_flush(Link *link) {
link->ndisc_dnssl = set_free(link->ndisc_dnssl);
link->ndisc_captive_portals = set_free(link->ndisc_captive_portals);
link->ndisc_pref64 = set_free(link->ndisc_pref64);
link->ndisc_redirects = set_free(link->ndisc_redirects);
}
static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {

View file

@ -292,6 +292,7 @@ DHCPv6.RapidCommit, config_parse_bool,
DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel)
DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release)
DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context)
IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect)
IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway)
IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_autonomous_prefix)

View file

@ -474,6 +474,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.ipv4_rp_filter = _IP_REVERSE_PATH_FILTER_INVALID,
.ndisc = -1,
.ndisc_use_redirect = true,
.ndisc_use_dns = true,
.ndisc_use_gateway = true,
.ndisc_use_captive_portal = true,

View file

@ -337,6 +337,7 @@ struct Network {
/* NDisc support */
int ndisc;
bool ndisc_use_redirect;
bool ndisc_use_dns;
bool ndisc_use_gateway;
bool ndisc_use_route_prefix;

View file

@ -40,6 +40,7 @@ _not_installed_headers = [
'sd-ndisc.h',
'sd-ndisc-neighbor.h',
'sd-ndisc-protocol.h',
'sd-ndisc-redirect.h',
'sd-ndisc-router.h',
'sd-netlink.h',
'sd-network.h',

View file

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef foosdndiscredirectfoo
#define foosdndiscredirectfoo
/***
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <https://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <sys/types.h>
#include <time.h>
#include "_sd-common.h"
_SD_BEGIN_DECLARATIONS;
typedef struct sd_ndisc_redirect sd_ndisc_redirect;
sd_ndisc_redirect* sd_ndisc_redirect_ref(sd_ndisc_redirect *na);
sd_ndisc_redirect* sd_ndisc_redirect_unref(sd_ndisc_redirect *na);
_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_redirect, sd_ndisc_redirect_unref);
int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr);
int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *na, struct in6_addr *ret);
int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *na, struct in6_addr *ret);
int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *na, struct in6_addr *ret);
int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *na, struct ether_addr *ret);
int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *na, struct ip6_hdr *ret);
_SD_END_DECLARATIONS;
#endif

View file

@ -28,6 +28,7 @@
#include "sd-event.h"
#include "sd-ndisc-neighbor.h"
#include "sd-ndisc-protocol.h"
#include "sd-ndisc-redirect.h"
#include "sd-ndisc-router.h"
#include "_sd-common.h"
@ -40,6 +41,7 @@ __extension__ typedef enum sd_ndisc_event_t {
SD_NDISC_EVENT_TIMEOUT,
SD_NDISC_EVENT_ROUTER,
SD_NDISC_EVENT_NEIGHBOR,
SD_NDISC_EVENT_REDIRECT,
_SD_NDISC_EVENT_MAX,
_SD_NDISC_EVENT_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(NDISC_EVENT)

View file

@ -60,6 +60,7 @@ networkctl_bin = shutil.which('networkctl', path=which_paths)
resolvectl_bin = shutil.which('resolvectl', path=which_paths)
timedatectl_bin = shutil.which('timedatectl', path=which_paths)
udevadm_bin = shutil.which('udevadm', path=which_paths)
test_ndisc_send = None
build_dir = None
source_dir = None
@ -1128,6 +1129,16 @@ class Utilities():
self.assertRegex(output, route_regex)
def wait_route_dropped(self, link, route_regex, table='main', ipv='', timeout_sec=100):
for i in range(timeout_sec):
if i > 0:
time.sleep(1)
output = check_output(f'ip {ipv} route show dev {link} table {table}')
if not re.search(route_regex, output):
break
self.assertNotRegex(output, route_regex)
def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0'):
if not shutil.which('selinuxenabled'):
print('## Checking NetLabel skipped: selinuxenabled command not found.')
@ -5529,6 +5540,38 @@ class NetworkdRATests(unittest.TestCase, Utilities):
self.check_ipv6_token_static()
def test_ndisc_redirect(self):
if not os.path.exists(test_ndisc_send):
self.skipTest(f"{test_ndisc_send} does not exist.")
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
start_networkd()
self.check_ipv6_token_static()
# Introduce two redirect routes.
check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:1:1a:2b:3c:4d --redirect-destination 2002:da8:1:1:1a:2b:3c:4d')
check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::1 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d')
self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10)
self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::1 proto redirect', ipv='-6', timeout_sec=10)
# Change the target address of the redirects.
check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::2 --redirect-destination 2002:da8:1:1:1a:2b:3c:4d')
check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1::3 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d')
self.wait_route_dropped('veth99', r'2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10)
self.wait_route_dropped('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::1 proto redirect', ipv='-6', timeout_sec=10)
self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d via 2002:da8:1::2 proto redirect', ipv='-6', timeout_sec=10)
self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d via 2002:da8:1::3 proto redirect', ipv='-6', timeout_sec=10)
# Send Neighbor Advertisement without the router flag to announce the default router is not available anymore.
# Then, verify that all redirect routes and the default route are dropped.
output = check_output('ip -6 address show dev veth-peer scope link')
veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group()
print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}')
check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no')
self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10)
self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10)
def test_ipv6_token_prefixstable(self):
copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
start_networkd()
@ -7612,6 +7655,11 @@ if __name__ == '__main__':
udevadm_cmd = valgrind_cmd.split() + [udevadm_bin]
wait_online_cmd = valgrind_cmd.split() + [wait_online_bin]
if build_dir:
test_ndisc_send = os.path.normpath(os.path.join(build_dir, 'test-ndisc-send'))
else:
test_ndisc_send = '/usr/lib/tests/test-ndisc-send'
if asan_options:
env.update({'ASAN_OPTIONS': asan_options})
if lsan_options: