]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Refactor Prefix Delegation configuration so we use integers instead
authorRoy Marples <roy@marples.name>
Sat, 1 Jun 2013 21:30:18 +0000 (21:30 +0000)
committerRoy Marples <roy@marples.name>
Sat, 1 Jun 2013 21:30:18 +0000 (21:30 +0000)
of hex based arrays.
The SLA can now be applied to any router given prefix length provided the
SLA can fit into the prefix and desired prefix length.
We now wait for a carrier and LL address before adding the addresses from the
delegated prefix. Before adding this code, I added some code to generate a
LL address from the hardware address. This is now #ifdef'ed out as I don't
want to throw it away in-case dhcpcd ever needs to create LL addresses
itself.

Many thanks to Martin Husemann who developed code for these functions:
ipv6_userprefix, in6_to_h64 and h64_to_in6
which are used to generate a prefix from the delegated prefix and the SLA

dhcp6.c
dhcpcd.conf.5.in
if-options.c
if-options.h
ipv6.c
ipv6.h

diff --git a/dhcp6.c b/dhcp6.c
index 83bacd15d09958b24507dc7c97f6561ef87c1191..c6f988b5c769569454fafb6121447fc32371973e 100644 (file)
--- a/dhcp6.c
+++ b/dhcp6.c
@@ -1240,7 +1240,7 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
                        break;
                }
                a->iface = ifp;
-               a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK;
+               a->flags = IPV6_AF_NEW;
                a->dadcallback = dhcp6_dadcallback;
                memcpy(a->iaid, iaid, sizeof(a->iaid));
                p = D6_COPTION_DATA(o);
