]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
ip6: Implement IPv6 address sharing
authorRoy Marples <roy@marples.name>
Sat, 5 Jan 2019 11:42:12 +0000 (11:42 +0000)
committerRoy Marples <roy@marples.name>
Sat, 5 Jan 2019 11:42:12 +0000 (11:42 +0000)
This allows the same IPv6 address to exist on more than one
interface. Whenever dhcpcd address an IPv6 address, it will
advertise it along with the hardware address of the preferred
interface.

This is heavliy reliant on the kernel supporting this as it's the
kernel that handle the Duplicate Address Detection.
In a nutshell it needs to support RFC 7527 and ignore NA packets
from any hardware address the host owns.

Currently the only known kernel that fully supports this is
NetBSD-8.99.27

src/dhcp6.c
src/dhcp6.h
src/dhcpcd.c
src/if-bsd.c
src/if-linux.c
src/if.h
src/ipv6.c
src/ipv6.h
src/ipv6nd.c
src/ipv6nd.h

index 502d58f00b194965743c1612ed47ed61f81a57f0..6fef9899ea3515eb95fa0981064a50842909a502 100644 (file)
@@ -1516,6 +1516,7 @@ dhcp6_dadcallback(void *arg)
                                if (valid)
                                        dhcpcd_daemonise(ifp->ctx);
                        }
+                       ipv6nd_advertise(ia);
                }
        }
 }
@@ -3952,6 +3953,21 @@ dhcp6_dropnondelegates(struct interface *ifp)
        dhcp6_drop(ifp, "EXPIRE6");
 }
 
+void
+dhcp6_abort(struct interface *ifp)
+{
+       struct dhcp6_state *state;
+       struct ipv6_addr *ia;
+
+       eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp);
+       state = D6_STATE(ifp);
+       if (state == NULL)
+               return;
+       TAILQ_FOREACH(ia, &state->addrs, next) {
+               ipv6nd_advertise(ia);
+       }
+}
+
 void
 dhcp6_handleifa(int cmd, struct ipv6_addr *ia, pid_t pid)
 {
index 5211dae4b43fba97165976a4bd81159b95203378..53f4a567e701dd44b5876054d7ce1208875bcebe 100644 (file)
@@ -232,6 +232,7 @@ ssize_t dhcp6_env(char **, const char *, const struct interface *,
 void dhcp6_free(struct interface *);
 void dhcp6_handleifa(int, struct ipv6_addr *, pid_t);
 int dhcp6_dadcompleted(const struct interface *);
+void dhcp6_abort(struct interface *);
 void dhcp6_drop(struct interface *, const char *);
 void dhcp6_dropnondelegates(struct interface *ifp);
 int dhcp6_dump(struct interface *);
index d41a5dd1c9f9a5cdba28538d9eaaf8d0a8771671..6c46820bd726f4d181e410c6cda606e18ca7508a 100644 (file)
@@ -735,6 +735,7 @@ dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags,
 #endif
                        dhcp_abort(ifp);
                        ipv6nd_expire(ifp, 0);
+                       dhcp6_abort(ifp);
 #else
                        dhcpcd_drop(ifp, 0);
 #endif
index 7df647a2ea06d400449e6a9030ea58d4438b3e93..5f76b5441ce032bd5dd2d8f03c806b6f01dc49a1 100644 (file)
@@ -1271,7 +1271,8 @@ if_machinearch(char *str, size_t len)
 
 #ifdef INET6
 #if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \
-    defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME)
+    defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) || \
+    defined(IPV6CTL_FORWARDING)
 #define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0)
 #define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1)
 static int
@@ -1355,6 +1356,19 @@ ip6_temp_valid_lifetime(__unused const char *ifname)
 }
 #endif
 
+int
+ip6_forwarding(__unused const char *ifname)
+{
+       int val;
+
+#ifdef IPV6CTL_FORWARDING
+       val = get_inet6_sysctl(IPV6CTL_FORWARDING);
+#else
+       val = get_inet6_sysctlbyname("net.inet6.ip6.forwarding");
+#endif
+       return val < 0 ? 0 : val;
+}
+
 #ifdef SIOCIFAFATTACH
 static int
 af_attach(int s, const struct interface *ifp, int af)
