From: Roy Marples Date: Sat, 1 Jun 2013 21:30:18 +0000 (+0000) Subject: Refactor Prefix Delegation configuration so we use integers instead X-Git-Tag: v5.99.7~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=367f7b11b4ddedc7e4aeb058925ad79c1cec15dc;p=thirdparty%2Fdhcpcd.git Refactor Prefix Delegation configuration so we use integers instead 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 --- diff --git a/dhcp6.c b/dhcp6.c index 83bacd15..c6f988b5 100644 --- 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; diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 6e303091..9f374e1d 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -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 diff --git a/if-options.c b/if-options.c index 40d9f037..993ddca8 100644 --- a/if-options.c +++ b/if-options.c @@ -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 diff --git a/if-options.h b/if-options.h index b4c03041..7b2a74bb 100644 --- a/if-options.h +++ b/if-options.h @@ -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 7efecaa9..6708d449 100644 --- a/ipv6.c +++ b/ipv6.c @@ -66,6 +66,12 @@ # 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 2b894929..635f16b6 100644 --- 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 *,