Merge pull request #5598 from pfl/ndisc_prefix_delegation

Initial Router Advertisment implementation
This commit is contained in:
Lennart Poettering 2017-05-19 11:17:07 +02:00 committed by GitHub
commit b2d0c14214
21 changed files with 1815 additions and 82 deletions

View file

@ -3644,6 +3644,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 \
@ -3667,6 +3668,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 \
@ -3760,6 +3763,16 @@ test_ndisc_rs_LDADD = \
libudev.la \
libsystemd-shared.la
test_ndisc_ra_SOURCES = \
src/systemd/sd-ndisc.h \
src/libsystemd-network/icmp6-util.h \
src/libsystemd-network/test-ndisc-ra.c
test_ndisc_ra_LDADD = \
libsystemd-network.la \
libudev.la \
libsystemd-shared.la
test_dhcp6_client_SOURCES = \
src/systemd/sd-dhcp6-client.h \
src/libsystemd-network/dhcp6-internal.h \
@ -3786,6 +3799,7 @@ tests += \
test-sd-dhcp-lease \
test-ipv4ll \
test-ndisc-rs \
test-ndisc-ra \
test-dhcp6-client \
test-lldp
@ -5864,6 +5878,8 @@ libnetworkd_core_la_SOURCES = \
src/network/networkd-dhcp4.c \
src/network/networkd-dhcp6.c \
src/network/networkd-ndisc.h \
src/network/networkd-radv.c \
src/network/networkd-radv.h \
src/network/networkd-ndisc.c \
src/network/networkd-network.h \
src/network/networkd-network.c \

View file

@ -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 index) {
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 index) {
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 index) {
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, const struct ether_addr *ether_addr) {
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;
}

View file

@ -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);

View file

