Skip to content

Commit

Permalink
windows: fix netmask detection
Browse files Browse the repository at this point in the history
uv_interface_addresses was using the linked list pointed to by
the FirstPrefix member of IP_ADAPTER_ADDRESSES in order to compute the
network prefix / network mask.

This was flawed in several ways:
- FirstPrefix can be NULL, and we would crash.
- On Windows Vista and later, the prefix list includes three IP adapter prefixes
  for each IP address assigned to the adapter. We were assuming a 1:1 mapping
  with the unicast address list.
- Even on Windows versions (i.e. XP) where the prefix list is supposed to have
  one and only one element for each unicast address, the order of the two lists
  is not guaranteed to be the same.

This fix was inspired and adapted from a commit in the Chromium project:
https://codereview.chromium.org/25167002/diff/6001/net/base/net_util_win.cc

See MSDN article for reference:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa366058(v=vs.85).aspx

Excerpt from MSDN below:

In addition, the linked IP_ADAPTER_UNICAST_ADDRESS structures pointed to
by the FirstUnicastAddress member and the linked IP_ADAPTER_PREFIX
structures pointed to by the FirstPrefix member are maintained as separate
internal linked lists by the operating system. As a result, the order of
linked IP_ADAPTER_UNICAST_ADDRESS structures pointed to by the
FirstUnicastAddress member does not have any relationship with the order
of linked IP_ADAPTER_PREFIX structures pointed to by the FirstPrefix member.

On Windows Vista and later, the linked IP_ADAPTER_PREFIX structures pointed to
by the FirstPrefix member include three IP adapter prefixes for each IP address
assigned to the adapter. These include the host IP address prefix, the subnet
IP address prefix, and the subnet broadcast IP address prefix. In addition, for
each adapter there is a multicast address prefix and a broadcast address prefix.
  • Loading branch information
orangemocha authored and saghul committed Oct 13, 2014
1 parent c18205a commit 68ac0a6
Showing 1 changed file with 145 additions and 36 deletions.
181 changes: 145 additions & 36 deletions src/win/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -778,11 +778,76 @@ void uv_free_cpu_info(uv_cpu_info_t* cpu_infos, int count) {
}


static int is_windows_version_or_greater(DWORD os_major,
DWORD os_minor,
WORD service_pack_major,
WORD service_pack_minor) {
OSVERSIONINFOEX osvi;
DWORDLONG condition_mask = 0;
int op = VER_GREATER_EQUAL;

/* Initialize the OSVERSIONINFOEX structure. */
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = os_major;
osvi.dwMinorVersion = os_minor;
osvi.wServicePackMajor = service_pack_major;
osvi.wServicePackMinor = service_pack_minor;

/* Initialize the condition mask. */
VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, op);
VER_SET_CONDITION(condition_mask, VER_MINORVERSION, op);
VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, op);
VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, op);

/* Perform the test. */
return (int) VerifyVersionInfo(
&osvi,
VER_MAJORVERSION | VER_MINORVERSION |
VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR,
condition_mask);
}


static int address_prefix_match(int family,
struct sockaddr* address,
struct sockaddr* prefix_address,
int prefix_len) {
uint8_t* address_data;
uint8_t* prefix_address_data;
int i;

assert(address->sa_family == family);
assert(prefix_address->sa_family == family);

if (family == AF_INET6) {
address_data = (uint8_t*) &(((struct sockaddr_in6 *) address)->sin6_addr);
prefix_address_data =
(uint8_t*) &(((struct sockaddr_in6 *) prefix_address)->sin6_addr);
} else {
address_data = (uint8_t*) &(((struct sockaddr_in *) address)->sin_addr);
prefix_address_data =
(uint8_t*) &(((struct sockaddr_in *) prefix_address)->sin_addr);
}

for (i = 0; i < prefix_len >> 3; i++) {
if (address_data[i] != prefix_address_data[i])
return 0;
}

if (prefix_len % 8)
return prefix_address_data[i] ==
(address_data[i] & (0xff << (8 - prefix_len % 8)));

return 1;
}


