#include "ipv6.h"
#include "ipv6nd.h"
+#ifndef ARP_MOD_NAME
+# define ARP_MOD_NAME "arp"
+#endif
+
#define COPYOUT(sin, sa) do { \
if ((sa) && ((sa)->sa_family == AF_INET)) \
(sin) = ((const struct sockaddr_in *)(const void *) \
uint8_t broadcast[DLPI_PHYSADDR_MAX];
};
TAILQ_HEAD(dl_if_head, dl_if);
+#endif
struct priv {
+#ifdef INET
struct dl_if_head dl_ifs;
-};
#endif
+#ifdef INET6
+ int pf_inet6_fd;
+#endif
+};
int
if_init(__unused struct interface *ifp)
int
if_opensockets_os(struct dhcpcd_ctx *ctx)
{
-#ifdef INET
struct priv *priv;
if ((priv = malloc(sizeof(*priv))) == NULL)
return -1;
ctx->priv = priv;
+#ifdef INET
TAILQ_INIT(&priv->dl_ifs);
-#else
- ctx->priv = NULL;
+#endif
+
+#ifdef INET6
+ priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ /* Don't return an error so we at least work on kernels witout INET6
+ * even though we expect INET6 support.
+ * We will fail noisily elsewhere anyway. */
#endif
ctx->link_fd = socket(PF_ROUTE,
void
if_closesockets_os(struct dhcpcd_ctx *ctx)
{
+#ifdef INET6
+ struct priv *priv;
+
+ priv = (struct priv *)ctx->priv;
+ if (priv->pf_inet6_fd != -1)
+ close(priv->pf_inet6_fd);
+#endif
/* each interface should have closed itself */
free(ctx->priv);
return retval;
}
+static int
+if_addaddr(int fd, const char *ifname,
+ struct sockaddr_storage *addr, struct sockaddr_storage *mask)
+{
+ struct lifreq lifr;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+
+ /* First assign the netmask. */
+ lifr.lifr_addr = *mask;
+ if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1)
+ return -1;
+
+ /* Then assign the address. */
+ lifr.lifr_addr = *addr;
+ if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1)
+ return -1;
+
+ /* Now bring it up. */
+ if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1)
+ return -1;
+ if (!(lifr.lifr_flags & IFF_UP)) {
+ lifr.lifr_flags |= IFF_UP;
+ if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct lifreq lifr;
+ int s;
+
+ memset(&lifr, 0, sizeof(lifr));
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ lifr.lifr_addr.ss_family = af;
+ if (af == AF_INET)
+ s = ctx->pf_inet_fd;
+ else {
+ struct priv *priv;
+
+ priv = (struct priv *)ctx->priv;
+ s = priv->pf_inet6_fd;
+ }
+ return ioctl(s,
+ cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF,
+ &lifr) == -1 && errno != EEXIST ? -1 : 0;
+}
+
+static int
+if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ dlpi_handle_t dh;
+ int fd, af_fd, mux_fd, retval;
+ struct lifreq lifr;
+ const char *udp_dev;
+
+ memset(&lifr, 0, sizeof(lifr));
+ switch (af) {
+ case AF_INET:
+ lifr.lifr_flags = IFF_IPV4;
+ af_fd = ctx->pf_inet_fd;
+ udp_dev = UDP_DEV_NAME;
+ break;
+ case AF_INET6:
+ {
+ struct priv *priv;
+
+ lifr.lifr_flags = IFF_IPV6;
+ priv = (struct priv *)ctx->priv;
+ af_fd = priv->pf_inet6_fd;
+ udp_dev = UDP6_DEV_NAME;
+ break;
+ }
+ default:
+ errno = EPROTONOSUPPORT;
+ return -1;
+ }
+
+ if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = dlpi_fd(dh);
+ retval = -1;
+ mux_fd = -1;
+ if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1)
+ goto out;
+ strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name));
+ if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1)
+ goto out;
+
+ /* Get full flags. */
+ if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1)
+ goto out;
+
+ /* Open UDP as a multiplexor to PLINK the interface stream.
+ * UDP is used because STREAMS will not let you PLINK a driver
+ * under itself and IP is generally at the bottom of the stream. */
+ if ((mux_fd = open(udp_dev, O_RDWR)) == -1)
+ goto out;
+ /* POP off all undesired modules. */
+ while (ioctl(mux_fd, I_POP, 0) != -1)
+ ;
+ if (errno != EINVAL)
+ goto out;
+
+ if (lifr.lifr_flags & IFF_IPV4 && !(lifr.lifr_flags & IFF_NOARP)) {
+ if (ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1)
+ goto out;
+ }
+
+ /* PLINK the interface stream so it persists. */
+ if (ioctl(mux_fd, I_PLINK, fd) == -1)
+ goto out;
+
+ retval = 0;
+
+out:
+ dlpi_close(dh);
+ if (mux_fd != -1)
+ close(mux_fd);
+ return retval;
+}
+
+static int
+if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct sockaddr_storage addr, mask;
+
+ /* For the time being, don't unplumb the interface, just
+ * set the address to zero. */
+ memset(&addr, 0, sizeof(addr));
+ addr.ss_family = af;
+ memset(&mask, 0, sizeof(mask));
+ mask.ss_family = af;
+ return if_addaddr(ctx->pf_inet_fd, ifname , &addr, &mask);
+}
+
+static int
+if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname)
+{
+ struct if_spec spec;
+
+ if (if_nametospec(ifname, &spec) == -1)
+ return -1;
+ if (spec.lun != -1)
+ return if_plumblif(cmd, ctx, af, ifname);
+ if (cmd == RTM_NEWADDR)
+ return if_plumbif(ctx, af, ifname);
+ else
+ return if_unplumbif(ctx, af, ifname);
+}
+
#ifdef INET
const char *if_pfname = "DLPI";
return -1;
*flags = RAW_EOF; /* We only ever read one packet. */
mlen = len;
+ *flags = RAW_EOF; /* We only ever read one packet. */
r = dlpi_recv(di->dh, NULL, NULL, data, &mlen, -1, NULL);
return r == DLPI_SUCCESS ? (ssize_t)mlen : -1;
}
int
if_address(unsigned char cmd, const struct ipv4_addr *ia)
{
+ struct sockaddr_storage ss_addr, ss_mask;
+ struct sockaddr_in *sin_addr, *sin_mask;
- UNUSED(cmd);
- UNUSED(ia);
- errno = ENOTSUP;
- return -1;
+ /* Either remove the alias or ensure it exists. */
+ if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1)
+ return -1;
+
+ if (cmd == RTM_DELADDR)
+ return 0;
+
+ if (cmd != RTM_NEWADDR) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ sin_addr = (struct sockaddr_in *)&ss_addr;
+ sin_addr->sin_family = AF_INET;
+ sin_addr->sin_addr = ia->addr;
+ sin_mask = (struct sockaddr_in *)&ss_mask;
+ sin_mask->sin_family = AF_INET;
+ sin_mask->sin_addr = ia->mask;
+ return if_addaddr(ia->iface->ctx->pf_inet_fd,
+ ia->alias, &ss_addr, &ss_mask);
}
int
int
if_address6(unsigned char cmd, const struct ipv6_addr *ia)
{
+ struct sockaddr_storage ss_addr, ss_mask;
+ struct sockaddr_in6 *sin6_addr, *sin6_mask;
+ struct priv *priv;
- UNUSED(cmd);
- UNUSED(ia);
- errno = ENOTSUP;
- return -1;
+ if (cmd == RTM_DELADDR)
+ return if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias);
+
+ if (cmd != RTM_NEWADDR) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ priv = (struct priv *)ia->iface->ctx->priv;
+ sin6_addr = (struct sockaddr_in6 *)&ss_addr;
+ sin6_addr->sin6_family = AF_INET6;
+ sin6_addr->sin6_addr = ia->addr;
+ sin6_mask = (struct sockaddr_in6 *)&ss_mask;
+ sin6_mask->sin6_family = AF_INET6;
+ ipv6_mask(&sin6_mask->sin6_addr, ia->prefix_len);
+ return if_addaddr(priv->pf_inet6_fd,
+ ia->alias, &ss_addr, &ss_mask);
}
int
return state;
}
+#ifdef ALIAS_ADDR
+/* Find the next logical aliase address we can use. */
+static int
+ipv4_aliasaddr(struct ipv4_addr *ia, struct ipv4_addr **repl)
+{
+ struct ipv4_state *state;
+ struct ipv4_addr *iap;
+ unsigned int lun;
+ char alias[IF_NAMESIZE];
+
+ if (ia->alias[0] != '\0')
+ return 0;
+
+ lun = 0;
+ state = IPV4_STATE(ia->iface);
+find_lun:
+ if (lun == 0)
+ strlcpy(alias, ia->iface->name, sizeof(alias));
+ else
+ snprintf(alias, sizeof(alias), "%s:%u", ia->iface->name, lun);
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (iap->iface != ia->iface)
+ continue;
+ if (iap->addr.s_addr == INADDR_ANY) {
+ /* No address assigned? Lets use it. */
+ strlcpy(ia->alias, iap->alias, sizeof(ia->alias));
+ if (repl)
+ *repl = iap;
+ return 1;
+ }
+ if (strcmp(iap->alias, alias) == 0)
+ break;
+ }
+ if (iap != NULL) {
+ if (lun == UINT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ lun++;
+ goto find_lun;
+ }
+ strlcpy(ia->alias, alias, sizeof(ia->alias));
+ return 0;
+}
+#endif
+
struct ipv4_addr *
ipv4_addaddr(struct interface *ifp, const struct in_addr *addr,
const struct in_addr *mask, const struct in_addr *bcast)
{
struct ipv4_state *state;
struct ipv4_addr *ia;
+#ifdef ALIAS_ADDR
+ int replaced;
+ struct ipv4_addr *replaced_ia;
+#endif
if ((state = ipv4_getstate(ifp)) == NULL) {
logger(ifp->ctx, LOG_ERR, "%s: ipv4_getstate: %m", __func__);
#endif
snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d",
inet_ntoa(*addr), inet_ntocidr(*mask));
+
+#ifdef ALIAS_ADDR
+ if ((replaced = ipv4_aliasaddr(ia, &replaced_ia)) == -1) {
+ logger(ifp->ctx, LOG_ERR, "%s: ipv4_aliasaddr: %m", ifp->name);
+ free(ia);
+ return NULL;
+ }
+#endif
+
logger(ifp->ctx, LOG_DEBUG, "%s: adding IP address %s broadcast %s",
ifp->name, ia->saddr, inet_ntoa(*bcast));
if (if_address(RTM_NEWADDR, ia) == -1) {
return NULL;
}
+#ifdef ALIAS_ADDR
+ if (replaced) {
+ TAILQ_REMOVE(&state->addrs, replaced_ia, next);
+ free(replaced_ia);
+ }
+#endif
+
TAILQ_INSERT_TAIL(&state->addrs, ia, next);
return ia;
}
}
ia->iface = ifp;
ia->addr = *addr;
+#ifdef ALIAS_ADDR
+ strlcpy(ia->alias, ifname, sizeof(ia->alias));
+#endif
TAILQ_INSERT_TAIL(&state->addrs, ia, next);
}
/* Mask could have changed */
}
}
-int
-ipv6_addaddr(struct ipv6_addr *ap, const struct timespec *now)
+static int
+ipv6_addaddr1(struct ipv6_addr *ap, const struct timespec *now)
{
struct interface *ifp;
struct ipv6_state *state;
return 0;
}
+#ifdef ALIAS_ADDR
+/* Find the next logical aliase address we can use. */
+static int
+ipv6_aliasaddr(struct ipv6_addr *ia, struct ipv6_addr **repl)
+{
+ struct ipv6_state *state;
+ struct ipv6_addr *iap;
+ unsigned int unit;
+ char alias[IF_NAMESIZE];
+
+ unit = 0;
+ state = IPV6_STATE(ia->iface);
+find_unit:
+ if (unit == 0)
+ strlcpy(alias, ia->iface->name, sizeof(alias));
+ else
+ snprintf(alias, sizeof(alias), "%s:%u", ia->iface->name, unit);
+ TAILQ_FOREACH(iap, &state->addrs, next) {
+ if (iap->iface != ia->iface)
+ continue;
+ if (IN6_IS_ADDR_UNSPECIFIED(&iap->addr)) {
+ /* No address assigned? Lets use it. */
+ strlcpy(ia->alias, iap->alias, sizeof(ia->alias));
+ if (repl)
+ *repl = iap;
+ return 1;
+ }
+ if (strcmp(iap->alias, alias) == 0)
+ break;
+ }
+ if (iap != NULL) {
+ if (unit == UINT_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ unit++;
+ goto find_unit;
+ }
+ strlcpy(ia->alias, alias, sizeof(ia->alias));
+ return 0;
+}
+#endif
+
+int
+ipv6_addaddr(struct ipv6_addr *ia, const struct timespec *now)
+{
+ int r;
+#ifdef ALIAS_ADDR
+ int replaced;
+ struct ipv6_addr *replaced_ia;
+
+ if ((replaced = ipv6_aliasaddr(ia, &replaced_ia)) == -1)
+ return -1;
+#endif
+
+ if ((r = ipv6_addaddr1(ia, now)) == 0) {
+#ifdef ALIAS_ADDR
+ if (replaced) {
+ struct ipv6_state *state;
+
+ state = IPV6_STATE(ia->iface);
+ TAILQ_REMOVE(&state->addrs, replaced_ia, next);
+ ipv6_freeaddr(replaced_ia);
+ }
+#endif
+ }
+ return r;
+}
int
ipv6_findaddrmatch(const struct ipv6_addr *addr, const struct in6_addr *match,
"%s: calloc: %m", __func__);
break;
}
+#ifdef ALIAS_ADDR
+ strlcpy(ap->alias, ifname, sizeof(ap->alias));
+#endif
ap->iface = ifp;
ap->addr = *addr;
ap->prefix_len = prefix_len;