Commit e191f1e8 authored by Francis Dupont's avatar Francis Dupont
Browse files

Merged #28761 (Linux interface discovery)

parent 66e80033
......@@ -985,6 +985,16 @@ by Eric Young (eay@cryptsoft.com).
"Run out of memory." on the standard error and exists with status 1.
[ISC-Bugs #32744]
- The linux interface discovery code has been modified to use getifaddrs()
as is done for BSD and OS-X. Prior to this the code would only recognize
the first address on an interface and thereby omit vlans.
Thanks to Jiri Popelka at Redhat, Marius Tomaschewski at Suse, and Wei
Kong at Novell, who all submitted patches.
[ISC-Bugs #28761]
[ISC-Bugs #31992]
[ISC-Bugs #25428]
[ISC-Bugs #31940]
Changes since 4.2.0 (new features)
- If a client renews before 'dhcp-cache-threshold' percent of its lease
......
......@@ -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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment