Backports the following commits from systemd 234:
ce905cb44620e979862e63743133d354c90d28df icmp6-util: Bind Router Advertisement socket
9dab3e066bb5dfeefa28854ab7f95648eaf0d75d sd-radv: Receive Router Solicitations
d0d6045f658ac3e043235aee68a540650e0f98e0 sd-radv: Send Router Advertisments
dd39628ac19c691917aeeb01babaef411f146039 sd-radv: Implement Router Advertisement timeout handling
938c9ff7d374086b996d6a9a484f88ebd94731a3 sd-radv: Add Router Advertisement functionality
b076a89bb331b91a7cafcd77fb3c13484805b24f sd-radv: Add Router Advertisement prefix handling
1e4a54de3a79a0ba764e482122e381398eaf3e25 icmp6-util: Move multicast address definitions
42a7accc3dec5350f0e88ffa145a55293b6fd0e9 sd-ndisc.c: Move Router Solicitation sending after timer computaion
7b72b034e7c406209dfc55643b79ec0cb2636e4f sd-ndisc: Implement Router Solicitation backoff method
33261358b2a16d895d7beb5b34ebdacadb56cb56 sd-ndisc: Reset counter for sent Router Solicitations (#5874)
b40e6a51d55531314666e866d7f9813fce385023 networkd: RFC compliant autonomous prefix handling (#5636)
Makefile.am | 3 +
src/libsystemd-network/icmp6-util.c | 117 ++++++++++-
src/libsystemd-network/icmp6-util.h | 13 ++
src/libsystemd-network/ndisc-internal.h | 7 +-
src/libsystemd-network/radv-internal.h | 88 +++++++++
src/libsystemd-network/sd-ndisc.c | 184 ++++++++---------
src/libsystemd-network/sd-radv.c | 653 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/libsystemd-network/test-ndisc-rs.c | 15 ++
src/network/networkd-ndisc.c | 30 ++-
src/systemd/sd-radv.h | 81 ++++++++
10 files changed, 1086 insertions(+), 105 deletions(-)
create mode 100644 src/libsystemd-network/radv-internal.h
create mode 100644 src/libsystemd-network/sd-radv.c
create mode 100644 src/systemd/sd-radv.h
diff -rupN systemd-base/Makefile.am systemd/Makefile.am
--- systemd-base/Makefile.am 2017-09-18 14:39:25.988517310 -0700
+++ systemd/Makefile.am 2017-09-18 14:58:25.561666434 -0700
@@ -3629,6 +3629,7 @@ libsystemd_network_la_SOURCES = \
src/systemd/sd-ipv4ll.h \
src/systemd/sd-ipv4acd.h \
src/systemd/sd-ndisc.h \
+ src/systemd/sd-radv.h \
src/systemd/sd-dhcp6-client.h \
src/systemd/sd-dhcp6-lease.h \
src/systemd/sd-lldp.h \
@@ -3652,6 +3653,8 @@ libsystemd_network_la_SOURCES = \
src/libsystemd-network/ndisc-internal.h \
src/libsystemd-network/ndisc-router.h \
src/libsystemd-network/ndisc-router.c \
+ src/libsystemd-network/sd-radv.c \
+ src/libsystemd-network/radv-internal.h \
src/libsystemd-network/icmp6-util.h \
src/libsystemd-network/icmp6-util.c \
src/libsystemd-network/sd-dhcp6-client.c \
diff -rupN systemd-base/src/libsystemd-network/icmp6-util.c systemd/src/libsystemd-network/icmp6-util.c
--- systemd-base/src/libsystemd-network/icmp6-util.c 2017-09-18 14:39:26.016590111 -0700
+++ systemd/src/libsystemd-network/icmp6-util.c 2017-09-18 15:06:31.020575497 -0700
@@ -32,6 +32,7 @@
#include "fd-util.h"
#include "icmp6-util.h"
#include "socket-util.h"
+#include "in-addr-util.h"
#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
@@ -41,12 +42,9 @@
{ { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
-int icmp6_bind_router_solicitation(int index) {
- struct icmp6_filter filter = { };
- struct ipv6_mreq mreq = {
- .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
- .ipv6mr_interface = index,
- };
+static int icmp6_bind_router_message(const struct icmp6_filter *filter,
+ const struct ipv6_mreq *mreq) {
+ int index = mreq->ipv6mr_interface;
_cleanup_close_ int s = -1;
char ifname[IF_NAMESIZE] = "";
static const int zero = 0, one = 1, hops = 255;
@@ -56,9 +54,11 @@ int icmp6_bind_router_solicitation(int i
if (s < 0)
return -errno;
- ICMP6_FILTER_SETBLOCKALL(&filter);
- ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
- r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
+ r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter));
+ if (r < 0)
+ return -errno;
+
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq));
if (r < 0)
return -errno;
@@ -78,7 +78,7 @@ int icmp6_bind_router_solicitation(int i
if (r < 0)
return -errno;
- r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof(hops));
if (r < 0)
return -errno;
@@ -102,6 +102,32 @@ int icmp6_bind_router_solicitation(int i
return r;
}
+int icmp6_bind_router_solicitation(int index) {
+ struct icmp6_filter filter = {};
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ .ipv6mr_interface = index,
+ };
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+
+ return icmp6_bind_router_message(&filter, &mreq);
+}
+
+int icmp6_bind_router_advertisement(int index) {
+ struct icmp6_filter filter = {};
+ struct ipv6_mreq mreq = {
+ .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT,
+ .ipv6mr_interface = index,
+ };
+
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+
+ return icmp6_bind_router_message(&filter, &mreq);
+}
+
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
struct sockaddr_in6 dst = {
.sin6_family = AF_INET6,
@@ -139,3 +165,74 @@ int icmp6_send_router_solicitation(int s
return 0;
}
+
+int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
+ triple_timestamp *timestamp) {
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
+ CMSG_SPACE(sizeof(struct timeval))];
+ } control = {};
+ struct iovec iov = {};
+ union sockaddr_union sa = {};
+ struct msghdr msg = {
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa),
+ .msg_iov = &iov,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+ ssize_t len;
+
+ iov.iov_base = buffer;
+ iov.iov_len = size;
+
+ len = recvmsg(fd, &msg, MSG_DONTWAIT);
+ if (len < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return -errno;
+ }
+
+ if ((size_t) len != size)
+ return -EINVAL;
+
+ if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
+ sa.in6.sin6_family == AF_INET6) {
+
+ *dst = sa.in6.sin6_addr;
+ if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) dst) <= 0)
+ return -EADDRNOTAVAIL;
+
+ } else if (msg.msg_namelen > 0)
+ return -EPFNOSUPPORT;
+
+ /* namelen == 0 only happens when running the test-suite over a socketpair */
+
+ assert(!(msg.msg_flags & MSG_CTRUNC));
+ assert(!(msg.msg_flags & MSG_TRUNC));
+
+ CMSG_FOREACH(cmsg, &msg) {
+ if (cmsg->cmsg_level == SOL_IPV6 &&
+ cmsg->cmsg_type == IPV6_HOPLIMIT &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+ int hops = *(int*) CMSG_DATA(cmsg);
+
+ if (hops != 255)
+ return -EMULTIHOP;
+ }
+
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ triple_timestamp_from_realtime(timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+ }
+
+ if (!triple_timestamp_is_set(timestamp))
+ triple_timestamp_get(timestamp);
+
+ return 0;
+}
diff -rupN systemd-base/src/libsystemd-network/icmp6-util.h systemd/src/libsystemd-network/icmp6-util.h
--- systemd-base/src/libsystemd-network/icmp6-util.h 2017-09-18 14:39:26.016590111 -0700
+++ systemd/src/libsystemd-network/icmp6-util.h 2017-09-18 15:06:31.020575497 -0700
@@ -21,5 +21,18 @@
#include <net/ethernet.h>
+#include "time-util.h"
+
+#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } }
+
+#define IN6ADDR_ALL_NODES_MULTICAST_INIT \
+ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }
+
int icmp6_bind_router_solicitation(int index);
+int icmp6_bind_router_advertisement(int index);
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst,
+ triple_timestamp *timestamp);
diff -rupN systemd-base/src/libsystemd-network/ndisc-internal.h systemd/src/libsystemd-network/ndisc-internal.h
--- systemd-base/src/libsystemd-network/ndisc-internal.h 2017-09-18 14:39:26.016590111 -0700
+++ systemd/src/libsystemd-network/ndisc-internal.h 2017-09-18 14:55:04.905317356 -0700
@@ -23,6 +23,10 @@
#include "sd-ndisc.h"
+#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
+#define NDISC_MAX_ROUTER_SOLICITATION_INTERVAL (3600U * USEC_PER_SEC)
+#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
+
struct sd_ndisc {
unsigned n_ref;
@@ -38,8 +42,9 @@ struct sd_ndisc {
sd_event_source *recv_event_source;
sd_event_source *timeout_event_source;
+ sd_event_source *timeout_no_ra;
- unsigned nd_sent;
+ usec_t retransmit_time;
sd_ndisc_callback_t callback;
void *userdata;
diff -rupN systemd-base/src/libsystemd-network/radv-internal.h systemd/src/libsystemd-network/radv-internal.h
--- systemd-base/src/libsystemd-network/radv-internal.h 1969-12-31 16:00:00.000000000 -0800
+++ systemd/src/libsystemd-network/radv-internal.h 2017-09-18 15:00:40.022354604 -0700
@@ -0,0 +1,88 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2017 Intel Corporation. All rights reserved.
+
+ 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-radv.h"
+
+#include "log.h"
+#include "list.h"
+#include "sparse-endian.h"
+
+#define SD_RADV_DEFAULT_MIN_TIMEOUT_USEC (200*USEC_PER_SEC)
+#define SD_RADV_DEFAULT_MAX_TIMEOUT_USEC (600*USEC_PER_SEC)
+assert_cc(SD_RADV_DEFAULT_MIN_TIMEOUT_USEC <= SD_RADV_DEFAULT_MAX_TIMEOUT_USEC)
+
+#define SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC (16*USEC_PER_SEC)
+#define SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS 3
+#define SD_RADV_MAX_FINAL_RTR_ADVERTISEMENTS 3
+#define SD_RADV_MIN_DELAY_BETWEEN_RAS 3
+#define SD_RADV_MAX_RA_DELAY_TIME_USEC (500*USEC_PER_MSEC)
+
+enum RAdvState {
+ SD_RADV_STATE_IDLE = 0,
+ SD_RADV_STATE_ADVERTISING = 1,
+};
+typedef enum RAdvState RAdvState;
+
+struct sd_radv {
+ unsigned n_ref;
+ RAdvState state;
+
+ int ifindex;
+
+ sd_event *event;
+ int event_priority;
+
+ struct ether_addr mac_addr;
+ uint8_t hop_limit;
+ uint8_t flags;
+ uint32_t mtu;
+ uint16_t lifetime;
+
+ int fd;
+ unsigned ra_sent;
+ sd_event_source *recv_event_source;
+ sd_event_source *timeout_event_source;
+
+ unsigned n_prefixes;
+ LIST_HEAD(sd_radv_prefix, prefixes);
+};
+
+struct sd_radv_prefix {
+ unsigned n_ref;
+
+ struct {
+ uint8_t type;
+ uint8_t length;
+ uint8_t prefixlen;
+ uint8_t flags;
+ be32_t valid_lifetime;
+ be32_t preferred_lifetime;
+ uint32_t reserved;
+ struct in6_addr in6_addr;
+ } _packed_ opt;
+
+ LIST_FIELDS(struct sd_radv_prefix, prefix);
+};
+
+#define log_radv_full(level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "RADV: " fmt, ##__VA_ARGS__)
+#define log_radv_errno(error, fmt, ...) log_radv_full(LOG_DEBUG, error, fmt, ##__VA_ARGS__)
+#define log_radv_warning_errno(error, fmt, ...) log_radv_full(LOG_WARNING, error, fmt, ##__VA_ARGS__)
+#define log_radv(fmt, ...) log_radv_errno(0, fmt, ##__VA_ARGS__)
diff -rupN systemd-base/src/libsystemd-network/sd-ndisc.c systemd/src/libsystemd-network/sd-ndisc.c
--- systemd-base/src/libsystemd-network/sd-ndisc.c 2017-09-18 14:39:26.016590111 -0700
+++ systemd/src/libsystemd-network/sd-ndisc.c 2017-09-18 15:00:40.038396204 -0700
@@ -28,12 +28,12 @@
#include "in-addr-util.h"
#include "ndisc-internal.h"
#include "ndisc-router.h"
+#include "random-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "util.h"
-#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC)
-#define NDISC_MAX_ROUTER_SOLICITATIONS 3U
+#define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
assert(ndisc);
@@ -129,6 +129,8 @@ static int ndisc_reset(sd_ndisc *nd) {
assert(nd);
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+ nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
+ nd->retransmit_time = 0;
nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
nd->fd = safe_close(nd->fd);
@@ -221,23 +223,9 @@ static int ndisc_handle_datagram(sd_ndis
static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
_cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
sd_ndisc *nd = userdata;
- union {
- struct cmsghdr cmsghdr;
- uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */
- CMSG_SPACE(sizeof(struct timeval))];
- } control = {};
- struct iovec iov = {};
- union sockaddr_union sa = {};
- struct msghdr msg = {
- .msg_name = &sa.sa,
- .msg_namelen = sizeof(sa),
- .msg_iov = &iov,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- struct cmsghdr *cmsg;
- ssize_t len, buflen;
+ ssize_t buflen;
+ int r;
+ _cleanup_free_ char *addr = NULL;
assert(s);
assert(nd);
@@ -251,110 +239,90 @@ static int ndisc_recv(sd_event_source *s
if (!rt)
return -ENOMEM;
- iov.iov_base = NDISC_ROUTER_RAW(rt);
- iov.iov_len = rt->raw_size;
-
- len = recvmsg(fd, &msg, MSG_DONTWAIT);
- if (len < 0) {
- if (errno == EAGAIN || errno == EINTR)
- return 0;
-
- return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m");
- }
-
- if ((size_t) len != rt->raw_size) {
- log_ndisc("Packet size mismatch.");
- return -EINVAL;
- }
-
- if (msg.msg_namelen == sizeof(struct sockaddr_in6) &&
- sa.in6.sin6_family == AF_INET6) {
-
- if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) {
- _cleanup_free_ char *addr = NULL;
-
- (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr);
- log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr));
- return 0;
- }
-
- rt->address = sa.in6.sin6_addr;
-
- } else if (msg.msg_namelen > 0) {
- log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen);
- return -EINVAL;
- }
-
- /* namelen == 0 only happens when running the test-suite over a socketpair */
-
- assert(!(msg.msg_flags & MSG_CTRUNC));
- assert(!(msg.msg_flags & MSG_TRUNC));
-
- CMSG_FOREACH(cmsg, &msg) {
- if (cmsg->cmsg_level == SOL_IPV6 &&
- cmsg->cmsg_type == IPV6_HOPLIMIT &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
- int hops = *(int*) CMSG_DATA(cmsg);
-
- if (hops != 255) {
- log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops);
- return 0;
- }
+ r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
+ &rt->timestamp);
+ if (r < 0) {
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr);
+ log_ndisc("Received RA from non-link-local address %s. Ignoring", addr);
+ break;
+
+ case -EMULTIHOP:
+ log_ndisc("Received RA with invalid hop limit. Ignoring.");
+ break;
+
+ case -EPFNOSUPPORT:
+ log_ndisc("Received invalid source address from ICMPv6 socket.");
+ break;
}
- if (cmsg->cmsg_level == SOL_SOCKET &&
- cmsg->cmsg_type == SO_TIMESTAMP &&
- cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
- triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg)));
+ return 0;
}
- if (!triple_timestamp_is_set(&rt->timestamp))
- triple_timestamp_get(&rt->timestamp);
-
nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
return ndisc_handle_datagram(nd, rt);
}
+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 +
+ (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
+}
+
static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
sd_ndisc *nd = userdata;
- usec_t time_now, next_timeout;
+ usec_t time_now;
int r;
+ char time_string[FORMAT_TIMESPAN_MAX];
assert(s);
assert(nd);
assert(nd->event);
- if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) {
- nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
- ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
- return 0;
+ assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+
+ nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
+
+ if (!nd->retransmit_time)
+ nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
+ else {
+ if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
+ nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
+ else
+ nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
}
- r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
- if (r < 0) {
- log_ndisc_errno(r, "Error sending Router Solicitation: %m");
+ r = sd_event_add_time(nd->event, &nd->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ time_now + nd->retransmit_time,
+ 10 * USEC_PER_MSEC, ndisc_timeout, nd);
+ if (r < 0)
goto fail;
- }
- log_ndisc("Sent Router Solicitation");
- nd->nd_sent++;
+ r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority);
+ if (r < 0)
+ goto fail;
- assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
- next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL;
+ (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout-no-ra");
- r = sd_event_source_set_time(nd->timeout_event_source, next_timeout);
+ r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
if (r < 0) {
- log_ndisc_errno(r, "Error updating timer: %m");
+ log_ndisc_errno(r, "Error reenabling timer: %m");
goto fail;
}
- r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT);
+ r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
if (r < 0) {
- log_ndisc_errno(r, "Error reenabling timer: %m");
+ log_ndisc_errno(r, "Error sending Router Solicitation: %m");
goto fail;
}
+ log_ndisc("Sent Router Solicitation, next solicitation in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ nd->retransmit_time, USEC_PER_SEC));
+
return 0;
fail:
@@ -362,6 +330,20 @@ fail:
return 0;
}
+static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ndisc *nd = userdata;
+
+ assert(s);
+ assert(nd);
+
+ log_ndisc("No RA received before link confirmation timeout");
+
+ nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
+ ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
+
+ return 0;
+}
+
_public_ int sd_ndisc_stop(sd_ndisc *nd) {
assert_return(nd, -EINVAL);
@@ -376,6 +358,7 @@ _public_ int sd_ndisc_stop(sd_ndisc *nd)
_public_ int sd_ndisc_start(sd_ndisc *nd) {
int r;
+ usec_t time_now;
assert_return(nd, -EINVAL);
assert_return(nd->event, -EINVAL);
@@ -387,6 +370,10 @@ _public_ int sd_ndisc_start(sd_ndisc *nd
assert(!nd->recv_event_source);
assert(!nd->timeout_event_source);
+ r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ goto fail;
+
nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
if (nd->fd < 0)
return nd->fd;
@@ -411,6 +398,19 @@ _public_ int sd_ndisc_start(sd_ndisc *nd
(void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout");
+ r = sd_event_add_time(nd->event, &nd->timeout_no_ra,
+ clock_boottime_or_monotonic(),
+ time_now + NDISC_TIMEOUT_NO_RA_USEC,
+ 10 * USEC_PER_MSEC, ndisc_timeout_no_ra, nd);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(nd->timeout_no_ra, nd->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(nd->timeout_no_ra, "ndisc-timeout-no-ra");
+
log_ndisc("Started IPv6 Router Solicitation client");
return 1;
diff -rupN systemd-base/src/libsystemd-network/sd-radv.c systemd/src/libsystemd-network/sd-radv.c
--- systemd-base/src/libsystemd-network/sd-radv.c 1969-12-31 16:00:00.000000000 -0800
+++ systemd/src/libsystemd-network/sd-radv.c 2017-09-18 15:00:40.022354604 -0700
@@ -0,0 +1,653 @@
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2017 Intel Corporation. All rights reserved.
+
+ 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <linux/in6.h>
+
+#include "sd-radv.h"
+
+#include "macro.h"
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "icmp6-util.h"
+#include "in-addr-util.h"
+#include "radv-internal.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "util.h"
+#include "random-util.h"
+
+_public_ int sd_radv_new(sd_radv **ret) {
+ _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ ra = new0(sd_radv, 1);
+ if (!ra)
+ return -ENOMEM;
+
+ ra->n_ref = 1;
+ ra->fd = -1;
+
+ LIST_HEAD_INIT(ra->prefixes);
+
+ *ret = ra;
+ ra = NULL;
+
+ return 0;
+}
+
+_public_ int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority) {
+ int r;
+
+ assert_return(ra, -EINVAL);
+ assert_return(!ra->event, -EBUSY);
+
+ if (event)
+ ra->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&ra->event);
+ if (r < 0)
+ return 0;
+ }
+
+ ra->event_priority = priority;
+
+ return 0;
+}
+
+_public_ int sd_radv_detach_event(sd_radv *ra) {
+
+ assert_return(ra, -EINVAL);
+
+ ra->event = sd_event_unref(ra->event);
+ return 0;
+}
+
+_public_ sd_event *sd_radv_get_event(sd_radv *ra) {
+ assert_return(ra, NULL);
+
+ return ra->event;
+}
+
+static void radv_reset(sd_radv *ra) {
+
+ ra->timeout_event_source =
+ sd_event_source_unref(ra->timeout_event_source);
+
+ ra->recv_event_source =
+ sd_event_source_unref(ra->recv_event_source);
+
+ ra->ra_sent = 0;
+}
+
+_public_ sd_radv *sd_radv_ref(sd_radv *ra) {
+ if (!ra)
+ return NULL;
+
+ assert(ra->n_ref > 0);
+ ra->n_ref++;
+
+ return ra;
+}
+
+_public_ sd_radv *sd_radv_unref(sd_radv *ra) {
+ if (!ra)
+ return NULL;
+
+ assert(ra->n_ref > 0);
+ ra->n_ref--;
+
+ if (ra->n_ref > 0)
+ return NULL;
+
+ while (ra->prefixes) {
+ sd_radv_prefix *p = ra->prefixes;
+
+ LIST_REMOVE(prefix, ra->prefixes, p);
+ sd_radv_prefix_unref(p);
+ }
+
+ radv_reset(ra);
+
+ sd_radv_detach_event(ra);
+ return mfree(ra);
+}
+
+static int radv_send(sd_radv *ra, const struct in6_addr *dst,
+ const uint32_t router_lifetime) {
+ static const struct ether_addr mac_zero = {};
+ sd_radv_prefix *p;
+ struct sockaddr_in6 dst_addr = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = IN6ADDR_ALL_NODES_MULTICAST_INIT,
+ };
+ struct nd_router_advert adv = {};
+ struct {
+ struct nd_opt_hdr opthdr;
+ struct ether_addr slladdr;
+ } _packed_ opt_mac = {
+ .opthdr = {
+ .nd_opt_type = ND_OPT_SOURCE_LINKADDR,
+ .nd_opt_len = (sizeof(struct nd_opt_hdr) +
+ sizeof(struct ether_addr) - 1) /8 + 1,
+ },
+ };
+ struct nd_opt_mtu opt_mtu = {
+ .nd_opt_mtu_type = ND_OPT_MTU,
+ .nd_opt_mtu_len = 1,
+ };
+ /* Reserve iov space for RA header, linkaddr, MTU + N prefixes */
+ struct iovec iov[3 + ra->n_prefixes];
+ struct msghdr msg = {
+ .msg_name = &dst_addr,
+ .msg_namelen = sizeof(dst_addr),
+ .msg_iov = iov,
+ };
+
+ if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst))
+ dst_addr.sin6_addr = *dst;
+
+ adv.nd_ra_type = ND_ROUTER_ADVERT;
+ adv.nd_ra_curhoplimit = ra->hop_limit;
+ adv.nd_ra_flags_reserved = ra->flags;
+ adv.nd_ra_router_lifetime = htobe16(router_lifetime);
+ iov[msg.msg_iovlen].iov_base = &adv;
+ iov[msg.msg_iovlen].iov_len = sizeof(adv);
+ msg.msg_iovlen++;
+
+ /* MAC address is optional, either because the link does not use L2
+ addresses or load sharing is desired. See RFC 4861, Section 4.2 */
+ if (memcmp(&mac_zero, &ra->mac_addr, sizeof(mac_zero))) {
+ opt_mac.slladdr = ra->mac_addr;
+ iov[msg.msg_iovlen].iov_base = &opt_mac;
+ iov[msg.msg_iovlen].iov_len = sizeof(opt_mac);
+ msg.msg_iovlen++;
+ }
+
+ if (ra->mtu) {
+ opt_mtu.nd_opt_mtu_mtu = htobe32(ra->mtu);
+ iov[msg.msg_iovlen].iov_base = &opt_mtu;
+ iov[msg.msg_iovlen].iov_len = sizeof(opt_mtu);
+ msg.msg_iovlen++;
+ }
+
+ LIST_FOREACH(prefix, p, ra->prefixes) {
+ iov[msg.msg_iovlen].iov_base = &p->opt;
+ iov[msg.msg_iovlen].iov_len = sizeof(p->opt);
+ msg.msg_iovlen++;
+ }
+
+ if (sendmsg(ra->fd, &msg, 0) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ sd_radv *ra = userdata;
+ _cleanup_free_ char *addr = NULL;
+ struct in6_addr src;
+ triple_timestamp timestamp;
+ int r;
+ ssize_t buflen;
+ _cleanup_free_ char *buf = NULL;
+
+ assert(s);
+ assert(ra);
+ assert(ra->event);
+
+ buflen = next_datagram_size_fd(fd);
+
+ if ((unsigned) buflen < sizeof(struct nd_router_solicit))
+ return log_radv("Too short packet received");
+
+ buf = new0(char, buflen);
+ if (!buf)
+ return 0;
+
+ r = icmp6_receive(fd, buf, buflen, &src, ×tamp);
+ if (r < 0) {
+ switch (r) {
+ case -EADDRNOTAVAIL:
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+ log_radv("Received RS from non-link-local address %s. Ignoring", addr);
+ break;
+
+ case -EMULTIHOP:
+ log_radv("Received RS with invalid hop limit. Ignoring.");
+ break;
+
+ case -EPFNOSUPPORT:
+ log_radv("Received invalid source address from ICMPv6 socket. Ignoring.");
+ break;
+
+ default:
+ log_radv_warning_errno(r, "Error receiving from ICMPv6 socket: %m");
+ break;
+ }
+
+ return 0;
+ }
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr);
+
+ r = radv_send(ra, &src, ra->lifetime);
+ if (r < 0)
+ log_radv_warning_errno(r, "Unable to send solicited Router Advertisment to %s: %m", addr);
+ else
+ log_radv("Sent solicited Router Advertisement to %s", addr);
+
+ return 0;
+}
+
+static usec_t radv_compute_timeout(usec_t min, usec_t max) {
+ assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC);
+
+ return min + (random_u32() % (max - min));
+}
+
+static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ int r;
+ sd_radv *ra = userdata;
+ usec_t min_timeout = SD_RADV_DEFAULT_MIN_TIMEOUT_USEC;
+ usec_t max_timeout = SD_RADV_DEFAULT_MAX_TIMEOUT_USEC;
+ usec_t time_now, timeout;
+ char time_string[FORMAT_TIMESPAN_MAX];
+
+ assert(s);
+ assert(ra);
+ assert(ra->event);
+
+ ra->timeout_event_source = sd_event_source_unref(ra->timeout_event_source);
+
+ r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ goto fail;
+
+ r = radv_send(ra, NULL, ra->lifetime);
+ if (r < 0)
+ log_radv_warning_errno(r, "Unable to send Router Advertisement: %m");
+
+ /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */
+ if (ra->ra_sent < SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) {
+ max_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC;
+ min_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC / 3;
+ }
+
+ timeout = radv_compute_timeout(min_timeout, max_timeout);
+
+ log_radv("Next Router Advertisement in %s",
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX,
+ timeout, USEC_PER_SEC));
+
+ r = sd_event_add_time(ra->event, &ra->timeout_event_source,
+ clock_boottime_or_monotonic(),
+ time_now + timeout, MSEC_PER_SEC,
+ radv_timeout, ra);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(ra->timeout_event_source,
+ ra->event_priority);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_description(ra->timeout_event_source,
+ "radv-timeout");
+ if (r < 0)
+ goto fail;
+
+ ra->ra_sent++;
+
+fail:
+ if (r < 0)
+ sd_radv_stop(ra);
+
+ return 0;
+}
+
+_public_ int sd_radv_stop(sd_radv *ra) {
+ int r;
+
+ assert_return(ra, -EINVAL);
+
+ log_radv("Stopping IPv6 Router Advertisement daemon");
+
+ /* RFC 4861, Section 6.2.5, send at least one Router Advertisement
+ with zero lifetime */
+ r = radv_send(ra, NULL, 0);
+ if (r < 0)
+ log_radv_warning_errno(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m");
+
+ radv_reset(ra);
+ ra->fd = safe_close(ra->fd);
+ ra->state = SD_RADV_STATE_IDLE;
+
+ return 0;
+}
+
+_public_ int sd_radv_start(sd_radv *ra) {
+ int r = 0;
+
+ assert_return(ra, -EINVAL);
+ assert_return(ra->event, -EINVAL);
+ assert_return(ra->ifindex > 0, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return 0;
+
+ r = sd_event_add_time(ra->event, &ra->timeout_event_source,
+ clock_boottime_or_monotonic(), 0, 0,
+ radv_timeout, ra);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(ra->timeout_event_source,
+ ra->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(ra->timeout_event_source,
+ "radv-timeout");
+
+ r = icmp6_bind_router_advertisement(ra->ifindex);
+ if (r < 0)
+ goto fail;
+
+ ra->fd = r;
+
+ r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra);
+ if (r < 0)
+ goto fail;
+
+ r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message");
+
+ ra->state = SD_RADV_STATE_ADVERTISING;
+
+ log_radv("Started IPv6 Router Advertisement daemon");
+
+ return 0;
+
+ fail:
+ radv_reset(ra);
+
+ return r;
+}
+
+_public_ int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
+ assert_return(ra, -EINVAL);
+ assert_return(ifindex >= -1, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->ifindex = ifindex;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ if (mac_addr)
+ ra->mac_addr = *mac_addr;
+ else
+ zero(ra->mac_addr);
+
+ return 0;
+}
+
+_public_ int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu) {
+ assert_return(ra, -EINVAL);
+ assert_return(mtu >= 1280, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->mtu = mtu;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ ra->hop_limit = hop_limit;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_router_lifetime(sd_radv *ra, uint32_t router_lifetime) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the
+ preference value MUST be set to (00) by the sender..." */
+ if (router_lifetime == 0 &&
+ (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3))
+ return -ETIME;
+
+ ra->lifetime = router_lifetime;
+
+ return 0;
+}
+
+_public_ int sd_radv_set_managed_information(sd_radv *ra, int managed) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed);
+
+ return 0;
+}
+
+_public_ int sd_radv_set_other_information(sd_radv *ra, int other) {
+ assert_return(ra, -EINVAL);
+
+ if (ra->state != SD_RADV_STATE_IDLE)
+ return -EBUSY;
+
+ SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other);
+
+ return 0;
+}
+
+_public_ int sd_radv_set_preference(sd_radv *ra, unsigned preference) {
+ int r = 0;
+
+ assert_return(ra, -EINVAL);
+ assert_return(IN_SET(preference,
+ SD_NDISC_PREFERENCE_LOW,
+ SD_NDISC_PREFERENCE_MEDIUM,
+ SD_NDISC_PREFERENCE_HIGH), -EINVAL);
+
+ ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3);
+
+ return r;
+}
+
+_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) {
+ sd_radv_prefix *cur;
+ _cleanup_free_ char *addr_p = NULL;
+
+ assert_return(ra, -EINVAL);
+
+ if (!p)
+ return -EINVAL;
+
+ LIST_FOREACH(prefix, cur, ra->prefixes) {
+ int r;
+
+ r = in_addr_prefix_intersect(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ cur->opt.prefixlen,
+ (union in_addr_union*) &p->opt.in6_addr,
+ p->opt.prefixlen);
+ if (r > 0) {
+ _cleanup_free_ char *addr_cur = NULL;
+
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &cur->opt.in6_addr,
+ &addr_cur);
+ (void) in_addr_to_string(AF_INET6,
+ (union in_addr_union*) &p->opt.in6_addr,
+ &addr_p);
+
+ log_radv("IPv6 prefix %s/%u already configured, ignoring %s/%u",
+ addr_cur, cur->opt.prefixlen,
+ addr_p, p->opt.prefixlen);
+
+ return -EEXIST;
+ }
+ }
+
+ p = sd_radv_prefix_ref(p);
+
+ LIST_APPEND(prefix, ra->prefixes, p);
+
+ ra->n_prefixes++;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &p->opt.in6_addr, &addr_p);
+ log_radv("Added prefix %s/%d", addr_p, p->opt.prefixlen);
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_new(sd_radv_prefix **ret) {
+ _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ p = new0(sd_radv_prefix, 1);
+ if (!p)
+ return -ENOMEM;
+
+ p->n_ref = 1;
+
+ p->opt.type = ND_OPT_PREFIX_INFORMATION;
+ p->opt.length = (sizeof(p->opt) - 1) /8 + 1;
+
+ p->opt.prefixlen = 64;
+
+ /* RFC 4861, Section 6.2.1 */
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_ONLINK, true);
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_AUTO, true);
+ p->opt.preferred_lifetime = htobe32(604800);
+ p->opt.valid_lifetime = htobe32(2592000);
+
+ LIST_INIT(prefix, p);
+
+ *ret = p;
+ p = NULL;
+
+ return 0;
+}
+
+_public_ sd_radv_prefix *sd_radv_prefix_ref(sd_radv_prefix *p) {
+ if (!p)
+ return NULL;
+
+ assert(p->n_ref > 0);
+ p->n_ref++;
+
+ return p;
+}
+
+_public_ sd_radv_prefix *sd_radv_prefix_unref(sd_radv_prefix *p) {
+ if (!p)
+ return NULL;
+
+ assert(p->n_ref > 0);
+ p->n_ref--;
+
+ if (p->n_ref > 0)
+ return NULL;
+
+ return mfree(p);
+}
+
+_public_ int sd_radv_prefix_set_prefix(sd_radv_prefix *p, struct in6_addr *in6_addr,
+ unsigned char prefixlen) {
+ assert_return(p, -EINVAL);
+ assert_return(in6_addr, -EINVAL);
+
+ if (prefixlen < 3 || prefixlen > 128)
+ return -EINVAL;
+
+ if (prefixlen > 64)
+ /* unusual but allowed, log it */
+ log_radv("Unusual prefix length %d greater than 64", prefixlen);
+
+ p->opt.in6_addr = *in6_addr;
+ p->opt.prefixlen = prefixlen;
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink) {
+ assert_return(p, -EINVAL);
+
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_ONLINK, onlink);
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_set_address_autoconfiguration(sd_radv_prefix *p,
+ int address_autoconfiguration) {
+ assert_return(p, -EINVAL);
+
+ SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_AUTO, address_autoconfiguration);
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p,
+ uint32_t valid_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.valid_lifetime = htobe32(valid_lifetime);
+
+ return 0;
+}
+
+_public_ int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p,
+ uint32_t preferred_lifetime) {
+ assert_return(p, -EINVAL);
+
+ p->opt.preferred_lifetime = htobe32(preferred_lifetime);
+
+ return 0;
+}
diff -rupN systemd-base/src/libsystemd-network/test-ndisc-rs.c systemd/src/libsystemd-network/test-ndisc-rs.c
--- systemd-base/src/libsystemd-network/test-ndisc-rs.c 2017-09-18 14:39:26.016590111 -0700
+++ systemd/src/libsystemd-network/test-ndisc-rs.c 2017-09-18 15:07:09.099322638 -0700
@@ -193,6 +193,21 @@ int icmp6_bind_router_solicitation(int i
return test_fd[0];
}
+int icmp6_bind_router_advertisement(int index) {
+
+ return -ENOSYS;
+}
+
+int icmp6_receive(int fd, void *iov_base, size_t iov_len,
+ struct in6_addr *dst, triple_timestamp *timestamp) {
+ assert (read (fd, iov_base, iov_len) == (ssize_t)iov_len);
+
+ if (timestamp)
+ triple_timestamp_get(timestamp);
+
+ return 0;
+}
+
static int send_ra(uint8_t flags) {
uint8_t advertisement[] = {
0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4,
diff -rupN systemd-base/src/network/networkd-ndisc.c systemd/src/network/networkd-ndisc.c
--- systemd-base/src/network/networkd-ndisc.c 2017-09-18 14:39:26.020600510 -0700
+++ systemd/src/network/networkd-ndisc.c 2017-09-18 14:39:40.690643380 -0700
@@ -27,6 +27,7 @@
#define NDISC_DNSSL_MAX 64U
#define NDISC_RDNSS_MAX 64U
+#define NDISC_PREFIX_LFT_MIN 7200U
static int ndisc_netlink_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) {
_cleanup_link_unref_ Link *link = userdata;
@@ -152,13 +153,21 @@ static void ndisc_router_process_default
static void ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
_cleanup_address_free_ Address *address = NULL;
- uint32_t lifetime_valid, lifetime_preferred;
+ Address *existing_address;
+ uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining;
+ usec_t time_now;
unsigned prefixlen;
int r;
assert(link);
assert(rt);
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+ return;
+ }
+
r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
if (r < 0) {
log_link_error_errno(link, r, "Failed to get prefix length: %m");
@@ -207,7 +216,24 @@ static void ndisc_router_process_autonom
address->prefixlen = prefixlen;
address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
address->cinfo.ifa_prefered = lifetime_preferred;
- address->cinfo.ifa_valid = lifetime_valid;
+
+ /* see RFC4862 section 5.5.3.e */
+ r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address);
+ if (r > 0) {
+ lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
+ if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
+ address->cinfo.ifa_valid = lifetime_valid;
+ else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
+ address->cinfo.ifa_valid = lifetime_remaining;
+ else
+ address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
+ } else if (lifetime_valid > 0)
+ address->cinfo.ifa_valid = lifetime_valid;
+ else
+ return; /* see RFC4862 section 5.5.3.d */
+
+ if (address->cinfo.ifa_valid == 0)
+ return;
r = address_configure(address, link, ndisc_netlink_handler, true);
if (r < 0) {
diff -rupN systemd-base/src/systemd/sd-radv.h systemd/src/systemd/sd-radv.h
--- systemd-base/src/systemd/sd-radv.h 1969-12-31 16:00:00.000000000 -0800
+++ systemd/src/systemd/sd-radv.h 2017-09-18 14:58:44.807575602 -0700
@@ -0,0 +1,81 @@
+#ifndef foosdradvfoo
+#define foosdradvfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright (C) 2017 Intel Corporation. All rights reserved.
+
+ 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <net/ethernet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+
+#include "sd-ndisc.h"
+
+#include "sd-event.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_radv sd_radv;
+typedef struct sd_radv_prefix sd_radv_prefix;
+
+/* Router Advertisment */
+int sd_radv_new(sd_radv **ret);
+sd_radv *sd_radv_ref(sd_radv *ra);
+sd_radv *sd_radv_unref(sd_radv *ra);
+
+int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority);
+int sd_radv_detach_event(sd_radv *nd);
+sd_event *sd_radv_get_event(sd_radv *ra);
+
+int sd_radv_start(sd_radv *ra);
+int sd_radv_stop(sd_radv *ra);
+
+int sd_radv_set_ifindex(sd_radv *ra, int interface_index);
+int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr);
+int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu);
+int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit);
+int sd_radv_set_router_lifetime(sd_radv *ra, uint32_t router_lifetime);
+int sd_radv_set_managed_information(sd_radv *ra, int managed);
+int sd_radv_set_other_information(sd_radv *ra, int other);
+int sd_radv_set_preference(sd_radv *ra, unsigned preference);
+int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p);
+
+/* Advertised prefixes */
+int sd_radv_prefix_new(sd_radv_prefix **ret);
+sd_radv_prefix *sd_radv_prefix_ref(sd_radv_prefix *ra);
+sd_radv_prefix *sd_radv_prefix_unref(sd_radv_prefix *ra);
+
+int sd_radv_prefix_set_prefix(sd_radv_prefix *p, struct in6_addr *in6_addr,
+ unsigned char prefixlen);
+int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink);
+int sd_radv_prefix_set_address_autoconfiguration(sd_radv_prefix *p,
+ int address_autoconfiguration);
+int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p,
+ uint32_t valid_lifetime);
+int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p,
+ uint32_t preferred_lifetime);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv, sd_radv_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_prefix, sd_radv_prefix_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif