mirror of
https://gitlab.isc.org/isc-projects/dhcp
synced 2025-08-30 13:57:50 +00:00
Merged #28761 (Linux interface discovery)
This commit is contained in:
10
RELNOTES
10
RELNOTES
@@ -985,6 +985,16 @@ by Eric Young (eay@cryptsoft.com).
|
|||||||
"Run out of memory." on the standard error and exists with status 1.
|
"Run out of memory." on the standard error and exists with status 1.
|
||||||
[ISC-Bugs #32744]
|
[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)
|
Changes since 4.2.0 (new features)
|
||||||
|
|
||||||
- If a client renews before 'dhcp-cache-threshold' percent of its lease
|
- 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;
|
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
|
#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.
|
* function.
|
||||||
*
|
*
|
||||||
* The getifaddrs() man page describes the use.
|
* The getifaddrs() man page describes the use.
|
||||||
@@ -806,6 +427,8 @@ begin_iface_scan(struct iface_conf_list *ifaces) {
|
|||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
|
next_iface(struct iface_info *info, int *err, struct iface_conf_list *ifaces) {
|
||||||
|
size_t sa_len = 0;
|
||||||
|
|
||||||
if (ifaces->next == NULL) {
|
if (ifaces->next == NULL) {
|
||||||
*err = 0;
|
*err = 0;
|
||||||
return 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));
|
memset(info, 0, sizeof(struct iface_info));
|
||||||
strncpy(info->name, ifaces->next->ifa_name, sizeof(info->name) - 1);
|
strncpy(info->name, ifaces->next->ifa_name, sizeof(info->name) - 1);
|
||||||
memcpy(&info->addr, ifaces->next->ifa_addr,
|
memset(&info->addr, 0 , sizeof(info->addr));
|
||||||
ifaces->next->ifa_addr->sa_len);
|
/*
|
||||||
|
* 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;
|
info->flags = ifaces->next->ifa_flags;
|
||||||
ifaces->next = ifaces->next->ifa_next;
|
ifaces->next = ifaces->next->ifa_next;
|
||||||
*err = 0;
|
*err = 0;
|
||||||
|
Reference in New Issue
Block a user