+
+static void
+close_socket(int fd) {
+ close(fd);
+}
+
+#else /* !__linux__ */
+/* PCAP-based implementation */
+
+static pcap_t *__pp;
+static char __pcap_errbuf[PCAP_ERRBUF_SIZE];
+static uint8_t __lladdr[ETHER_ADDRLEN];
+
+#ifndef elementsof
+#define elementsof(array) (sizeof(array)/sizeof(array[0]))
+#endif
+
+static int
+__get_ether_addr(int ifindex, u_char *lladdr)
+{
+ int mib[6];
+ char *buf;
+ struct if_msghdr *ifm;
+ char *lim;
+ char *next;
+ struct sockaddr_dl *sdl;
+ size_t len;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_IFLIST;
+ mib[5] = ifindex;
+
+ if (sysctl(mib, elementsof(mib), NULL, &len, NULL, 0) != 0) {
+ daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
+ strerror(errno));
+ return (-1);
+ }
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ daemon_log(LOG_ERR, "malloc(%d): %s", len, strerror(errno));
+ return (-1);
+ }
+
+ if (sysctl(mib, elementsof(mib), buf, &len, NULL, 0) != 0) {
+ daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
+ strerror(errno));
+ free(buf);
+ return (-1);
+ }
+
+ lim = buf + len;
+ for (next = buf; next < lim; next += ifm->ifm_msglen) {
+ ifm = (struct if_msghdr *)next;
+ if (ifm->ifm_type == RTM_IFINFO) {
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ memcpy(lladdr, LLADDR(sdl), ETHER_ADDRLEN);
+ }
+ }
+ free(buf);
+
+ return (0);
+}
+
+#define PCAP_TIMEOUT 500 /* 0.5s */
+
+static int
+open_socket(int iface, uint8_t *hw_address)
+{
+ struct bpf_program bpf;
+ char *filter;
+ char ifname[IFNAMSIZ];
+ pcap_t *pp;
+ int err;
+ int fd;
+
+ assert(__pp == NULL);
+
+ if (interface_up(iface) < 0) {
+ return (-1);
+ }
+ if (__get_ether_addr(iface, __lladdr) == -1) {
+ return (-1);
+ }
+ if (if_indextoname(iface, ifname) == NULL) {
+ return (-1);
+ }
+
+ /*
+ * Using a timeout for BPF is fairly portable across BSDs. On most
+ * modern versions, using the timeout/nonblock/poll method results in
+ * fairly sane behavior, with the timeout only coming into play during
+ * the next_ex() call itself (so, for us, that's only when there's
+ * data). On older versions, it may result in a PCAP_TIMEOUT busy-wait
+ * on some versions, though, as the poll() may terminate at the
+ * PCAP_TIMEOUT instead of the poll() timeout.
+ */
+ pp = pcap_open_live(ifname, 1500, 0, PCAP_TIMEOUT, __pcap_errbuf);
+ if (pp == NULL) {
+ return (-1);
+ }
+ err = pcap_set_datalink(pp, DLT_EN10MB);
+ if (err == -1) {
+ daemon_log(LOG_ERR, "pcap_set_datalink: %s", pcap_geterr(pp));
+ pcap_close(pp);
+ return (-1);
+ }
+ err = pcap_setdirection(pp, PCAP_D_IN);
+ if (err == -1) {
+ daemon_log(LOG_ERR, "pcap_setdirection: %s", pcap_geterr(pp));
+ pcap_close(pp);
+ return (-1);
+ }
+
+ fd = pcap_get_selectable_fd(pp);
+ if (fd == -1) {
+ pcap_close(pp);
+ return (-1);
+ }
+
+ /*
+ * Using setnonblock is a portability stop-gap. Using the timeout in
+ * combination with setnonblock will ensure on most BSDs that the
+ * next_ex call returns in a timely fashion.
+ */
+ err = pcap_setnonblock(pp, 1, __pcap_errbuf);
+ if (err == -1) {
+ pcap_close(pp);
+ return (-1);
+ }
+
+ filter = avahi_strdup_printf("arp and (ether dst ff:ff:ff:ff:ff:ff or "
+ "%02x:%02x:%02x:%02x:%02x:%02x)",
+ __lladdr[0], __lladdr[1],
+ __lladdr[2], __lladdr[3],
+ __lladdr[4], __lladdr[5]);
+ DEBUG(daemon_log(LOG_DEBUG, "Using pcap filter '%s'", filter));
+
+ err = pcap_compile(pp, &bpf, filter, 1, 0);
+ avahi_free(filter);
+ if (err == -1) {
+ daemon_log(LOG_ERR, "pcap_compile: %s", pcap_geterr(pp));
+ pcap_close(pp);
+ return (-1);
+ }
+ err = pcap_setfilter(pp, &bpf);
+ if (err == -1) {
+ daemon_log(LOG_ERR, "pcap_setfilter: %s", pcap_geterr(pp));
+ pcap_close(pp);
+ return (-1);
+ }
+ pcap_freecode(&bpf);
+
+ /* Stash pcap-specific context away. */
+ memcpy(hw_address, __lladdr, ETHER_ADDRLEN);
+ __pp = pp;
+
+ return (fd);
+}
+
+static void
+close_socket(int fd __unused)
+{
+
+ assert(__pp != NULL);
+ pcap_close(__pp);
+ __pp = NULL;
+}
+
+/*
+ * We trick avahi into allocating sizeof(packet) + sizeof(ether_header),
+ * and prepend the required ethernet header information before sending.
+ */
+static int
+send_packet(int fd __unused, int iface __unused, ArpPacket *packet,
+ size_t packet_len)
+{
+ struct ether_header *eh;
+
+ assert(__pp != NULL);
+ assert(packet != NULL);
+
+ eh = (struct ether_header *)packet->ether_header;
+ memset(eh->ether_dhost, 0xFF, ETHER_ADDRLEN);
+ memcpy(eh->ether_shost, __lladdr, ETHER_ADDRLEN);
+ eh->ether_type = htons(0x0806);
+
+ return (pcap_inject(__pp, (void *)eh, packet_len + sizeof(*eh)));
+}
+
+static int
+recv_packet(int fd __unused, ArpPacket **packet, size_t *packet_len)
+{
+ struct pcap_pkthdr *ph;
+ u_char *pd;
+ ArpPacket *ap;
+ int err;
+ int retval;
+
+ assert(__pp != NULL);
+ assert(packet != NULL);
+ assert(packet_len != NULL);
+
+ *packet = NULL;
+ *packet_len = 0;
+ retval = -1;
+
+ err = pcap_next_ex(__pp, &ph, (const u_char **)&pd);
+ if (err == 1 && ph->caplen <= ph->len) {
+ ap = packet_new(ph->caplen);
+ memcpy(ap->ether_header, pd, ph->caplen);
+ *packet = ap;
+ *packet_len = (ph->caplen - sizeof(struct ether_header));
+ retval = 0;
+ } else if (err >= 0) {
+ /*
+ * err == 1: Just drop bogus packets (>1500 for an arp packet!?)
+ * on the floor.
+ *
+ * err == 0: We might have had traffic on the pcap fd that
+ * didn't match the filter, in which case we'll get 0 packets.
+ */
+ retval = 0;
+ } else {
+ daemon_log(LOG_ERR, "pcap_next_ex(%d): %s",
+ err, pcap_geterr(__pp));
+ }
+
+ return (retval);
+}
+#endif /* __linux__ */
+