NetBSD/sbin/ifconfig/af_inet6.c

457 lines
12 KiB
C

/* $NetBSD: af_inet6.c,v 1.2 2005/03/20 02:44:50 thorpej Exp $ */
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: af_inet6.c,v 1.2 2005/03/20 02:44:50 thorpej Exp $");
#endif /* not lint */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet6/nd6.h>
#include <err.h>
#include <errno.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "extern.h"
#include "af_inet6.h"
static struct in6_ifreq ifr6;
struct in6_ifreq in6_ridreq;
struct in6_aliasreq in6_addreq;
static char *
sec2str(time_t total)
{
static char result[256];
int days, hours, mins, secs;
int first = 1;
char *p = result;
char *end = &result[sizeof(result)];
int n;
if (0) { /*XXX*/
days = total / 3600 / 24;
hours = (total / 3600) % 24;
mins = (total / 60) % 60;
secs = total % 60;
if (days) {
first = 0;
n = snprintf(p, end - p, "%dd", days);
if (n < 0 || n >= end - p)
return(result);
p += n;
}
if (!first || hours) {
first = 0;
n = snprintf(p, end - p, "%dh", hours);
if (n < 0 || n >= end - p)
return(result);
p += n;
}
if (!first || mins) {
first = 0;
n = snprintf(p, end - p, "%dm", mins);
if (n < 0 || n >= end - p)
return(result);
p += n;
}
snprintf(p, end - p, "%ds", secs);
} else
snprintf(p, end - p, "%lu", (u_long)total);
return(result);
}
static int
prefix(void *val, int size)
{
u_char *pname = (u_char *)val;
int byte, bit, plen = 0;
for (byte = 0; byte < size; byte++, plen += 8)
if (pname[byte] != 0xff)
break;
if (byte == size)
return (plen);
for (bit = 7; bit != 0; bit--, plen++)
if (!(pname[byte] & (1 << bit)))
break;
for (; bit != 0; bit--)
if (pname[byte] & (1 << bit))
return(0);
byte++;
for (; byte < size; byte++)
if (pname[byte])
return(0);
return (plen);
}
void
setia6flags(const char *vname, int value)
{
if (value < 0) {
value = -value;
in6_addreq.ifra_flags &= ~value;
} else
in6_addreq.ifra_flags |= value;
}
void
setia6pltime(const char *val, int d)
{
setia6lifetime("pltime", val);
}
void
setia6vltime(const char *val, int d)
{
setia6lifetime("vltime", val);
}
void
setia6lifetime(const char *cmd, const char *val)
{
time_t newval, t;
char *ep;
t = time(NULL);
newval = (time_t)strtoul(val, &ep, 0);
if (val == ep)
errx(EXIT_FAILURE, "invalid %s", cmd);
if (afp->af_af != AF_INET6)
errx(EXIT_FAILURE, "%s not allowed for the AF", cmd);
if (strcmp(cmd, "vltime") == 0) {
in6_addreq.ifra_lifetime.ia6t_expire = t + newval;
in6_addreq.ifra_lifetime.ia6t_vltime = newval;
} else if (strcmp(cmd, "pltime") == 0) {
in6_addreq.ifra_lifetime.ia6t_preferred = t + newval;
in6_addreq.ifra_lifetime.ia6t_pltime = newval;
}
}
void
setia6eui64(const char *cmd, int val)
{
struct ifaddrs *ifap, *ifa;
const struct sockaddr_in6 *sin6 = NULL;
const struct in6_addr *lladdr = NULL;
struct in6_addr *in6;
if (afp->af_af != AF_INET6)
errx(EXIT_FAILURE, "%s not allowed for the AF", cmd);
in6 = (struct in6_addr *)&in6_addreq.ifra_addr.sin6_addr;
if (memcmp(&in6addr_any.s6_addr[8], &in6->s6_addr[8], 8) != 0)
errx(EXIT_FAILURE, "interface index is already filled");
if (getifaddrs(&ifap) != 0)
err(EXIT_FAILURE, "getifaddrs");
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (ifa->ifa_addr->sa_family == AF_INET6 &&
strcmp(ifa->ifa_name, name) == 0) {
sin6 = (const struct sockaddr_in6 *)ifa->ifa_addr;
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
lladdr = &sin6->sin6_addr;
break;
}
}
}
if (!lladdr)
errx(EXIT_FAILURE, "could not determine link local address");
memcpy(&in6->s6_addr[8], &lladdr->s6_addr[8], 8);
freeifaddrs(ifap);
}
void
in6_fillscopeid(struct sockaddr_in6 *sin6)
{
#if defined(__KAME__) && defined(KAME_SCOPEID)
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
sin6->sin6_scope_id =
ntohs(*(u_int16_t *)&sin6->sin6_addr.s6_addr[2]);
sin6->sin6_addr.s6_addr[2] = sin6->sin6_addr.s6_addr[3] = 0;
}
#endif
}
/* XXX not really an alias */
void
in6_alias(struct in6_ifreq *creq)
{
struct sockaddr_in6 *sin6;
char hbuf[NI_MAXHOST];
u_int32_t scopeid;
const int niflag = NI_NUMERICHOST;
/* Get the non-alias address for this interface. */
getsock(AF_INET6);
if (s < 0) {
if (errno == EPROTONOSUPPORT)
return;
err(EXIT_FAILURE, "socket");
}
sin6 = (struct sockaddr_in6 *)&creq->ifr_addr;
in6_fillscopeid(sin6);
scopeid = sin6->sin6_scope_id;
if (getnameinfo((struct sockaddr *)sin6, sin6->sin6_len,
hbuf, sizeof(hbuf), NULL, 0, niflag))
strlcpy(hbuf, "", sizeof(hbuf)); /* some message? */
printf("\tinet6 %s", hbuf);
if (flags & IFF_POINTOPOINT) {
(void) memset(&ifr6, 0, sizeof(ifr6));
(void) strncpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = creq->ifr_addr;
if (ioctl(s, SIOCGIFDSTADDR_IN6, &ifr6) == -1) {
if (errno != EADDRNOTAVAIL)
warn("SIOCGIFDSTADDR_IN6");
(void) memset(&ifr6.ifr_addr, 0, sizeof(ifr6.ifr_addr));
ifr6.ifr_addr.sin6_family = AF_INET6;
ifr6.ifr_addr.sin6_len = sizeof(struct sockaddr_in6);
}
sin6 = (struct sockaddr_in6 *)&ifr6.ifr_addr;
in6_fillscopeid(sin6);
hbuf[0] = '\0';
if (getnameinfo((struct sockaddr *)sin6, sin6->sin6_len,
hbuf, sizeof(hbuf), NULL, 0, niflag))
strlcpy(hbuf, "", sizeof(hbuf)); /* some message? */
printf(" -> %s", hbuf);
}
(void) memset(&ifr6, 0, sizeof(ifr6));
(void) strncpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = creq->ifr_addr;
if (ioctl(s, SIOCGIFNETMASK_IN6, &ifr6) == -1) {
if (errno != EADDRNOTAVAIL)
warn("SIOCGIFNETMASK_IN6");
} else {
sin6 = (struct sockaddr_in6 *)&ifr6.ifr_addr;
printf(" prefixlen %d", prefix(&sin6->sin6_addr,
sizeof(struct in6_addr)));
}
(void) memset(&ifr6, 0, sizeof(ifr6));
(void) strncpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = creq->ifr_addr;
if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) == -1) {
if (errno != EADDRNOTAVAIL)
warn("SIOCGIFAFLAG_IN6");
} else {
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_ANYCAST)
printf(" anycast");
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
printf(" tentative");
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DUPLICATED)
printf(" duplicated");
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DETACHED)
printf(" detached");
if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
printf(" deprecated");
}
if (scopeid)
printf(" scopeid 0x%x", scopeid);
if (Lflag) {
struct in6_addrlifetime *lifetime;
(void) memset(&ifr6, 0, sizeof(ifr6));
(void) strncpy(ifr6.ifr_name, name, sizeof(ifr6.ifr_name));
ifr6.ifr_addr = creq->ifr_addr;
lifetime = &ifr6.ifr_ifru.ifru_lifetime;
if (ioctl(s, SIOCGIFALIFETIME_IN6, &ifr6) == -1) {
if (errno != EADDRNOTAVAIL)
warn("SIOCGIFALIFETIME_IN6");
} else if (lifetime->ia6t_preferred || lifetime->ia6t_expire) {
time_t t = time(NULL);
printf(" pltime ");
if (lifetime->ia6t_preferred) {
printf("%s", lifetime->ia6t_preferred < t
? "0"
: sec2str(lifetime->ia6t_preferred - t));
} else
printf("infty");
printf(" vltime ");
if (lifetime->ia6t_expire) {
printf("%s", lifetime->ia6t_expire < t
? "0"
: sec2str(lifetime->ia6t_expire - t));
} else
printf("infty");
}
}
printf("\n");
}
void
in6_status(int force)
{
struct ifaddrs *ifap, *ifa;
struct in6_ifreq isifr;
if (getifaddrs(&ifap) != 0)
err(EXIT_FAILURE, "getifaddrs");
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (strcmp(name, ifa->ifa_name) != 0)
continue;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
if (sizeof(isifr.ifr_addr) < ifa->ifa_addr->sa_len)
continue;
memset(&isifr, 0, sizeof(isifr));
strncpy(isifr.ifr_name, ifa->ifa_name, sizeof(isifr.ifr_name));
memcpy(&isifr.ifr_addr, ifa->ifa_addr, ifa->ifa_addr->sa_len);
in6_alias(&isifr);
}
freeifaddrs(ifap);
}
#define SIN6(x) ((struct sockaddr_in6 *) &(x))
struct sockaddr_in6 *sin6tab[] = {
SIN6(in6_ridreq.ifr_addr), SIN6(in6_addreq.ifra_addr),
SIN6(in6_addreq.ifra_prefixmask), SIN6(in6_addreq.ifra_dstaddr)};
void
in6_getaddr(const char *str, int which)
{
#if defined(__KAME__) && defined(KAME_SCOPEID)
struct sockaddr_in6 *sin6 = sin6tab[which];
struct addrinfo hints, *res;
int error;
char *slash = NULL;
if (which == ADDR) {
if ((slash = strrchr(str, '/')) != NULL)
*slash = '\0';
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_DGRAM;
#if 0 /* in_getaddr() allows FQDN */
hints.ai_flags = AI_NUMERICHOST;
#endif
error = getaddrinfo(str, "0", &hints, &res);
if (error && slash) {
/* try again treating the '/' as part of the name */
*slash = '/';
slash = NULL;
error = getaddrinfo(str, "0", &hints, &res);
}
if (error)
errx(EXIT_FAILURE, "%s: %s", str, gai_strerror(error));
if (res->ai_next)
errx(EXIT_FAILURE, "%s: resolved to multiple addresses", str);
if (res->ai_addrlen != sizeof(struct sockaddr_in6))
errx(EXIT_FAILURE, "%s: bad value", str);
memcpy(sin6, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && sin6->sin6_scope_id) {
*(u_int16_t *)&sin6->sin6_addr.s6_addr[2] =
htons(sin6->sin6_scope_id);
sin6->sin6_scope_id = 0;
}
if (slash) {
in6_getprefix(slash + 1, MASK);
explicit_prefix = 1;
}
#else
struct sockaddr_in6 *gasin = sin6tab[which];
gasin->sin6_len = sizeof(*gasin);
if (which != MASK)
gasin->sin6_family = AF_INET6;
if (which == ADDR) {
char *p = NULL;
if((p = strrchr(str, '/')) != NULL) {
*p = '\0';
in6_getprefix(p + 1, MASK);
explicit_prefix = 1;
}
}
if (inet_pton(AF_INET6, str, &gasin->sin6_addr) != 1)
errx(EXIT_FAILURE, "%s: bad value", str);
#endif
}
void
in6_getprefix(const char *plen, int which)
{
struct sockaddr_in6 *gpsin = sin6tab[which];
u_char *cp;
int len = strtol(plen, (char **)NULL, 10);
if ((len < 0) || (len > 128))
errx(EXIT_FAILURE, "%s: bad value", plen);
gpsin->sin6_len = sizeof(*gpsin);
if (which != MASK)
gpsin->sin6_family = AF_INET6;
if ((len == 0) || (len == 128)) {
memset(&gpsin->sin6_addr, 0xff, sizeof(struct in6_addr));
return;
}
memset((void *)&gpsin->sin6_addr, 0x00, sizeof(gpsin->sin6_addr));
for (cp = (u_char *)&gpsin->sin6_addr; len > 7; len -= 8)
*cp++ = 0xff;
if (len)
*cp = 0xff << (8 - len);
}
void
in6_init(void)
{
in6_addreq.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME;
in6_addreq.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME;
}