int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
int* count_ptr) {
IP_ADAPTER_ADDRESSES* win_address_buf;
ULONG win_address_buf_size;
IP_ADAPTER_ADDRESSES* win_address;
IP_ADAPTER_ADDRESSES* adapter;

uv_interface_address_t* uv_address_buf;
char* name_buf;
Expand All @@ -791,6 +856,23 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,

int count;

int is_vista_or_greater;
ULONG flags;

is_vista_or_greater = is_windows_version_or_greater(6, 0, 0, 0);
if (is_vista_or_greater) {
flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER;
} else {
/* We need at least XP SP1. */
if (!is_windows_version_or_greater(5, 1, 1, 0))
return UV_ENOTSUP;

flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_PREFIX;
}


/* Fetch the size of the adapters reported by windows, and then get the */
/* list itself. */
win_address_buf_size = 0;
Expand All @@ -803,7 +885,7 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
/* ERROR_BUFFER_OVERFLOW, and the required buffer size will be stored in */
/* win_address_buf_size. */
r = GetAdaptersAddresses(AF_UNSPEC,
GAA_FLAG_INCLUDE_PREFIX,
flags,
NULL,
win_address_buf,
&win_address_buf_size);
Expand Down Expand Up @@ -862,25 +944,23 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
count = 0;
uv_address_buf_size = 0;

for (win_address = win_address_buf;
win_address != NULL;
win_address = win_address->Next) {
/* Use IP_ADAPTER_UNICAST_ADDRESS_XP to retain backwards compatibility */
/* with Windows XP */
IP_ADAPTER_UNICAST_ADDRESS_XP* unicast_address;
for (adapter = win_address_buf;
adapter != NULL;
adapter = adapter->Next) {
IP_ADAPTER_UNICAST_ADDRESS* unicast_address;
int name_size;

/* Interfaces that are not 'up' should not be reported. Also skip */
/* interfaces that have no associated unicast address, as to avoid */
/* allocating space for the name for this interface. */
if (win_address->OperStatus != IfOperStatusUp ||
win_address->FirstUnicastAddress == NULL)
if (adapter->OperStatus != IfOperStatusUp ||
adapter->FirstUnicastAddress == NULL)
continue;

/* Compute the size of the interface name. */
name_size = WideCharToMultiByte(CP_UTF8,
0,
win_address->FriendlyName,
adapter->FriendlyName,
-1,
NULL,
0,
Expand All @@ -894,8 +974,8 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,

/* Count the number of addresses associated with this interface, and */
/* compute the size. */
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS_XP*)
win_address->FirstUnicastAddress;
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS*)
adapter->FirstUnicastAddress;
unicast_address != NULL;
unicast_address = unicast_address->Next) {
count++;
Expand All @@ -916,16 +996,15 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
name_buf = (char*) (uv_address_buf + count);

/* Fill out the output buffer. */
for (win_address = win_address_buf;
win_address != NULL;
win_address = win_address->Next) {
IP_ADAPTER_UNICAST_ADDRESS_XP* unicast_address;
IP_ADAPTER_PREFIX* prefix;
for (adapter = win_address_buf;
adapter != NULL;
adapter = adapter->Next) {
IP_ADAPTER_UNICAST_ADDRESS* unicast_address;
int name_size;
size_t max_name_size;

if (win_address->OperStatus != IfOperStatusUp ||
win_address->FirstUnicastAddress == NULL)
if (adapter->OperStatus != IfOperStatusUp ||
adapter->FirstUnicastAddress == NULL)
continue;

/* Convert the interface name to UTF8. */
Expand All @@ -934,7 +1013,7 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
max_name_size = INT_MAX;
name_size = WideCharToMultiByte(CP_UTF8,
0,
win_address->FriendlyName,
adapter->FriendlyName,
-1,
name_buf,
(int) max_name_size,
Expand All @@ -946,47 +1025,77 @@ int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
return uv_translate_sys_error(GetLastError());
}

prefix = win_address->FirstPrefix;

/* Add an uv_interface_address_t element for every unicast address. */
/* Walk the prefix list in tandem with the address list. */
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS_XP*)
win_address->FirstUnicastAddress;
unicast_address != NULL && prefix != NULL;
unicast_address = unicast_address->Next, prefix = prefix->Next) {
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS*)
adapter->FirstUnicastAddress;
unicast_address != NULL;
unicast_address = unicast_address->Next) {
struct sockaddr* sa;
ULONG prefix_len;

sa = unicast_address->Address.lpSockaddr;
prefix_len = prefix->PrefixLength;

/* XP has no OnLinkPrefixLength field. */
if (is_vista_or_greater) {
prefix_len = unicast_address->OnLinkPrefixLength;
} else {
/* Prior to Windows Vista the FirstPrefix pointed to the list with
* single prefix for each IP address assigned to the adapter.
* Order of FirstPrefix does not match order of FirstUnicastAddress,
* so we need to find corresponding prefix.
*/
IP_ADAPTER_PREFIX* prefix;
prefix_len = 0;

for (prefix = adapter->FirstPrefix; prefix; prefix = prefix->Next) {
/* We want the longest matching prefix. */
if (prefix->Address.lpSockaddr->sa_family != sa->sa_family ||
prefix->PrefixLength <= prefix_len)
continue;

if (address_prefix_match(sa->sa_family, sa,
prefix->Address.lpSockaddr, prefix->PrefixLength)) {
prefix_len = prefix->PrefixLength;
}
}

/* If there is no matching prefix information, return a single-host
* subnet mask (e.g. 255.255.255.255 for IPv4).
*/
if (!prefix_len)
prefix_len = (sa->sa_family == AF_INET6) ? 128 : 32;
}

memset(uv_address, 0, sizeof *uv_address);

uv_address->name = name_buf;

if (win_address->PhysicalAddressLength == sizeof(uv_address->phys_addr)) {
if (adapter->PhysicalAddressLength == sizeof(uv_address->phys_addr)) {
memcpy(uv_address->phys_addr,
win_address->PhysicalAddress,
adapter->PhysicalAddress,
sizeof(uv_address->phys_addr));
}

uv_address->is_internal =
(win_address->IfType == IF_TYPE_SOFTWARE_LOOPBACK);
(adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK);

if (sa->sa_family == AF_INET6) {
uv_address->address.address6 = *((struct sockaddr_in6 *) sa);

uv_address->netmask.netmask6.sin6_family = AF_INET6;
memset(uv_address->netmask.netmask6.sin6_addr.s6_addr, 0xff, prefix_len >> 3);
uv_address->netmask.netmask6.sin6_addr.s6_addr[prefix_len >> 3] =
0xff << (8 - prefix_len % 8);
/* This check ensures that we don't write past the size of the data. */
if (prefix_len % 8) {
uv_address->netmask.netmask6.sin6_addr.s6_addr[prefix_len >> 3] =
0xff << (8 - prefix_len % 8);
}

} else {
uv_address->address.address4 = *((struct sockaddr_in *) sa);

uv_address->netmask.netmask4.sin_family = AF_INET;
uv_address->netmask.netmask4.sin_addr.s_addr =
htonl(0xffffffff << (32 - prefix_len));
uv_address->netmask.netmask4.sin_addr.s_addr = (prefix_len > 0) ?
htonl(0xffffffff << (32 - prefix_len)) : 0;
}

uv_address++;
Expand Down

0 comments on commit 68ac0a6

Please sign in to comment.