@ -18,6 +18,8 @@ sources = files('''
ndisc-internal.h
ndisc-router.h
ndisc-router.c
sd-radv.c
radv-internal.h
icmp6-util.h
icmp6-util.c
sd-dhcp6-client.c

View file

@ -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__)

View file

@ -222,23 +222,9 @@ static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
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);
@ -252,66 +238,27 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
if (!rt)
return -ENOMEM;
iov.iov_base = NDISC_ROUTER_RAW(rt);
iov.iov_len = rt->raw_size;
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;
len = recvmsg(fd, &msg, MSG_DONTWAIT);
if (len < 0) {
if (errno == EAGAIN || errno == EINTR)
return 0;
case -EMULTIHOP:
log_ndisc("Received RA with invalid hop limit. Ignoring.");
break;
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;
case -EPFNOSUPPORT:
log_ndisc("Received invalid source address from ICMPv6 socket.");
break;
}
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;
return 0;
}
/* 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;
}
}
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)));
}
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);

View file

@ -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, &timestamp);
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;
}

View file

@ -0,0 +1,359 @@
/***
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 <arpa/inet.h>
#include "sd-radv.h"
#include "alloc-util.h"
#include "hexdecoct.h"
#include "icmp6-util.h"
#include "socket-util.h"
#include "strv.h"
static struct ether_addr mac_addr = {
.ether_addr_octet = { 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 }
};
static uint8_t advertisement[] = {
/* ICMPv6 Router Advertisement, no checksum */
0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Source Link Layer Address Option */
0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53,
/* Prefix Information Option */
0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4,
0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00,
0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Prefix Information Option */
0x03, 0x04, 0x40, 0xc0, 0x00, 0x27, 0x8d, 0x00,
0x00, 0x09, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00,
0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Prefix Information Option */
0x03, 0x04, 0x30, 0xc0, 0x00, 0x27, 0x8d, 0x00,
0x00, 0x09, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00,
0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* Recursive DNS Server Option - not yet supported */
0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
/* DNS Search List Option - not yet supported */
0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74,
0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static sd_event_source *test_hangcheck;
static bool test_stopped;
static int test_fd[2];
static sd_event_source *recv_router_advertisement;
static struct {
struct in6_addr address;
unsigned char prefixlen;
uint32_t valid;
uint32_t preferred;
bool succesful;
} prefix[] = {
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64,
500, 440, true },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64,
/* indicate default valid and preferred lifetimes for the test code */
0, 0, true },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 58,
0, 0,
/* indicate that this prefix already exists */
false },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 120,
0, 0,
/* indicate that this prefix already exists */
false },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 12,
0, 0,
/* indicate that this prefix already exists */
false },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 48,
0, 0, true },
{ { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 60,
0, 0,
/* indicate that this prefix already exists */
false },
};
static int test_rs_hangcheck(sd_event_source *s, uint64_t usec,
void *userdata) {
assert_se(false);
return 0;
}
static void test_radv_prefix(void) {
sd_radv_prefix *p;
printf("* %s\n", __FUNCTION__);
assert_se(sd_radv_prefix_new(&p) >= 0);
assert_se(sd_radv_prefix_set_onlink(NULL, true) < 0);
assert_se(sd_radv_prefix_set_onlink(p, true) >= 0);
assert_se(sd_radv_prefix_set_onlink(p, false) >= 0);
assert_se(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0);
assert_se(sd_radv_prefix_set_address_autoconfiguration(p, true) >= 0);
assert_se(sd_radv_prefix_set_address_autoconfiguration(p, false) >= 0);
assert_se(sd_radv_prefix_set_valid_lifetime(NULL, true) < 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, ~0) >= 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, 42) >= 0);
assert_se(sd_radv_prefix_set_valid_lifetime(p, 0) >= 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, true) < 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, ~0) >= 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, 42) >= 0);
assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0) >= 0);
assert_se(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0);
assert_se(sd_radv_prefix_set_prefix(p, NULL, 0) < 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 64) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 0) < 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 1) < 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 2) < 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 3) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 125) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 128) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0);
p = sd_radv_prefix_unref(p);
assert_se(!p);
}
static void test_radv(void) {
sd_radv *ra;
printf("* %s\n", __FUNCTION__);
assert_se(sd_radv_new(&ra) >= 0);
assert_se(ra);
assert_se(sd_radv_set_ifindex(NULL, 0) < 0);
assert_se(sd_radv_set_ifindex(ra, 0) >= 0);
assert_se(sd_radv_set_ifindex(ra, -1) >= 0);
assert_se(sd_radv_set_ifindex(ra, -2) < 0);
assert_se(sd_radv_set_ifindex(ra, 42) >= 0);
assert_se(sd_radv_set_mac(NULL, NULL) < 0);
assert_se(sd_radv_set_mac(ra, NULL) >= 0);
assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0);
assert_se(sd_radv_set_mtu(NULL, 0) < 0);
assert_se(sd_radv_set_mtu(ra, 0) < 0);
assert_se(sd_radv_set_mtu(ra, 1279) < 0);
assert_se(sd_radv_set_mtu(ra, 1280) >= 0);
assert_se(sd_radv_set_mtu(ra, ~0) >= 0);
assert_se(sd_radv_set_hop_limit(NULL, 0) < 0);
assert_se(sd_radv_set_hop_limit(ra, 0) >= 0);
assert_se(sd_radv_set_hop_limit(ra, ~0) >= 0);
assert_se(sd_radv_set_router_lifetime(NULL, 0) < 0);
assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, ~0) >= 0);
assert_se(sd_radv_set_preference(NULL, 0) < 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_LOW) >= 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
assert_se(sd_radv_set_preference(ra, ~0) < 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, 42000) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, 0) < 0);
assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0);
assert_se(sd_radv_set_managed_information(NULL, true) < 0);
assert_se(sd_radv_set_managed_information(ra, true) >= 0);
assert_se(sd_radv_set_managed_information(ra, false) >= 0);
assert_se(sd_radv_set_other_information(NULL, true) < 0);
assert_se(sd_radv_set_other_information(ra, true) >= 0);
assert_se(sd_radv_set_other_information(ra, false) >= 0);
ra = sd_radv_unref(ra);
assert_se(!ra);
}
int icmp6_bind_router_solicitation(int index) {
return -ENOSYS;
}
int icmp6_bind_router_advertisement(int index) {
assert_se(index == 42);
return test_fd[1];
}
int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) {
return 0;
}
int icmp6_receive(int fd, void *iov_base, size_t iov_len,
struct in6_addr *dst, triple_timestamp *timestamp) {
assert_se(read (fd, iov_base, iov_len) == (ssize_t)iov_len);
if (timestamp)
triple_timestamp_get(timestamp);
return 0;
}
static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
sd_radv *ra = userdata;
unsigned char buf[120];
size_t i;
read(test_fd[0], &buf, sizeof(buf));
/* router lifetime must be zero when test is stopped */
if (test_stopped) {
advertisement[6] = 0x00;
advertisement[7] = 0x00;
}
printf ("Received Router Advertisement with lifetime %u\n",
(advertisement[6] << 8) + advertisement[7]);
/* test only up to buf size, rest is not yet implemented */
for (i = 0; i < sizeof(buf); i++) {
printf("0x%02x", buf[i]);
assert_se(buf[i] == advertisement[i]);
if ((i + 1) % 8)
printf(", ");
else
printf("\n");
}
if (test_stopped) {
sd_event *e;
e = sd_radv_get_event(ra);
sd_event_exit(e, 0);
return 0;
}
assert_se(sd_radv_stop(ra) >= 0);
test_stopped = true;
return 0;
}
static void test_ra(void) {
sd_event *e;
sd_radv *ra;
usec_t time_now = now(clock_boottime_or_monotonic());
unsigned int i;
printf("* %s\n", __FUNCTION__);
assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, test_fd) >= 0);
assert_se(sd_event_new(&e) >= 0);
assert_se(sd_radv_new(&ra) >= 0);
assert_se(ra);
assert_se(sd_radv_attach_event(ra, e, 0) >= 0);
assert_se(sd_radv_set_ifindex(ra, 42) >= 0);
assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0);
assert_se(sd_radv_set_router_lifetime(ra, 180) >= 0);
assert_se(sd_radv_set_hop_limit(ra, 64) >= 0);
assert_se(sd_radv_set_managed_information(ra, true) >= 0);
assert_se(sd_radv_set_other_information(ra, true) >= 0);
for (i = 0; i < ELEMENTSOF(prefix); i++) {
sd_radv_prefix *p;
printf("Test prefix %u\n", i);
assert_se(sd_radv_prefix_new(&p) >= 0);
assert_se(sd_radv_prefix_set_prefix(p, &prefix[i].address,
prefix[i].prefixlen) >= 0);
if (prefix[i].valid)
assert_se(sd_radv_prefix_set_valid_lifetime(p, prefix[i].valid) >= 0);
if (prefix[i].preferred)
assert_se(sd_radv_prefix_set_preferred_lifetime(p, prefix[i].preferred) >= 0);
assert_se((sd_radv_add_prefix(ra, p) >= 0) == prefix[i].succesful);
assert_se(sd_radv_add_prefix(ra, p) < 0);
p = sd_radv_prefix_unref(p);
assert_se(!p);
}
assert_se(sd_event_add_io(e, &recv_router_advertisement, test_fd[0],
EPOLLIN, radv_recv, ra) >= 0);
assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(),
time_now + 2 *USEC_PER_SEC, 0,
test_rs_hangcheck, NULL) >= 0);
assert_se(sd_radv_start(ra) >= 0);
sd_event_loop(e);
test_hangcheck = sd_event_source_unref(test_hangcheck);
ra = sd_radv_unref(ra);
assert_se(!ra);
close(test_fd[0]);
sd_event_unref(e);
}
int main(int argc, char *argv[]) {
log_set_max_level(LOG_DEBUG);
log_parse_environment();
log_open();
test_radv_prefix();
test_radv();
test_ra();
printf("* done\n");
return 0;
}

View file

@ -193,6 +193,21 @@ int icmp6_bind_router_solicitation(int index) {
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,

View file

@ -54,6 +54,8 @@ sources = files('''
networkd-manager.h
networkd-ndisc.c
networkd-ndisc.h
networkd-radv.c
networkd-radv.h
networkd-network-bus.c
networkd-network.c
networkd-network.h

View file

@ -932,3 +932,252 @@ bool address_is_ready(const Address *a) {
return !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED));
}
int config_parse_router_preference(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
if (streq(rvalue, "high"))
network->router_preference = SD_NDISC_PREFERENCE_HIGH;
else if (STR_IN_SET(rvalue, "medium", "normal", "default"))
network->router_preference = SD_NDISC_PREFERENCE_MEDIUM;
else if (streq(rvalue, "low"))
network->router_preference = SD_NDISC_PREFERENCE_LOW;
else
log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Router preference '%s' is invalid, ignoring assignment: %m", rvalue);
return 0;
}
void prefix_free(Prefix *prefix) {
if (!prefix)
return;
if (prefix->network) {
LIST_REMOVE(prefixes, prefix->network->static_prefixes, prefix);
assert(prefix->network->n_static_prefixes > 0);
prefix->network->n_static_prefixes--;
if (prefix->section)
hashmap_remove(prefix->network->prefixes_by_section,
prefix->section);
}
prefix->radv_prefix = sd_radv_prefix_unref(prefix->radv_prefix);
free(prefix);
}
int prefix_new(Prefix **ret) {
Prefix *prefix = NULL;
prefix = new0(Prefix, 1);
if (!prefix)
return -ENOMEM;
if (sd_radv_prefix_new(&prefix->radv_prefix) < 0)
return -ENOMEM;
*ret = prefix;
prefix = NULL;
return 0;
}
int prefix_new_static(Network *network, const char *filename,
unsigned section_line, Prefix **ret) {
_cleanup_network_config_section_free_ NetworkConfigSection *n = NULL;
_cleanup_prefix_free_ Prefix *prefix = NULL;
int r;
assert(network);
assert(ret);
assert(!!filename == (section_line > 0));
if (filename) {
r = network_config_section_new(filename, section_line, &n);
if (r < 0)
return r;
if (section_line) {
prefix = hashmap_get(network->prefixes_by_section, n);
if (prefix) {
*ret = prefix;
prefix = NULL;
return 0;
}
}
}
r = prefix_new(&prefix);
if (r < 0)
return r;
if (filename) {
prefix->section = n;
n = NULL;
r = hashmap_put(network->prefixes_by_section, prefix->section,
prefix);
if (r < 0)
return r;
}
prefix->network = network;
LIST_APPEND(prefixes, network->static_prefixes, prefix);
network->n_static_prefixes++;
*ret = prefix;
prefix = NULL;
return 0;
}
int config_parse_prefix(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_prefix_free_ Prefix *p = NULL;
uint8_t prefixlen = 64;
union in_addr_union in6addr;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = prefix_new_static(network, filename, section_line, &p);
if (r < 0)
return r;
r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue);
return 0;
}
if (sd_radv_prefix_set_prefix(p->radv_prefix, &in6addr.in6, prefixlen) < 0)
return -EADDRNOTAVAIL;
log_syntax(unit, LOG_INFO, filename, line, r, "Found prefix %s", rvalue);
p = NULL;
return 0;
}
int config_parse_prefix_flags(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_prefix_free_ Prefix *p = NULL;
int r, val;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = prefix_new_static(network, filename, section_line, &p);
if (r < 0)
return r;
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue);
return 0;
}
val = r;
if (streq(lvalue, "OnLink"))
r = sd_radv_prefix_set_onlink(p->radv_prefix, val);
else if (streq(lvalue, "AddressAutoconfiguration"))
r = sd_radv_prefix_set_address_autoconfiguration(p->radv_prefix, val);
if (r < 0)
return r;
p = NULL;
return 0;
}
int config_parse_prefix_lifetime(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Network *network = userdata;
_cleanup_prefix_free_ Prefix *p = NULL;
usec_t usec;
int r;
assert(filename);
assert(section);
assert(lvalue);
assert(rvalue);
assert(data);
r = prefix_new_static(network, filename, section_line, &p);
if (r < 0)
return r;
r = parse_sec(rvalue, &usec);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Lifetime is invalid, ignoring assignment: %s", rvalue);
return 0;
}
/* a value of 0xffffffff represents infinity, 0x0 means this host is
not a router */
if (streq(lvalue, "PreferredLifetimeSec"))
r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix,
(usec + USEC_PER_SEC - 1) / USEC_PER_SEC);
else if (streq(lvalue, "ValidLifetimeSec"))
r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix,
(usec + USEC_PER_SEC - 1) / USEC_PER_SEC);
if (r < 0)
return r;
p = NULL;
return 0;
};

