Update README.md
[oweals/nmrpflash.git] / ethsock.c
index 40d992c008c27bebf021aa6d205ce88160a94be7..d754653ca11e48ab49e0e7da02315516b725d105 100644 (file)
--- a/ethsock.c
+++ b/ethsock.c
@@ -1,30 +1,87 @@
-#include <sys/socket.h>
 #include <sys/types.h>
-#include <net/if_dl.h>
 #include <stdbool.h>
-#include <ifaddrs.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include "nmrpd.h"
+
+#if defined(NMRPFLASH_WINDOWS)
+#define NMRPFLASH_NETALIAS_PREFIX "net"
+#define WPCAP
+#include <pcap.h>
+#else
 #include <pcap.h>
-#include "ethsock.h"
+#include <ifaddrs.h>
+#if defined(NMRPFLASH_LINUX)
+#define NMRPFLASH_AF_PACKET AF_PACKET
+#include <linux/if_packet.h>
+#else
+#define NMRPFLASH_AF_PACKET AF_LINK
+#include <net/if_types.h>
+#endif
+#endif
 
 #ifndef MIN
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
+
 struct ethsock
 {
        pcap_t *pcap;
-       struct timeval timeout;
+#ifndef NMRPFLASH_WINDOWS
        int fd;
+#else
+       HANDLE handle;
+#endif
+       unsigned timeout;
        uint8_t hwaddr[6];
 };
 
