mirror of
https://gitlab.isc.org/isc-projects/dhcp
synced 2025-08-30 05:47:45 +00:00
Merged #28761 (Linux interface discovery)
This commit is contained in:
parent
66e800337e
commit
e191f1e8de
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.
|
||||
[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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user