Extend network-interface-list to return IPv6 and network info

Bug#38218

* src/process.c (Fnetwork_interface_list): Extend argument list to
allow requesting full network info and/or IPv4/IPv6 info.
(network_interface_list) [HAVE_GETIFADDRS]: Use getifaddrs to retrieve
interface IP addresses.

* src/process.h: Update prototype of network_interface_list.

* src/w32.c (g_b_init_get_adapters_addresses): New init flag.
(globals_of_w32): Initialize it.
(GetAdaptersAddresses_Proc): New function typedef.
(get_adapters_addresses): New wrapper function.
(init_winsock): Load htonl and ntohl.
(sys_htonl, sys_ntohl): New wrapper functions.
(network_interface_list): Implement in terms of
get_adapters_addresses.

* nt/inc/sys/socket.h: Add sys_htonl and sys_ntohl prototypes.

* etc/NEWS: Announce IPv4/IPv6 changes in network-interface-list.

* doc/lispref/processes.texi (Misc Network): Document updated arglist
and return values for network-interface-list.
This commit is contained in:
Robert Pluim 2019-11-15 11:11:30 +01:00
parent 5c3d0cf791
commit 650a514e99
7 changed files with 423 additions and 74 deletions

View file

@ -2971,12 +2971,67 @@ non-@code{nil} if that particular network option is supported by
on network connections. Note that they are supported only on some
systems.
@defun network-interface-list
This function returns a list describing the network interfaces
of the machine you are using. The value is an alist whose
elements have the form @code{(@var{name} . @var{address})}.
@var{address} has the same form as the @var{local-address}
and @var{remote-address} arguments to @code{make-network-process}.
@defun network-interface-list &optional full family
This function returns a list describing the network interfaces of the
machine you are using. The value is an alist whose elements have the
form @code{(@var{ifname} . @var{address})}. @var{ifname} is a string
naming the interface, @var{address} has the same form as the
@var{local-address} and @var{remote-address} arguments to
@code{make-network-process}, i.e. a vector of integers. By default
both IPv4 and IPv6 addresses are returned if possible.
Optional argument @var{full} non-@code{nil} means to instead return a
list of one or more elements of the form @w{@code{(@var{ifname}
@var{addr} @var{bcast} @var{netmask})}}. @var{ifname} is a non-unique
string naming the interface. @var{addr}, @var{bcast}, and
@var{netmask} are vectors of integers detailing the IP address,
broadcast address, and network mask.
Optional argument @var{family} specified as symbol @code{ipv4} or
@code{ipv6} restricts the returned information to IPv4 and IPv6
addresses respectively, independently of the value of @var{full}.
Speficying @code{ipv6} when IPv6 support is not available will result
in an error being signaled.
Some examples:
@example
(network-interface-list) @result{}
(("vmnet8" .
[172 16 76 1 0])
("vmnet1" .
[172 16 206 1 0])
("lo0" .
[65152 0 0 0 0 0 0 1 0])
("lo0" .
[0 0 0 0 0 0 0 1 0])
("lo0" .
[127 0 0 1 0]))
@end example
@example
(network-interface-list t) @result{}
(("vmnet8"
[172 16 76 1 0]
[172 16 76 255 0]
[255 255 255 0 0])
("vmnet1"
[172 16 206 1 0]
[172 16 206 255 0]
[255 255 255 0 0])
("lo0"
[65152 0 0 0 0 0 0 1 0]
[65152 0 0 0 65535 65535 65535 65535 0]
[65535 65535 65535 65535 0 0 0 0 0])
("lo0"
[0 0 0 0 0 0 0 1 0]
[0 0 0 0 0 0 0 1 0]
[65535 65535 65535 65535 65535 65535 65535 65535 0])
("lo0"
[127 0 0 1 0]
[127 255 255 255 0]
[255 0 0 0 0]))
@end example
@end defun
@defun network-interface-info ifname
@ -2996,6 +3051,8 @@ The layer 2 address (Ethernet MAC address, for instance).
@item flags
The current flags of the interface.
@end table
Note that this function returns only IPv4 information.
@end defun
@defun format-network-address address &optional omit-port

View file

