ipc: cache windows lookups to avoid O(n^2) with nested lookups
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
		
							parent
							
								
									f65c82456d
								
							
						
					
					
						commit
						d58df7ed10
					
				@ -10,6 +10,7 @@
 | 
				
			|||||||
#include <stdio.h>
 | 
					#include <stdio.h>
 | 
				
			||||||
#include <stdbool.h>
 | 
					#include <stdbool.h>
 | 
				
			||||||
#include <fcntl.h>
 | 
					#include <fcntl.h>
 | 
				
			||||||
 | 
					#include <hashtable.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static FILE *userspace_interface_file(const char *iface)
 | 
					static FILE *userspace_interface_file(const char *iface)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -113,6 +114,9 @@ err:
 | 
				
			|||||||
	return NULL;
 | 
						return NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool have_cached_interfaces;
 | 
				
			||||||
 | 
					static struct hashtable cached_interfaces;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool userspace_has_wireguard_interface(const char *iface)
 | 
					static bool userspace_has_wireguard_interface(const char *iface)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	char fname[MAX_PATH];
 | 
						char fname[MAX_PATH];
 | 
				
			||||||
@ -120,10 +124,13 @@ static bool userspace_has_wireguard_interface(const char *iface)
 | 
				
			|||||||
	HANDLE find_handle;
 | 
						HANDLE find_handle;
 | 
				
			||||||
	bool ret = false;
 | 
						bool ret = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (have_cached_interfaces)
 | 
				
			||||||
 | 
							return hashtable_find_entry(&cached_interfaces, iface) != NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	snprintf(fname, sizeof(fname), "ProtectedPrefix\\Administrators\\WireGuard\\%s", iface);
 | 
						snprintf(fname, sizeof(fname), "ProtectedPrefix\\Administrators\\WireGuard\\%s", iface);
 | 
				
			||||||
	find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
 | 
						find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
 | 
				
			||||||
	if (find_handle == INVALID_HANDLE_VALUE)
 | 
						if (find_handle == INVALID_HANDLE_VALUE)
 | 
				
			||||||
		return -GetLastError();
 | 
							return -EIO;
 | 
				
			||||||
	do {
 | 
						do {
 | 
				
			||||||
		if (!strcmp(fname, find_data.cFileName)) {
 | 
							if (!strcmp(fname, find_data.cFileName)) {
 | 
				
			||||||
			ret = true;
 | 
								ret = true;
 | 
				
			||||||
@ -139,18 +146,25 @@ static int userspace_get_wireguard_interfaces(struct string_list *list)
 | 
				
			|||||||
	static const char prefix[] = "ProtectedPrefix\\Administrators\\WireGuard\\";
 | 
						static const char prefix[] = "ProtectedPrefix\\Administrators\\WireGuard\\";
 | 
				
			||||||
	WIN32_FIND_DATA find_data;
 | 
						WIN32_FIND_DATA find_data;
 | 
				
			||||||
	HANDLE find_handle;
 | 
						HANDLE find_handle;
 | 
				
			||||||
 | 
						char *iface;
 | 
				
			||||||
	int ret = 0;
 | 
						int ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
 | 
						find_handle = FindFirstFile("\\\\.\\pipe\\*", &find_data);
 | 
				
			||||||
	if (find_handle == INVALID_HANDLE_VALUE)
 | 
						if (find_handle == INVALID_HANDLE_VALUE)
 | 
				
			||||||
		return -GetLastError();
 | 
							return -EIO;
 | 
				
			||||||
	do {
 | 
						do {
 | 
				
			||||||
		if (strncmp(prefix, find_data.cFileName, strlen(prefix)))
 | 
							if (strncmp(prefix, find_data.cFileName, strlen(prefix)))
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		ret = string_list_add(list, find_data.cFileName + strlen(prefix));
 | 
							iface = find_data.cFileName + strlen(prefix);
 | 
				
			||||||
 | 
							ret = string_list_add(list, iface);
 | 
				
			||||||
		if (ret < 0)
 | 
							if (ret < 0)
 | 
				
			||||||
			goto out;
 | 
								goto out;
 | 
				
			||||||
 | 
							if (!hashtable_find_or_insert_entry(&cached_interfaces, iface)) {
 | 
				
			||||||
 | 
								ret = -errno;
 | 
				
			||||||
 | 
								goto out;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	} while (FindNextFile(find_handle, &find_data));
 | 
						} while (FindNextFile(find_handle, &find_data));
 | 
				
			||||||
 | 
						have_cached_interfaces = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
out:
 | 
					out:
 | 
				
			||||||
	FindClose(find_handle);
 | 
						FindClose(find_handle);
 | 
				
			||||||
 | 
				
			|||||||
@ -13,12 +13,17 @@
 | 
				
			|||||||
#include <ddk/ndisguid.h>
 | 
					#include <ddk/ndisguid.h>
 | 
				
			||||||
#include <nci.h>
 | 
					#include <nci.h>
 | 
				
			||||||
#include <wireguard.h>
 | 
					#include <wireguard.h>
 | 
				
			||||||
 | 
					#include <hashtable.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define IPC_SUPPORTS_KERNEL_INTERFACE
 | 
					#define IPC_SUPPORTS_KERNEL_INTERFACE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool have_cached_kernel_interfaces;
 | 
				
			||||||
 | 
					static struct hashtable cached_kernel_interfaces;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int kernel_get_wireguard_interfaces(struct string_list *list)
 | 
					static int kernel_get_wireguard_interfaces(struct string_list *list)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
						HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
				
			||||||
 | 
						bool will_have_cached_kernel_interfaces = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (dev_info == INVALID_HANDLE_VALUE) {
 | 
						if (dev_info == INVALID_HANDLE_VALUE) {
 | 
				
			||||||
		errno = EACCES;
 | 
							errno = EACCES;
 | 
				
			||||||
@ -33,6 +38,7 @@ static int kernel_get_wireguard_interfaces(struct string_list *list)
 | 
				
			|||||||
		HKEY key;
 | 
							HKEY key;
 | 
				
			||||||
		GUID instance_id;
 | 
							GUID instance_id;
 | 
				
			||||||
		char *interface_name;
 | 
							char *interface_name;
 | 
				
			||||||
 | 
							struct hashtable_entry *entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) {
 | 
							if (!SetupDiEnumDeviceInfo(dev_info, i, &dev_info_data)) {
 | 
				
			||||||
			if (GetLastError() == ERROR_NO_MORE_ITEMS)
 | 
								if (GetLastError() == ERROR_NO_MORE_ITEMS)
 | 
				
			||||||
@ -105,7 +111,25 @@ static int kernel_get_wireguard_interfaces(struct string_list *list)
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		string_list_add(list, interface_name);
 | 
							string_list_add(list, interface_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							entry = hashtable_find_or_insert_entry(&cached_kernel_interfaces, interface_name);
 | 
				
			||||||
		free(interface_name);
 | 
							free(interface_name);
 | 
				
			||||||
 | 
							if (!entry)
 | 
				
			||||||
 | 
								goto cleanup_entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, NULL, 0, &buf_len) || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
 | 
				
			||||||
 | 
								goto cleanup_entry;
 | 
				
			||||||
 | 
							entry->value = calloc(sizeof(WCHAR), buf_len);
 | 
				
			||||||
 | 
							if (!entry->value)
 | 
				
			||||||
 | 
								goto cleanup_entry;
 | 
				
			||||||
 | 
							if (!SetupDiGetDeviceInstanceIdW(dev_info, &dev_info_data, entry->value, buf_len, &buf_len)) {
 | 
				
			||||||
 | 
								free(entry->value);
 | 
				
			||||||
 | 
								entry->value = NULL;
 | 
				
			||||||
 | 
								goto cleanup_entry;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cleanup_entry:
 | 
				
			||||||
 | 
							will_have_cached_kernel_interfaces |= entry != NULL && entry->value != NULL;
 | 
				
			||||||
cleanup_buf:
 | 
					cleanup_buf:
 | 
				
			||||||
		free(buf);
 | 
							free(buf);
 | 
				
			||||||
cleanup_key:
 | 
					cleanup_key:
 | 
				
			||||||
@ -113,15 +137,48 @@ cleanup_key:
 | 
				
			|||||||
skip:;
 | 
					skip:;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	SetupDiDestroyDeviceInfoList(dev_info);
 | 
						SetupDiDestroyDeviceInfoList(dev_info);
 | 
				
			||||||
 | 
						have_cached_kernel_interfaces = will_have_cached_kernel_interfaces;
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static HANDLE kernel_interface_handle(const char *iface)
 | 
					static HANDLE kernel_interface_handle(const char *iface)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	HDEVINFO dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
						HDEVINFO dev_info;
 | 
				
			||||||
	WCHAR *interfaces = NULL;
 | 
						WCHAR *interfaces = NULL;
 | 
				
			||||||
	HANDLE handle;
 | 
						HANDLE handle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (have_cached_kernel_interfaces) {
 | 
				
			||||||
 | 
							struct hashtable_entry *entry = hashtable_find_entry(&cached_kernel_interfaces, iface);
 | 
				
			||||||
 | 
							if (entry) {
 | 
				
			||||||
 | 
								DWORD buf_len;
 | 
				
			||||||
 | 
								if (CM_Get_Device_Interface_List_SizeW(
 | 
				
			||||||
 | 
									&buf_len, (GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)entry->value,
 | 
				
			||||||
 | 
									CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS)
 | 
				
			||||||
 | 
									goto err_hash;
 | 
				
			||||||
 | 
								interfaces = calloc(buf_len, sizeof(*interfaces));
 | 
				
			||||||
 | 
								if (!interfaces)
 | 
				
			||||||
 | 
									goto err_hash;
 | 
				
			||||||
 | 
								if (CM_Get_Device_Interface_ListW(
 | 
				
			||||||
 | 
									(GUID *)&GUID_DEVINTERFACE_NET, (DEVINSTID_W)entry->value, interfaces, buf_len,
 | 
				
			||||||
 | 
									CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS || !interfaces[0]) {
 | 
				
			||||||
 | 
									free(interfaces);
 | 
				
			||||||
 | 
									interfaces = NULL;
 | 
				
			||||||
 | 
									goto err_hash;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								handle = CreateFileW(interfaces, GENERIC_READ | GENERIC_WRITE,
 | 
				
			||||||
 | 
										     FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 | 
				
			||||||
 | 
										     OPEN_EXISTING, 0, NULL);
 | 
				
			||||||
 | 
								free(interfaces);
 | 
				
			||||||
 | 
								if (handle == INVALID_HANDLE_VALUE)
 | 
				
			||||||
 | 
									goto err_hash;
 | 
				
			||||||
 | 
								return handle;
 | 
				
			||||||
 | 
					err_hash:
 | 
				
			||||||
 | 
								errno = EACCES;
 | 
				
			||||||
 | 
								return NULL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dev_info = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
				
			||||||
	if (dev_info == INVALID_HANDLE_VALUE)
 | 
						if (dev_info == INVALID_HANDLE_VALUE)
 | 
				
			||||||
		return NULL;
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										61
									
								
								src/wincompat/include/hashtable.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/wincompat/include/hashtable.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					/* SPDX-License-Identifier: GPL-2.0
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef _HASHTABLE_H
 | 
				
			||||||
 | 
					#define _HASHTABLE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum { HASHTABLE_ENTRY_BUCKETS_POW2 = 1 << 10 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct hashtable_entry {
 | 
				
			||||||
 | 
						char *key;
 | 
				
			||||||
 | 
						void *value;
 | 
				
			||||||
 | 
						struct hashtable_entry *next;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct hashtable {
 | 
				
			||||||
 | 
						struct hashtable_entry *entry_buckets[HASHTABLE_ENTRY_BUCKETS_POW2];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static unsigned int hashtable_bucket(const char *str)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						unsigned long hash = 5381;
 | 
				
			||||||
 | 
						char c;
 | 
				
			||||||
 | 
						while ((c = *str++))
 | 
				
			||||||
 | 
							hash = ((hash << 5) + hash) ^ c;
 | 
				
			||||||
 | 
						return hash & (HASHTABLE_ENTRY_BUCKETS_POW2 - 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct hashtable_entry *hashtable_find_entry(struct hashtable *hashtable, const char *key)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct hashtable_entry *entry;
 | 
				
			||||||
 | 
						for (entry = hashtable->entry_buckets[hashtable_bucket(key)]; entry; entry = entry->next) {
 | 
				
			||||||
 | 
							if (!strcmp(entry->key, key))
 | 
				
			||||||
 | 
								return entry;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static struct hashtable_entry *hashtable_find_or_insert_entry(struct hashtable *hashtable, const char *key)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						struct hashtable_entry **entry;
 | 
				
			||||||
 | 
						for (entry = &hashtable->entry_buckets[hashtable_bucket(key)]; *entry; entry = &(*entry)->next) {
 | 
				
			||||||
 | 
							if (!strcmp((*entry)->key, key))
 | 
				
			||||||
 | 
								return *entry;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						*entry = calloc(1, sizeof(**entry));
 | 
				
			||||||
 | 
						if (!*entry)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						(*entry)->key = strdup(key);
 | 
				
			||||||
 | 
						if (!(*entry)->key) {
 | 
				
			||||||
 | 
							free(*entry);
 | 
				
			||||||
 | 
							*entry = NULL;
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return *entry;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
		Reference in New Issue
	
	Block a user