]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-radv.c
network: radv: Send RA on newly-added dynamic prefix
[thirdparty/systemd.git] / src / libsystemd-network / sd-radv.c
index a7bc422db0f06473d20456606415d85f0bba065b..6163cf169143467713c6b64213f840f20e872a59 100644 (file)
@@ -9,7 +9,6 @@
 
 #include "sd-radv.h"
 
-#include "macro.h"
 #include "alloc-util.h"
 #include "dns-domain.h"
 #include "ether-addr-util.h"
 #include "fd-util.h"
 #include "icmp6-util.h"
 #include "in-addr-util.h"
+#include "io-util.h"
+#include "macro.h"
+#include "memory-util.h"
 #include "radv-internal.h"
+#include "random-util.h"
 #include "socket-util.h"
 #include "string-util.h"
 #include "strv.h"
-#include "util.h"
-#include "random-util.h"
 
 _public_ int sd_radv_new(sd_radv **ret) {
         _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL;
@@ -76,6 +77,12 @@ _public_ sd_event *sd_radv_get_event(sd_radv *ra) {
         return ra->event;
 }
 
+_public_ int sd_radv_is_running(sd_radv *ra) {
+        assert_return(ra, false);
+
+        return ra->state != SD_RADV_STATE_IDLE;
+}
+
 static void radv_reset(sd_radv *ra) {
         assert(ra);
 
@@ -88,7 +95,8 @@ static void radv_reset(sd_radv *ra) {
 }
 
 static sd_radv *radv_free(sd_radv *ra) {
-        assert(ra);
+        if (!ra)
+                return NULL;
 
         while (ra->prefixes) {
                 sd_radv_prefix *p = ra->prefixes;
@@ -97,6 +105,13 @@ static sd_radv *radv_free(sd_radv *ra) {
                 sd_radv_prefix_unref(p);
         }
 
+        while (ra->route_prefixes) {
+                sd_radv_route_prefix *p = ra->route_prefixes;
+
+                LIST_REMOVE(prefix, ra->route_prefixes, p);
+                sd_radv_route_prefix_unref(p);
+        }
+
         free(ra->rdnss);
         free(ra->dnssl);
 
@@ -113,8 +128,8 @@ static sd_radv *radv_free(sd_radv *ra) {
 
 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free);
 
-static int radv_send(sd_radv *ra, const struct in6_addr *dst,
-                     const uint32_t router_lifetime) {
+static int radv_send(sd_radv *ra, const struct in6_addr *dst, uint32_t router_lifetime) {
+        sd_radv_route_prefix *rt;
         sd_radv_prefix *p;
         struct sockaddr_in6 dst_addr = {
                 .sin6_family = AF_INET6,
@@ -135,9 +150,9 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
                 .nd_opt_mtu_type = ND_OPT_MTU,
                 .nd_opt_mtu_len = 1,
         };
-        /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, RDNSS
+        /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, N routes, RDNSS
            and DNSSL */
-        struct iovec iov[5 + ra->n_prefixes];
+        struct iovec iov[5 + ra->n_prefixes + ra->n_route_prefixes];
         struct msghdr msg = {
                 .msg_name = &dst_addr,
                 .msg_namelen = sizeof(dst_addr),
@@ -146,35 +161,31 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
         usec_t time_now;
         int r;
 
+        assert(ra);
+
         r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
                 return r;
 
-        if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst))
+        if (dst && !IN6_IS_ADDR_UNSPECIFIED(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++;
+        iov[msg.msg_iovlen++] = IOVEC_MAKE(&adv, sizeof(adv));
 
         /* 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 (!ether_addr_is_null(&ra->mac_addr)) {
                 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++;
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mac, sizeof(opt_mac));
         }
 
         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++;
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mtu, sizeof(opt_mtu));
         }
 
         LIST_FOREACH(prefix, p, ra->prefixes) {
@@ -190,22 +201,17 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst,
                         else
                                 p->opt.preferred_lifetime = htobe32((p->preferred_until - time_now) / USEC_PER_SEC);
                 }
-                iov[msg.msg_iovlen].iov_base = &p->opt;
-                iov[msg.msg_iovlen].iov_len = sizeof(p->opt);
-                msg.msg_iovlen++;
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(&p->opt, sizeof(p->opt));
         }
 
-        if (ra->rdnss) {
-                iov[msg.msg_iovlen].iov_base = ra->rdnss;
-                iov[msg.msg_iovlen].iov_len = ra->rdnss->length * 8;
-                msg.msg_iovlen++;
-        }
+        LIST_FOREACH(prefix, rt, ra->route_prefixes)
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(&rt->opt, sizeof(rt->opt));
 
-        if (ra->dnssl) {
-                iov[msg.msg_iovlen].iov_base = ra->dnssl;
-                iov[msg.msg_iovlen].iov_len = ra->dnssl->length * 8;
-                msg.msg_iovlen++;
-        }
+        if (ra->rdnss)
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->rdnss, ra->rdnss->length * 8);
+
+        if (ra->dnssl)
+                iov[msg.msg_iovlen++] = IOVEC_MAKE(ra->dnssl, ra->dnssl->length * 8);
 
         if (sendmsg(ra->fd, &msg, 0) < 0)
                 return -errno;
@@ -227,13 +233,12 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat
         assert(ra->event);
 
         buflen = next_datagram_size_fd(fd);
-
-        if ((unsigned) buflen < sizeof(struct nd_router_solicit))
-                return log_radv("Too short packet received");
+        if (buflen < 0)
+                return (int) buflen;
 
         buf = new0(char, buflen);
         if (!buf)
-                return 0;
+                return -ENOMEM;
 
         r = icmp6_receive(fd, buf, buflen, &src, &timestamp);
         if (r < 0) {
@@ -262,13 +267,18 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat
                 return 0;
         }
 
+        if ((size_t) buflen < sizeof(struct nd_router_solicit)) {
+                log_radv("Too short packet received");
+                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_errno(r, "Unable to send solicited Router Advertisement to %s: %m", addr);
+                log_radv_errno(r, "Unable to send solicited Router Advertisement to %s: %m", strnull(addr));
         else
-                log_radv("Sent solicited Router Advertisement to %s", addr);
+                log_radv("Sent solicited Router Advertisement to %s", strnull(addr));
 
         return 0;
 }
@@ -276,6 +286,10 @@ static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdat
 static usec_t radv_compute_timeout(usec_t min, usec_t max) {
         assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC);
 
+        /* RFC 4861: min must be no less than 3s, max must be no less than 4s */
+        min = MAX(min, 3*USEC_PER_SEC);
+        max = MAX(max, 4*USEC_PER_SEC);
+
         return min + (random_u32() % (max - min));
 }
 
@@ -305,6 +319,13 @@ static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
                 min_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC / 3;
         }
 
+        /* RFC 4861, Section 6.2.1, lifetime must be at least MaxRtrAdvInterval,
+           so lower the interval here */
+        if (ra->lifetime > 0 && (ra->lifetime * USEC_PER_SEC) < max_timeout) {
+                max_timeout = ra->lifetime * USEC_PER_SEC;
+                min_timeout = max_timeout / 3;
+        }
+
         timeout = radv_compute_timeout(min_timeout, max_timeout);
 
         log_radv("Next Router Advertisement in %s",
@@ -353,7 +374,7 @@ _public_ int sd_radv_stop(sd_radv *ra) {
 }
 
 _public_ int sd_radv_start(sd_radv *ra) {
-        int r = 0;
+        int r;
 
         assert_return(ra, -EINVAL);
         assert_return(ra->event, -EINVAL);
@@ -400,7 +421,7 @@ _public_ int sd_radv_start(sd_radv *ra) {
 
 _public_ int sd_radv_set_ifindex(sd_radv *ra, int ifindex) {
         assert_return(ra, -EINVAL);
-        assert_return(ifindex >= -1, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
 
         if (ra->state != SD_RADV_STATE_IDLE)
                 return -EBUSY;
@@ -497,7 +518,7 @@ _public_ int sd_radv_set_preference(sd_radv *ra, unsigned preference) {
         return r;
 }
 
-_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) {
+_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, int dynamic) {
         sd_radv_prefix *cur;
         int r;
         _cleanup_free_ char *addr_p = NULL;
@@ -510,6 +531,10 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) {
         if (!p)
                 return -EINVAL;
 
+        /* Refuse prefixes that don't have a prefix set */
+        if (IN6_IS_ADDR_UNSPECIFIED(&p->opt.in6_addr))
+                return -ENOEXEC;
+
         LIST_FOREACH(prefix, cur, ra->prefixes) {
 
                 r = in_addr_prefix_intersect(AF_INET6,
@@ -553,6 +578,15 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) {
 
         cur = p;
 
+        /* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */
+        if (ra->ra_sent > 0) {
+            r = radv_send(ra, NULL, ra->lifetime);
+            if (r < 0)
+                    log_radv_errno(r, "Unable to send Router Advertisement for added prefix: %m");
+            else
+                    log_radv("Sent Router Advertisement for added prefix");
+        }
+
  update:
         r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
         if (r < 0)
@@ -571,8 +605,7 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) {
         cur->valid_until = valid_until;
         cur->preferred_until = preferred_until;
 
-        log_radv("%s prefix %s/%u preferred %s valid %s",
-                 cur? "Updated": "Added",
+        log_radv("Updated prefix %s/%u preferred %s valid %s",
                  addr_p, p->opt.prefixlen,
                  format_timespan(time_string_preferred, FORMAT_TIMESPAN_MAX,
                                  preferred, USEC_PER_SEC),
@@ -601,6 +634,7 @@ _public_ sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra,
 
                 LIST_REMOVE(prefix, ra->prefixes, cur);
                 ra->n_prefixes--;
+                sd_radv_prefix_unref(cur);
 
                 break;
         }
@@ -608,6 +642,85 @@ _public_ sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra,
         return cur;
 }
 
+_public_ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p, int dynamic) {
+        char time_string_valid[FORMAT_TIMESPAN_MAX];
+        usec_t time_now, valid, valid_until;
+        _cleanup_free_ char *pretty = NULL;
+        sd_radv_route_prefix *cur;
+        int r;
+
+        assert_return(ra, -EINVAL);
+
+        if (!p)
+                return -EINVAL;
+
+        (void) in_addr_to_string(AF_INET6,
+                                 (union in_addr_union*) &p->opt.in6_addr,
+                                 &pretty);
+
+        LIST_FOREACH(prefix, cur, ra->route_prefixes) {
+                _cleanup_free_ char *addr = NULL;
+
+                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)
+                        return r;
+                if (r == 0)
+                        continue;
+
+                if (dynamic && cur->opt.prefixlen == p->opt.prefixlen)
+                        goto update;
+
+                (void) in_addr_to_string(AF_INET6,
+                                         (union in_addr_union*) &cur->opt.in6_addr,
+                                         &addr);
+                log_radv("IPv6 route prefix %s/%u already configured, ignoring %s/%u",
+                         strempty(addr), cur->opt.prefixlen,
+                         strempty(pretty), p->opt.prefixlen);
+
+                return -EEXIST;
+        }
+
+        p = sd_radv_route_prefix_ref(p);
+
+        LIST_APPEND(prefix, ra->route_prefixes, p);
+        ra->n_route_prefixes++;
+
+        cur = p;
+        if (!dynamic) {
+                log_radv("Added prefix %s/%u", strempty(pretty), p->opt.prefixlen);
+                return 0;
+        }
+
+        /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */
+        if (ra->ra_sent > 0) {
+            r = radv_send(ra, NULL, ra->lifetime);
+            if (r < 0)
+                    log_radv_errno(r, "Unable to send Router Advertisement for added route prefix: %m");
+            else
+                    log_radv("Sent Router Advertisement for added route prefix");
+        }
+
+ update:
+        r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now);
+        if (r < 0)
+                return r;
+
+        valid = be32toh(p->opt.lifetime) * USEC_PER_SEC;
+        valid_until = usec_add(valid, time_now);
+        if (valid_until == USEC_INFINITY)
+                return -EOVERFLOW;
+
+        log_radv("Updated route prefix %s/%u valid %s",
+                 strempty(pretty), p->opt.prefixlen,
+                 format_timespan(time_string_valid, FORMAT_TIMESPAN_MAX, valid, USEC_PER_SEC));
+
+        return 0;
+}
+
 _public_ int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime,
                                const struct in6_addr *dns, size_t n_dns) {
         _cleanup_free_ struct sd_radv_opt_dns *opt_rdnss = NULL;
@@ -692,31 +805,29 @@ _public_ int sd_radv_set_dnssl(sd_radv *ra, uint32_t lifetime,
 }
 
 _public_ int sd_radv_prefix_new(sd_radv_prefix **ret) {
-        _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
+        sd_radv_prefix *p;
 
         assert_return(ret, -EINVAL);
 
-        p = new0(sd_radv_prefix, 1);
+        p = new(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;
+        *p = (sd_radv_prefix) {
+                .n_ref = 1,
 
-        /* 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);
+                .opt.type = ND_OPT_PREFIX_INFORMATION,
+                .opt.length = (sizeof(p->opt) - 1)/8 + 1,
+                .opt.prefixlen = 64,
 
-        LIST_INIT(prefix, p);
+                /* RFC 4861, Section 6.2.1 */
+                .opt.flags = ND_OPT_PI_FLAG_ONLINK|ND_OPT_PI_FLAG_AUTO,
 
-        *ret = TAKE_PTR(p);
+                .opt.preferred_lifetime = htobe32(604800),
+                .opt.valid_lifetime = htobe32(2592000),
+        };
 
+        *ret = p;
         return 0;
 }
 
@@ -740,6 +851,18 @@ _public_ int sd_radv_prefix_set_prefix(sd_radv_prefix *p, const struct in6_addr
         return 0;
 }
 
+_public_ int sd_radv_prefix_get_prefix(sd_radv_prefix *p, struct in6_addr *ret_in6_addr,
+                                       unsigned char *ret_prefixlen) {
+        assert_return(p, -EINVAL);
+        assert_return(ret_in6_addr, -EINVAL);
+        assert_return(ret_prefixlen, -EINVAL);
+
+        *ret_in6_addr = p->opt.in6_addr;
+        *ret_prefixlen = p->opt.prefixlen;
+
+        return 0;
+}
+
 _public_ int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink) {
         assert_return(p, -EINVAL);
 
@@ -774,3 +897,54 @@ _public_ int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p,
 
         return 0;
 }
+
+_public_ int sd_radv_route_prefix_new(sd_radv_route_prefix **ret) {
+        sd_radv_route_prefix *p;
+
+        assert_return(ret, -EINVAL);
+
+        p = new(sd_radv_route_prefix, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_radv_route_prefix) {
+                .n_ref = 1,
+
+                .opt.type = SD_RADV_OPT_ROUTE_INFORMATION,
+                .opt.length = DIV_ROUND_UP(sizeof(p->opt), 8),
+                .opt.prefixlen = 64,
+
+                .opt.lifetime = htobe32(604800),
+        };
+
+        *ret = p;
+        return 0;
+}
+
+DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv_route_prefix, sd_radv_route_prefix, mfree);
+
+_public_ int sd_radv_prefix_set_route_prefix(sd_radv_route_prefix *p, const struct in6_addr *in6_addr,
+                                             unsigned char prefixlen) {
+        assert_return(p, -EINVAL);
+        assert_return(in6_addr, -EINVAL);
+
+        if (prefixlen > 128)
+                return -EINVAL;
+
+        if (prefixlen > 64)
+                /* unusual but allowed, log it */
+                log_radv("Unusual prefix length %u greater than 64", prefixlen);
+
+        p->opt.in6_addr = *in6_addr;
+        p->opt.prefixlen = prefixlen;
+
+        return 0;
+}
+
+_public_ int sd_radv_route_prefix_set_lifetime(sd_radv_route_prefix *p, uint32_t valid_lifetime) {
+        assert_return(p, -EINVAL);
+
+        p->opt.lifetime = htobe32(valid_lifetime);
+
+        return 0;
+}