@ -240,6 +240,11 @@ in addition to the decimal/hex/octal representation. Default nil.
** New function 'network-lookup-address-info'.
This does IPv4 and/or IPv6 address lookups on hostnames.
+++
** 'network-interface-list' can now return IPv4 and IPv6 addresses.
IPv4 and IPv6 addresses are now returned by default if available,
optionally including netmask/broadcast address information.
---
** Control of the threshold for using the 'distant-foreground' color.
The threshold for color distance below which the 'distant-foreground'

View file

@ -228,21 +228,22 @@ host address is a localhost address, or in the same subnet as one
of the local interfaces, this function returns nil. Non-nil
otherwise."
(let ((addresses (network-lookup-address-info host))
(network-interface-list (network-interface-list))
(network-interface-list (network-interface-list t))
(off-net t))
(when
(or (and (functionp nsm-trust-local-network)
(funcall nsm-trust-local-network))
nsm-trust-local-network)
(mapc
(lambda (address)
(lambda (ip)
(mapc
(lambda (iface)
(let ((info (network-interface-info (car iface))))
(lambda (info)
(let ((local-ip (nth 1 info))
(mask (nth 2 info)))
(when
(nsm-network-same-subnet (substring (car info) 0 -1)
(substring (car (cddr info)) 0 -1)
(substring address 0 -1))
(nsm-network-same-subnet (substring local-ip 0 -1)
(substring mask 0 -1)
(substring ip 0 -1))
(setq off-net nil))))
network-interface-list))
addresses))

View file

@ -92,6 +92,8 @@ typedef unsigned short uint16_t;
#define connect sys_connect
#define htons sys_htons
#define ntohs sys_ntohs
#define htonl sys_htonl
#define ntohl sys_ntohl
#define inet_addr sys_inet_addr
#define gethostname sys_gethostname
#define gethostbyname sys_gethostbyname
@ -112,6 +114,8 @@ int sys_bind (int s, const struct sockaddr *addr, int namelen);
int sys_connect (int s, const struct sockaddr *addr, int namelen);
u_short sys_htons (u_short hostshort);
u_short sys_ntohs (u_short netshort);
u_long sys_htonl (u_long hostlong);
u_long sys_ntohl (u_long netlong);
unsigned long sys_inet_addr (const char * cp);
int sys_gethostname (char * name, int namelen);
struct hostent * sys_gethostbyname (const char * name);

View file

@ -4255,73 +4255,86 @@ usage: (make-network-process &rest ARGS) */)
}
#ifdef HAVE_NET_IF_H
#ifdef SIOCGIFCONF
#ifdef HAVE_GETIFADDRS
static Lisp_Object
network_interface_list (void)
network_interface_list (bool full, unsigned short match)
{
struct ifconf ifconf;
struct ifreq *ifreq;
void *buf = NULL;
ptrdiff_t buf_size = 512;
int s;
Lisp_Object res;
ptrdiff_t count;
Lisp_Object res = Qnil;
struct ifaddrs *ifap;
s = socket (AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (s < 0)
if (getifaddrs (&ifap) == -1)
return Qnil;
count = SPECPDL_INDEX ();
record_unwind_protect_int (close_file_unwind, s);
do
for (struct ifaddrs *it = ifap; it != NULL; it = it->ifa_next)
{
buf = xpalloc (buf, &buf_size, 1, INT_MAX, 1);
ifconf.ifc_buf = buf;
ifconf.ifc_len = buf_size;
if (ioctl (s, SIOCGIFCONF, &ifconf))
{
emacs_close (s);
xfree (buf);
return Qnil;
}
}
while (ifconf.ifc_len == buf_size);
int len;
int addr_len;
uint32_t *maskp;
uint32_t *addrp;
Lisp_Object elt = Qnil;
res = unbind_to (count, Qnil);
ifreq = ifconf.ifc_req;
while ((char *) ifreq < (char *) ifconf.ifc_req + ifconf.ifc_len)
{
struct ifreq *ifq = ifreq;
#ifdef HAVE_STRUCT_IFREQ_IFR_ADDR_SA_LEN
#define SIZEOF_IFREQ(sif) \
((sif)->ifr_addr.sa_len < sizeof (struct sockaddr) \
? sizeof (*(sif)) : sizeof ((sif)->ifr_name) + (sif)->ifr_addr.sa_len)
int len = SIZEOF_IFREQ (ifq);
#else
int len = sizeof (*ifreq);
/* BSD can allegedly return interfaces with a NULL address. */
if (it->ifa_addr == NULL)
continue;
if (match && it->ifa_addr->sa_family != match)
continue;
if (it->ifa_addr->sa_family == AF_INET)
{
DECLARE_POINTER_ALIAS (sin1, struct sockaddr_in, it->ifa_netmask);
maskp = (uint32_t *)&sin1->sin_addr;
DECLARE_POINTER_ALIAS (sin2, struct sockaddr_in, it->ifa_addr);
addrp = (uint32_t *)&sin2->sin_addr;
len = sizeof (struct sockaddr_in);
addr_len = 1;
}
#ifdef AF_INET6
else if (it->ifa_addr->sa_family == AF_INET6)
{
DECLARE_POINTER_ALIAS (sin6_1, struct sockaddr_in6, it->ifa_netmask);
maskp = (uint32_t *) &sin6_1->sin6_addr;
DECLARE_POINTER_ALIAS (sin6_2, struct sockaddr_in6, it->ifa_addr);
addrp = (uint32_t *) &sin6_2->sin6_addr;
len = sizeof (struct sockaddr_in6);
addr_len = 4;
}
#endif
char namebuf[sizeof (ifq->ifr_name) + 1];
ifreq = (struct ifreq *) ((char *) ifreq + len);
else
continue;
if (ifq->ifr_addr.sa_family != AF_INET)
continue;
Lisp_Object addr = conv_sockaddr_to_lisp (it->ifa_addr, len);
memcpy (namebuf, ifq->ifr_name, sizeof (ifq->ifr_name));
namebuf[sizeof (ifq->ifr_name)] = 0;
res = Fcons (Fcons (build_string (namebuf),
conv_sockaddr_to_lisp (&ifq->ifr_addr,
sizeof (struct sockaddr))),
res);
if (full)
{
elt = Fcons (conv_sockaddr_to_lisp (it->ifa_netmask, len), elt);
/* There is an it->ifa_broadaddr field, but its contents are
unreliable, so always calculate the broadcast address from
the address and the netmask. */
int i;
uint32_t mask;
for (i = 0; i < addr_len; i++)
{
mask = maskp[i];
maskp[i] = (addrp[i] & mask) | ~mask;
}
elt = Fcons (conv_sockaddr_to_lisp (it->ifa_netmask, len), elt);
elt = Fcons (addr, elt);
}
else
{
elt = addr;
}
res = Fcons (Fcons (build_string (it->ifa_name), elt), res);
}
#ifdef HAVE_FREEIFADDRS
freeifaddrs (ifap);
#endif
xfree (buf);
return res;
}
#endif /* SIOCGIFCONF */
#endif /* HAVE_GETIFADDRS */
#ifdef HAVE_NET_IF_H
#if defined (SIOCGIFADDR) || defined (SIOCGIFHWADDR) || defined (SIOCGIFFLAGS)
struct ifflag_def {
@ -4550,17 +4563,46 @@ network_interface_info (Lisp_Object ifname)
#endif /* defined (HAVE_NET_IF_H) */
DEFUN ("network-interface-list", Fnetwork_interface_list,
Snetwork_interface_list, 0, 0, 0,
Snetwork_interface_list, 0, 2, 0,
doc: /* Return an alist of all network interfaces and their network address.
Each element is a cons, the car of which is a string containing the
interface name, and the cdr is the network address in internal
format; see the description of ADDRESS in `make-network-process'.
Each element is cons of the form (IFNAME . IP) where IFNAME is a
string containing the interface name, and IP is the network address in
internal format; see the description of ADDRESS in
`make-network-process'. The interface name is not guaranteed to be
unique.
Optional parameter FULL non-nil means return all IP address info for
each interface. Each element is then a list of the form
(IFNAME IP BCAST MASK)
where IFNAME is the interface name, IP the IP address,
BCAST the broadcast address, and MASK the network mask.
Optional parameter FAMILY controls the type of addresses to return.
The default of nil means both IPv4 and IPv6, symbol `ipv4' means IPv4
only, symbol `ipv6' means IPv6 only.
See also `network-interface-info', which is limited to IPv4 only.
If the information is not available, return nil. */)
(void)
(Lisp_Object full, Lisp_Object family)
{
#if (defined HAVE_NET_IF_H && defined SIOCGIFCONF) || defined WINDOWSNT
return network_interface_list ();
#if defined HAVE_GETIFADDRS || defined WINDOWSNT
unsigned short match;
bool full_info = false;
if (! NILP (full))
full_info = true;
if (NILP (family))
match = 0;
else if (EQ (family, Qipv4))
match = AF_INET;
#ifdef AF_INET6
else if (EQ (family, Qipv6))
match = AF_INET6;
#endif
else
error ("Unsupported address family");
return network_interface_list (full_info, match);
#else
return Qnil;
#endif

View file

@ -291,7 +291,7 @@ extern void catch_child_signal (void);
extern void restore_nofile_limit (void);
#ifdef WINDOWSNT
extern Lisp_Object network_interface_list (void);
extern Lisp_Object network_interface_list (bool full, unsigned short match);
extern Lisp_Object network_interface_info (Lisp_Object);
#endif

244
src/w32.c
View file

@ -227,6 +227,8 @@ typedef struct _REPARSE_DATA_BUFFER {
#undef connect
#undef htons
#undef ntohs
#undef htonl
#undef ntohl
#undef inet_addr
#undef gethostname
#undef gethostbyname
@ -326,6 +328,7 @@ static BOOL g_b_init_set_file_security_a;
static BOOL g_b_init_set_named_security_info_w;
static BOOL g_b_init_set_named_security_info_a;
static BOOL g_b_init_get_adapters_info;
static BOOL g_b_init_get_adapters_addresses;
static BOOL g_b_init_reg_open_key_ex_w;
static BOOL g_b_init_reg_query_value_ex_w;
static BOOL g_b_init_expand_environment_strings_w;
@ -503,6 +506,12 @@ typedef BOOL (WINAPI *IsValidSecurityDescriptor_Proc) (PSECURITY_DESCRIPTOR);
typedef DWORD (WINAPI *GetAdaptersInfo_Proc) (
PIP_ADAPTER_INFO pAdapterInfo,
PULONG pOutBufLen);
typedef DWORD (WINAPI *GetAdaptersAddresses_Proc) (
ULONG,
ULONG,
PVOID,
PIP_ADAPTER_ADDRESSES,
PULONG);
int (WINAPI *pMultiByteToWideChar)(UINT,DWORD,LPCSTR,int,LPWSTR,int);
int (WINAPI *pWideCharToMultiByte)(UINT,DWORD,LPCWSTR,int,LPSTR,int,LPCSTR,LPBOOL);
@ -1368,6 +1377,31 @@ get_adapters_info (PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen)
return s_pfn_Get_Adapters_Info (pAdapterInfo, pOutBufLen);
}
static DWORD WINAPI
get_adapters_addresses (ULONG family, PIP_ADAPTER_ADDRESSES pAdapterAddresses, PULONG pOutBufLen)
{
static GetAdaptersAddresses_Proc s_pfn_Get_Adapters_Addresses = NULL;
HMODULE hm_iphlpapi = NULL;
if (is_windows_9x () == TRUE)
return ERROR_NOT_SUPPORTED;
if (g_b_init_get_adapters_addresses == 0)
{
g_b_init_get_adapters_addresses = 1;
hm_iphlpapi = LoadLibrary ("Iphlpapi.dll");
if (hm_iphlpapi)
s_pfn_Get_Adapters_Addresses = (GetAdaptersAddresses_Proc)
get_proc_addr (hm_iphlpapi, "GetAdaptersAddresses");
}
if (s_pfn_Get_Adapters_Addresses == NULL)
return ERROR_NOT_SUPPORTED;
ULONG flags = GAA_FLAG_SKIP_ANYCAST
| GAA_FLAG_SKIP_MULTICAST
| GAA_FLAG_SKIP_DNS_SERVER;
return s_pfn_Get_Adapters_Addresses (family, flags, NULL, pAdapterAddresses, pOutBufLen);
}
static LONG WINAPI
reg_open_key_ex_w (HKEY hkey, LPCWSTR lpSubKey, DWORD ulOptions,
REGSAM samDesired, PHKEY phkResult)
@ -7414,6 +7448,8 @@ int (PASCAL *pfn_WSACleanup) (void);
u_short (PASCAL *pfn_htons) (u_short hostshort);
u_short (PASCAL *pfn_ntohs) (u_short netshort);
u_long (PASCAL *pfn_htonl) (u_long hostlong);
u_long (PASCAL *pfn_ntohl) (u_long netlong);
unsigned long (PASCAL *pfn_inet_addr) (const char * cp);
int (PASCAL *pfn_gethostname) (char * name, int namelen);
struct hostent * (PASCAL *pfn_gethostbyname) (const char * name);
@ -7504,6 +7540,8 @@ init_winsock (int load_now)
LOAD_PROC (shutdown);
LOAD_PROC (htons);
LOAD_PROC (ntohs);
LOAD_PROC (htonl);
LOAD_PROC (ntohl);
LOAD_PROC (inet_addr);
LOAD_PROC (gethostname);
LOAD_PROC (gethostbyname);
@ -7884,6 +7922,19 @@ sys_ntohs (u_short netshort)
return (winsock_lib != NULL) ?
pfn_ntohs (netshort) : netshort;
}
u_long
sys_htonl (u_long hostlong)
{
return (winsock_lib != NULL) ?
pfn_htonl (hostlong) : hostlong;
}
u_long
sys_ntohl (u_long netlong)
{
return (winsock_lib != NULL) ?
pfn_ntohl (netlong) : netlong;
}
unsigned long
sys_inet_addr (const char * cp)
@ -9382,9 +9433,197 @@ network_interface_get_info (Lisp_Object ifname)
}
Lisp_Object
network_interface_list (void)
network_interface_list (bool full, unsigned short match)
{
return network_interface_get_info (Qnil);
ULONG ainfo_len = sizeof (IP_ADAPTER_ADDRESSES);
ULONG family = match;
IP_ADAPTER_ADDRESSES *adapter, *ainfo = xmalloc (ainfo_len);
DWORD retval = get_adapters_addresses (family, ainfo, &ainfo_len);
Lisp_Object res = Qnil;
if (retval == ERROR_BUFFER_OVERFLOW)
{
ainfo = xrealloc (ainfo, ainfo_len);
retval = get_adapters_addresses (family, ainfo, &ainfo_len);
}
if (retval != ERROR_SUCCESS)
{
xfree (ainfo);
return res;
}
/* For the below, we need some winsock functions, so make sure
the winsock DLL is loaded. If we cannot successfully load
it, they will have no use of the information we provide,
anyway, so punt. */
if (!winsock_lib && !init_winsock (1))
return res;
int eth_count = 0, tr_count = 0, fddi_count = 0, ppp_count = 0;
int sl_count = 0, wlan_count = 0, lo_count = 0, ifx_count = 0;
int tnl_count = 0;
int if_num;
char namebuf[MAX_ADAPTER_NAME_LENGTH + 4];
static const char *ifmt[] = {
"eth%d", "tr%d", "fddi%d", "ppp%d", "sl%d", "wlan%d",
"lo%d", "ifx%d", "tunnel%d"
};
enum {
NONE = -1,
ETHERNET = 0,
TOKENRING = 1,
FDDI = 2,
PPP = 3,
SLIP = 4,
WLAN = 5,
LOOPBACK = 6,
OTHER_IF = 7,
TUNNEL = 8
} ifmt_idx;
for (adapter = ainfo; adapter; adapter = adapter->Next)
{
/* Present Unix-compatible interface names, instead of the
Windows names, which are really GUIDs not readable by
humans. */
switch (adapter->IfType)
{
case IF_TYPE_ETHERNET_CSMACD:
ifmt_idx = ETHERNET;
if_num = eth_count++;
break;
case IF_TYPE_ISO88025_TOKENRING:
ifmt_idx = TOKENRING;
if_num = tr_count++;
break;
case IF_TYPE_FDDI:
ifmt_idx = FDDI;
if_num = fddi_count++;
break;
case IF_TYPE_PPP:
ifmt_idx = PPP;
if_num = ppp_count++;
break;
case IF_TYPE_SLIP:
ifmt_idx = SLIP;
if_num = sl_count++;
break;
case IF_TYPE_IEEE80211:
ifmt_idx = WLAN;
if_num = wlan_count++;
break;
case IF_TYPE_SOFTWARE_LOOPBACK:
ifmt_idx = LOOPBACK;
if_num = lo_count++;
break;
case IF_TYPE_TUNNEL:
ifmt_idx = TUNNEL;
if_num = tnl_count++;
break;
default:
ifmt_idx = OTHER_IF;
if_num = ifx_count++;
break;
}
sprintf (namebuf, ifmt[ifmt_idx], if_num);
IP_ADAPTER_UNICAST_ADDRESS *address;
for (address = adapter->FirstUnicastAddress; address; address = address->Next)
{
int len;
int addr_len;
uint32_t *maskp;
uint32_t *addrp;
Lisp_Object elt = Qnil;
struct sockaddr *ifa_addr = address->Address.lpSockaddr;
if (ifa_addr == NULL)
continue;
if (match && ifa_addr->sa_family != match)
continue;
struct sockaddr_in ipv4;
#ifdef AF_INET6
struct sockaddr_in6 ipv6;
#endif
struct sockaddr *sin;
if (ifa_addr->sa_family == AF_INET)
{
ipv4.sin_family = AF_INET;
ipv4.sin_port = 0;
DECLARE_POINTER_ALIAS (sin_in, struct sockaddr_in, ifa_addr);
addrp = (uint32_t *)&sin_in->sin_addr;
maskp = (uint32_t *)&ipv4.sin_addr;
sin = (struct sockaddr *)&ipv4;
len = sizeof (struct sockaddr_in);
addr_len = 1;
}
#ifdef AF_INET6
else if (ifa_addr->sa_family == AF_INET6)
{
ipv6.sin6_family = AF_INET6;
ipv6.sin6_port = 0;
DECLARE_POINTER_ALIAS (sin_in6, struct sockaddr_in6, ifa_addr);
addrp = (uint32_t *)&sin_in6->sin6_addr;
maskp = (uint32_t *)&ipv6.sin6_addr;
sin = (struct sockaddr *)&ipv6;
len = sizeof (struct sockaddr_in6);
addr_len = 4;
}
#endif
else
continue;
Lisp_Object addr = conv_sockaddr_to_lisp (ifa_addr, len);
if (full)
{
/* GetAdaptersAddress returns information in network
byte order, so convert from host to network order
when generating the netmask. */
int i;
ULONG numbits = address->OnLinkPrefixLength;
for (i = 0; i < addr_len; i++)
{
if (numbits >= 32)
{
maskp[i] = -1U;
numbits -= 32;
}
else if (numbits)
{
maskp[i] = sys_htonl (-1U << (32 - numbits));
numbits = 0;
}
else
{
maskp[i] = 0;
}
}
elt = Fcons (conv_sockaddr_to_lisp (sin, len), elt);
uint32_t mask;
for (i = 0; i < addr_len; i++)
{
mask = maskp[i];
maskp[i] = (addrp[i] & mask) | ~mask;
}
elt = Fcons (conv_sockaddr_to_lisp (sin, len), elt);
elt = Fcons (addr, elt);
}
else
{
elt = addr;
}
res = Fcons (Fcons (build_string (namebuf), elt), res);
}
}
xfree (ainfo);
return res;
}
Lisp_Object
@ -10099,6 +10338,7 @@ globals_of_w32 (void)
g_b_init_set_named_security_info_w = 0;
g_b_init_set_named_security_info_a = 0;
g_b_init_get_adapters_info = 0;
g_b_init_get_adapters_addresses = 0;
g_b_init_reg_open_key_ex_w = 0;
g_b_init_reg_query_value_ex_w = 0;
g_b_init_expand_environment_strings_w = 0;