mirror of
https://github.com/systemd/systemd
synced 2024-07-21 18:24:38 +00:00
Merge pull request #32104 from yuwata/network-ndisc-redirect
network/ndisc: add support for Redirect message
This commit is contained in:
commit
0f0d001254
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'),
|
||||
},
|
||||
|
|
21
src/libsystemd-network/ndisc-redirect-internal.h
Normal file
21
src/libsystemd-network/ndisc-redirect-internal.h
Normal 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);
|
123
src/libsystemd-network/sd-ndisc-redirect.c
Normal file
123
src/libsystemd-network/sd-ndisc-redirect.c
Normal 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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
454
src/libsystemd-network/test-ndisc-send.c
Normal file
454
src/libsystemd-network/test-ndisc-send.c
Normal 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);
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(¤t_address, &original_address))
|
||||
return 0; /* the router address is not changed */
|
||||
|
||||
r = update_default_router_address(link, &original_address, ¤t_address);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = ndisc_update_redirect_sender(link, &original_address, ¤t_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] = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
46
src/systemd/sd-ndisc-redirect.h
Normal file
46
src/systemd/sd-ndisc-redirect.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue