// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module


// BridgeWin32.c
// Ethernet Bridge Program (Win32)

#ifdef OS_WIN32

#define BRIDGE_C

#include "BridgeWin32.h"

#include "Admin.h"
#include "Connection.h"
#include "SeLowUser.h"

#include "Mayaqua/Cfg.h"
#include "Mayaqua/FileIO.h"
#include "Mayaqua/Internat.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Microsoft.h"
#include "Mayaqua/Object.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/Tick64.h"
#include "Mayaqua/Str.h"

#include <Ws2tcpip.h>

static WP *wp = NULL;
static LIST *eth_list = NULL;

static LOCK *eth_list_lock = NULL;
static bool is_see_mode = false;
static bool is_using_selow = false;
static bool enable_selow = true;

static bool g_bridge_win32_show_all_if = false;

#define	LOAD_DLL_ADDR(name)				\
	{									\
		void *addr = GetProcAddress(h, #name);	\
		Copy(&wp->name, &addr, sizeof(void *));	\
	}

// Set the flag which indicates whether using SeLow
void Win32SetEnableSeLow(bool b)
{
	enable_selow = b;
}

// Get the flag which indicates whether using SeLow
bool Win32GetEnableSeLow()
{
	return enable_selow;
}

// Set the flag which indicates whether enumerating all interfaces
void Win32EthSetShowAllIf(bool b)
{
	g_bridge_win32_show_all_if = b;
}

// Get the flag which indicates whether enumerating all interfaces
bool Win32EthGetShowAllIf()
{
	return g_bridge_win32_show_all_if;
}

// Compare Ethernet device list
int CmpRpcEnumEthVLan(void *p1, void *p2)
{
	RPC_ENUM_ETH_VLAN_ITEM *v1, *v2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	v1 = *((RPC_ENUM_ETH_VLAN_ITEM **)p1);
	v2 = *((RPC_ENUM_ETH_VLAN_ITEM **)p2);
	if (v1 == NULL || v2 == NULL)
	{
		return 0;
	}

	return StrCmpi(v1->DeviceName, v2->DeviceName);
}

// Get the value of MTU (Not supported in Windows)
UINT EthGetMtu(ETH *e)
{
	return 0;
}

// Set the value of MTU (Not supported in Windows)
bool EthSetMtu(ETH *e, UINT mtu)
{
	return false;
}

// Check whether setting MEU value (Not supported in Windows)
bool EthIsChangeMtuSupported(ETH *e)
{
	return false;
}

// Set the state of VLAN tag pass-through 
bool SetVLanEnableStatus(char *title, bool enable)
{
	RPC_ENUM_ETH_VLAN t;
	RPC_ENUM_ETH_VLAN_ITEM *e;
	bool ret = false;
	char key[MAX_SIZE];
	char tcpkey[MAX_SIZE];
	char short_key[MAX_SIZE];
	// Validate arguments
	if (title == NULL)
	{
		return false;
	}

	Zero(&t, sizeof(t));
	if (EnumEthVLanWin32(&t) == false)
	{
		return false;
	}

	e = FindEthVLanItem(&t, title);

	if (e != NULL)
	{
		if (GetClassRegKeyWin32(key, sizeof(key), short_key, sizeof(short_key), e->Guid))
		{
			if (StrCmpi(e->DriverType, "Intel") == 0)
			{
				if (enable)
				{
					MsRegWriteStr(REG_LOCAL_MACHINE, key, "VlanFiltering", "0");
					MsRegWriteStr(REG_LOCAL_MACHINE, key, "TaggingMode", "0");
					MsRegWriteInt(REG_LOCAL_MACHINE, key, "MonitorMode", 1);
					MsRegWriteInt(REG_LOCAL_MACHINE, key, "MonitorModeEnabled", 1);
				}
				else
				{
					if (MsRegReadInt(REG_LOCAL_MACHINE, key, "TaggingMode") == 0)
					{
						MsRegDeleteValue(REG_LOCAL_MACHINE, key, "TaggingMode");
					}

					if (MsRegReadInt(REG_LOCAL_MACHINE, key, "MonitorMode") == 1)
					{
						MsRegDeleteValue(REG_LOCAL_MACHINE, key, "MonitorMode");
					}

					if (MsRegReadInt(REG_LOCAL_MACHINE, key, "MonitorModeEnabled") == 1)
					{
						MsRegDeleteValue(REG_LOCAL_MACHINE, key, "MonitorModeEnabled");
					}
				}

				ret = true;
			}
			else if (StrCmpi(e->DriverType, "Broadcom") == 0)
			{
				if (enable)
				{
					MsRegWriteStr(REG_LOCAL_MACHINE, key, "PreserveVlanInfoInRxPacket", "1");
				}
				else
				{
					MsRegDeleteValue(REG_LOCAL_MACHINE, key, "PreserveVlanInfoInRxPacket");
				}

				ret = true;
			}
			else if (StrCmpi(e->DriverType, "Marvell") == 0)
			{
				if (enable)
				{
					MsRegWriteInt(REG_LOCAL_MACHINE, key, "SkDisableVlanStrip", 1);
				}
				else
				{
					MsRegDeleteValue(REG_LOCAL_MACHINE, key, "SkDisableVlanStrip");
				}

				ret = true;
			}

			Format(tcpkey, sizeof(tcpkey),
				"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s",
				e->Guid);

			if (enable)
			{
				if (MsRegIsValue(REG_LOCAL_MACHINE, tcpkey, "MTU") == false)
				{
					MsRegWriteInt(REG_LOCAL_MACHINE, tcpkey, "MTU", 1500);
				}
			}
			else
			{
				UINT mtu = MsRegReadInt(REG_LOCAL_MACHINE, tcpkey, "MTU");
				if (mtu == 1500)
				{
					MsRegDeleteValue(REG_LOCAL_MACHINE, tcpkey, "MTU");
				}
			}
		}
	}

	FreeRpcEnumEthVLan(&t);

	return ret;
}

// Find Ethernet device
RPC_ENUM_ETH_VLAN_ITEM *FindEthVLanItem(RPC_ENUM_ETH_VLAN *t, char *name)
{
	UINT i;
	// Validate arguments
	if (t == NULL || name == NULL)
	{
		return NULL;
	}

	for (i = 0;i < t->NumItem;i++)
	{
		if (StrCmpi(t->Items[i].DeviceName, name) == 0)
		{
			return &t->Items[i];
		}
	}

	return NULL;
}

// Get the state of VLAN tag pass-through 
void GetVLanEnableStatus(RPC_ENUM_ETH_VLAN_ITEM *e)
{
	char key[MAX_SIZE];
	char short_key[MAX_SIZE];
	char tcpkey[MAX_SIZE];
	// Validate arguments
	if (e == NULL)
	{
		return;
	}

	e->Enabled = false;

	if (e->Support == false)
	{
		return;
	}

	if (GetClassRegKeyWin32(key, sizeof(key), short_key, sizeof(short_key), e->Guid) == false)
	{
		return;
	}

	Format(tcpkey, sizeof(tcpkey),
		"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\%s",
		e->Guid);

	if (StrCmpi(e->DriverType, "Intel") == 0)
	{
		char *VlanFiltering = MsRegReadStr(REG_LOCAL_MACHINE, key, "VlanFiltering");
		UINT MonitorMode = MsRegReadInt(REG_LOCAL_MACHINE, key, "MonitorMode");
		UINT MonitorModeEnabled = MsRegReadInt(REG_LOCAL_MACHINE, key, "MonitorModeEnabled");
		char *TaggingMode = MsRegReadStr(REG_LOCAL_MACHINE, key, "TaggingMode");

		if (StrCmpi(VlanFiltering, "0") == 0 &&
			MonitorMode == 1 &&
			MonitorModeEnabled == 1 &&
			StrCmpi(TaggingMode, "0") == 0)
		{
			e->Enabled = true;
		}

		Free(VlanFiltering);
		Free(TaggingMode);
	}
	else if (StrCmpi(e->DriverType, "Broadcom") == 0)
	{
		char *PreserveVlanInfoInRxPacket = MsRegReadStr(REG_LOCAL_MACHINE,
			key, "PreserveVlanInfoInRxPacket");

		if (StrCmpi(PreserveVlanInfoInRxPacket, "1") == 0)
		{
			e->Enabled = true;
		}

		Free(PreserveVlanInfoInRxPacket);
	}
	else if (StrCmpi(e->DriverType, "Marvell") == 0)
	{
		DWORD SkDisableVlanStrip = MsRegReadInt(REG_LOCAL_MACHINE,
			key, "SkDisableVlanStrip");

		if (SkDisableVlanStrip == 1)
		{
			e->Enabled = true;
		}
	}

	if (MsRegIsValue(REG_LOCAL_MACHINE, tcpkey, "MTU") == false)
	{
		e->Enabled = false;
	}
}

// Get VLAN tag pass-through availability of the device
void GetVLanSupportStatus(RPC_ENUM_ETH_VLAN_ITEM *e)
{
	BUF *b;
	char filename[MAX_SIZE];
	void *wow;
	// Validate arguments
	if (e == NULL)
	{
		return;
	}

	wow = MsDisableWow64FileSystemRedirection();

	// Read the device driver file
	CombinePath(filename, sizeof(filename), MsGetSystem32Dir(), "drivers");
	CombinePath(filename, sizeof(filename), filename, e->DriverName);

	b = ReadDump(filename);

	if (b != NULL)
	{
		char intel1[] = "VlanFiltering";
		char intel2[] = "V\0l\0a\0n\0F\0i\0l\0t\0e\0r\0i\0n\0g";
		char intel3[] = "MonitorMode";
		char intel4[] = "M\0o\0n\0i\0t\0o\0r\0M\0o\0d\0e";
		char intel5[] = "TaggingMode";
		char intel6[] = "T\0a\0g\0g\0i\0n\0g\0M\0o\0d\0e";
		char broadcom1[] = "PreserveVlanInfoInRxPacket";
		char broadcom2[] = "P\0r\0e\0s\0e\0r\0v\0e\0V\0l\0a\0n\0I\0n\0f\0o\0I\0n\0R\0x\0P\0a\0c\0k\0e\0t";
		char marvell1[] = "SkDisableVlanStrip";
		char marvell2[] = "S\0k\0D\0i\0s\0a\0b\0l\0e\0V\0l\0a\0n\0S\0t\0r\0i\0p";
		char *driver_type = "";

		if (SearchBin(b->Buf, 0, b->Size, intel1, sizeof(intel1)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, intel2, sizeof(intel2)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, intel3, sizeof(intel3)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, intel4, sizeof(intel4)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, intel5, sizeof(intel5)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, intel6, sizeof(intel6)) != INFINITE)
		{
			driver_type = "Intel";
		}
		else if (SearchBin(b->Buf, 0, b->Size, broadcom1, sizeof(broadcom1)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, broadcom2, sizeof(broadcom2)) != INFINITE)
		{
			driver_type = "Broadcom";
		}
		else if (SearchBin(b->Buf, 0, b->Size, marvell1, sizeof(marvell1)) != INFINITE
			|| SearchBin(b->Buf, 0, b->Size, marvell2, sizeof(marvell2)) != INFINITE)
		{
			driver_type = "Marvell";
		}

		if (IsEmptyStr(driver_type) == false)
		{
			StrCpy(e->DriverType, sizeof(e->DriverType), driver_type);
			e->Support = true;
		}

		FreeBuf(b);
	}

	MsRestoreWow64FileSystemRedirection(wow);
}

// Get the device instance id from short_key
char *SearchDeviceInstanceIdFromShortKey(char *short_key)
{
	char *ret = NULL;
	TOKEN_LIST *t1;
	// Validate arguments
	if (short_key == NULL)
	{
		return NULL;
	}

	t1 = MsRegEnumKey(REG_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum");

	if (t1 != NULL)
	{
		TOKEN_LIST *t2;
		char tmp[MAX_SIZE];
		UINT i;

		for (i = 0;i < t1->NumTokens;i++)
		{
			Format(tmp, sizeof(tmp), "SYSTEM\\CurrentControlSet\\Enum\\%s", t1->Token[i]);

			t2 = MsRegEnumKey(REG_LOCAL_MACHINE, tmp);

			if (t2 != NULL)
			{
				TOKEN_LIST *t3;
				UINT i;

				for (i = 0;i < t2->NumTokens;i++)
				{
					char tmp2[MAX_SIZE];

					Format(tmp2, sizeof(tmp2), "%s\\%s", tmp, t2->Token[i]);

					t3 = MsRegEnumKey(REG_LOCAL_MACHINE, tmp2);

					if (t3 != NULL)
					{
						UINT i;

						for (i = 0;i < t3->NumTokens;i++)
						{
							char tmp3[MAX_SIZE];
							char *s;

							Format(tmp3, sizeof(tmp3), "%s\\%s", tmp2, t3->Token[i]);

							s = MsRegReadStr(REG_LOCAL_MACHINE, tmp3, "Driver");

							if (s != NULL)
							{
								if (StrCmpi(s, short_key) == 0)
								{
									if (ret != NULL)
									{
										Free(ret);
									}

									ret = CopyStr(tmp3 + StrLen("SYSTEM\\CurrentControlSet\\Enum\\"));
								}

								Free(s);
							}
						}

						FreeToken(t3);
					}
				}

				FreeToken(t2);
			}
		}

		FreeToken(t1);
	}

	return ret;
}

// Get VLAN tag pass-through availability of all devices
bool EnumEthVLanWin32(RPC_ENUM_ETH_VLAN *t)
{
	UINT i;
	LIST *o;
	// Validate arguments
	if (t == NULL)
	{
		return false;
	}

	Zero(t, sizeof(RPC_ENUM_ETH_VLAN));

	if (IsEthSupported() == false)
	{
		return false;
	}

	// Get device list
	Lock(eth_list_lock);

	InitEthAdaptersList();

	o = NewListFast(CmpRpcEnumEthVLan);

	for (i = 0;i < LIST_NUM(eth_list);i++)
	{
		WP_ADAPTER *a = LIST_DATA(eth_list, i);

		if (IsEmptyStr(a->Guid) == false)
		{
			char class_key[MAX_SIZE];
			char short_key[MAX_SIZE];

			if (GetClassRegKeyWin32(class_key, sizeof(class_key),
				short_key, sizeof(short_key), a->Guid))
			{
				char *device_instance_id = MsRegReadStr(REG_LOCAL_MACHINE, class_key, "DeviceInstanceID");

				if (IsEmptyStr(device_instance_id))
				{
					Free(device_instance_id);
					device_instance_id = SearchDeviceInstanceIdFromShortKey(short_key);
				}

				if (IsEmptyStr(device_instance_id) == false)
				{
					char device_key[MAX_SIZE];
					char *service_name;

					Format(device_key, sizeof(device_key), "SYSTEM\\CurrentControlSet\\Enum\\%s",
						device_instance_id);

					service_name = MsRegReadStr(REG_LOCAL_MACHINE, device_key, "Service");
					if (IsEmptyStr(service_name) == false)
					{
						char service_key[MAX_SIZE];
						char *sys;

						Format(service_key, sizeof(service_key),
							"SYSTEM\\CurrentControlSet\\services\\%s",
							service_name);

						sys = MsRegReadStr(REG_LOCAL_MACHINE, service_key, "ImagePath");

						if (IsEmptyStr(sys) == false)
						{
							char sysname[MAX_PATH];

							GetFileNameFromFilePath(sysname, sizeof(sysname), sys);

							Trim(sysname);

							if (EndWith(sysname, ".sys"))
							{
								// device found
								RPC_ENUM_ETH_VLAN_ITEM *e = ZeroMalloc(sizeof(RPC_ENUM_ETH_VLAN_ITEM));

								StrCpy(e->DeviceName, sizeof(e->DeviceName), a->Title);
								StrCpy(e->Guid, sizeof(e->Guid), a->Guid);
								StrCpy(e->DeviceInstanceId, sizeof(e->DeviceInstanceId), device_instance_id);
								StrCpy(e->DriverName, sizeof(e->DriverName), sysname);

								// Get VLAN tag pass-through availability of the device
								GetVLanSupportStatus(e);

								// Get current pass-through setting of the device
								GetVLanEnableStatus(e);

								Insert(o, e);
							}
						}

						Free(sys);
					}

					Free(service_name);
				}

				Free(device_instance_id);
			}
		}
	}

	t->NumItem = LIST_NUM(o);
	t->Items = ZeroMalloc(sizeof(RPC_ENUM_ETH_VLAN_ITEM) * i);

	for (i = 0;i < LIST_NUM(o);i++)
	{
		RPC_ENUM_ETH_VLAN_ITEM *e = LIST_DATA(o, i);

		Copy(&t->Items[i], e, sizeof(RPC_ENUM_ETH_VLAN_ITEM));

		Free(e);
	}

	ReleaseList(o);

	Unlock(eth_list_lock);

	return true;
}

// Get registry key of the network class data by GUID
bool GetClassRegKeyWin32(char *key, UINT key_size, char *short_key, UINT short_key_size, char *guid)
{
	TOKEN_LIST *t;
	bool ret = false;
	UINT i;
	// Validate arguments
	if (key == NULL || short_key == NULL || guid == NULL)
	{
		return false;
	}

	t = MsRegEnumKey(REG_LOCAL_MACHINE,
		"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}");
	if (t == NULL)
	{
		return false;
	}

	for (i = 0;i < t->NumTokens;i++)
	{
		char keyname[MAX_SIZE];
		char *value;

		Format(keyname, sizeof(keyname),
			"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\%s",
			t->Token[i]);

		value = MsRegReadStr(REG_LOCAL_MACHINE, keyname, "NetCfgInstanceId");

		if (StrCmpi(value, guid) == 0)
		{
			ret = true;

			StrCpy(key, key_size, keyname);

			Format(short_key, short_key_size, "{4D36E972-E325-11CE-BFC1-08002BE10318}\\%s",
				t->Token[i]);
		}

		Free(value);
	}

	FreeToken(t);

	return ret;
}

// Send multiple packets
void EthPutPackets(ETH *e, UINT num, void **datas, UINT *sizes)
{
	UINT i, total_size;
	UCHAR *buf;
	UINT write_pointer;
	UINT err = 0;
	// Validate arguments
	if (e == NULL || num == 0 || datas == NULL || sizes == NULL)
	{
		return;
	}
	if (e->HasFatalError)
	{
		return;
	}

	if (e->SuAdapter != NULL)
	{
		bool ok = true;

		// Send packets with SeLow
		for (i = 0;i < num;i++)
		{
			UCHAR *data = datas[i];
			UINT size = sizes[i];

			if (ok)
			{
				// Actually, only enqueuing
				ok = SuPutPacket(e->SuAdapter, data, size);
			}

			if (ok == false)
			{
				// Free memory on write error
				Free(data);
			}
		}

		if (ok)
		{
			// Send all data in queue at once
			ok = SuPutPacket(e->SuAdapter, NULL, 0);
		}

		if (ok == false)
		{
			// Error occurred
			e->HasFatalError = true;
		}

		return;
	}

	if (IsWin32BridgeWithSee() == false)
	{
		if (e->LastSetSingleCpu == 0 || (e->LastSetSingleCpu + 10000) <= Tick64())
		{
			e->LastSetSingleCpu = Tick64();
			MsSetThreadSingleCpu();
		}
	}

	// Calculate buffer size
	total_size = 0;
	for (i = 0;i < num;i++)
	{
		void *data = datas[i];
		UINT size = sizes[i];
		if (data != NULL && size >= 14 && size <= MAX_PACKET_SIZE)
		{
			total_size += size + sizeof(struct dump_bpf_hdr);
		}
	}

	buf = MallocFast(total_size * 100 / 75 + 1600);

	write_pointer = 0;
	// Enqueue
	for (i = 0;i < num;i++)
	{
		void *data = datas[i];
		UINT size = sizes[i];
		if (data != NULL && size >= 14 && size <= MAX_PACKET_SIZE)
		{
			struct dump_bpf_hdr *h;

			h = (struct dump_bpf_hdr *)(buf + write_pointer);
			Zero(h, sizeof(struct dump_bpf_hdr));
			h->caplen = h->len = size;
			write_pointer += sizeof(struct dump_bpf_hdr);
			Copy(buf + write_pointer, data, size);
			write_pointer += size;

			PROBE_DATA2("EthPutPackets", data, size);
		}
		// Free original buffer
		Free(data);
	}

	// Send
	if (total_size != 0)
	{
		err = wp->PacketSendPackets(e->Adapter, buf, total_size, true);
	}

	Free(buf);

	if (err == 0x7FFFFFFF)
	{
		// Critical error (infinite loop) occurred on sending
		e->HasFatalError = true;
	}
}

// Send a packet
void EthPutPacket(ETH *e, void *data, UINT size)
{
	// Validate arguments
	if (e == NULL || data == NULL || size == 0)
	{
		return;
	}

	EthPutPackets(e, 1, &data, &size);
}

// Read next packet
UINT EthGetPacket(ETH *e, void **data)
{
	BLOCK *b;
	bool flag = false;
	// Validate arguments
	if (e == NULL || data == NULL)
	{
		return INFINITE;
	}
	if (e->HasFatalError)
	{
		return INFINITE;
	}

	if (e->SuAdapter != NULL)
	{
		// Read packet with SeLow
		UINT size;
		if (SuGetNextPacket(e->SuAdapter, data, &size) == false)
		{
			// Error occurred
			e->HasFatalError = true;
			return INFINITE;
		}

		return size;
	}

RETRY:
	// Check the presence of the packet in queue
	b = GetNext(e->PacketQueue);
	if (b != NULL)
	{
		UINT size;
		size = b->Size;
		*data = b->Buf;
		Free(b);

		if (e->PacketQueue->num_item == 0)
		{
			e->Empty = true;
		}

		return size;
	}

	if (e->Empty)
	{
		e->Empty = false;
		return 0;
	}

	if (flag == false)
	{
		// Try to get next packet
		PROBE_STR("EthGetPacket: PacketInitPacket");
		wp->PacketInitPacket(e->Packet, e->Buffer, e->BufferSize);
		PROBE_STR("EthGetPacket: PacketReceivePacket");
		if (wp->PacketReceivePacket(e->Adapter, e->Packet, false) == false)
		{
			// Failed
			return INFINITE;
		}
		else
		{
			UCHAR *buf;
			UINT total;
			UINT offset;

			buf = (UCHAR *)e->Packet->Buffer;
			total = e->Packet->ulBytesReceived;
			offset = 0;

			while (offset < total)
			{
				struct bpf_hdr *header;
				UINT packet_size;
				UCHAR *packet_data;

				header = (struct bpf_hdr *)(buf + offset);
				packet_size = header->bh_caplen;
				offset += header->bh_hdrlen;
				packet_data = buf + offset;
				offset = Packet_WORDALIGN(offset + packet_size);

				if (packet_size >= 14)
				{
					UCHAR *tmp;
					BLOCK *b;

					PROBE_DATA2("EthGetPacket: NewBlock", packet_data, packet_size);
					
					tmp = MallocFast(packet_size);

					Copy(tmp, packet_data, packet_size);
					b = NewBlock(tmp, packet_size, 0);
					InsertQueue(e->PacketQueue, b);
				}
			}

			flag = true;
			goto RETRY;
		}
	}

	// No more packet
	return 0;
}

// Get cancel object
CANCEL *EthGetCancel(ETH *e)
{
	// Validate arguments
	if (e == NULL)
	{
		return NULL;
	}

	AddRef(e->Cancel->ref);

	return e->Cancel;
}

// Close adapter
void CloseEth(ETH *e)
{
	BLOCK *b;
	// Validate arguments
	if (e == NULL)
	{
		return;
	}

	ReleaseCancel(e->Cancel);

	if (e->SuAdapter != NULL)
	{
		// Close SeLow adapter
		SuCloseAdapter(e->SuAdapter);
		SuFree(e->Su);
	}
	else
	{
		// Close SEE adapter
		wp->PacketCloseAdapter(e->Adapter);
		wp->PacketFreePacket(e->Packet);
		wp->PacketFreePacket(e->PutPacket);
	}

	while (b = GetNext(e->PacketQueue))
	{
		FreeBlock(b);
	}
	ReleaseQueue(e->PacketQueue);

	Free(e->Name);
	Free(e->Title);
	Free(e->Buffer);

	Free(e);
}

// Search adapter with the name
struct WP_ADAPTER *Win32EthSearch(char *name)
{
	UINT i;
	UINT id;
	char simple_name[MAX_SIZE];
	WP_ADAPTER *ret = NULL;

	id = Win32EthGetNameAndIdFromCombinedName(simple_name, sizeof(simple_name), name);

	if (id != 0)
	{
		UINT num_match = 0;
		// Search with ID when ID is specified
		for (i = 0;i < LIST_NUM(eth_list);i++)
		{
			WP_ADAPTER *a = LIST_DATA(eth_list, i);

			if (a->Id != 0 && a->Id == id)
			{
				ret = a;
				num_match++;
			}
		}

		if (num_match >= 2)
		{
			// If the ID matches to 2 or more devices, search with the name
			for (i = 0;i < LIST_NUM(eth_list);i++)
			{
				WP_ADAPTER *a = LIST_DATA(eth_list, i);

				if (a->Id != 0 && a->Id == id)
				{
					if (StrCmpi(a->Title, name) == 0)
					{
						ret = a;
						break;
					}
				}
			}
		}
	}
	else
	{
		// Search with name when ID is not specified
		for (i = 0;i < LIST_NUM(eth_list);i++)
		{
			WP_ADAPTER *a = LIST_DATA(eth_list, i);

			if (StrCmpi(a->Title, name) == 0)
			{
				ret = a;
				break;
			}
		}
	}

	return ret;
}

// Open adapter
ETH *OpenEth(char *name, bool local, bool tapmode, char *tapaddr)
{
	ETH *ret;
	void *p;

	p = MsDisableWow64FileSystemRedirection();

	ret = OpenEthInternal(name, local, tapmode, tapaddr);

	MsRestoreWow64FileSystemRedirection(p);

	return ret;
}
ETH *OpenEthInternal(char *name, bool local, bool tapmode, char *tapaddr)
{
	WP_ADAPTER *t;
	ETH *e;
	ADAPTER *a = NULL;
	HANDLE h;
	CANCEL *c;
	MS_ADAPTER *ms;
	char name_with_id[MAX_SIZE];
	SU *su = NULL;
	SU_ADAPTER *su_adapter = NULL;
	// Validate arguments
	if (name == NULL || IsEthSupported() == false)
	{
		return NULL;
	}

	if (tapmode)
	{
		// Tap is not supported in Windows
		return NULL;
	}

	Lock(eth_list_lock);

	InitEthAdaptersList();

	t = Win32EthSearch(name);

	if (t == NULL)
	{
		Unlock(eth_list_lock);
		return NULL;
	}

	Debug("OpenEthInternal: %s\n", t->Name);

	if (StartWith(t->Name, SL_ADAPTER_ID_PREFIX))
	{
		// Open with SU
		su = SuInit();
		if (su == NULL)
		{
			// Fail to initialize SU
			Unlock(eth_list_lock);
			return NULL;
		}

		su_adapter = SuOpenAdapter(su, t->Name);

		if (su_adapter == NULL)
		{
			// Fail to get adapter
			SuFree(su);
			Unlock(eth_list_lock);
			return NULL;
		}

		is_using_selow = true;
	}
	else
	{
		// Open with SEE
		a = wp->PacketOpenAdapter(t->Name);
		if (a == NULL)
		{
			Unlock(eth_list_lock);
			return NULL;
		}

		if (IsWin32BridgeWithSee() == false)
		{
			MsSetThreadSingleCpu();
		}

		is_using_selow = false;
	}

	e = ZeroMalloc(sizeof(ETH));
	e->Name = CopyStr(t->Name);

	Win32EthMakeCombinedName(name_with_id, sizeof(name_with_id), t->Title, t->Guid);
	e->Title = CopyStr(name_with_id);

	if (su_adapter != NULL)
	{
		// SU
		e->SuAdapter = su_adapter;
		e->Su = su;

		// Get event object
		h = e->SuAdapter->hEvent;

		c = NewCancelSpecial(h);
		e->Cancel = c;
	}
	else
	{
		// SEE
		e->Adapter = a;

		wp->PacketSetBuff(e->Adapter, BRIDGE_WIN32_ETH_BUFFER);
		wp->PacketSetHwFilter(e->Adapter, local ? 0x0080 : 0x0020);
		wp->PacketSetMode(e->Adapter, PACKET_MODE_CAPT);
		wp->PacketSetReadTimeout(e->Adapter, -1);
		wp->PacketSetNumWrites(e->Adapter, 1);

		if (wp->PacketSetLoopbackBehavior != NULL)
		{
			// Filter loopback packet in kernel
			if (GET_KETA(GetOsType(), 100) >= 3)
			{
				if (MsIsWindows8() == false)
				{
					// Enable for Windows XP, Server 2003 or later
					// But disable for Windows 8 or later
					bool ret = wp->PacketSetLoopbackBehavior(e->Adapter, 1);
					Debug("*** PacketSetLoopbackBehavior: %u\n", ret);

					e->LoopbackBlock = ret;
				}
			}
		}

		// Get event object
		h = wp->PacketGetReadEvent(e->Adapter);

		c = NewCancelSpecial(h);
		e->Cancel = c;

		e->Packet = wp->PacketAllocatePacket();

		e->PutPacket = wp->PacketAllocatePacket();
	}

	e->Buffer = Malloc(BRIDGE_WIN32_ETH_BUFFER);
	e->BufferSize = BRIDGE_WIN32_ETH_BUFFER;

	e->PacketQueue = NewQueue();

	// Get MAC address by GUID
	ms = MsGetAdapterByGuid(t->Guid);
	if (ms != NULL)
	{
		if (ms->AddressSize == 6)
		{
			Copy(e->MacAddress, ms->Address, 6);
		}

		MsFreeAdapter(ms);
	}

	Unlock(eth_list_lock);

	return e;
}

// Generate a combined name from NIC name and GUID
void Win32EthMakeCombinedName(char *dst, UINT dst_size, char *nicname, char *guid)
{
	// Validate arguments
	if (dst == NULL || nicname == NULL || guid == NULL)
	{
		return;
	}

	if (IsEmptyStr(guid) == false)
	{
		Format(dst, dst_size, "%s (ID=%010u)", nicname, Win32EthGenIdFromGuid(guid));
	}
	else
	{
		StrCpy(dst, dst_size, nicname);
	}
}

// Decompose combined name
UINT Win32EthGetNameAndIdFromCombinedName(char *name, UINT name_size, char *str)
{
	UINT ret = 0;
	char id_str[MAX_SIZE];
	UINT len;
	// Validate arguments
	ClearStr(name, name_size);
	StrCpy(name, name_size, str);
	if (name == NULL || str == NULL)
	{
		return 0;
	}

	len = StrLen(str);

	if (len >= 16)
	{
		StrCpy(id_str, sizeof(id_str), str + len - 16);

		if (StartWith(id_str, " (ID="))
		{
			if (EndWith(id_str, ")"))
			{
				char num[MAX_SIZE];

				Zero(num, sizeof(num));
				StrCpy(num, sizeof(num), id_str + 5);

				num[StrLen(num) - 1] = 0;

				ret = ToInt(num);

				if (ret != 0)
				{
					name[len - 16] = 0;
				}
			}
		}
	}

	return ret;
}

// Generate an ID from GUID
UINT Win32EthGenIdFromGuid(char *guid)
{
	char tmp[MAX_SIZE];
	UCHAR hash[SHA1_SIZE];
	UINT i;
	// Validate arguments
	if (guid == NULL)
	{
		return 0;
	}

	StrCpy(tmp, sizeof(tmp), guid);
	Trim(tmp);
	StrUpper(tmp);

	Sha1(hash, tmp, StrLen(tmp));

	Copy(&i, hash, sizeof(UINT));

	i = Endian32(i);

	if (i == 0)
	{
		i = 1;
	}

	return i;
}

// Get Ethernet adapter list
TOKEN_LIST *GetEthList()
{
	UINT v;

	return GetEthListEx(&v, true, false);
}
TOKEN_LIST *GetEthListEx(UINT *total_num_including_hidden, bool enum_normal, bool enum_rawip)
{
	TOKEN_LIST *ret;
	UINT i;
	UINT j;
	UINT dummy_int;
	MS_ADAPTER_LIST *adapter_list;

	if (IsEthSupported() == false)
	{
		return NULL;
	}

	if (enum_normal == false)
	{
		return NullToken();
	}

	if (total_num_including_hidden == NULL)
	{
		total_num_including_hidden = &dummy_int;
	}

	*total_num_including_hidden = 0;

	Lock(eth_list_lock);

	InitEthAdaptersList();

	adapter_list = MsCreateAdapterList();

	ret = ZeroMalloc(sizeof(TOKEN_LIST));
	ret->NumTokens = LIST_NUM(eth_list);
	ret->Token = ZeroMalloc(sizeof(char *) * ret->NumTokens);
	j = 0;
	for (i = 0;i < ret->NumTokens;i++)
	{
		char tmp[MAX_SIZE];
		WP_ADAPTER *a = LIST_DATA(eth_list, i);
		MS_ADAPTER *msa = NULL;
		bool show = true;

		if (Win32EthGetShowAllIf() == false)
		{
			msa = MsGetAdapterByGuidFromList(adapter_list, a->Guid);

			if (InStr(a->Title, "vpn client adapter"))
			{
				// Hide virtual NIC for VPN client
				show = false;
			}

			if (InStr(a->Title, "tunnel adapter"))
			{
				// Hide tunnel adapter
				show = false;
			}

			if (InStr(a->Title, "teredo tunnel"))
			{
				// Hide tunnel adapter
				show = false;
			}

			if (InStr(a->Title, "MS Tunnel Interface"))
			{
				// Hide tunnel adapter
				show = false;
			}

			if (InStr(a->Title, "pseudo-interface"))
			{
				// Hide tunnel adapter
				show = false;
			}
		}

		if (msa != NULL)
		{
			// Hide except physical Ethernet NIC
			if (msa->IsNotEthernetLan)
			{
				show = false;
			}

			MsFreeAdapter(msa);
		}

		Win32EthMakeCombinedName(tmp, sizeof(tmp), a->Title, a->Guid);

		if (show)
		{
			ret->Token[j++] = CopyStr(tmp);

			Debug("%s - %s\n", a->Guid, a->Title);
		}
	}

	*total_num_including_hidden = ret->NumTokens;

	ret->NumTokens = j;

	Unlock(eth_list_lock);

	MsFreeAdapterList(adapter_list);

	return ret;
}

// Compare the name of WP_ADAPTER
int CompareWpAdapter(void *p1, void *p2)
{
	int i;
	WP_ADAPTER *a1, *a2;
	if (p1 == NULL || p2 == NULL)
	{
		return 0;
	}
	a1 = *(WP_ADAPTER **)p1;
	a2 = *(WP_ADAPTER **)p2;
	if (a1 == NULL || a2 == NULL)
	{
		return 0;
	}
	i = StrCmpi(a1->Title, a2->Title);
	return i;
}

// Get whether the SeLow is used 
bool Win32IsUsingSeLow()
{
	return is_using_selow;
}

// Get Ethernet adapter list
LIST *GetEthAdapterList()
{
	void *p;
	LIST *o;

	p = MsDisableWow64FileSystemRedirection();

	o = GetEthAdapterListInternal();

	MsRestoreWow64FileSystemRedirection(p);

	return o;
}
LIST *GetEthAdapterListInternal()
{
	LIST *o;
	LIST *ret;
	UINT size;
	char *buf;
	UINT i, j;
	char *qos_tag = " (Microsoft's Packet Scheduler)";
	SU *su = NULL;
	LIST *su_adapter_list = NULL;

	// Try to use SeLow
	if (enable_selow)
	{
		su = SuInit();
	}

	o = NewListFast(CompareWpAdapter);

	size = 200000;
	buf = ZeroMalloc(size);

	// Try to enumerate with SeLow
	if (su != NULL)
	{
		su_adapter_list = SuGetAdapterList(su);

		if (su_adapter_list == NULL)
		{
			// Fail to enumerate
			SuFree(su);
			su = NULL;
			//WHERE;
			is_using_selow = false;
		}
		else
		{
			//WHERE;
			is_using_selow = true;
		}
	}
	else
	{
		is_using_selow = false;
	}

	if (su_adapter_list != NULL)
	{
		// If 1 or more adapters are enumerated by SeLow, create adapter list object
		UINT i;

		for (i = 0;i < LIST_NUM(su_adapter_list);i++)
		{
			SU_ADAPTER_LIST *t = LIST_DATA(su_adapter_list, i);
			WP_ADAPTER *a = ZeroMalloc(sizeof(WP_ADAPTER));

			StrCpy(a->Name, sizeof(a->Name), t->Name);
			StrCpy(a->Guid, sizeof(a->Guid), t->Guid);
			StrCpy(a->Title, sizeof(a->Title), t->Info.FriendlyName);

			TrimCrlf(a->Title);
			Trim(a->Title);
			TrimCrlf(a->Title);
			Trim(a->Title);

			if (EndWith(a->Title, qos_tag))
			{
				a->Title[StrLen(a->Title) - StrLen(qos_tag)] = 0;
				TrimCrlf(a->Title);
				Trim(a->Title);
				TrimCrlf(a->Title);
				Trim(a->Title);
			}

			Add(o, a);
		}
	}
	else
	{
		// When SeLow is not used, create adapter list with SEE or WinPcap
		if (wp->PacketGetAdapterNames(buf, &size) == false)
		{
			Free(buf);
			return o;
		}

		i = 0;

		if (true)
		{
			// Windows NT
			if (size >= 2 && buf[0] != 0 && buf[1] != 0)
			{
				goto ANSI_STR;
			}

			while (true)
			{
				wchar_t tmp[MAX_SIZE];
				WP_ADAPTER *a;
				UniStrCpy(tmp, sizeof(tmp), L"");

				if (*((wchar_t *)(&buf[i])) == 0)
				{
					i += sizeof(wchar_t);
					break;
				}

				for (;*((wchar_t *)(&buf[i])) != 0;i += sizeof(wchar_t))
				{
					wchar_t str[2];
					str[0] = *((wchar_t *)(&buf[i]));
					str[1] = 0;
					UniStrCat(tmp, sizeof(tmp), str);
				}

				i += sizeof(wchar_t);

				a = ZeroMalloc(sizeof(WP_ADAPTER));
				UniToStr(a->Name, sizeof(a->Name), tmp);

				Add(o, a);
			}
		}
		else
		{
ANSI_STR:
			while (true)
			{
				char tmp[MAX_SIZE];
				WP_ADAPTER *a;
				StrCpy(tmp, sizeof(tmp), "");

				if (*((char *)(&buf[i])) == 0)
				{
					i += sizeof(char);
					break;
				}

				for (;*((char *)(&buf[i])) != 0;i += sizeof(char))
				{
					char str[2];
					str[0] = *((char *)(&buf[i]));
					str[1] = 0;
					StrCat(tmp, sizeof(tmp), str);
				}

				i += sizeof(char);

				a = ZeroMalloc(sizeof(WP_ADAPTER));
				StrCpy(a->Name, sizeof(a->Name), tmp);

				Add(o, a);
			}
		}

		for (j = 0;j < LIST_NUM(o);j++)
		{
			WP_ADAPTER *a = LIST_DATA(o, j);

			StrCpy(a->Title, sizeof(a->Title), &buf[i]);
			i += StrSize(a->Title);

			TrimCrlf(a->Title);
			Trim(a->Title);
			TrimCrlf(a->Title);
			Trim(a->Title);

			if (EndWith(a->Title, qos_tag))
			{
				a->Title[StrLen(a->Title) - StrLen(qos_tag)] = 0;
				TrimCrlf(a->Title);
				Trim(a->Title);
				TrimCrlf(a->Title);
				Trim(a->Title);
			}
		}
	}

	for (j = 0;j < LIST_NUM(o);j++)
	{
		// Extract GUID
		WP_ADAPTER *a = LIST_DATA(o, j);

		if (IsEmptyStr(a->Guid))
		{
			StrCpy(a->Guid, sizeof(a->Guid), a->Name);
			ReplaceStr(a->Guid, sizeof(a->Guid), a->Guid, "\\Device\\SEE_", "");
			ReplaceStr(a->Guid, sizeof(a->Guid), a->Guid, "\\Device\\NPF_", "");
			ReplaceStr(a->Guid, sizeof(a->Guid), a->Guid, "\\Device\\PCD_", "");
		}
	}

	// Sort
	if (su_adapter_list != NULL)
	{
		// Since adapter list made by SeLow is already sorted, don't sort here
		Sort(o);
	}

	ret = NewListFast(CompareWpAdapter);

	for (i = 0;i < LIST_NUM(o);i++)
	{
		WP_ADAPTER *a = LIST_DATA(o, i);
		ADAPTER *ad;
		bool is_ethernet = false;
		bool ok = false;

		if (SearchStrEx(a->Title, "ppp", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "wan", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "dialup", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "pptp", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "telepho", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "modem", 0, false) != INFINITE ||
			SearchStrEx(a->Title, "ras", 0, false) != INFINITE)
		{
			Free(a);
			continue;
		}

		// Determine whether the adapter type is Ethernet
		if (su == NULL)
		{
			// Determine with See
			ad = wp->PacketOpenAdapter(a->Name);
			if (ad != NULL)
			{
				NetType type;
				if (wp->PacketGetNetType(ad, &type))
				{
					if (type.LinkType == 0)
					{
						is_ethernet = true;
					}
				}

				wp->PacketCloseAdapter(ad);
			}
		}
		else
		{
			// In using SeLow, all devices should be Ethernet device
			is_ethernet = true;
		}

		if (is_ethernet)
		{
			// Add only Ethernet device
			char tmp[MAX_SIZE];
			UINT k;

			StrCpy(tmp, sizeof(tmp), a->Title);

			for (k = 0;;k++)
			{
				if (k == 0)
				{
					StrCpy(tmp, sizeof(tmp), a->Title);
				}
				else
				{
					Format(tmp, sizeof(tmp), "%s (%u)", a->Title, k + 1);
				}

				ok = true;
				for (j = 0;j < LIST_NUM(ret);j++)
				{
					WP_ADAPTER *aa = LIST_DATA(ret, j);
					if (StrCmpi(aa->Title, tmp) == 0)
					{
						ok = false;
					}
				}

				if (ok)
				{
					break;
				}
			}

			StrCpy(a->Title, sizeof(a->Title), tmp);
			a->Id = Win32EthGenIdFromGuid(a->Guid);
			Add(ret, a);
		}

		if (ok == false)
		{
			Free(a);
		}
	}

	Free(buf);

	Sort(ret);

	ReleaseList(o);

	if (su != NULL)
	{
		SuFreeAdapterList(su_adapter_list);

		SuFree(su);
	}

	return ret;
}

// Initialize Ethernet adapter list
void InitEthAdaptersList()
{
	if (eth_list != NULL)
	{
		FreeEthAdaptersList();
		eth_list = NULL;
	}
	eth_list = GetEthAdapterList();
}

// Free Ethernet adapter list
void FreeEthAdaptersList()
{
	UINT i;
	if (eth_list == NULL)
	{
		return;
	}
	for (i = 0;i < LIST_NUM(eth_list);i++)
	{
		WP_ADAPTER *a = LIST_DATA(eth_list, i);
		Free(a);
	}
	ReleaseList(eth_list);
	eth_list = NULL;
}

// Is the SU supported
bool Win32EthIsSuSupported()
{
	bool ret = false;
	SU *su = SuInit();

	if (su != NULL)
	{
		ret = true;
	}

	SuFree(su);

	return ret;
}

// Is the Ethernet supported
bool IsEthSupported()
{
	bool ret = IsEthSupportedInner();

	if (ret == false)
	{
		ret = Win32EthIsSuSupported();
	}

	return ret;
}
bool IsEthSupportedInner()
{
	if (wp == NULL)
	{
		return false;
	}

	return wp->Inited;
}

// Is the PCD driver supported in current OS
bool IsPcdSupported()
{
	return !MsIsWindows10();
}

// Save build number of PCD driver
void SavePcdDriverBuild(UINT build)
{
	MsRegWriteInt(REG_LOCAL_MACHINE, BRIDGE_WIN32_PCD_REGKEY, BRIDGE_WIN32_PCD_BUILDVALUE,
		build);
}

// Load build number of PCD driver
UINT LoadPcdDriverBuild()
{
	return MsRegReadInt(REG_LOCAL_MACHINE, BRIDGE_WIN32_PCD_REGKEY, BRIDGE_WIN32_PCD_BUILDVALUE);
}

// Try to install PCD driver
HINSTANCE InstallPcdDriver()
{
	HINSTANCE ret;
	void *p = MsDisableWow64FileSystemRedirection();

	ret = InstallPcdDriverInternal();

	MsRestoreWow64FileSystemRedirection(p);

	return ret;
}
HINSTANCE InstallPcdDriverInternal()
{
	char tmp[MAX_PATH];
	bool install_driver = true;
	HINSTANCE h;
	char *dll_filename;

	// Confirm whether the see.sys is installed in system32\drivers folder
	Format(tmp, sizeof(tmp), "%s\\drivers\\see.sys", MsGetSystem32Dir());

	if (IsFileExists(tmp))
	{
		// If driver file is exist, try to get build number from registry
		if (LoadPcdDriverBuild() >= CEDAR_VERSION_BUILD)
		{
			// Already latest driver is installed
			install_driver = false;
		}
	}

	if (install_driver)
	{
		char *src_filename = BRIDGE_WIN32_PCD_SYS;
		// If need to install the driver, confirm user is administrator
		if (MsIsAdmin() == false)
		{
			// Non administrator can't install driver
			return NULL;
		}

		if (MsIsX64())
		{
			src_filename = BRIDGE_WIN32_PCD_SYS_X64;
		}

		// Copy see.sys
		if (FileCopy(src_filename, tmp) == false)
		{
			return NULL;
		}

		// Save build number
		SavePcdDriverBuild(CEDAR_VERSION_BUILD);
	}

	dll_filename = BRIDGE_WIN32_PCD_DLL;

	if (Is64())
	{
		if (MsIsX64())
		{
			dll_filename = BRIDGE_WIN32_PCD_DLL_X64;
		}
	}

	// Try to load see.dll and initialize
	h = MsLoadLibrary(dll_filename);
	if (h == NULL)
	{
		return NULL;
	}

	return h;
}

// Initialize Ethernet
void InitEth()
{
	HINSTANCE h;
	if (wp != NULL)
	{
		// Already initialized
		return;
	}

	eth_list_lock = NewLock();

	wp = ZeroMalloc(sizeof(WP));

	is_see_mode = false;

	if (IsPcdSupported())
	{
		// PCD is supported in this OS
		h = InstallPcdDriver();
		if (h != NULL)
		{
			// Try to initialize with PCD
			if (InitWpWithLoadLibrary(wp, h) == false)
			{
				Debug("InitEth: SEE Failed.\n");
				FreeLibrary(h);
			}
			else
			{
				Debug("InitEth: SEE Loaded.\n");
				is_see_mode = true;
			}
		}
	}

	if (wp->Inited == false)
	{
		// Try to initialize with Packet.dll of WinPcap
		h = LoadLibrary(BRIDGE_WIN32_PACKET_DLL);
		if (h != NULL)
		{
			if (InitWpWithLoadLibrary(wp, h) == false)
			{
				Debug("InitEth: Packet.dll Failed.\n");
				FreeLibrary(h);
			}
			else
			{
				Debug("InitEth: Packet.dll Loaded.\n");
			}
		}
	}
}

// Get whether local-bridge uses see.sys
bool IsWin32BridgeWithSee()
{
	return is_see_mode;
}

// Initialize WP structure with DLL
bool InitWpWithLoadLibrary(WP *wp, HINSTANCE h)
{
	TOKEN_LIST *o;
	UINT total_num = 0;
	// Validate arguments
	if (wp == NULL || h == NULL)
	{
		return false;
	}
	wp->Inited = true;
	wp->hPacketDll = h;

	LOAD_DLL_ADDR(PacketGetVersion);
	LOAD_DLL_ADDR(PacketGetDriverVersion);
	LOAD_DLL_ADDR(PacketSetMinToCopy);
	LOAD_DLL_ADDR(PacketSetNumWrites);
	LOAD_DLL_ADDR(PacketSetMode);
	LOAD_DLL_ADDR(PacketSetReadTimeout);
	LOAD_DLL_ADDR(PacketSetBpf);
	LOAD_DLL_ADDR(PacketSetSnapLen);
	LOAD_DLL_ADDR(PacketGetStats);
	LOAD_DLL_ADDR(PacketGetStatsEx);
	LOAD_DLL_ADDR(PacketSetBuff);
	LOAD_DLL_ADDR(PacketGetNetType);
	LOAD_DLL_ADDR(PacketOpenAdapter);
	LOAD_DLL_ADDR(PacketSendPacket);
	LOAD_DLL_ADDR(PacketSendPackets);
	LOAD_DLL_ADDR(PacketAllocatePacket);
	LOAD_DLL_ADDR(PacketInitPacket);
	LOAD_DLL_ADDR(PacketFreePacket);
	LOAD_DLL_ADDR(PacketReceivePacket);
	LOAD_DLL_ADDR(PacketSetHwFilter);
	LOAD_DLL_ADDR(PacketGetAdapterNames);
	LOAD_DLL_ADDR(PacketGetNetInfoEx);
	LOAD_DLL_ADDR(PacketRequest);
	LOAD_DLL_ADDR(PacketGetReadEvent);
	LOAD_DLL_ADDR(PacketSetDumpName);
	LOAD_DLL_ADDR(PacketSetDumpLimits);
	LOAD_DLL_ADDR(PacketSetDumpLimits);
	LOAD_DLL_ADDR(PacketIsDumpEnded);
	LOAD_DLL_ADDR(PacketStopDriver);
	LOAD_DLL_ADDR(PacketCloseAdapter);
	LOAD_DLL_ADDR(PacketSetLoopbackBehavior);

	if (wp->PacketSetMinToCopy == NULL ||
		wp->PacketSetNumWrites == NULL ||
		wp->PacketSetMode == NULL ||
		wp->PacketSetReadTimeout == NULL ||
		wp->PacketSetBuff == NULL ||
		wp->PacketGetNetType == NULL ||
		wp->PacketOpenAdapter == NULL ||
		wp->PacketSendPacket == NULL ||
		wp->PacketSendPackets == NULL ||
		wp->PacketAllocatePacket == NULL ||
		wp->PacketInitPacket == NULL ||
		wp->PacketFreePacket == NULL ||
		wp->PacketReceivePacket == NULL ||
		wp->PacketSetHwFilter == NULL ||
		wp->PacketGetAdapterNames == NULL ||
		wp->PacketGetNetInfoEx == NULL ||
		wp->PacketCloseAdapter == NULL)
	{
RELEASE:
		wp->Inited = false;
		wp->hPacketDll = NULL;

		return false;
	}

	o = GetEthListEx(&total_num, true, false);
	if (o == NULL || total_num == 0)
	{
		FreeToken(o);
		goto RELEASE;
	}

	FreeToken(o);

	return true;
}

// Free Ethernet
void FreeEth()
{
	if (wp == NULL)
	{
		// Not initialized
		return;
	}

	// Free adapter list
	FreeEthAdaptersList();

	if (wp->Inited)
	{
		// Free DLL
		FreeLibrary(wp->hPacketDll);
	}

	Free(wp);
	wp = NULL;

	DeleteLock(eth_list_lock);
	eth_list_lock = NULL;
}

// Get network connection name from Ethernet device name
void GetEthNetworkConnectionName(wchar_t *dst, UINT size, char *device_name)
{
	WP_ADAPTER *t;
	char *tmp = NULL, guid[MAX_SIZE];
	wchar_t *ncname = NULL;

	UniStrCpy(dst, size, L"");

	// Validate arguments
	if (device_name == NULL || IsEthSupported() == false)
	{
		return;
	}

	Lock(eth_list_lock);

	InitEthAdaptersList();

	t = Win32EthSearch(device_name);

	if (t == NULL)
	{
		Unlock(eth_list_lock);
		return;
	}

	tmp = CopyStr(t->Name);
	Unlock(eth_list_lock);

	if (IsEmptyStr(t->Guid) == false)
	{
		StrCpy(guid, sizeof(guid), t->Guid);

		Free(tmp);
	}
	else
	{
		ReplaceStr(guid, sizeof(guid), tmp, "\\Device\\SEE_", "");
		Free(tmp);

		ReplaceStr(guid, sizeof(guid), guid, "\\Device\\NPF_", "");
		ReplaceStr(guid, sizeof(guid), guid, "\\Device\\PCD_", "");
	}

	if(guid == NULL)
	{
		return;
	}

	ncname = MsGetNetworkConnectionName(guid);
	if(ncname != NULL)
	{
		UniStrCpy(dst, size, ncname);
	}
	Free(ncname);
}

#endif
