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
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);
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);
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)
{
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,
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)
{
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++;
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;
.\" 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
.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
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
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;
}
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;
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;
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;
_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] &&
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))
{
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
struct if_sla {
char ifname[IF_NAMESIZE];
- uint8_t sla[16];
- short sla_len;
+ uint32_t sla;
+ short prefix_len;
};
struct if_iaid {
# 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;
{
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
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)
{
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 *,