summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorFrancis Dupont <fdupont@isc.org>2017-03-08 16:01:38 +0100
committerFrancis Dupont <fdupont@isc.org>2017-03-08 16:01:38 +0100
commite191f1e8de0889c636a922faa0733cf99a39351b (patch)
tree588681c7b1bfeccd8095f15b8b49d4474b8abd9f /common
parent66e800337ea054b227ebcf0bed97a639df613d3e (diff)
downloadisc-dhcp-e191f1e8de0889c636a922faa0733cf99a39351b.tar.gz
Merged #28761 (Linux interface discovery)
Diffstat (limited to 'common')
-rw-r--r--common/discover.c404
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;