index e9223e58fa34a1dbe20720382b9edf2270516742..1dd0bab746e24aa89b3667cef337d07bc56122f2 100644 (file)
@@ -1756,4 +1756,18 @@ ip6_temp_valid_lifetime(const char *ifname)
        return val < 0 ? TEMP_VALID_LIFETIME : val;
 }
 #endif /* IPV6_MANAGETEMPADDR */
+
+int
+ip6_forwarding(const char *ifname)
+{
+       char path[256];
+       int val;
+
+       if (ifname == NULL)
+               ifname = "all";
+       snprintf(path, sizeof(path), "%s/%s/forwarding", prefix, ifname);
+       val = check_proc_int(path);
+       return val == -1 ? 0 : val;
+}
+
 #endif /* INET6 */
index 51ef75c05017da65a80da1a9153b4eadb7b65bf6..d260d2d1a99150687db35ac75446037d5150230f 100644 (file)
--- a/src/if.h
+++ b/src/if.h
@@ -197,6 +197,7 @@ int ip6_temp_valid_lifetime(const char *ifname);
 #else
 #define ip6_use_tempaddr(a) (0)
 #endif
+int ip6_forwarding(const char *ifname);
 
 int if_address6(unsigned char, const struct ipv6_addr *);
 int if_addrflags6(const struct interface *, const struct in6_addr *,
index 49315d148f2393e79e2a41e6c1bcc01febce8eed..38cb06d85c8e4002a5432bdffb0e389919eb97f0 100644 (file)
@@ -626,32 +626,19 @@ ipv6_deleteaddr(struct ipv6_addr *ia)
                        break;
                }
        }
+
+       /* Advertise the address if it exists on another interface. */
+       ipv6nd_advertise(ia);
 }
 
 static int
 ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
 {
        struct interface *ifp;
-       struct ipv6_state *state;
-       struct ipv6_addr *ia2;
        uint32_t pltime, vltime;
+       bool vltime_was_zero;
        __printflike(1, 2) void (*logfunc)(const char *, ...);
 
-       /* Ensure no other interface has this address */
-       TAILQ_FOREACH(ifp, ia->iface->ctx->ifaces, next) {
-               if (ifp == ia->iface)
-                       continue;
-               state = IPV6_STATE(ifp);
-               if (state == NULL)
-                       continue;
-               TAILQ_FOREACH(ia2, &state->addrs, next) {
-                       if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) {
-                               ipv6_deleteaddr(ia2);
-                               break;
-                       }
-               }
-       }
-
        /* Remember the interface of the address. */
        ifp = ia->iface;
 
@@ -714,7 +701,7 @@ ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
                    " seconds",
                    ifp->name, ia->prefix_pltime, ia->prefix_vltime);
 
-
+       vltime_was_zero = ia->prefix_vltime == 0;
        if (if_address6(RTM_NEWADDR, ia) == -1) {
                logerr(__func__);
                /* Restore real pltime and vltime */
@@ -778,6 +765,10 @@ ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
        }
 #endif
 
+       /* Re-advertise the preferred address to be safe. */
+       if (!vltime_was_zero)
+               ipv6nd_advertise(ia);
+
        return 0;
 }
 
@@ -909,7 +900,7 @@ ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, unsigned int
 ssize_t
 ipv6_addaddrs(struct ipv6_addrhead *addrs)
 {
-       struct ipv6_addr *ap, *apn, *apf;
+       struct ipv6_addr *ap, *apn;
        ssize_t i;
        struct timespec now;
 
@@ -935,27 +926,6 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs)
                } else if (!(ap->flags & IPV6_AF_STALE) &&
                    !IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
                {
-                       apf = ipv6_findaddr(ap->iface->ctx,
-                           &ap->addr, IPV6_AF_ADDED);
-                       if (apf && apf->iface != ap->iface) {
-                               if (apf->iface->metric <= ap->iface->metric) {
-                                       loginfox("%s: preferring %s on %s",
-                                           ap->iface->name,
-                                           ap->saddr,
-                                           apf->iface->name);
-                                       continue;
-                               }
-                               loginfox("%s: preferring %s on %s",
-                                   apf->iface->name,
-                                   ap->saddr,
-                                   ap->iface->name);
-                               if (if_address6(RTM_DELADDR, apf) == -1 &&
-                                   errno != EADDRNOTAVAIL && errno != ENXIO)
-                                       logerr(__func__);
-                               apf->flags &=
-                                   ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
-                       } else if (apf)
-                               apf->flags &= ~IPV6_AF_ADDED;
                        if (ap->flags & IPV6_AF_NEW)
                                i++;
                        if (!timespecisset(&now))
@@ -989,6 +959,7 @@ ipv6_freeaddr(struct ipv6_addr *ia)
        }
 
        eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia);
