diff options
author | Francis Dupont <fdupont@isc.org> | 2017-03-08 16:01:38 +0100 |
---|---|---|
committer | Francis Dupont <fdupont@isc.org> | 2017-03-08 16:01:38 +0100 |
commit | e191f1e8de0889c636a922faa0733cf99a39351b (patch) | |
tree | 588681c7b1bfeccd8095f15b8b49d4474b8abd9f /common | |
parent | 66e800337ea054b227ebcf0bed97a639df613d3e (diff) | |
download | isc-dhcp-e191f1e8de0889c636a922faa0733cf99a39351b.tar.gz |
Merged #28761 (Linux interface discovery)
Diffstat (limited to 'common')
-rw-r--r-- | common/discover.c | 404 |
1 files changed, 21 insertions, 383 deletions
diff --git a/common/discover.c b/common/discover.c index 8e7f6328..6824ec4b 100644 --- a/common/discover.c +++ b/common/discover.c @@ -373,392 +373,13 @@ end_iface_scan(struct iface_conf_list *ifaces) { ifaces->sock = -1; } -#elif __linux /* !HAVE_SIOCGLIFCONF */ -/* - * Linux support - * ------------- - * - * In Linux, we use the /proc pseudo-filesystem to get information - * about interfaces, along with selected ioctl() calls. - * - * Linux low level access is documented in the netdevice man page. - */ - -/* - * Structure holding state about the scan. - */ -struct iface_conf_list { - int sock; /* file descriptor used to get information */ - FILE *fp; /* input from /proc/net/dev */ -#ifdef DHCPv6 - FILE *fp6; /* input from /proc/net/if_inet6 */ -#endif -}; - -/* - * Structure used to return information about a specific interface. - */ -struct iface_info { - char name[IFNAMSIZ]; /* name of the interface, e.g. "eth0" */ - struct sockaddr_storage addr; /* address information */ - isc_uint64_t flags; /* interface flags, e.g. IFF_LOOPBACK */ -}; - -/* - * Start a scan of interfaces. - * - * The iface_conf_list structure maintains state for this process. - */ -int -begin_iface_scan(struct iface_conf_list *ifaces) { - char buf[IF_LINE_LENGTH]; - int len; - int i; - - ifaces->fp = fopen("/proc/net/dev", "r"); - if (ifaces->fp == NULL) { - log_error("Error opening '/proc/net/dev' to list interfaces"); - return 0; - } - - /* - * The first 2 lines are header information, so read and ignore them. - */ - for (i=0; i<2; i++) { - if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) { - log_error("Error reading headers from '/proc/net/dev'"); - fclose(ifaces->fp); - ifaces->fp = NULL; - return 0; - } - len = strlen(buf); - if ((len <= 0) || (buf[len-1] != '\n')) { - log_error("Bad header line in '/proc/net/dev'"); - fclose(ifaces->fp); - ifaces->fp = NULL; - return 0; - } - } - - ifaces->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (ifaces->sock < 0) { - log_error("Error creating socket to list interfaces; %m"); - fclose(ifaces->fp); - ifaces->fp = NULL; - return 0; - } - -#ifdef DHCPv6 - if (local_family == AF_INET6) { - ifaces->fp6 = fopen("/proc/net/if_inet6", "r"); - if (ifaces->fp6 == NULL) { - log_error("Error opening '/proc/net/if_inet6' to " - "list IPv6 interfaces; %m"); - close(ifaces->sock); - ifaces->sock = -1; - fclose(ifaces->fp); - ifaces->fp = NULL; - return 0; - } - } -#endif - - return 1; -} - -/* - * Read our IPv4 interfaces from /proc/net/dev. - * - * The file looks something like this: - * - * Inter-| Receive ... - * face |bytes packets errs drop fifo frame ... - * lo: 1580562 4207 0 0 0 0 ... - * eth0: 0 0 0 0 0 0 ... - * eth1:1801552440 37895 0 14 0 ... - * - * We only care about the interface name, which is at the start of - * each line. - * - * We use an ioctl() to get the address and flags for each interface. - */ -static int -next_iface4(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { - char buf[IF_LINE_LENGTH]; - int len; - char *p; - char *name; - struct ifreq tmp; - - /* - * Loop exits when we find an interface that has an address, or - * when we run out of interfaces. - */ - for (;;) { - do { - /* - * Read the next line in the file. - */ - if (fgets(buf, sizeof(buf), ifaces->fp) == NULL) { - if (ferror(ifaces->fp)) { - *err = 1; - log_error("Error reading interface " - "information"); - } else { - *err = 0; - } - return 0; - } - - /* - * Make sure the line is a nice, - * newline-terminated line. - */ - len = strlen(buf); - if ((len <= 0) || (buf[len-1] != '\n')) { - log_error("Bad line reading interface " - "information"); - *err = 1; - return 0; - } - - /* - * Figure out our name. - */ - p = strrchr(buf, ':'); - if (p == NULL) { - log_error("Bad line reading interface " - "information (no colon)"); - *err = 1; - return 0; - } - *p = '\0'; - name = buf; - while (isspace(*name)) { - name++; - } - - /* - * Copy our name into our interface structure. - */ - len = p - name; - if (len >= sizeof(info->name)) { - *err = 1; - log_error("Interface name '%s' too long", name); - return 0; - } - strncpy(info->name, name, sizeof(info->name) - 1); - -#ifdef ALIAS_NAMED_PERMUTED - /* interface aliases look like "eth0:1" or "wlan1:3" */ - s = strchr(info->name, ':'); - if (s != NULL) { - *s = '\0'; - } -#endif - -#ifdef SKIP_DUMMY_INTERFACES - } while (strncmp(info->name, "dummy", 5) == 0); -#else - } while (0); -#endif - - memset(&tmp, 0, sizeof(tmp)); - strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1); - if (ioctl(ifaces->sock, SIOCGIFADDR, &tmp) < 0) { - if (errno == EADDRNOTAVAIL) { - continue; - } - log_error("Error getting interface address " - "for '%s'; %m", name); - *err = 1; - return 0; - } - memcpy(&info->addr, &tmp.ifr_addr, sizeof(tmp.ifr_addr)); - - memset(&tmp, 0, sizeof(tmp)); - strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1); - if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) { - log_error("Error getting interface flags for '%s'; %m", - name); - *err = 1; - return 0; - } - info->flags = tmp.ifr_flags; - - *err = 0; - return 1; - } -} - -#ifdef DHCPv6 -/* - * Read our IPv6 interfaces from /proc/net/if_inet6. - * - * The file looks something like this: - * - * fe80000000000000025056fffec00008 05 40 20 80 vmnet8 - * 00000000000000000000000000000001 01 80 10 80 lo - * fe80000000000000025056fffec00001 06 40 20 80 vmnet1 - * 200108881936000202166ffffe497d9b 03 40 00 00 eth1 - * fe8000000000000002166ffffe497d9b 03 40 20 80 eth1 - * - * We get IPv6 address from the start, the interface name from the end, - * and ioctl() to get flags. - */ -static int -next_iface6(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { - char buf[IF_LINE_LENGTH]; - int len; - char *p; - char *name; - int i; - struct sockaddr_in6 addr; - struct ifreq tmp; - - do { - /* - * Read the next line in the file. - */ - if (fgets(buf, sizeof(buf), ifaces->fp6) == NULL) { - if (ferror(ifaces->fp6)) { - *err = 1; - log_error("Error reading IPv6 " - "interface information"); - } else { - *err = 0; - } - return 0; - } - - /* - * Make sure the line is a nice, newline-terminated line. - */ - len = strlen(buf); - if ((len <= 0) || (buf[len-1] != '\n')) { - log_error("Bad line reading IPv6 " - "interface information"); - *err = 1; - return 0; - } - - /* - * Figure out our name. - */ - buf[--len] = '\0'; - p = strrchr(buf, ' '); - if (p == NULL) { - log_error("Bad line reading IPv6 interface " - "information (no space)"); - *err = 1; - return 0; - } - name = p+1; - - /* - * Copy our name into our interface structure. - */ - len = strlen(name); - if (len >= sizeof(info->name)) { - *err = 1; - log_error("IPv6 interface name '%s' too long", name); - return 0; - } - strncpy(info->name, name, sizeof(info->name) - 1); - -#ifdef SKIP_DUMMY_INTERFACES - } while (strncmp(info->name, "dummy", 5) == 0); -#else - } while (0); -#endif - - /* - * Double-check we start with the IPv6 address. - */ - for (i=0; i<32; i++) { - if (!isxdigit(buf[i]) || isupper(buf[i])) { - *err = 1; - log_error("Bad line reading IPv6 interface address " - "for '%s'", name); - return 0; - } - } - - /* - * Load our socket structure. - */ - memset(&addr, 0, sizeof(addr)); - addr.sin6_family = AF_INET6; - for (i=0; i<16; i++) { - unsigned char byte; - static const char hex[] = "0123456789abcdef"; - byte = ((index(hex, buf[i * 2]) - hex) << 4) | - (index(hex, buf[i * 2 + 1]) - hex); - addr.sin6_addr.s6_addr[i] = byte; - } - memcpy(&info->addr, &addr, sizeof(addr)); - - /* - * Get our flags. - */ - memset(&tmp, 0, sizeof(tmp)); - strncpy(tmp.ifr_name, name, sizeof(tmp.ifr_name) - 1); - if (ioctl(ifaces->sock, SIOCGIFFLAGS, &tmp) < 0) { - log_error("Error getting interface flags for '%s'; %m", name); - *err = 1; - return 0; - } - info->flags = tmp.ifr_flags; - - *err = 0; - return 1; -} -#endif /* DHCPv6 */ - -/* - * Retrieve the next interface. - * - * Returns information in the info structure. - * Sets err to 1 if there is an error, otherwise 0. - */ -int -next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { - memset(info, 0, sizeof(struct iface_info)); - if (next_iface4(info, err, ifaces)) { - return 1; - } -#ifdef DHCPv6 - if (!(*err)) { - if (local_family == AF_INET6) - return next_iface6(info, err, ifaces); - } -#endif - return 0; -} - -/* - * End scan of interfaces. - */ -void -end_iface_scan(struct iface_conf_list *ifaces) { - fclose(ifaces->fp); - ifaces->fp = NULL; - close(ifaces->sock); - ifaces->sock = -1; -#ifdef DHCPv6 - if (local_family == AF_INET6) { - fclose(ifaces->fp6); - ifaces->fp6 = NULL; - } -#endif -} #else /* - * BSD support + * BSD/Linux support * ----------- * - * FreeBSD, NetBSD, OpenBSD, and OS X all have the getifaddrs() + * FreeBSD, NetBSD, OpenBSD, OS X/macOS and Linux all have the getifaddrs() * function. * * The getifaddrs() man page describes the use. @@ -806,6 +427,8 @@ begin_iface_scan(struct iface_conf_list *ifaces) { */ int next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { + size_t sa_len = 0; + if (ifaces->next == NULL) { *err = 0; return 0; @@ -818,8 +441,23 @@ next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) { } memset(info, 0, sizeof(struct iface_info)); strncpy(info->name, ifaces->next->ifa_name, sizeof(info->name) - 1); - memcpy(&info->addr, ifaces->next->ifa_addr, - ifaces->next->ifa_addr->sa_len); + memset(&info->addr, 0 , sizeof(info->addr)); + /* + * getifaddrs() can on Linux with some interfaces like PPP or TEQL + * result in a record with no address (ifa_addr). + */ + if (ifaces->next->ifa_addr != NULL) { +/* Linux lacks the sa_len member in struct sockaddr. */ +#if defined(__linux) + if (ifaces->next->ifa_addr->sa_family == AF_INET) + sa_len = sizeof(struct sockaddr_in); + else if (ifaces->next->ifa_addr->sa_family == AF_INET6) + sa_len = sizeof(struct sockaddr_in6); +#else + sa_len = ifaces->next->ifa_addr->sa_len; +#endif + memcpy(&info->addr, ifaces->next->ifa_addr, sa_len); + } info->flags = ifaces->next->ifa_flags; ifaces->next = ifaces->next->ifa_next; *err = 0; |