-static bool ethsock_fill_hwaddr(struct ethsock *sock, const char *interface)
+const char *mac_to_str(uint8_t *mac)
+{
+       static char buf[18];
+       snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
+                       mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+       return buf;
+}
+
+static int x_pcap_findalldevs(pcap_if_t **devs)
+{
+       char errbuf[PCAP_ERRBUF_SIZE];
+       if (pcap_findalldevs(devs, errbuf) != 0) {
+               fprintf(stderr, "%s.\n", errbuf);
+               return -1;
+       }
+
+       return 0;
+}
+
+#ifndef NMRPFLASH_WINDOWS
+static inline bool sockaddr_get_hwaddr(struct sockaddr *sa, uint8_t *hwaddr)
 {
-       struct ifaddrs *ifas, *ifa;
        void *src;
+
+       if (sa->sa_family != NMRPFLASH_AF_PACKET) {
+               return false;
+       }
+
+#ifndef NMRPFLASH_LINUX
+       if (((struct sockaddr_dl*)addr)->sdl_type != IFT_ETHER) {
+               return false;
+       }
+       src = LLADDR((struct sockaddr_dl*)sa);
+#else
+       src = ((struct sockaddr_ll*)sa)->sll_addr;
+#endif
+
+       memcpy(hwaddr, src, 6);
+       return true;
+}
+
+static bool get_hwaddr_from_intf(const char *intf, uint8_t *hwaddr)
+{
+       struct ifaddrs *ifas, *ifa;
        bool found;
 
        if (getifaddrs(&ifas) != 0) {
@@ -35,38 +92,169 @@ static bool ethsock_fill_hwaddr(struct ethsock *sock, const char *interface)
        found = false;
 
        for (ifa = ifas; ifa; ifa = ifa->ifa_next) {
-               if (!strcmp(ifa->ifa_name, interface)) {
-#ifdef __linux__
-                       if (ifa->ifa_addr->sa_family != AF_PACKET) {
-                               continue;
+               if (!strcmp(ifa->ifa_name, intf)) {
+                       if (sockaddr_get_hwaddr(ifa->ifa_addr, hwaddr)) {
+                               found = true;
+                               break;
                        }
-                       src = ((struct sockaddr_ll*)ifa->ifa_addr)->sll_addr;
+               }
+       }
+
+       freeifaddrs(ifas);
+       return found;
+}
+
 #else
-                       if (ifa->ifa_addr->sa_family != AF_LINK) {
+
+void win_perror2(const char *msg, DWORD err)
+{
+       char *buf = NULL;
+       FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                       FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+                       NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                       (LPTSTR)&buf, 0, NULL);
+
+       if (buf) {
+               /* FormatMessageA terminates buf with CRLF! */
+               fprintf(stderr, "%s: %s", msg, buf);
+               LocalFree(buf);
+       } else {
+               fprintf(stderr, "%s: error %d\n", msg, (int)err);
+       }
+}
+
+static bool get_hwaddr_from_intf(const char *intf, uint8_t *hwaddr)
+{
+       PIP_ADAPTER_INFO adapters, adapter;
+       DWORD ret;
+       ULONG i, bufLen = 0;
+       bool found = false;
+
+       if ((ret = GetAdaptersInfo(NULL, &bufLen)) != ERROR_BUFFER_OVERFLOW) {
+               win_perror2("GetAdaptersInfo", ret);
+               return false;
+       }
+
+       adapters = malloc(bufLen);
+       if (!adapters) {
+               perror("malloc");
+               return false;
+       }
+
+       if ((ret = GetAdaptersInfo(adapters, &bufLen) == NO_ERROR)) {
+               for (adapter = adapters; adapter; adapter = adapter->Next) {
+                       if (adapter->Type != MIB_IF_TYPE_ETHERNET) {
                                continue;
                        }
-                       src = LLADDR((struct sockaddr_dl*)ifa->ifa_addr);
-#endif
-                       memcpy(sock->hwaddr, src, 6);
-                       found = true;
+
+                       /* Interface names from WinPcap are "\Device\NPF_{GUID}", while
+                        * AdapterName from GetAdaptersInfo is just "{GUID}".*/
+                       if (strstr(intf, adapter->AdapterName)) {
+                               if (adapter->AddressLength == 6) {
+                                       for (i = 0; i != 6; ++i) {
+                                               hwaddr[i] = adapter->Address[i];
+                                       }
+
+                                       found = true;
+                                       break;
+                               }
+                       }
+               }
+       } else {
+               win_perror2("GetAdaptersInfo", ret);
+       }
+
+       free(adapters);
+       return found;
+}
+
+static const char *intf_alias_to_wpcap(const char *intf)
+{
+       static char buf[128];
+       pcap_if_t *devs, *dev;
+       unsigned i = 0, dev_num = 0;
+
+       if (intf[0] == '\\') {
+               return intf;
+       } else if (sscanf(intf, NMRPFLASH_NETALIAS_PREFIX "%u", &dev_num) != 1) {
+               fprintf(stderr, "Invalid interface alias.\n");
+               return NULL;
+       }
+
+       if (x_pcap_findalldevs(&devs) != 0) {
+               return NULL;
+       }
+
+       for (dev = devs; dev; dev = dev->next, ++i) {
+               if (i == dev_num) {
+                       if (verbosity) {
+                               printf("%s%u: %s\n", NMRPFLASH_NETALIAS_PREFIX, i, dev->name);
+                       }
+                       strncpy(buf, dev->name, sizeof(buf) - 1);
+                       buf[sizeof(buf) - 1] = '\0';
                        break;
                }
        }
 
-       if (!found) {
-               fprintf(stderr, "Failed to get MAC address of interface %s.\n", interface);
+       pcap_freealldevs(devs);
+
+       if (!dev) {
+               fprintf(stderr, "Interface alias not found.\n");
+               return NULL;
+       }
+
+       return buf;
+}
+
+static const char *intf_get_pretty_name(const char *intf)
+{
+       static char buf[512];
+       char *guid;
+       HKEY hkey;
+       LONG err;
+       DWORD len;
+
+       guid = strstr(intf, "NPF_{");
+       if (!guid) {
+               return NULL;
        }
 
-       freeifaddrs(ifas);
-       return found;
+       guid += 4;
+
+       snprintf(buf, sizeof(buf),
+                       "System\\CurrentControlSet\\Control\\Network\\"
+                       "{4D36E972-E325-11CE-BFC1-08002BE10318}\\"
+                       "%s\\Connection", guid);
+       err = RegOpenKeyExA(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &hkey);
+       if (err != ERROR_SUCCESS) {
+               if (verbosity > 1) {
+                       win_perror2("RegOpenKeyExA", err);
+               }
+               return NULL;
+       }
+
+       len = sizeof(buf);
+       err = RegQueryValueExA(hkey, "Name", NULL, NULL, (LPBYTE)buf, &len);
+       if (err == ERROR_SUCCESS) {
+               intf = buf;
+       } else {
+               if (verbosity > 1) {
+                       win_perror2("RegQueryValueExA", err);
+               }
+               intf = NULL;
+       }
+
+       RegCloseKey(hkey);
+       return intf;
 }
+#endif
 
 inline uint8_t *ethsock_get_hwaddr(struct ethsock *sock)
 {
        return sock->hwaddr;
 }
 
-struct ethsock *ethsock_create(const char *interface, uint16_t protocol)
+struct ethsock *ethsock_create(const char *intf, uint16_t protocol)
 {
        char buf[PCAP_ERRBUF_SIZE];
        struct bpf_program fp;
@@ -79,15 +267,18 @@ struct ethsock *ethsock_create(const char *interface, uint16_t protocol)
                return NULL;
        }
 
-       if (!ethsock_fill_hwaddr(sock, interface)) {
-               goto cleanup_malloc;
+#ifdef NMRPFLASH_WINDOWS
+       intf = intf_alias_to_wpcap(intf);
+       if (!intf) {
+               return NULL;
        }
+#endif
 
        buf[0] = '\0';
 
-       sock->pcap = pcap_open_live(interface, BUFSIZ, 1, 1, buf);
+       sock->pcap = pcap_open_live(intf, BUFSIZ, 1, 1, buf);
        if (!sock->pcap) {
-               fprintf(stderr, "%s\n", buf);
+               fprintf(stderr, "%s.\n", buf);
                goto cleanup_malloc;
        }
 
@@ -96,24 +287,49 @@ struct ethsock *ethsock_create(const char *interface, uint16_t protocol)
        }
 
        if (pcap_datalink(sock->pcap) != DLT_EN10MB) {
-               fprintf(stderr, "Interface %s not supported.\n", interface);
+               fprintf(stderr, "%s is not an ethernet interface.\n",
+                               intf);
                goto cleanup_pcap;
        }
 
+       if (!get_hwaddr_from_intf(intf, sock->hwaddr)) {
+               fprintf(stderr, "Failed to get MAC address of interface.\n");
+               goto cleanup_malloc;
+       }
+
+#ifndef NMRPFLASH_WINDOWS
        sock->fd = pcap_get_selectable_fd(sock->pcap);
        if (sock->fd == -1) {
                fprintf(stderr, "No selectable file descriptor available.\n");
                goto cleanup_pcap;
        }
+#else
+       sock->handle = pcap_getevent(sock->pcap);
+       if (!sock->handle) {
+               fprintf(stderr, "No event handle available.\n");
+               goto cleanup_pcap;
+       }
+
+       err = pcap_setmintocopy(sock->pcap, 1);
+       if (err) {
+               pcap_perror(sock->pcap, "pcap_setmintocopy");
+               goto cleanup_pcap;
+       }
+#endif
 
-       snprintf(buf, sizeof(buf), "ether proto %04x", protocol);
-       err = pcap_compile(sock->pcap, &fp, buf, 0, PCAP_NETMASK_UNKNOWN);
+       snprintf(buf, sizeof(buf), "ether proto 0x%04x and not ether src %s",
+                       protocol, mac_to_str(sock->hwaddr));
+
+       err = pcap_compile(sock->pcap, &fp, buf, 0, 0);
        if (err) {
                pcap_perror(sock->pcap, "pcap_compile");
                goto cleanup_pcap;
        }
 
-       if ((err = pcap_setfilter(sock->pcap, &fp))) {
+       err = pcap_setfilter(sock->pcap, &fp);
+       pcap_freecode(&fp);
+
+       if (err) {
                pcap_perror(sock->pcap, "pcap_setfilter");
                goto cleanup_pcap;
        }
@@ -127,25 +343,53 @@ cleanup_malloc:
        return NULL;
 }
 
+int select_fd(int fd, unsigned timeout)
+{
+       struct timeval tv;
+       int status;
+       fd_set fds;
+
+       FD_ZERO(&fds);
+       FD_SET(fd, &fds);
+
+       tv.tv_sec = timeout / 1000;
+       tv.tv_usec = 1000 * (timeout % 1000);
+
+       status = select(fd + 1, &fds, NULL, NULL, &tv);
+       if (status < 0) {
+               sock_perror("select");
+       }
+
+       return status;
+}
+
 ssize_t ethsock_recv(struct ethsock *sock, void *buf, size_t len)
 {
        struct pcap_pkthdr* hdr;
        const u_char *capbuf;
        int status;
-       fd_set fds;
-
-       if (sock->timeout.tv_sec || sock->timeout.tv_usec) {
-               FD_ZERO(&fds);
-               FD_SET(sock->fd, &fds);
+#ifdef NMRPFLASH_WINDOWS
+       DWORD ret;
 
-               status = select(sock->fd + 1, &fds, NULL, NULL, &sock->timeout);
-               if (status == -1) {
-                       perror("select");
+       if (sock->timeout) {
+               ret = WaitForSingleObject(sock->handle, sock->timeout);
+               if (ret == WAIT_TIMEOUT) {
+                       return 0;
+               } else if (ret != WAIT_OBJECT_0) {
+                       win_perror2("WaitForSingleObject", ret);
+                       return -1;
+               }
+       }
+#else
+       if (sock->timeout) {
+               status = select_fd(sock->fd, sock->timeout);
+               if (status < 0) {
                        return -1;
                } else if (status == 0) {
-                       return 1;
+                       return 0;
                }
        }
+#endif
 
        status = pcap_next_ex(sock->pcap, &hdr, &capbuf);
        switch (status) {
@@ -165,7 +409,7 @@ ssize_t ethsock_recv(struct ethsock *sock, void *buf, size_t len)
 
 int ethsock_send(struct ethsock *sock, void *buf, size_t len)
 {
-#if defined(_WIN32) || defined(_WIN64)
+#ifdef NMRPFLASH_WINDOWS
        if (pcap_sendpacket(sock->pcap, buf, len) == 0) {
                return 0;
        } else {
@@ -189,9 +433,111 @@ int ethsock_close(struct ethsock *sock)
        return 0;
 }
 
-int ethsock_set_timeout(struct ethsock *sock, unsigned msec)
+inline int ethsock_set_timeout(struct ethsock *sock, unsigned msec)
 {
-       sock->timeout.tv_sec = msec / 1000;
-       sock->timeout.tv_usec = (msec % 1000) * 1000;
+       sock->timeout = msec;
+       return 0;
+}
+
+static bool get_hwaddr_from_pcap(const pcap_if_t *dev, uint8_t *hwaddr)
+{
+#ifndef NMRPFLASH_WINDOWS
+       pcap_addr_t *addr;
+       int i;
+
+       for (addr = dev->addresses; addr; addr = addr->next) {
+               if (verbosity > 1) {
+                       printf("%s: sa_family=%d, sa_data={ ", dev->name,
+                                       addr->addr->sa_family);
+                       for (i = 0; i != sizeof(addr->addr->sa_data); ++i) {
+                               printf("%02x ", addr->addr->sa_data[i] & 0xff);
+                       }
+                       printf("}\n");
+               }
+
+               if (sockaddr_get_hwaddr(addr->addr, hwaddr)) {
+                       return true;
+               }
+       }
+#endif
+
+       return get_hwaddr_from_intf(dev->name, hwaddr);
+}
+
+int ethsock_list_all(void)
+{
+       pcap_if_t *devs, *dev;
+       pcap_addr_t *addr;
+       uint8_t hwaddr[6];
+       unsigned dev_num = 0, dev_ok = 0;
+#ifdef NMRPFLASH_WINDOWS
+       const char *pretty;
+#endif
+
+       if (x_pcap_findalldevs(&devs) != 0) {
+               return -1;
+       }
+
+       memset(hwaddr, 0, 6);
+
+       for (dev = devs; dev; dev = dev->next, ++dev_num) {
+               if (dev->flags & PCAP_IF_LOOPBACK) {
+                       if (verbosity) {
+                               printf("%-15s  (loopback device)\n", dev->name);
+                       }
+                       continue;
+               }
+
+               if (!get_hwaddr_from_pcap(dev, hwaddr)) {
+                       if (verbosity) {
+                               printf("%-15s  (not an ethernet device)\n",
+                                               dev->name);
+                       }
+                       continue;
+               }
+
+#ifndef NMRPFLASH_WINDOWS
+               printf("%-15s", dev->name);
+#else
+               /* Call this here so *_perror() calls don't happen within a line */
+               pretty = intf_get_pretty_name(dev->name);
+
+               if (!verbosity) {
+                       printf("%s%u", NMRPFLASH_NETALIAS_PREFIX, dev_num);
+               } else {
+                       printf("%s", dev->name);
+               }
+#endif
+
+               for (addr = dev->addresses; addr; addr = addr->next) {
+                       if (addr->addr->sa_family == AF_INET) {
+                               printf("  %-15s",
+                                               inet_ntoa(((struct sockaddr_in*)addr->addr)->sin_addr));
+                               break;
+                       }
+               }
+
+               if (!addr) {
+                       printf("  %-15s", "0.0.0.0");
+               }
+
+               printf("  %s", mac_to_str(hwaddr));
+
+#ifdef NMRPFLASH_WINDOWS
+               if (pretty) {
+                       printf("  (%s)", pretty);
+               } else if (dev->description) {
+                       printf("  (%s)", dev->description);
+               }
+
+#endif
+               printf("\n");
+               ++dev_ok;
+       }
+
+       if (!dev_ok) {
+               printf("No suitable network interfaces found.\n");
+       }
+
        return 0;
 }