+       free(ia->na);
        free(ia);
 }
 
@@ -1108,6 +1079,9 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx,
        case RTM_DELADDR:
                if (ia != NULL) {
                        TAILQ_REMOVE(&state->addrs, ia, next);
+                       /* Advertise the address if it exists on
+                        * another interface. */
+                       ipv6nd_advertise(ia);
                        /* We'll free it at the end of the function. */
                }
                break;
index 73f6538b4f6dcc55ace9e9b868c73a501f97e793..b6ed17b7a6f2b82a5b834a3234244e8ab2edfbcb 100644 (file)
@@ -44,7 +44,8 @@
 #  endif
 #endif
 
-#define ALLROUTERS "ff02::2"
+#define        ALLNODES                "ff02::1"
+#define        ALLROUTERS              "ff02::2"
 
 #define EUI64_GBIT             0x01
 #define EUI64_UBIT             0x02
@@ -169,6 +170,10 @@ struct ipv6_addr {
        void (*dadcallback)(void *);
        int dadcounter;
 
+       struct nd_neighbor_advert *na;
+       size_t na_len;
+       int na_count;
+
 #ifdef ALIAS_ADDR
        char alias[IF_NAMESIZE];
 #endif
index 8b446e9cdb2bbdf9ec67501f3b7a2bd5b2331e84..d6dc749df4b14b163fb6d73d3a4a538a0baa32e0 100644 (file)
@@ -34,6 +34,7 @@
 #include <netinet/ip6.h>
 #include <netinet/icmp6.h>
 
+#include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stddef.h>
@@ -331,6 +332,135 @@ ipv6nd_sendrsprobe(void *arg)
        }
 }
 
+static void
+ipv6nd_sendadvertisement(void *arg)
+{
+       struct ipv6_addr *ia = arg;
+       struct interface *ifp = ia->iface;
+       struct dhcpcd_ctx *ctx = ifp->ctx;
+       struct sockaddr_in6 dst;
+       struct cmsghdr *cm;
+       struct in6_pktinfo pi;
+       const struct rs_state *state = RS_CSTATE(ifp);
+
+       if (state == NULL || ifp->carrier == LINK_DOWN)
+               goto freeit;
+
+       memset(&dst, 0, sizeof(dst));
+       dst.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+       dst.sin6_len = sizeof(dst);
+#endif
+       dst.sin6_scope_id = ifp->index;
+       if (inet_pton(AF_INET6, ALLNODES, &dst.sin6_addr) != 1) {
+               logerr(__func__);
+               return;
+       }
+
+       ctx->sndhdr.msg_name = (void *)&dst;
+       ctx->sndhdr.msg_iov[0].iov_base = ia->na;
+       ctx->sndhdr.msg_iov[0].iov_len = ia->na_len;
+
+       /* Set the outbound interface. */
+       cm = CMSG_FIRSTHDR(&ctx->sndhdr);
+       assert(cm != NULL);
+       cm->cmsg_level = IPPROTO_IPV6;
+       cm->cmsg_type = IPV6_PKTINFO;
+       cm->cmsg_len = CMSG_LEN(sizeof(pi));
+       memset(&pi, 0, sizeof(pi));
+       pi.ipi6_ifindex = ifp->index;
+       memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+       logdebugx("%s: sending NA for %s", ifp->name, ia->saddr);
+       if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1)
+               logerr(__func__);
+
+       if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) {
+               eloop_timeout_add_sec(ctx->eloop,
+                   state->retrans / 1000, ipv6nd_sendadvertisement, ia);
+               return;
+       }
+
+freeit:
+       free(ia->na);
+       ia->na = NULL;
+       ia->na_count = 0;
+}
+
+void
+ipv6nd_advertise(struct ipv6_addr *ia)
+{
+       struct dhcpcd_ctx *ctx;
+       struct interface *ifp;
+       struct ipv6_state *state;
+       struct ipv6_addr *iap, *iaf;
+       struct nd_neighbor_advert *na;
+
+       if (IN6_IS_ADDR_MULTICAST(&ia->addr))
+               return;
+
+       ctx = ia->iface->ctx;
+       if_sortinterfaces(ctx);
+       /* Find the most preferred address to advertise. */
+       iaf = NULL;
+       TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+               state = IPV6_STATE(ifp);
+               if (state == NULL || ifp->carrier == LINK_DOWN)
+                       continue;
+
+               TAILQ_FOREACH(iap, &state->addrs, next) {
+                       if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr))
+                               continue;
+
+                       /* Cancel any current advertisement. */
+                       eloop_timeout_delete(ctx->eloop,
+                           ipv6nd_sendadvertisement, iap);
+
+                       /* Don't advertise what we can't use. */
+                       if (iap->prefix_vltime == 0 ||
+                           iap->addr_flags & IN6_IFF_NOTUSEABLE)
+                               continue;
+
+                       if (iaf == NULL)
+                               iaf = iap;
+               }
+       }
+       if (iaf == NULL)
+               return;
+
+       /* Make the packet. */
+       ifp = iaf->iface;
+       iaf->na_len = sizeof(*na);
+       if (ifp->hwlen != 0)
+               iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2);
+       na = calloc(1, iaf->na_len);
+       if (na == NULL) {
+               logerr(__func__);
+               return;
+       }
+
+       na->nd_na_type = ND_NEIGHBOR_ADVERT;
+       na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE;
+       if (ip6_forwarding(ifp->name) == 1)
+               na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER;
+       na->nd_na_target = ia->addr;
+
+       if (ifp->hwlen != 0) {
+               struct nd_opt_hdr *opt;
+
+               opt = (struct nd_opt_hdr *)(na + 1);
+               opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
+               opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3);
+               memcpy(opt + 1, ifp->hwaddr, ifp->hwlen);
+       }
+
+       iaf->na_count = 0;
+       free(iaf->na);
+       iaf->na = na;
+       eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf);
+       ipv6nd_sendadvertisement(iaf);
+}
+
 void
 ipv6nd_expire(struct interface *ifp, uint32_t seconds)
 {
@@ -718,6 +848,7 @@ try_script:
                                        return;
                        }
                }
+               ipv6nd_advertise(ia);
        }
 }
 
@@ -869,8 +1000,11 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, struct interface *ifp,
                if (rap->reachable > MAX_REACHABLE_TIME)
                        rap->reachable = 0;
        }
-       if (nd_ra->nd_ra_retransmit)
-               rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+       if (nd_ra->nd_ra_retransmit) {
+               struct rs_state *state = RS_STATE(ifp);
+
+               state->retrans = rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
+       }
        if (rap->lifetime)
                rap->expired = 0;
        rap->hasdns = 0;
@@ -1628,6 +1762,7 @@ ipv6nd_startrs1(void *arg)
                return;
        }
 
+       state->retrans = RETRANS_TIMER;
        state->rsprobes = 0;
        ipv6nd_sendrsprobe(ifp);
 }
index e6a11d687af06d7516e9738f98d31dd72595bd3f..449a4dc9cc511c024afc2c83bdd9a0af65644448 100644 (file)
@@ -58,9 +58,11 @@ struct rs_state {
        struct nd_router_solicit *rs;
        size_t rslen;
        int rsprobes;
+       uint32_t retrans;
 };
 
 #define        RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
+#define        RS_CSTATE(a) ((const struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND])
 #define        RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a)))
 
 #ifndef MAX_RTR_SOLICITATION_DELAY
@@ -68,6 +70,7 @@ struct rs_state {
 #define        MAX_UNICAST_SOLICIT             3       /* 3 transmissions */
 #define        RTR_SOLICITATION_INTERVAL       4       /* seconds */
 #define        MAX_RTR_SOLICITATIONS           3       /* times */
+#define        MAX_NEIGHBOR_ADVERTISEMENT      3       /* 3 transmissions */
 #endif
 
 /* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */
@@ -99,6 +102,7 @@ int ipv6nd_hasra(const struct interface *);
 int ipv6nd_hasradhcp(const struct interface *);
 void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t);
 int ipv6nd_dadcompleted(const struct interface *);
+void ipv6nd_advertise(struct ipv6_addr *);
 void ipv6nd_expire(struct interface *, uint32_t);
 void ipv6nd_drop(struct interface *);
 void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);