]> git.meshlink.io Git - catta/blobdiff - avahi-autoipd/main.c
Enough is enough! I have had it with these motherf**ng gcc on this motherf**ng shut...
[catta] / avahi-autoipd / main.c
index c1a3d901da25467c3664b92734782d62bc18ab05..27c5a29509f740a2233b9b74d34bed43c16e0bdf 100644 (file)
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <sys/wait.h>
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+
+#ifdef __linux__
 #include <netpacket/packet.h>
+#endif
 #include <net/ethernet.h>
-#include <fcntl.h>
-#include <time.h>
+#include <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_dl.h>
+#include <net/route.h>
+#endif
+#include <arpa/inet.h>
+
 #include <assert.h>
 #include <errno.h>
-#include <string.h>
 #include <inttypes.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-#include <sys/ioctl.h>
-#include <poll.h>
-#include <net/if.h>
+#include <fcntl.h>
+#include <stdlib.h>
 #include <stdio.h>
-#include <getopt.h>
 #include <signal.h>
-#include <sys/wait.h>
-#include <pwd.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
 #include <grp.h>
+#include <poll.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#include <pcap.h>
+#endif
 
 #include <avahi-common/malloc.h>
 #include <avahi-common/timeval.h>
-
 #include <avahi-daemon/setproctitle.h>
 
 #include <libdaemon/dfork.h>
 #include "main.h"
 #include "iface.h"
 
-#ifndef __linux__
-#error "avahi-autoipd is only available on Linux for now"
-#endif
-
 /* An implementation of RFC 3927 */
 
 /* Constants from the RFC */
@@ -84,6 +97,7 @@
 #define IPV4LL_BROADCAST 0xA9FEFFFFL
 
 #define ETHER_ADDRLEN 6
