]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Use DLPI to send and receive ARP and BOOTP packets.
authorRoy Marples <roy@marples.name>
Tue, 17 May 2016 20:26:09 +0000 (20:26 +0000)
committerRoy Marples <roy@marples.name>
Tue, 17 May 2016 20:26:09 +0000 (20:26 +0000)
Use pfmod to filter the packets we want in the kernel.

if-sun.c

index 9ffbf338d7d7747e9c39970cd9092f9fa3836726..9a54835d3dfa19322789864f33da9bfb09b2427e 100644 (file)
--- a/if-sun.c
+++ b/if-sun.c
@@ -1,6 +1,6 @@
 /*
- * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2016 Roy Marples <roy@marples.name>
+ * Solaris interface driver for dhcpcd
+ * Copyright (c) 2016 Roy Marples <roy@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
  * SUCH DAMAGE.
  */
 
-#include <sys/ioctl.h>
-#include <sys/tihdr.h>
-#include <sys/utsname.h>
-
 #include <errno.h>
 #include <fcntl.h>
 #include <ifaddrs.h>
 #include <libdlpi.h>
+#include <stddef.h>
 #include <stdlib.h>
 #include <stropts.h>
 #include <string.h>
 #include <net/if_dl.h>
 #include <net/if_types.h>
 
+#include <netinet/if_ether.h>
+#include <netinet/udp.h>
+
+#include <sys/ioctl.h>
+#include <sys/pfmod.h>
+#include <sys/tihdr.h>
+#include <sys/utsname.h>
+
 #include "config.h"
 #include "common.h"
 #include "dhcp.h"
 #include "ipv6.h"
 #include "ipv6nd.h"
 
+#ifdef INET
+/* Instead of using DLPI directly, we use libdlpi which is
+ * Solaris sepcific. */
+struct dl_if {
+       TAILQ_ENTRY(dl_if)      next;
+       struct interface        *iface;
+       int                     fd;
+       dlpi_handle_t           dh;
+       uint8_t                 broadcast[DLPI_PHYSADDR_MAX];
+};
+TAILQ_HEAD(dl_if_head, dl_if);
+
+struct priv {
+       struct dl_if_head dl_ifs;
+};
+#endif
+
 int
 if_init(__unused struct interface *ifp)
 {
@@ -69,9 +91,23 @@ 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;
+       TAILQ_INIT(&priv->dl_ifs);
+#else
+       ctx->priv = NULL;
+#endif
 
        ctx->link_fd = socket(PF_ROUTE,
            SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+#ifdef INET
+       if (ctx->link_fd == -1)
+               free(ctx->priv);
+#endif
        return ctx->link_fd == -1 ? -1 : 0;
 }
 
@@ -79,7 +115,8 @@ void
 if_closesockets_os(struct dhcpcd_ctx *ctx)
 {
 
-       UNUSED(ctx);
+       /* each interface should have closed itself */
+       free(ctx->priv);
 }
 
 int
@@ -118,8 +155,8 @@ if_machinearch(char *str, size_t len)
 }
 
 struct linkwalk {
-       struct ifaddrs  *lw_ifa;
-       int             lw_error;
+       struct ifaddrs          *lw_ifa;
+       int                     lw_error;
 };
 
 static boolean_t
@@ -192,8 +229,8 @@ failed1:
 static struct ifaddrs *
 if_ifa_lo0(void)
 {
-       struct ifaddrs *ifa;
-       struct sockaddr_dl *sdl;
+       struct ifaddrs          *ifa;
+       struct sockaddr_dl      *sdl;
 
        if ((ifa = calloc(1, sizeof(*ifa))) == NULL)
                return NULL;
@@ -239,13 +276,15 @@ if_getifaddrs(struct ifaddrs **ifap)
 static void
 if_octetstr(char *buf, const Octet_t *o, ssize_t len)
 {
-       int i;
-       char *p;
+       int                     i;
+       char                    *p;
 
        p = buf;
        for (i = 0; i < o->o_length; i++) {
                if ((p + 1) - buf < len)
                        *p++ = o->o_bytes[i];
+               else
+                       break;
        }
        *p = '\0';
 }
@@ -349,52 +388,161 @@ out:
 }
 
 #ifdef INET