@@ -1492,24 +1492,20 @@ static struct ipv6_addr *
 dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix,
     const struct if_sla *sla, struct interface *ifs)
 {
+       struct in6_addr addr;
        struct dhcp6_state *state;
        struct ipv6_addr *a, *ap;
-       int b, i, l, pl, hl;
-       const uint8_t *p;
        char iabuf[INET6_ADDRSTRLEN];
        const char *ia;
 
-       state = D6_STATE(ifp);
-
-       l = prefix->prefix_len + sla->sla_len;
-       hl= (ifp->hwlen * 8) + (2 * 8); /* 2 magic bytes for ethernet */
-       pl = l + hl;
-       if (pl < 0 || pl > 128) {
-               syslog(LOG_ERR, "%s: invalid prefix length (%d + %d + %d = %d)",
-                   ifs->name, prefix->prefix_len, sla->sla_len, hl, pl);
+       if (ipv6_userprefix(&prefix->prefix, prefix->prefix_len,
+               sla->sla, &addr, sla->prefix_len) == -1)
+       {
+               syslog(LOG_ERR, "%s: invalid prefix", ifp->name);
                return NULL;
        }
 
+       state = D6_STATE(ifp);
        if (state == NULL) {
                ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
                state = D6_STATE(ifp);
@@ -1535,17 +1531,8 @@ dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix,
        memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid));
        a->prefix_pltime = prefix->prefix_pltime;
        a->prefix_vltime = prefix->prefix_vltime;
-       a->prefix_len = l;
-
-       memset(&a->prefix.s6_addr, 0, sizeof(a->prefix.s6_addr));
-       for (i = 0, b = prefix->prefix_len; b > 0; b -= 8, i++)
-               a->prefix.s6_addr[i] = prefix->prefix.s6_addr[i];
-       p = &sla->sla[3];
-       i = (128 - hl) / 8;
-       for (b = sla->sla_len; b > 7; b -= 8, p--)
-               a->prefix.s6_addr[--i] = *p;
-       if (b)
-               a->prefix.s6_addr[--i] |= *p;
+       memcpy(&a->prefix.s6_addr, &addr.s6_addr, sizeof(a->prefix.s6_addr));
+       a->prefix_len = sla->prefix_len;
 
        if (ipv6_makeaddr(&a->addr, ifp, &a->prefix, a->prefix_len) == -1)
        {
@@ -1581,12 +1568,19 @@ dhcp6_delegate_prefix(struct interface *ifp)
        struct if_iaid *iaid;
        struct if_sla *sla;
        struct interface *ifd;
+       uint8_t carrier_warned;
 
        ifo = ifp->options;
        state = D6_STATE(ifp);
        TAILQ_FOREACH(ifd, ifaces, next) {
                k = 0;
+               carrier_warned = 0;
                TAILQ_FOREACH(ap, &state->addrs, next) {
+                       if (ap->flags & IPV6_AF_NEW) {
+                               ap->flags &= ~IPV6_AF_NEW;
+                               syslog(LOG_DEBUG, "%s: delegated prefix %s",
+                                   ifp->name, ap->saddr);
+                       }
                        for (i = 0; i < ifo->iaid_len; i++) {
                                iaid = &ifo->iaid[i];
                                if (memcmp(iaid->iaid, ap->iaid,
@@ -1596,19 +1590,38 @@ dhcp6_delegate_prefix(struct interface *ifp)
                                        sla = &iaid->sla[j];
                                        if (strcmp(ifd->name, sla->ifname))
                                                continue;
+                                       if (ifd->carrier == LINK_DOWN) {
+                                               syslog(LOG_DEBUG,
+                                                   "%s: has no carrier, cannot"
+                                                   " delegate addresses",
+                                                   ifd->name);
+                                               carrier_warned = 1;
+                                               break;
+                                       }
                                        if (dhcp6_delegate_addr(ifd, ap,
                                            sla, ifp))
                                                k++;
                                }
+                               if (carrier_warned)
+                                       break;
                        }
+                       if (carrier_warned)
+                               break;
                }
-               if (k) {
+               if (k && !carrier_warned) {
                        ifd_state = D6_STATE(ifd);
                        ipv6ns_probeaddrs(&ifd_state->addrs);
                }
        }
 }
 
+static void
+dhcp6_find_delegates1(void *arg)
+{
+
+       dhcp6_find_delegates(arg);
+}
+
 int
 dhcp6_find_delegates(struct interface *ifp)
 {
@@ -1638,6 +1651,16 @@ dhcp6_find_delegates(struct interface *ifp)
                                        sla = &iaid->sla[j];
                                        if (strcmp(ifp->name, sla->ifname))
                                                continue;
+                                       if (ipv6_linklocal(ifp) == NULL) {
+                                               syslog(LOG_DEBUG,
+                                                   "%s: delaying adding"
+                                                   " delegated addresses for"
+                                                   " LL address",
+                                                   ifp->name);
+                                               ipv6_addlinklocalcallback(ifp,
+                                                   dhcp6_find_delegates1, ifp);
+                                               return 1;
+                                       }
                                        if (dhcp6_delegate_addr(ifp, ap,
                                            sla, ifd))
                                            k++;
@@ -2029,8 +2052,10 @@ dhcp6_start(struct interface *ifp, enum DH6S init_state)
 
        state = D6_STATE(ifp);
        if (state) {
-               if (state->state == DH6S_DELEGATED)
-                       return dhcp6_find_delegates(ifp);
+               if (state->state == DH6S_DELEGATED) {
+                       dhcp6_find_delegates(ifp);
+                       return 0;
+               }
                /* We're already running DHCP6 */
                /* XXX: What if the managed flag changes? */
                return 0;
index 6e303091ec60a001e3f7cdd45cf17d3b61bf78df..9f374e1d83f1ffd0c32bbc6618e7a49c335b8524 100644 (file)
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd April 5, 2013
+.Dd June 1, 2013
 .Dt DHCPCD.CONF 5 SMM
 .Os
 .Sh NAME
@@ -144,7 +144,7 @@ otherwise the interface index is used.
 .It Ic ia_ta Ar iaid
 Request a DHCPv6 Temporary Address for
 .Ar iaid .
-.It Ic ia_pd Ar iaid Op Ar interface / Ar sla_id Op / Ar sla_len
+.It Ic ia_pd Ar iaid Op Ar interface / Ar sla_id Op / Ar prefix_len
 Request a DHCPv6 Delegated Prefix for
 .Ar iaid .
 If an
@@ -155,14 +155,18 @@ is given,
 then an address is also assigned to that
 .Ar interface .
 A default
-.Ar sla_len
-of 16 is assumed if not given.
+.Ar prefix_len
+of 64 is assumed if not given.
+.Ar sla_id
+is an integer and is added to the prefix which must fit inside
+.Ar prefix_len
+less the length of the delegated prefix.
 IPv6RS should be disabled globally when requesting a Prefix Delegation like so:
 .Pp
 .D1 noipv6rs
 .Pp
 .D1 interface eth0
-.D1 ia_pd 00:11:22:33 eth1/33:44:55:66
+.D1 ia_pd 1 eth1/0 eth2/1
 .It Ic ipv4only
 Only configure IPv4.
 .It Ic ipv6only
index 40d9f037eb8bab836216b45d099780658d73956f..993ddca82631bbb7e868bb70e4342436f7aa0794 100644 (file)
@@ -142,6 +142,8 @@ atoint(const char *s)
        if ((errno != 0 && n == 0) || s == t ||
            (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)))
        {
+               if (errno == 0)
+                       errno = EINVAL;
                syslog(LOG_ERR, "`%s' out of range", s);
                return -1;
        }
@@ -421,6 +423,7 @@ static int
 parse_option(struct if_options *ifo, int opt, const char *arg)
 {
        int i;
+       long l;
        char *p = NULL, *fp, *np, **nconf;
        ssize_t s;
        size_t sl;
@@ -429,6 +432,7 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
        struct rt *rt;
        const struct dhcp_opt *d;
        uint8_t *request, *require, *no;
+       uint32_t u32;
        struct if_iaid *iaid;
        uint8_t _iaid[4];
        struct if_sla *sla;
@@ -955,6 +959,13 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
                fp = strchr(arg, ' ');
                if (fp)
                        *fp++ = '\0';
+               errno = 0;
+               l = strtol(arg, &np, 0);
+               if (l >= 0 && l <= UINT32_MAX && errno == 0 && *np == '\0') {
+                       u32 = htonl(l);
+                       memcpy(&_iaid, &u32, sizeof(_iaid));
+                       goto got_iaid;
+               }
                if ((s = parse_string((char *)_iaid, sizeof(_iaid), arg)) < 1) {
                        syslog(LOG_ERR, "%s: invalid IAID", arg);
                        return -1;
@@ -965,6 +976,7 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
                        _iaid[2] = '\0';
                if (s < 2)
                        _iaid[1] = '\0';
+got_iaid:
                iaid = NULL;
                for (sl = 0; sl < ifo->iaid_len; sl++) {
                        if (ifo->iaid[sl].iaid[0] == _iaid[0] &&
@@ -1007,10 +1019,6 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
                        np = strchr(p, '/');
                        if (np)
                                *np++ = '\0';
-                       else {
-                               syslog(LOG_ERR, "%s: missing sla", arg);
-                               return -1;
-                       }
                        if (strlcpy(sla->ifname, p,
                            sizeof(sla->ifname)) >= sizeof(sla->ifname))
                        {
@@ -1019,24 +1027,22 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
                                return -1;
                        }
                        p = np;
-                       np = strchr(p, '/');
-                       if (np)
-                               *np++ = '\0';
-                       if (parse_string((char *)sla->sla,
-                           sizeof(sla->sla), p) == -1)
-                       {
-                               syslog(LOG_ERR, "%s: sla: %m", arg);
-                               return -1;
-                       }
-                       if (np) {
-                               sla->sla_len = atoint(np);
-                               if (sla->sla_len < 0 || sla->sla_len > 128) {
-                                       syslog(LOG_ERR, "%s: sla len: range",
-                                           arg);
+                       if (p) {
+                               np = strchr(p, '/');
+                               if (np)
+                                       *np++ = '\0';
+                               errno = 0;
+                               sla->sla = atoint(p);
+                               if (errno)
                                        return -1;
-                               }
-                       } else
-                               sla->sla_len = 16;
+                               if (np) {
+                                       sla->prefix_len = atoint(np);
+                                       if (sla->prefix_len < 0 ||
+                                           sla->prefix_len > 128)
+                                               return -1;
+                               } else
+                                       sla->prefix_len = 64;
+                       }
                }
                break;
 #endif
index b4c03041061873c0b62b38a3b057f7b6ab137384..7b2a74bb09bb100c6a3d5ac34616021bab583217 100644 (file)
@@ -95,8 +95,8 @@ extern const struct option cf_options[];
 
 struct if_sla {
        char ifname[IF_NAMESIZE];
-       uint8_t sla[16];
-       short sla_len;
+       uint32_t sla;
+       short prefix_len;
 };
 
 struct if_iaid {
diff --git a/ipv6.c b/ipv6.c
index 7efecaa9ae123baf09da3c0ac4db750445cfa3c1..6708d4498534030d476160e0904bb1f299de721a 100644 (file)
--- a/ipv6.c
+++ b/ipv6.c
 #  define s6_addr32 __u6_addr.__u6_addr32
 #endif
 
+#define EUI64_GBIT     0x01
+#define EUI64_UBIT     0x02
+#define EUI64_TO_IFID(in6)     do {(in6)->s6_addr[8] ^= EUI64_UBIT; } \
+                                   while (/*CONSTCOND*/ 0)
+#define EUI64_GROUP(in6)       ((in6)->s6_addr[8] & EUI64_GBIT)
+
 static struct rt6head *routes;
 static uint8_t do_pfx_flush;
 
@@ -137,23 +143,93 @@ ipv6_makeaddr(struct in6_addr *addr, const struct interface *ifp,
 {
        const struct ipv6_state *state;
        const struct ll_addr *ap;
+#if 0
+       static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+       static u_int8_t allone[8] =
+           { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+#endif
 
        if (prefix_len < 0 || prefix_len > 64) {
                errno = EINVAL;
                return -1;
        }
 
+       memcpy(addr, prefix, sizeof(*prefix));
+
+       /* Try and make the address from the first local-link address */
        state = IPV6_CSTATE(ifp);
-       ap = TAILQ_FIRST(&state->ll_addrs);
-       if (ap == NULL) {
-               errno = ENOENT;
+       if (state) {
+               ap = TAILQ_FIRST(&state->ll_addrs);
+               if (ap) {
+                       addr->s6_addr32[2] = ap->addr.s6_addr32[2];
+                       addr->s6_addr32[3] = ap->addr.s6_addr32[3];
+                       return 0;
+               }
+       }
+
+       /* Because we delay a few functions until we get a local-link address
+        * there is little point in the below code.
+        * It exists in-case we need to create local-link addresses
+        * ourselves, but then we would need to be able to send RFC
+        * conformant DAD requests.
+        * See ipv6ns.c for why we need the kernel to do this. */
+       errno = ENOENT;
+       return -1;
+
+#if 0
+       /* Make an EUI64 based off our hardware address */
+       switch (ifp->family) {
+       case ARPHRD_ETHER:
+               /* Check for a valid hardware address */
+               if (ifp->hwlen != 8 && ifp->hwlen != 6) {
+                       errno = ENOTSUP;
+                       return -1;
+               }
+               if (memcmp(ifp->hwaddr, allzero, ifp->hwlen) == 0 ||
+                   memcmp(ifp->hwaddr, allone, ifp->hwlen) == 0)
+               {
+                       errno = EINVAL;
+                       return -1;
+               }
+
+               /* make a EUI64 address */
+               if (ifp->hwlen == 8)
+                       memcpy(&addr->s6_addr[8], ifp->hwaddr, 8);
+               else if (ifp->hwlen == 6) {
+                       addr->s6_addr[8] = ifp->hwaddr[0];
+                       addr->s6_addr[9] = ifp->hwaddr[1];
+                       addr->s6_addr[10] = ifp->hwaddr[2];
+                       addr->s6_addr[11] = 0xff;
+                       addr->s6_addr[12] = 0xfe;
+                       addr->s6_addr[13] = ifp->hwaddr[3];
+                       addr->s6_addr[14] = ifp->hwaddr[4];
+                       addr->s6_addr[15] = ifp->hwaddr[5];
+               }
+               break;
+       default:
+               errno = ENOTSUP;
+               return -1;
+       }
+
+       /* sanity check: g bit must not indicate "group" */
+       if (EUI64_GROUP(addr)) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       EUI64_TO_IFID(addr);
+
+       /* sanity check: ifid must not be all zero, avoid conflict with
+        * subnet router anycast */
+       if ((addr->s6_addr[8] & ~(EUI64_GBIT | EUI64_UBIT)) == 0x00 &&
+               memcmp(&addr->s6_addr[9], allzero, 7) == 0)
+       {
+               errno = EINVAL;
                return -1;
        }
 
-       memcpy(addr, prefix, sizeof(*prefix));
-       addr->s6_addr32[2] = ap->addr.s6_addr32[2];
-       addr->s6_addr32[3] = ap->addr.s6_addr32[3];
        return 0;
+#endif
 }
 
 int
@@ -232,6 +308,104 @@ ipv6_prefixlen(const struct in6_addr *mask)
        return x * NBBY + y;
 }
 
+static void
+in6_to_h64(const struct in6_addr *add, uint64_t *vhigh, uint64_t *vlow)
+{
+       uint64_t l, h;
+       const uint8_t *p = (const uint8_t *)&add->s6_addr;
+
+       h = ((uint64_t)p[0] << 56) |
+           ((uint64_t)p[1] << 48) |
+           ((uint64_t)p[2] << 40) |
+           ((uint64_t)p[3] << 32) |
+           ((uint64_t)p[4] << 24) |
+           ((uint64_t)p[5] << 16) |
+           ((uint64_t)p[6] << 8) |
+           (uint64_t)p[7];
+       p += 8;
+       l = ((uint64_t)p[0] << 56) |
+           ((uint64_t)p[1] << 48) |
+           ((uint64_t)p[2] << 40) |
+           ((uint64_t)p[3] << 32) |
+           ((uint64_t)p[4] << 24) |
+           ((uint64_t)p[5] << 16) |
+           ((uint64_t)p[6] << 8) |
+           (uint64_t)p[7];
+
+       *vhigh = h;
+       *vlow = l;
+}
+
+static void
+h64_to_in6(uint64_t vhigh, uint64_t vlow, struct in6_addr *add)
+{
+       uint8_t *p = (uint8_t *)&add->s6_addr;
+
+       p[0] = vhigh >> 56;
+       p[1] = vhigh >> 48;
+       p[2] = vhigh >> 40;
+       p[3] = vhigh >> 32;
+       p[4] = vhigh >> 24;
+       p[5] = vhigh >> 16;
+       p[6] = vhigh >> 8;
+       p[7] = vhigh;
+       p += 8;
+       p[0] = vlow >> 56;
+       p[1] = vlow >> 48;
+       p[2] = vlow >> 40;
+       p[3] = vlow >> 32;
+       p[4] = vlow >> 24;
+       p[5] = vlow >> 16;
+       p[6] = vlow >> 8;
+       p[7] = vlow;
+}
+
+int
+ipv6_userprefix(
+       const struct in6_addr *prefix,  // prefix from router
+       int prefix_len,                 // length of prefix received
+       uint64_t user_number,           // "random" number from user
+       struct in6_addr *result,        // resultant prefix
+       int result_len)                 // desired prefix length
+{
+       uint64_t vh, vl, user_low, user_high;
+
+       if (prefix_len < 0 || prefix_len > 64 ||
+           result_len < 0 || result_len > 64)
+       {
+               errno = EINVAL;
+               return -1;
+       }
+
+       /* Check that the user_number fits inside result_len less prefix_len */
+       if (result_len < prefix_len || user_number > INT_MAX ||
+           ffs((int)user_number) > result_len - prefix_len)
+       {
+              errno = ERANGE;
+              return -1;
+       }
+
+       /* virtually shift user number by dest_len, then split at 64 */
+       if (result_len >= 64) {
+               user_high = user_number << (result_len - 64);
+               user_low = 0;
+       } else {
+               user_high = user_number >> (64 - result_len);
+               user_low = user_number << result_len;
+       }
+
+       /* convert to two 64bit host order values */
+       in6_to_h64(prefix, &vh, &vl);
+
+       vh |= user_high;
+       vl |= user_low;
+
+       /* copy back result */
+       h64_to_in6(vh, vl, result);
+
+       return 0;
+}
+
 int
 ipv6_addaddr(struct ipv6_addr *ap)
 {
diff --git a/ipv6.h b/ipv6.h
index 2b894929fface8fe00aa71548ab88a4b05b66f4e..635f16b66c691211342a675c00d95c77f672ca78 100644 (file)
--- a/ipv6.h
+++ b/ipv6.h
@@ -144,6 +144,8 @@ int ipv6_makeaddr(struct in6_addr *, const struct interface *,
 int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int);
 int ipv6_mask(struct in6_addr *, int);
 int ipv6_prefixlen(const struct in6_addr *);
+int ipv6_userprefix( const struct in6_addr *, int prefix_len,
+    uint64_t user_number, struct in6_addr *result, int result_len);
 int ipv6_addaddr(struct ipv6_addr *);
 ssize_t ipv6_addaddrs(struct ipv6_addrhead *);
 void ipv6_handleifa(int, struct if_head *,