+#define ETHER_HDR_SIZE (2+2*ETHER_ADDRLEN)
 #define ARP_PACKET_SIZE (8+4+4+2*ETHER_ADDRLEN)
 
 typedef enum ArpOperation {
@@ -98,6 +112,11 @@ typedef struct ArpPacketInfo {
     uint8_t sender_hw_address[ETHER_ADDRLEN], target_hw_address[ETHER_ADDRLEN];
 } ArpPacketInfo;
 
+typedef struct ArpPacket {
+    uint8_t *ether_header;
+    uint8_t *ether_payload;
+} ArpPacket;
+
 static State state = STATE_START;
 static int n_iteration = 0;
 static int n_conflict = 0;
@@ -117,6 +136,7 @@ static int no_chroot = 0;
 #endif
 static int no_drop_root = 0;
 static int wrote_pid_file = 0;
+static char *action_script = NULL;
 
 static enum {
     DAEMON_RUN,
@@ -229,13 +249,16 @@ fail:
 static int save_address(const char *fn, uint32_t addr) {
     FILE *f;
     char buf[32];
+    mode_t u;
 
     assert(fn);
-    
+
+    u = umask(0033);
     if (!(f = fopen(fn, "w"))) {
         daemon_log(LOG_ERR, "fopen() failed: %s", strerror(errno));
         goto fail;
     }
+    umask(u);
 
     fprintf(f, "%s\n", inet_ntop(AF_INET, &addr, buf, sizeof (buf)));
     fclose(f);
@@ -246,19 +269,49 @@ fail:
     if (f)
         fclose(f);
 
+    umask(u);
+    
     return -1;
 }
 
-static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
+/*
+ * Allocate a buffer with two pointers in front, one of which is
+ * guaranteed to point ETHER_HDR_SIZE bytes into it.
+ */
+static ArpPacket* packet_new(size_t packet_len) {
+    ArpPacket *p;
+    uint8_t *b;
+
+    assert(packet_len > 0);
+
+#ifdef __linux__
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = NULL;
+    p->ether_payload = b + sizeof(struct ArpPacket);
+    
+#else
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + ETHER_HDR_SIZE + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = b + sizeof(struct ArpPacket);
+    p->ether_payload = b + sizeof(struct ArpPacket) + ETHER_HDR_SIZE;
+#endif
+
+    return p;
+}
+
+static ArpPacket* packet_new_with_info(const ArpPacketInfo *info, size_t *packet_len) {
+    ArpPacket *p = NULL;
     uint8_t *r;
 
     assert(info);
-    assert(packet_len);
     assert(info->operation == ARP_REQUEST || info->operation == ARP_RESPONSE);
+    assert(packet_len != NULL);
 
     *packet_len = ARP_PACKET_SIZE;
-    r = avahi_new0(uint8_t, *packet_len);
-    
+    p = packet_new(*packet_len);
+    r = p->ether_payload;
+
     r[1] = 1; /* HTYPE */
     r[2] = 8; /* PTYPE */
     r[4] = ETHER_ADDRLEN; /* HLEN */
@@ -270,10 +323,10 @@ static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
     memcpy(r+18, info->target_hw_address, ETHER_ADDRLEN);
     memcpy(r+24, &info->target_ip_address, 4);
 
-    return r;
+    return p;
 }
 
-static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
     ArpPacketInfo info;
     
     memset(&info, 0, sizeof(info));
@@ -281,10 +334,10 @@ static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, siz
     memcpy(info.sender_hw_address, hw_address, ETHER_ADDRLEN);
     info.target_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
     ArpPacketInfo info;
 
     memset(&info, 0, sizeof(info));
@@ -293,13 +346,15 @@ static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_addr
     info.target_ip_address = ip_address;
     info.sender_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static int packet_parse(const void *data, size_t packet_len, ArpPacketInfo *info) {
-    const uint8_t *p = data;
+static int packet_parse(const ArpPacket *packet, size_t packet_len, ArpPacketInfo *info) {
+    const uint8_t *p;
     
-    assert(data);
+    assert(packet);
+    p = (uint8_t *)packet->ether_payload;
+    assert(p);
 
     if (packet_len < ARP_PACKET_SIZE)
         return -1;
@@ -392,6 +447,10 @@ fail:
     return -1;
 }
 
+#ifdef __linux__
+
+/* Linux 'packet socket' specific implementation */
+
 static int open_socket(int iface, uint8_t *hw_address) {
     int fd = -1;
     struct sockaddr_ll sa;
@@ -437,7 +496,7 @@ fail:
     return -1;
 }
 
-static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
+static int send_packet(int fd, int iface, ArpPacket *packet, size_t packet_len) {
     struct sockaddr_ll sa;
     
     assert(fd >= 0);
@@ -451,7 +510,7 @@ static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
     sa.sll_halen = ETHER_ADDRLEN;
     memset(sa.sll_addr, 0xFF, ETHER_ADDRLEN);
 
-    if (sendto(fd, packet, packet_len, 0, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
+    if (sendto(fd, packet->ether_payload, packet_len, 0, (struct sockaddr*) &sa, sizeof(sa)) < 0) {
         daemon_log(LOG_ERR, "sendto() failed: %s", strerror(errno));
         return -1;
     }
@@ -459,7 +518,7 @@ static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
     return 0;
 }
 
-static int recv_packet(int fd, void **packet, size_t *packet_len) {
+static int recv_packet(int fd, ArpPacket **packet, size_t *packet_len) {
     int s;
     struct sockaddr_ll sa;
     socklen_t sa_len;
@@ -479,10 +538,10 @@ static int recv_packet(int fd, void **packet, size_t *packet_len) {
     if (s <= 0)
         s = 4096;
 
-    *packet = avahi_new(uint8_t, s);
+    *packet = packet_new(s);
 
     sa_len = sizeof(sa);
-    if ((r = recvfrom(fd, *packet, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
+    if ((r = recvfrom(fd, (*packet)->ether_payload, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
         daemon_log(LOG_ERR, "recvfrom() failed: %s", strerror(errno));
         goto fail;
     }
@@ -499,12 +558,219 @@ fail:
 
     return -1;
 }
+
+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);
+}
+
+static int
+open_socket(int iface, uint8_t *hw_address)
+{
+       struct bpf_program       bpf;
+       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);
+       }
+
+       pp = pcap_open_live(ifname, 1500, 0, 0, __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);
+       }
+#if 0
+       /* XXX: can we use this with pcap_next_ex() ? */
+       err = pcap_setnonblock(pp, 1, __pcap_errbuf);
+       if (err == -1) {
+               pcap_close(pp);
+               return (-1);
+       }
+#endif
+
+       err = pcap_compile(pp, &bpf,
+                          "arp and ether dst ff:ff:ff:ff:ff:ff", 1, 0);
+       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 == 1) {
+                       daemon_log(LOG_ERR, "pcap len > caplen");
+               } else {
+                       daemon_log(LOG_ERR, "pcap_next_ex: %s",
+                           pcap_geterr(__pp));
+               }
+       }
+
+       return (retval);
+}
+#endif /* __linux__ */
+
 int is_ll_address(uint32_t addr) {
     return
-        (ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK &&
-        ntohl(addr) != IPV4LL_NETWORK &&
-        ntohl(addr) != IPV4LL_BROADCAST;
+        ((ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK) &&
+        ((ntohl(addr) & 0x0000FF00) != 0x0000) &&
+        ((ntohl(addr) & 0x0000FF00) != 0xFF00);
 }
 
 static struct timeval *elapse_time(struct timeval *tv, unsigned msec, unsigned jitter) {
@@ -575,7 +841,7 @@ static FILE* fork_dispatcher(void) {
             }
             
             if (daemon_exec("/", &k,
-                            AVAHI_IPCONF_SCRIPT, AVAHI_IPCONF_SCRIPT,
+                            action_script, action_script,
                             callout_event_table[info.event],
                             name,
                             inet_ntop(AF_INET, &info.address, buf, sizeof(buf)), NULL) < 0) {
@@ -656,6 +922,9 @@ static int drop_privs(void) {
     int r;
     mode_t u;
 
+    pw = NULL;
+    gr = NULL;
+
     /* Get user/group ID */
     
     if (!no_drop_root) {
@@ -762,9 +1031,9 @@ static int drop_privs(void) {
         set_env("LOGNAME", pw->pw_name);
         set_env("HOME", pw->pw_dir);
         
-        daemon_log(LOG_ERR, "Successfully dropped root privileges.");
+        daemon_log(LOG_INFO, "Successfully dropped root privileges.");
     }
-    
+
     return 0;
 }
 
@@ -773,20 +1042,20 @@ static int loop(int iface, uint32_t addr) {
         FD_ARP,
         FD_IFACE,
         FD_SIGNAL,
-        FD_MAX,
+        FD_MAX
     };
 
     int fd = -1, ret = -1;
     struct timeval next_wakeup;
     int next_wakeup_valid = 0;
     char buf[64];
-    void *in_packet = NULL;
+    ArpPacket *in_packet = NULL;
     size_t in_packet_len;
-    void *out_packet = NULL;
+    ArpPacket *out_packet = NULL;
     size_t out_packet_len;
     uint8_t hw_address[ETHER_ADDRLEN];
     struct pollfd pollfds[FD_MAX];
-    int iface_fd;
+    int iface_fd = -1;
     Event event = EVENT_NULL;
     int retval_sent = !daemonize;
     State st;
@@ -830,7 +1099,7 @@ static int loop(int iface, uint32_t addr) {
         load_address(address_fn, &addr);
 
     if (addr && !is_ll_address(addr)) {
-        daemon_log(LOG_WARNING, "Requested address %s is not from IPv4LL range 169.254/16, ignoring.", inet_ntop(AF_INET, &addr, buf, sizeof(buf)));
+        daemon_log(LOG_WARNING, "Requested address %s is not from IPv4LL range 169.254/16 or a reserved address, ignoring.", inet_ntop(AF_INET, &addr, buf, sizeof(buf)));
         addr = 0;
     }
 
@@ -943,7 +1212,9 @@ static int loop(int iface, uint32_t addr) {
                 } else if (state == STATE_WAITING_PROBE || state == STATE_PROBING || state == STATE_WAITING_ANNOUNCE) {
                     /* Probe conflict */
                     conflict = info.target_ip_address == addr && memcmp(hw_address, info.sender_hw_address, ETHER_ADDRLEN);
-                    daemon_log(LOG_INFO, "Recieved conflicting probe ARP packet.");
+
+                    if (conflict)
+                        daemon_log(LOG_INFO, "Recieved conflicting probe ARP packet.");
                 }
 
                 if (conflict) {
@@ -1052,7 +1323,7 @@ static int loop(int iface, uint32_t addr) {
                 if (pollfds[FD_ARP].revents == POLLERR) {
                     /* The interface is probably down, let's recreate our socket */
                     
-                    close(fd);
+                    close_socket(fd);
 
                     if ((fd = open_socket(iface, hw_address)) < 0)
                         goto fail;
@@ -1122,7 +1393,7 @@ fail:
     avahi_free(in_packet);
     
     if (fd >= 0)
-        close(fd);
+        close_socket(fd);
 
     if (iface_fd >= 0)
         iface_done();
@@ -1152,9 +1423,11 @@ static void help(FILE *f, const char *a0) {
             "    -V --version        Show version\n"
             "    -S --start=ADDRESS  Start with this address from the IPv4LL range\n"
             "                        169.254.0.0/16\n"
+            "    -t --script=script  Action script to run (defaults to\n"
+            "                        /etc/avahi/avahi-autoipd.action)\n"
             "    -w --wait           Wait until an address has been acquired before\n"
             "                        daemonizing\n"
-            "       --force-bind     Assign an IPv4LL address even if routable address\n"
+            "       --force-bind     Assign an IPv4LL address even if routable address\n"
             "                        is already assigned\n"
             "       --no-drop-root   Don't drop privileges\n"
 #ifdef HAVE_CHROOT            
@@ -1187,6 +1460,7 @@ static int parse_command_line(int argc, char *argv[]) {
         { "check",         no_argument,       NULL, 'c' },
         { "version",       no_argument,       NULL, 'V' },
         { "start",         required_argument, NULL, 'S' },
+        { "script",        required_argument, NULL, 't' },
         { "wait",          no_argument,       NULL, 'w' },
         { "force-bind",    no_argument,       NULL, OPTION_FORCE_BIND },
         { "no-drop-root",  no_argument,       NULL, OPTION_NO_DROP_ROOT },
@@ -1198,8 +1472,7 @@ static int parse_command_line(int argc, char *argv[]) {
         { NULL, 0, NULL, 0 }
     };
 
-    opterr = 0;
-    while ((c = getopt_long(argc, argv, "hDskrcVS:w", long_options, NULL)) >= 0) {
+    while ((c = getopt_long(argc, argv, "hDskrcVS:t:w", long_options, NULL)) >= 0) {
 
         switch(c) {
             case 's':
@@ -1230,6 +1503,10 @@ static int parse_command_line(int argc, char *argv[]) {
                     return -1;
                 }
                 break;
+            case 't':
+                avahi_free(action_script);
+                action_script = avahi_strdup(optarg);
+                break;
             case 'w':
                 wait_for_address = 1;
                 break;
@@ -1257,7 +1534,6 @@ static int parse_command_line(int argc, char *argv[]) {
 #endif
 
             default:
-                fprintf(stderr, "Invalid command line argument: %c\n", c);
                 return -1;
         }
     }
@@ -1279,6 +1555,9 @@ static int parse_command_line(int argc, char *argv[]) {
         fprintf(stderr, "Too many arguments\n");
         return -1;
     }
+
+    if (!action_script)
+        action_script = avahi_strdup(AVAHI_IPCONF_SCRIPT);
         
     return 0;
 }
@@ -1410,12 +1689,7 @@ finish:
     avahi_free(pid_file_name);
     avahi_free(argv0);
     avahi_free(interface_name);
+    avahi_free(action_script);
 
     return r;
 }
-
-/* TODO:
-
-- man page
-
-*/