-const char *if_pfname = "SunOS";
+const char *if_pfname = "DLPI";
+
+static struct dl_if *
+if_findraw(struct interface *ifp, int fd)
+{
+       struct dl_if            *di;
+       struct priv             *priv;
+
+       priv = (struct priv *)ifp->ctx->priv;
+       TAILQ_FOREACH(di, &priv->dl_ifs, next) {
+               if (di->fd == fd)
+                       return di;
+       }
+       errno = ENXIO;
+       return NULL;
+}
 
 void
 if_closeraw(struct interface *ifp, int fd)
 {
+       struct dl_if            *di;
 
-       UNUSED(ifp);
-       UNUSED(fd);
+       if ((di = if_findraw(ifp, fd)) != NULL) {
+               struct priv     *priv;
+
+               priv = (struct priv *)ifp->ctx->priv;
+               TAILQ_REMOVE(&priv->dl_ifs, di, next);
+               dlpi_close(di->dh);
+               free(di);
+       }
 }
 
 int
 if_openraw(struct interface *ifp, uint16_t protocol)
 {
+       dlpi_handle_t           dh;
+       struct priv             *priv;
+       struct dl_if            *di;
+       dlpi_info_t             dlinfo;
+       struct packetfilt       pf;
+       ushort_t                *pfp;
+       struct strioctl         sioc;
 
-       UNUSED(ifp);
-       UNUSED(protocol);
-       errno = ENOTSUP;
+       if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS)
+               return -1;
+
+       /* We need to register pfmod, which is similar to BPF
+        * so the kernel can filter out the packets we don't need. */
+       if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS ||
+           dlpi_bind(dh, protocol, NULL) != DLPI_SUCCESS ||
+           (di = malloc(sizeof(*di))) == NULL)
+               goto failed1;
+
+       di->iface       = ifp;
+       di->dh          = dh;
+       di->fd          = dlpi_fd(dh);
+       memcpy(di->broadcast, dlinfo.di_bcastaddr, dlinfo.di_bcastaddrlen);
+       priv = (struct priv *)ifp->ctx->priv;
+
+       pf.Pf_Priority = 0;
+       pfp = pf.Pf_Filter;
+       /* pfmod operates on 16 bits, so divide offsets by 2.
+        * When working on a 8 bits, mask off the bits not teested. */
+       switch (protocol) {
+       case ETHERTYPE_IP:
+               /* Filter fragments. */
+               *pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_off) / 2);
+               *pfp++ = ENF_PUSHLIT | ENF_AND;
+               *pfp++ = htons(0x1fff | IP_MF);
+               *pfp++ = ENF_PUSHZERO | ENF_CAND;
+
+               /* Filter UDP. */
+               *pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_p) / 2);
+               *pfp++ = ENF_PUSHFF00 | ENF_AND;
+               *pfp++ = ENF_PUSHLIT | ENF_CAND;
+               *pfp++ = htons(IPPROTO_UDP);
+
+               /* Filter BOOTPC. */
+               *pfp++ = ENF_PUSHWORD +
+                   ((sizeof(struct ip) +
+                   offsetof(struct udphdr, uh_dport)) / 2);
+               *pfp++ = ENF_PUSHLIT | ENF_CAND;
+               *pfp++ = htons(BOOTPC);
+               break;
+
+       case ETHERTYPE_ARP:
+               /* We are only interested in IP. */
+               *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_hrd) / 2);
+               *pfp++ = ENF_PUSHLIT | ENF_CAND;
+               *pfp++ = htons(ARPHRD_ETHER);
+
+               /* Must be REQUEST or REPLY. */
+               *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2);
+               *pfp++ = ENF_PUSHLIT | ENF_CNAND;
+               *pfp++ = htons(ARPOP_REQUEST);
+               *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2);
+               *pfp++ = ENF_PUSHLIT | ENF_CAND;
+               *pfp++ = htons(ARPOP_REPLY);
+               break;
+
+       default:
+               errno = EPROTOTYPE;
+               goto failed;
+       }
+       pf.Pf_FilterLen = pfp - pf.Pf_Filter;
+
+       sioc.ic_cmd     = PFIOCSETF;
+       sioc.ic_timout  = INFTIM;
+       sioc.ic_len     = sizeof(pf);
+       sioc.ic_dp      = (void *)&pf;
+
+       /* Install the filter and then flush the stream. */
+       if (ioctl(di->fd, I_PUSH, "pfmod") == -1 ||
+           ioctl(di->fd, I_STR, &sioc) == -1 ||
+           ioctl(di->fd, I_FLUSH, FLUSHR) == -1)
+               goto failed;
+
+       TAILQ_INSERT_TAIL(&priv->dl_ifs, di, next);
+       return di->fd;
+
+failed:
+       free(di);
+failed1:
+       dlpi_close(dh);
        return -1;
 }
 
 ssize_t
-if_sendraw(const struct interface *ifp, int fd, uint16_t protocol,
+if_sendraw(const struct interface *cifp, int fd, __unused uint16_t protocol,
     const void *data, size_t len)
 {
+       struct dl_if            *di;
+       int                     r;
+       struct interface        *ifp = UNCONST(cifp);
 
-       UNUSED(ifp);
-       UNUSED(fd);
-       UNUSED(protocol);
-       UNUSED(data);
-       UNUSED(len);
-       errno = ENOTSUP;
-       return -1;
+       if ((di = if_findraw(ifp, fd)) == NULL)
+               return -1;
+       r = dlpi_send(di->dh, di->broadcast, ifp->hwlen, data, len, NULL);
+       return r == DLPI_SUCCESS ? (ssize_t)len : -1;
 }
 
 ssize_t
 if_readraw(struct interface *ifp, int fd,
     void *data, size_t len, int *flags)
 {
+       struct dl_if            *di;
+       int                     r;
+       size_t                  mlen;
 
-       UNUSED(ifp);
-       UNUSED(fd);
-       UNUSED(data);
-       UNUSED(len);
-       UNUSED(flags);
-       errno = ENOTSUP;
-       return -1;
+       *flags = 0;
+       if ((di = if_findraw(ifp, fd)) == NULL)
+               return -1;
+       mlen = len;
+       r = dlpi_recv(di->dh, NULL, NULL, data, &mlen, -1, NULL);
+       return r == DLPI_SUCCESS ? (ssize_t)mlen : -1;
 }
 
 int