View file

@ -25,6 +25,7 @@
#include "in-addr-util.h"
typedef struct Address Address;
typedef struct Prefix Prefix;
#include "networkd-link.h"
#include "networkd-network.h"
@ -35,6 +36,15 @@ typedef struct Network Network;
typedef struct Link Link;
typedef struct NetworkConfigSection NetworkConfigSection;
struct Prefix {
Network *network;
NetworkConfigSection *section;
sd_radv_prefix *radv_prefix;
LIST_FIELDS(Prefix, prefixes);
};
struct Address {
Network *network;
NetworkConfigSection *section;
@ -79,8 +89,20 @@ bool address_is_ready(const Address *a);
DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);
#define _cleanup_address_free_ _cleanup_(address_freep)
int prefix_new(Prefix **ret);
void prefix_free(Prefix *prefix);
int prefix_new_static(Network *network, const char *filename, unsigned section,
Prefix **ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(Prefix*, prefix_free);
#define _cleanup_prefix_free_ _cleanup_(prefix_freep)
int config_parse_address(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_broadcast(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_label(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_lifetime(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_address_flags(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_router_preference(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_prefix(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_prefix_flags(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_prefix_lifetime(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);

View file

@ -32,6 +32,7 @@
#include "networkd-lldp-tx.h"
#include "networkd-manager.h"
#include "networkd-ndisc.h"
#include "networkd-radv.h"
#include "set.h"
#include "socket-util.h"
#include "stdio-util.h"
@ -119,6 +120,15 @@ static bool link_ipv6_enabled(Link *link) {
return link_ipv6ll_enabled(link) || network_has_static_ipv6_addresses(link->network);
}
static bool link_radv_enabled(Link *link) {
assert(link);
if (!link_ipv6ll_enabled(link))
return false;
return link->network->router_prefix_delegation;
}
static bool link_lldp_rx_enabled(Link *link) {
assert(link);
@ -521,6 +531,7 @@ static void link_free(Link *link) {
sd_ipv4ll_unref(link->ipv4ll);
sd_dhcp6_client_unref(link->dhcp6_client);
sd_ndisc_unref(link->ndisc);
sd_radv_unref(link->radv);
if (link->manager)
hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
@ -640,6 +651,12 @@ static int link_stop_clients(Link *link) {
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m");
}
if (link->radv) {
k = sd_radv_stop(link->radv);
if (k < 0)
r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m");
}
link_lldp_emit_stop(link);
return r;
}
@ -1554,6 +1571,17 @@ static int link_acquire_ipv6_conf(Link *link) {
return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
}
if (link_radv_enabled(link)) {
assert(link->radv);
assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
log_link_debug(link, "Starting IPv6 Router Advertisements");
r = sd_radv_start(link->radv);
if (r < 0 && r != -EBUSY)
return log_link_warning_errno(link, r, "Could not start IPv6 Router Advertisement: %m");
}
return 0;
}
@ -2562,6 +2590,12 @@ static int link_configure(Link *link) {
return r;
}
if (link_radv_enabled(link)) {
r = radv_configure(link);
if (r < 0)
return r;
}
if (link_lldp_rx_enabled(link)) {
r = sd_lldp_new(&link->lldp);
if (r < 0)
@ -3098,6 +3132,12 @@ int link_update(Link *link, sd_netlink_message *m) {
return r;
}
}
if (link->radv) {
r = sd_radv_set_mtu(link->radv, link->mtu);
if (r < 0)
return log_link_warning_errno(link, r, "Could not set MTU for Router Advertisement: %m");
}
}
/* The kernel may broadcast NEWLINK messages without the MAC address
@ -3166,6 +3206,12 @@ int link_update(Link *link, sd_netlink_message *m) {
if (r < 0)
return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m");
}
if (link->radv) {
r = sd_radv_set_mac(link->radv, &link->mac);
if (r < 0)
return log_link_warning_errno(link, r, "Could not update MAC for Router Advertisement: %m");
}
}
}

View file

@ -28,6 +28,7 @@
#include "sd-ipv4ll.h"
#include "sd-lldp.h"
#include "sd-ndisc.h"
#include "sd-radv.h"
#include "sd-netlink.h"
#include "list.h"
@ -117,6 +118,8 @@ typedef struct Link {
Set *ndisc_rdnss;
Set *ndisc_dnssl;
sd_radv *radv;
sd_dhcp6_client *dhcp6_client;
bool rtnl_extended_attrs;

View file

@ -137,6 +137,16 @@ BridgeFDB.VLANId, config_parse_fdb_vlan_id,
BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0
BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
Network.IPv6PrefixDelegation, config_parse_bool, 0, offsetof(Network, router_prefix_delegation)
IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0
IPv6Prefix.Prefix, config_parse_prefix, 0, 0
IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0
IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)

View file

@ -115,6 +115,7 @@ static int network_load_one(Manager *manager, const char *filename) {
LIST_HEAD_INIT(network->static_fdb_entries);
LIST_HEAD_INIT(network->ipv6_proxy_ndp_addresses);
LIST_HEAD_INIT(network->address_labels);
LIST_HEAD_INIT(network->static_prefixes);
network->stacked_netdevs = hashmap_new(&string_hash_ops);
if (!network->stacked_netdevs)
@ -134,6 +135,10 @@ static int network_load_one(Manager *manager, const char *filename) {
network->address_labels_by_section = hashmap_new(&network_config_hash_ops);
if (!network->address_labels_by_section)
log_oom();
network->prefixes_by_section = hashmap_new(&network_config_hash_ops);
if (!network->prefixes_by_section)
return log_oom();
network->filename = strdup(filename);
@ -207,7 +212,9 @@ static int network_load_one(Manager *manager, const char *filename) {
"IPv6NDPProxyAddress\0"
"Bridge\0"
"BridgeFDB\0"
"BridgeVLAN\0",
"BridgeVLAN\0"
"IPv6PrefixDelegation\0"
"IPv6Prefix\0",
config_item_perf_lookup, network_network_gperf_lookup,
false, network);
if (r < 0)
@ -279,6 +286,7 @@ void network_free(Network *network) {
FdbEntry *fdb_entry;
IPv6ProxyNDPAddress *ipv6_proxy_ndp_address;
AddressLabel *label;
Prefix *prefix;
Iterator i;
if (!network)
@ -329,10 +337,14 @@ void network_free(Network *network) {
while ((label = network->address_labels))
address_label_free(label);
while ((prefix = network->static_prefixes))
prefix_free(prefix);
hashmap_free(network->addresses_by_section);
hashmap_free(network->routes_by_section);
hashmap_free(network->fdb_entries_by_section);
hashmap_free(network->address_labels_by_section);
hashmap_free(network->prefixes_by_section);
if (network->manager) {
if (network->manager->networks)

View file

@ -158,6 +158,13 @@ struct Network {
AddressFamilyBoolean link_local;
bool ipv4ll_route;
/* IPv6 prefix delegation support */
bool router_prefix_delegation;
usec_t router_lifetime_usec;
uint8_t router_preference;
bool router_managed;
bool router_other_information;
/* Bridge Support */
bool use_bpdu;
bool hairpin;
@ -205,17 +212,20 @@ struct Network {
LIST_HEAD(FdbEntry, static_fdb_entries);
LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses);
LIST_HEAD(AddressLabel, address_labels);
LIST_HEAD(Prefix, static_prefixes);
unsigned n_static_addresses;
unsigned n_static_routes;
unsigned n_static_fdb_entries;
unsigned n_ipv6_proxy_ndp_addresses;
unsigned n_address_labels;
unsigned n_static_prefixes;
Hashmap *addresses_by_section;
Hashmap *routes_by_section;
Hashmap *fdb_entries_by_section;
Hashmap *address_labels_by_section;
Hashmap *prefixes_by_section;
struct in_addr_data *dns;
unsigned n_dns;

View file

@ -0,0 +1,77 @@
/***
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 <arpa/inet.h>
#include "networkd-address.h"
#include "networkd-radv.h"
#include "sd-radv.h"
int radv_configure(Link *link) {
int r;
Prefix *p;
assert(link);
assert(link->network);
r = sd_radv_new(&link->radv);
if (r < 0)
return r;
r = sd_radv_attach_event(link->radv, NULL, 0);
if (r < 0)
return r;
r = sd_radv_set_mac(link->radv, &link->mac);
if (r < 0)
return r;
r = sd_radv_set_ifindex(link->radv, link->ifindex);
if (r < 0)
return r;
r = sd_radv_set_managed_information(link->radv, link->network->router_managed);
if (r < 0)
return r;
r = sd_radv_set_other_information(link->radv, link->network->router_other_information);
if (r < 0)
return r;
r = sd_radv_set_router_lifetime(link->radv,
link->network->router_lifetime_usec);
if (r < 0)
return r;
if (link->network->router_lifetime_usec > 0) {
r = sd_radv_set_preference(link->radv,
link->network->router_preference);
if (r < 0)
return r;
}
LIST_FOREACH(prefixes, p, link->network->static_prefixes) {
r = sd_radv_add_prefix(link->radv, p->radv_prefix);
if (r != -EEXIST && r < 0)
return r;
}
return 0;
}

View file

@ -0,0 +1,24 @@
#pragma once
/***
This file is part of systemd.
Copyright 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 "networkd-link.h"
int radv_configure(Link *link);

81
src/systemd/sd-radv.h Normal file
View file

@ -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

View file

@ -862,6 +862,13 @@ tests += [
libsystemd_network],
[]],
[['src/libsystemd-network/test-ndisc-ra.c',
'src/libsystemd-network/icmp6-util.h',
'src/systemd/sd-ndisc.h'],
[libshared,
libsystemd_network],
[]],
[['src/libsystemd-network/test-dhcp6-client.c',
'src/libsystemd-network/dhcp-identifier.h',
'src/libsystemd-network/dhcp-identifier.c',