]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Add initial support for aliased addresses on Solaris.
authorRoy Marples <roy@marples.name>
Fri, 22 Jul 2016 20:59:04 +0000 (20:59 +0000)
committerRoy Marples <roy@marples.name>
Fri, 22 Jul 2016 20:59:04 +0000 (20:59 +0000)
if-sun.c
if.h
ipv4.c
ipv4.h
ipv6.c
ipv6.h

index 68d1421e28d08ba5f37e434f87ccf4b9edbf9de2..5f6d5ed81078644bf5b96d5555d9e2a17bbb04e9 100644 (file)
--- a/if-sun.c
+++ b/if-sun.c
 #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 *)           \
@@ -88,11 +92,16 @@ struct dl_if {
        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)
@@ -111,15 +120,20 @@ if_conf(__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,
@@ -134,6 +148,13 @@ if_opensockets_os(struct dhcpcd_ctx *ctx)
 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);
@@ -827,6 +848,164 @@ out:
        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";
 
@@ -981,6 +1160,7 @@ if_readraw(struct interface *ifp, int fd,
                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;
 }
@@ -988,11 +1168,29 @@ if_readraw(struct interface *ifp, int fd,
 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
@@ -1083,11 +1281,27 @@ if_initrt(struct dhcpcd_ctx *ctx)
 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
diff --git a/if.h b/if.h
index a6a9c4fa9fe9f323a6e78984fbd028cd124cab5a..5bf93b5ba93d229b90f584b1c22d3ff469827c66 100644 (file)
--- a/if.h
+++ b/if.h
 #  endif
 #endif
 
+#ifndef ALIAS_ADDR
+#  ifdef __sun
+#    define ALIAS_ADDR
+#  endif
+#endif
+
 #include "config.h"
 #include "dhcpcd.h"
 #include "ipv4.h"
diff --git a/ipv4.c b/ipv4.c
index 12945b3432aa7c8ee90c85e99dbb1aef7d2f6142..79d205d5692de31284fe57490e552d9ce7a3ef7f 100644 (file)
--- a/ipv4.c
+++ b/ipv4.c
@@ -972,12 +972,62 @@ ipv4_getstate(struct interface *ifp)
        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__);
@@ -1006,6 +1056,15 @@ ipv4_addaddr(struct interface *ifp, const struct in_addr *addr,
 #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) {
@@ -1016,6 +1075,13 @@ ipv4_addaddr(struct interface *ifp, const struct in_addr *addr,
                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;
 }
@@ -1237,6 +1303,9 @@ ipv4_handleifa(struct dhcpcd_ctx *ctx,
                        }
                        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 */
diff --git a/ipv4.h b/ipv4.h
index cdc71a43c0e9d00f6056afafa823ab9c7503be39..fd3203903f6844b6bcc3e9bf3836d587b3386a33 100644 (file)
--- a/ipv4.h
+++ b/ipv4.h
@@ -86,6 +86,9 @@ struct ipv4_addr {
        struct interface *iface;
        int addr_flags;
        char saddr[INET_ADDRSTRLEN + 3];
+#ifdef ALIAS_ADDR
+       char alias[IF_NAMESIZE];
+#endif
 };
 TAILQ_HEAD(ipv4_addrhead, ipv4_addr);
 
diff --git a/ipv6.c b/ipv6.c
index 41c2eec34e09de7a413a981ceb8c91bfe525ad18..27434b67e1c367edd7807b29bbc74a6145c9597a 100644 (file)
--- a/ipv6.c
+++ b/ipv6.c
@@ -616,8 +616,8 @@ ipv6_deleteaddr(struct ipv6_addr *ia)
        }
 }
 
-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;
@@ -751,6 +751,74 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timespec *now)
        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,
@@ -1000,6 +1068,9 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx,
                                    "%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;
diff --git a/ipv6.h b/ipv6.h
index 68568a3811e4db5eb409baef3ba86a1bff9cfbd6..103fc49fd8ce32fb6fb8ccbd804c9f6135bc747d 100644 (file)
--- a/ipv6.h
+++ b/ipv6.h
@@ -32,6 +32,7 @@
 #include <netinet/in.h>
 
 #include "config.h"
+#include "if.h"
 
 #ifndef __linux__
 #  if !defined(__QNX__) && !defined(__sun)
@@ -167,6 +168,10 @@ struct ipv6_addr {
        uint8_t *ns;
        size_t nslen;
        int nsprobes;
+
+#ifdef ALIAS_ADDR
+       char alias[IF_NAMESIZE];
+#endif
 };
 
 #define        IPV6_AF_ONLINK          0x0001