diff --git a/Makefile.am b/Makefile.am index 8aa6e14be40..66670962cc7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index c2e4b0e9e3c..7fbebd6f275 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -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; +} diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 2b4dbc76cee..16b8be8298d 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -21,5 +21,18 @@ #include +#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 --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 3285bcaed11..78c74560b83 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -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 diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h new file mode 100644 index 00000000000..b21d4e54cbd --- /dev/null +++ b/src/libsystemd-network/radv-internal.h @@ -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 . +***/ + +#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 --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 83e57d43f73..0437e0b0b76 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -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); diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c new file mode 100644 index 00000000000..f23275a80cd --- /dev/null +++ b/src/libsystemd-network/sd-radv.c @@ -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 . +***/ + +#include +#include +#include +#include + +#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 --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c new file mode 100644 index 00000000000..6e6d05642dc --- /dev/null +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -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 . +***/ + +#include +#include + +#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; +} diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index d9669488be3..b4a65395999 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -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, diff --git a/src/network/meson.build b/src/network/meson.build index 5f97cc197b9..521b21a18fa 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -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 diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 2e6c763aba4..de918c3c299 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -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; +}; diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 71a07ea7a33..065328482ef 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -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); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 37bebcf1916..98a5e3109ef 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -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"); + } } } diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index be5c4f32842..6479f4a2e54 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -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; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b2c585f8664..a2d38501a53 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3c26c8609e8..6f2ae66d403 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c9326c7f1e5..b31921947dc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c new file mode 100644 index 00000000000..e5be145146a --- /dev/null +++ b/src/network/networkd-radv.c @@ -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 . +***/ + +#include +#include + +#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; +} diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h new file mode 100644 index 00000000000..a186b111a1e --- /dev/null +++ b/src/network/networkd-radv.h @@ -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 . +***/ + +#include "networkd-link.h" + +int radv_configure(Link *link); diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h new file mode 100644 index 00000000000..4cbd80db686 --- /dev/null +++ b/src/systemd/sd-radv.h @@ -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 . +***/ + +#include +#include +#include +#include + +#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 diff --git a/src/test/meson.build b/src/test/meson.build index 223c9797494..0324e3451a5 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -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',