/**********************************************************
 SixXS - Automatic IPv6 Connectivity Configuration Utility
***********************************************************
 Copyright 2003-2005 SixXS - http://www.sixxs.net
***********************************************************
 common/tun.c - Tunnel Device Handling
***********************************************************
 $Author: jeroen $
 $Id: tun.c,v 1.14 2007-01-11 00:29:18 jeroen Exp $
 $Date: 2007-01-11 00:29:18 $
**********************************************************/

#include "tun.h"
#include "aiccu.h"

/* The tun/tap device HANDLE */
#ifndef _WIN32
int			tun_fd;

/*
 * HAS_IFHEAD -> Tunnel Device produces packets with a tun_pi in the front
 * NEED_IFHEAD -> Tunnel Device produces packets with a tun_pi in the front, but it is not active per default
 */ 

#else
HANDLE			device_handle = INVALID_HANDLE_VALUE;
#define ETH_P_IPV6 0x86dd
#define ETH_ALEN 6
struct ether_header
{
	uint8_t		ether_dhost[ETH_ALEN];	/* destination eth addr */
	uint8_t		ether_shost[ETH_ALEN];	/* source ether addr    */
	uint16_t	ether_type;		/* packet type ID field */
};

/* Tap device constants which we use */
#define TAP_CONTROL_CODE(request,method) CTL_CODE(FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS)
#define TAP_IOCTL_GET_VERSION           TAP_CONTROL_CODE(2, METHOD_BUFFERED)
#define TAP_IOCTL_CONFIG_POINT_TO_POINT TAP_CONTROL_CODE(5, METHOD_BUFFERED)
#define TAP_IOCTL_SET_MEDIA_STATUS      TAP_CONTROL_CODE(6, METHOD_BUFFERED)
#define TAP_REGISTRY_KEY		"SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
#define TAP_ADAPTER_KEY			"SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
#define TAP_DEVICE_DIR			"\\\\.\\Global\\"
#define TAP_WIN32_MIN_MAJOR		8
#define TAP_WIN32_MIN_MINOR		1
#define TAP_COMPONENT_ID1		"tap0801"	/* Original Tun/Tap driver ID */
#define TAP_COMPONENT_ID2		"tap0802"	/* Windows Vista marked 801 as broken, thus use another ID */

#endif

void tun_log(int level, const char *what, const char *fmt, ...);
void tun_log(int level, const char *what, const char *fmt, ...)
{
	char	buf[1024];
	va_list ap;

        /* Clear them just in case */
        memset(buf, 0, sizeof(buf));

	snprintf(buf, sizeof(buf), "[tun-%s] ", what);

	/* Print the log message behind it */
	va_start(ap, fmt);
	vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
	va_end(ap);

	/* Actually Log it */
	dolog(level, buf);
}

static const char reader_name[] = "tundev->tun";
static const char writer_name[] = "tun->tundev";

#ifdef _WIN32
/* Windows doesn't have writev() but does have WSASend */
int writev(SOCKET sock, const struct iovec *vector, DWORD count)
{
	DWORD sent;
	WSASend(sock, (LPWSABUF)vector, count, &sent, 0, NULL, NULL);
	return sent;
}

uint16_t inchksum(const void *data, uint32_t length);
uint16_t inchksum(const void *data, uint32_t length)
{
        register long           sum = 0;
        register const uint16_t *wrd = (const uint16_t *)data;
        register long           slen = (long)length;

        while (slen >= 2)
        {
                sum += *wrd++;
                slen-=2;
        }

        if (slen > 0) sum+=*(const uint8_t *)wrd;

        while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);

        return (uint16_t)sum;
}

uint16_t ipv6_checksum(const struct ip6_hdr *ip6, uint8_t protocol, const void *data, const uint16_t length);
uint16_t ipv6_checksum(const struct ip6_hdr *ip6, uint8_t protocol, const void *data, const uint16_t length)
{
        struct
        {
                uint16_t        length;
                uint16_t        zero1;
                uint8_t         zero2;
                uint8_t         next;
        } pseudo;
        register uint32_t       chksum = 0;

        pseudo.length   = htons(length);
        pseudo.zero1    = 0;
        pseudo.zero2    = 0;
        pseudo.next     = protocol;

        /* IPv6 Source + Dest */
        chksum  = inchksum(&ip6->ip6_src, sizeof(ip6->ip6_src) + sizeof(ip6->ip6_dst));
        chksum += inchksum(&pseudo, sizeof(pseudo));
        chksum += inchksum(data, length);

        /* Wrap in the carries to reduce chksum to 16 bits. */
        chksum  = (chksum >> 16) + (chksum & 0xffff);
        chksum += (chksum >> 16);

        /* Take ones-complement and replace 0 with 0xFFFF. */
        chksum = (uint16_t) ~chksum;
        if (chksum == 0UL) chksum = 0xffffUL;
        return (uint16_t)chksum;
}
#endif

/*
 * Tun -> Socket
 *
 * Needs to be started in a separate thread
 * This gets done by tun_start()
 *
 */
#ifndef _WIN32
void *tun_reader(void *arg);
void *tun_reader(void *arg)
#else
DWORD WINAPI tun_reader(LPVOID arg);
DWORD WINAPI tun_reader(LPVOID arg)
#endif
{
	unsigned char		buf[2048];

	/* The function that actually does something with the buffer */
	struct tun_reader	*tun = (struct tun_reader *)arg;

#ifdef _WIN32
	DWORD			n, lenin;
	OVERLAPPED		overlapped;
	unsigned int		errcount = 0;

	struct nd_sol
	{
		struct ip6_hdr			ip;
		struct icmp6_hdr		icmp;
		struct nd_neighbor_solicit	sol;
	} *solic = (struct nd_sol *)&buf[sizeof(struct ether)];

	struct nd_adv
	{
		struct ip6_hdr			ip;
		struct icmp6_hdr		icmp;
		struct nd_neighbor_advert	adv;
	} advert;

	/* Create an event for overlapped results */
	overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
#else
	ssize_t			n;
#endif

	/* Forever */
	while (true)
	{
#ifndef _WIN32	
		n = read(tun_fd, buf, sizeof(buf));
		if (n <= 0)
		{
			/* Only report issues when the tunnel is actually up and running */
			if (g_aiccu->tunrunning) tun_log(LOG_ERR, reader_name, "Read error on Tun Device: %s (%d)\n", strerror(errno), errno);
			continue;
		}

#if defined(NEED_IFHEAD) || defined(HAS_IFHEAD)
		/* get the tun_pi struct out of there */
		memmove(&buf, &buf[4], n-4);
		n-=4;
#endif

		tun->function((char *)buf, (unsigned int)n);
#else /* Windows */
		overlapped.Offset = 0;
		overlapped.OffsetHigh = 0;

		memset(buf,0,sizeof(buf));
		n = ReadFile(device_handle, buf, sizeof(buf), &lenin, &overlapped);
		if (!n)
		{
			while (!n && GetLastError() == ERROR_IO_PENDING)
			{
				if (WaitForSingleObject(overlapped.hEvent, 20000) == WAIT_OBJECT_0)
				{
					n = GetOverlappedResult(device_handle, &overlapped, &lenin, FALSE);
				}
			}

			if (!n)
			{
				tun_log(LOG_ERR, reader_name, "Error reading from device: %u, %s (%d)\n", GetLastError(), strerror(errno), errno);
				errcount++;
				if (errcount > 10) break;
				continue;
			}
		}

		/* Check for neighbour discovery packets (ICMPv6, ND_SOL, hop=255)
		 * (XXX: doesn't check for a chain, but ND is usually without)
		 */
		if (	solic->ip.ip6_ctlun.ip6_un1.ip6_un1_nxt == IPPROTO_ICMPV6 &&
			solic->icmp.icmp6_type == ND_NEIGHBOR_SOLICIT &&
			solic->ip.ip6_ctlun.ip6_un1.ip6_un1_hlim == 255)
		{
			/* Ignore unspecified ND's as they are used for DAD */
			if (IN6_IS_ADDR_UNSPECIFIED(&solic->ip.ip6_src)) continue;

			/* Create our reply */
			memset(&advert, 0, sizeof(advert));
			advert.ip.ip6_ctlun.ip6_un2_vfc = 6 << 4;
			advert.ip.ip6_ctlun.ip6_un1.ip6_un1_flow = solic->ip.ip6_ctlun.ip6_un1.ip6_un1_flow;
			advert.ip.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(sizeof(advert.icmp) + sizeof(advert.adv));
			advert.ip.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
			advert.ip.ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;

			/* Swap src/dst */
			memcpy(&advert.ip.ip6_src, &solic->sol.nd_ns_target, sizeof(advert.ip.ip6_src));
			memcpy(&advert.ip.ip6_dst, &solic->ip.ip6_src, sizeof(advert.ip.ip6_dst));

			/* ICMP Neighbour Advertisement */
			advert.icmp.icmp6_type = ND_NEIGHBOR_ADVERT;
			advert.icmp.icmp6_code = 0;
			advert.icmp.icmp6_dataun.icmp6_un_data8[0] = 0xe0;
			memcpy(&advert.adv.nd_na_target, &solic->sol.nd_ns_target, sizeof(advert.adv.nd_na_target));
			/* Fake MAC address */
			advert.adv.nd_no_type = 2;
			advert.adv.nd_no_len = 1;
			advert.adv.nd_no_mac[0] = 0x00;
			advert.adv.nd_no_mac[1] = 0xff;
			advert.adv.nd_no_mac[2] = 0x25;
			advert.adv.nd_no_mac[3] = 0x02;
			advert.adv.nd_no_mac[4] = 0x19;
			advert.adv.nd_no_mac[5] = 0x78;

			/* ICMP has a checksum */
			advert.icmp.icmp6_cksum = ipv6_checksum(&advert.ip, IPPROTO_ICMPV6, (uint8_t *)&advert.icmp, sizeof(advert.icmp) + sizeof(advert.adv));

			/* We'll need to answer this back to the TAP device */
			tun_write((char *)&advert, (unsigned int)sizeof(advert));
			continue;
		}
		tun->function((char *)&buf[sizeof(struct ether)], (unsigned int)lenin - sizeof(struct ether));
#endif
	}

	D(dolog(LOG_DEBUG, "TUN Reader stopping\n"));
#ifndef _WIN32
	return NULL;
#else
	return 0;
#endif
}

/* Socket -> Tun */
void tun_write(char *buf, unsigned int length)
{
	unsigned int	c = 0;
#ifndef _WIN32
#ifdef linux
	struct iovec	dat[2];
	struct tun_pi   pi;
	memset(&pi, 0, sizeof(pi));

	pi.proto = htons(ETH_P_IPV6);

	dat[0].iov_base = &pi;
	dat[0].iov_len	= sizeof(pi);
	dat[1].iov_base = buf;
	dat[1].iov_len	= length;

	length += sizeof(pi);

	/* Forward the packet to the kernel */
	c = writev(tun_fd, dat, 2);

#else /* *BSD/Darwin */

	uint32_t	type = htonl(AF_INET6);
	struct iovec	dat[2];

	dat[0].iov_base = (void *)&type;
	dat[0].iov_len	= sizeof(type);
	dat[1].iov_base = buf;
	dat[1].iov_len	= length;

	length += sizeof(type);

	/* Forward the packet to the kernel */
	c = writev(tun_fd, dat, 2);

#endif

	if (c != length)
	{
		tun_log(LOG_ERR, writer_name, "Error while writing to TUN: %u != %u\n", c, length);
	}

#else /* Windows */
	DWORD		n, lenout;
	OVERLAPPED	overlapped;
	unsigned char	mbuf[4096];

	struct ether *eth = (struct ether *)mbuf;

	/* Sent the packet outbound */
	overlapped.Offset = 0;
	overlapped.OffsetHigh = 0;
	overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	memset(mbuf,0,sizeof(mbuf));
	eth->ether_dhost[0] = htons(0x3333);
	eth->ether_dhost[1] = htons(0xff00);
	eth->ether_dhost[2] = htons(0x0002);
	eth->ether_shost[0] = htons(0x00ff);
	eth->ether_shost[1] = htons(0x5342);
	eth->ether_shost[2] = htons(0x2768);
	eth->ether_type = htons(ETH_P_IPV6);
	memcpy(&mbuf[sizeof(*eth)],buf,length);

	n = WriteFile(device_handle, mbuf, sizeof(*eth)+length, &lenout, &overlapped);
	if (!n && GetLastError() == ERROR_IO_PENDING)
	{
		WaitForSingleObject(overlapped.hEvent, INFINITE);
		n = GetOverlappedResult(device_handle, &overlapped, &lenout, FALSE);
	}

	if (!n)
	{
		tun_log(LOG_ERR, writer_name, "Error writing to device: %u, %s (%d)\n", GetLastError(), strerror(errno), errno);
	}
#endif
}

#ifdef _WIN32

struct tap_reg
{
	char			*guid;
	struct tap_reg		*next;
};

struct panel_reg
{
	char			*name;
	char			*guid;
	struct panel_reg	*next;
};

/* Get a working tunnel adapter */
struct tap_reg *get_tap_reg(void)
{
	HKEY		adapter_key;
	LONG		status;
	DWORD		len;
	struct tap_reg	*first = NULL;
	struct tap_reg	*last = NULL;
	int		i = 0;

	status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TAP_ADAPTER_KEY, 0, KEY_READ, &adapter_key);
	if (status != ERROR_SUCCESS)
	{
		dolog(LOG_ERR, "Error opening registry key: %s\n", TAP_ADAPTER_KEY);
		return NULL;
	}

	while (true)
	{
		char	enum_name[256];
		char	unit_string[256];
		HKEY	unit_key;
		char	component_id_string[] = "ComponentId";
		char	component_id[256];
		char	net_cfg_instance_id_string[] = "NetCfgInstanceId";
		char	net_cfg_instance_id[256];
		DWORD	data_type;

		len = sizeof(enum_name);
		status = RegEnumKeyEx(adapter_key, i, enum_name, &len, NULL, NULL, NULL, NULL);
		if (status == ERROR_NO_MORE_ITEMS) break;
		else if (status != ERROR_SUCCESS)
		{
			dolog(LOG_ERR, "Error enumerating registry subkeys of key: %s (t0)\n", TAP_ADAPTER_KEY);
			break;
		}

		snprintf(unit_string, sizeof(unit_string), "%s\\%s", TAP_ADAPTER_KEY, enum_name);
		status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit_string, 0, KEY_READ, &unit_key);
		if (status != ERROR_SUCCESS)
		{
			dolog(LOG_WARNING, "Error opening registry key: %s (t1)\n", unit_string);
		}
		else
		{
			len = sizeof(component_id);
			status = RegQueryValueEx(unit_key, component_id_string,	NULL, &data_type, (LPBYTE)component_id, &len);
			if (status != ERROR_SUCCESS || data_type != REG_SZ)
			{
				dolog(LOG_WARNING, "Error opening registry key: %s\\%s (t2)\n", unit_string, component_id_string);
			}
			else
			{
				len = sizeof(net_cfg_instance_id);
				status = RegQueryValueEx(unit_key, net_cfg_instance_id_string, NULL, &data_type, (LPBYTE)net_cfg_instance_id, &len);
				if (status == ERROR_SUCCESS && data_type == REG_SZ)
				{
					if (	strcmp(component_id, TAP_COMPONENT_ID1) == 0 ||
						strcmp(component_id, TAP_COMPONENT_ID2) == 0)
					{
						struct tap_reg *reg = (struct tap_reg *)malloc(sizeof(*reg));
						memset(reg, 0, sizeof(*reg));
						reg->guid = strdup(net_cfg_instance_id);

						if (!first) first = reg;
						if (last) last->next = reg;
						last = reg;
					}
				}
			}

			RegCloseKey(unit_key);
		}
		i++;
	}

	RegCloseKey(adapter_key);
	return first;
}

void free_tap_reg(struct tap_reg *tap_reg)
{
	struct tap_reg *tr, *tr1;

	for (tr = tap_reg; tr != NULL; tr = tr1)
	{
		tr1 = tr->next;
		free(tr->guid);
		free(tr);
	}
}

void free_panel_reg(struct panel_reg *panel_reg)
{
	struct panel_reg *pr, *pr1;

	for (pr = panel_reg; pr != NULL; pr = pr1)
	{
		pr1 = pr->next;
		free(pr->guid);
		free(pr->name);
		free(pr);
	}
}


/* Collect GUID's and names of all the Connections that are available */
struct panel_reg *get_panel_reg(void)
{
	LONG			status;
	HKEY			network_connections_key;
	DWORD			len;
	struct panel_reg	*first = NULL;
	struct panel_reg	*last = NULL;
	int			i = 0;

	status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TAP_REGISTRY_KEY, 0, KEY_READ, &network_connections_key);

	if (status != ERROR_SUCCESS)
	{
		dolog(LOG_ERR, "Error opening registry key: %s (p0)\n", TAP_REGISTRY_KEY);
		return NULL;
	}

	while (true)
	{
		char		enum_name[256];
		char		connection_string[256];
		HKEY		connection_key;
		char		name_data[256];
		DWORD		name_type;
		const char	name_string[] = "Name";

		len = sizeof(enum_name);
		status = RegEnumKeyEx(network_connections_key, i, enum_name, &len, NULL, NULL, NULL, NULL);
		if (status == ERROR_NO_MORE_ITEMS) break;
		else if (status != ERROR_SUCCESS)
		{
			dolog(LOG_ERR, "Error enumerating registry subkeys of key: %s (p1)\n", TAP_REGISTRY_KEY);
			break;
		}

		i++;

		if (enum_name[0] != '{') continue;

		snprintf(connection_string, sizeof(connection_string), "%s\\%s\\Connection", TAP_REGISTRY_KEY, enum_name);

		status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, connection_string, 0,	KEY_READ, &connection_key);
		if (status != ERROR_SUCCESS)
		{
			dolog(LOG_WARNING, "Error opening registry key: %s (p2)\n", connection_string);
		}
		else
		{
			len = sizeof(name_data);
			status = RegQueryValueEx(connection_key, name_string, NULL, &name_type,	(LPBYTE)name_data, &len);

			if (status != ERROR_SUCCESS || name_type != REG_SZ)
			{
				dolog(LOG_WARNING, "Error opening registry key: %s\\%s\\%s (p3)\n", TAP_REGISTRY_KEY, (LPBYTE)connection_string, name_string);
			}
			else
			{
				struct panel_reg *reg = (struct panel_reg *)malloc(sizeof(*reg));
				memset(reg, 0, sizeof(*reg));
				reg->name = strdup(name_data);
				reg->guid = strdup(enum_name);

				/* link into return list */
				if (!first) first = reg;
				if (last) last->next = reg;
				last = reg;
			}

			RegCloseKey(connection_key);
		}
	}

	RegCloseKey(network_connections_key);

	return first;
}

void tun_list_tap_adapters(void)
{
	int			links;
	struct tap_reg		*tap_reg = get_tap_reg(), *tr, *tr1;
	struct panel_reg	*panel_reg = get_panel_reg(), *pr;

	dolog(LOG_INFO, "Available TAP-WIN32 adapters [name, GUID]:\n");

	/* loop through each TAP-Win32 adapter registry entry */
	for (tr = tap_reg; tr != NULL; tr = tr->next)
	{
		links = 0;

		/* loop through each network connections entry in the control panel */
		for (pr = panel_reg; pr != NULL; pr = pr->next)
		{
			if (strcmp(tr->guid, pr->guid) == 0)
			{
				dolog(LOG_INFO, "'%s' %s\n", pr->name, tr->guid);
				links++;
			}
		}

		if (links > 1)
		{
			dolog(LOG_WARNING, "*** Adapter with GUID %s has %u links from the Network Connections control panel, it should only be 1\n", tr->guid, links);
		}
		else if (links == 0)
		{
			dolog(LOG_WARNING, "[NULL] %s\n", tr->guid);
			dolog(LOG_WARNING, "*** Adapter with GUID %s doesn't have a link from the control panel\n", tr->guid);
		}

		/* check for TAP-Win32 adapter duplicated GUIDs */
		for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next)
		{
			if (tr != tr1 && strcmp(tr->guid, tr1->guid) == 0)
			{
				dolog(LOG_WARNING, "*** Duplicate Adapter GUID %s\n", tr->guid);
			}
		}
	}

	free_tap_reg(tap_reg);
	free_panel_reg(panel_reg);
}

bool tun_fixup_adapters(void)
{
	int			links, count = 0, found = 0;
	struct tap_reg		*tap_reg = get_tap_reg(), *tr = NULL, *tr1 = NULL;
	struct panel_reg	*panel_reg = get_panel_reg(), *pr = NULL, *first = NULL, *prf = NULL;
	bool			ok;

	/* loop through each TAP-Win32 adapter registry entry */
	for (tr = tap_reg; tr != NULL; tr = tr->next)
	{
		links = 0;
		ok = true;

		/* loop through each network connections entry in the control panel */
		for (pr = panel_reg; pr != NULL; pr = pr->next)
		{
			if (strcmp(tr->guid, pr->guid) == 0)
			{
				links++;
				prf = pr;

				/* Is this the one wanted by the user? */
				if (strcasecmp(g_aiccu->ipv6_interface, pr->name) == 0) found++;
			}
		}

		if (links > 1)
		{
			dolog(LOG_WARNING, "*** Adapter with GUID %s has %u links from the Network Connections control panel, it should only be 1\n", tr->guid, links);
			ok = false;
		}
		else if (links == 0)
		{
			dolog(LOG_WARNING, "[NULL] %s\n", tr->guid);
			dolog(LOG_WARNING, "*** Adapter with GUID %s doesn't have a link from the control panel\n", tr->guid);
			ok = false;
		}

		/* check for TAP-Win32 adapter duplicated GUIDs */
		for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next)
		{
			if (tr != tr1 && strcmp(tr->guid, tr1->guid) == 0)
			{
				dolog(LOG_WARNING, "*** Duplicate Adapter GUID %s\n", tr->guid);
				ok = false;
			}
		}

		if (ok)
		{
			count++;
			first = prf;
		}
	}

	ok = false;

	/* When the user didn't configure us correctly and we find a single TAP interface, just rename it */
	if (found == 0 && count == 1 && first)
	{
		dolog(LOG_INFO, "Renaming adapter '%s' to '%s' and using it\n", first->name, g_aiccu->ipv6_interface);
		aiccu_win32_rename_adapter(first->name);
		ok = true;
	}
	else if (found == 1 && count == 1)
	{
		D(dolog(LOG_DEBUG, "Using configured interface %s\n", g_aiccu->ipv6_interface));
		ok = true;
	}
	else
	{
		ok = false;
		dolog(LOG_WARNING, "Found = %u, Count = %u\n", found, count);
	}

	free_tap_reg(tap_reg);
	free_panel_reg(panel_reg);

	return ok;
}

#endif

bool tun_start(struct tun_reader *tun)
{
#ifndef _WIN32
	pthread_t		thread;
#ifdef linux
	struct ifreq		ifr;

	/* Create a new tap device */
	tun_fd = open("/dev/net/tun", O_RDWR);
	if (tun_fd == -1)
	{
		tun_log(LOG_ERR, "start", "Couldn't open device %s: %s (%d)\n", "/dev/net/tun", strerror(errno), errno);
		return false;
	}

	memset(&ifr, 0, sizeof(ifr));
	/* Request a TUN device */
	ifr.ifr_flags = IFF_TUN;
	/* Set the interface name */
	strncpy(ifr.ifr_name, g_aiccu->ipv6_interface, sizeof(ifr.ifr_name));

	if (ioctl(tun_fd, TUNSETIFF, &ifr))
	{
		tun_log(LOG_ERR, "start", "Couldn't set interface name to %s: %s (%d)\n",
			g_aiccu->ipv6_interface, strerror(errno), errno);
		return false;
	}

#else /* *BSD/Darwin */

	char		buf[128];
	unsigned int	i;
	int		mode = IFF_MULTICAST | IFF_POINTOPOINT;

	/* Try the configured interface */
	tun_log(LOG_DEBUG, "start", "Trying Configured TUN/TAP interface %s...\n", g_aiccu->ipv6_interface);
	snprintf(buf, sizeof(buf), "/dev/%s", g_aiccu->ipv6_interface);
	tun_fd = open(buf, O_RDWR);
	if (tun_fd < 0)
	{
		/* Fall back to trying all /dev/tun* devices */
		for (i = 0; i < 256; ++i)
		{
			snprintf(buf, sizeof(buf), "/dev/tun%u", i);
			tun_log(LOG_DEBUG, "start", "Trying TUN/TAP interface %s...\n", &buf[8]);
			tun_fd = open(buf, O_RDWR);
			if (tun_fd >= 0)
			{
				/* Copy over the name of the interface so that configging goes okay */
				if (g_aiccu->ipv6_interface) free(g_aiccu->ipv6_interface);
				snprintf(buf, sizeof(buf), "tun%u", i);
				g_aiccu->ipv6_interface = strdup(buf);
			}
			break;
		}
	}

	if (tun_fd < 0)
	{
		tun_log(LOG_ERR, "start", "Couldn't open device %s or /dev/tun*: %s (%d)\n", g_aiccu->ipv6_interface, strerror(errno), errno);
		return false;
	}

	tun_log(LOG_DEBUG, "start", "Using TUN/TAP interface %s\n", g_aiccu->ipv6_interface);

#ifndef _FREEBSD
#ifndef _DARWIN
#ifndef _AIX
	tun_log(LOG_DEBUG, "start", "Setting TUNSIFMODE for %s\n", g_aiccu->ipv6_interface);
	if (ioctl(tun_fd, TUNSIFMODE, &mode, sizeof(mode)) == -1)
	{
		tun_log(LOG_ERR, "start", "Couldn't set interface %s's TUNSIFMODE to MULTICAST|POINTOPOINT: %s (%d)\n",
			g_aiccu->ipv6_interface, strerror(errno), errno);
		close(tun_fd);
		tun_fd = -1;
		return false;
	}
#endif
#endif
#endif

#ifdef NEED_IFHEAD
	tun_log(LOG_DEBUG, "start", "Setting TUNSIFHEAD for %s\n", g_aiccu->ipv6_interface);
	mode = 1;
	if (ioctl(tun_fd, TUNSIFHEAD, &mode, sizeof(mode)) == -1)
	{
		tun_log(LOG_ERR, "start", "Couldn't set interface %s's TUNSIFHEAD to enabled: %s (%d)\n",
			g_aiccu->ipv6_interface, strerror(errno), errno);
		close(tun_fd);
		tun_fd = -1;
		return false;
	}
#endif

#endif /* linux */


#else /* Windows */

	HKEY		key;
	DWORD		pID;
	HANDLE		h;
	int		i;

	char		adapterid[1024];
	char		tapname[1024];
	DWORD		len;

	if (!tun_fixup_adapters())
	{
		tun_log(LOG_ERR, "start", "TAP-Win32 Adapter not configured properly...\n");
		return false;
	}

	/* Open registry and look for network adapters */
	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TAP_REGISTRY_KEY, 0, KEY_READ, &key))
	{
		tun_log(LOG_ERR, "start", "Could not open the networking registry key\n");
		return false;
	}

	for (i = 0; device_handle == INVALID_HANDLE_VALUE; i++)
	{
		len = sizeof(adapterid);
		if (RegEnumKeyEx(key, i, adapterid, &len, 0, 0, 0, NULL)) break;

		snprintf(tapname, sizeof(tapname), TAP_DEVICE_DIR "%s.tap", adapterid);
		tun_log(LOG_DEBUG, "start", "Trying %s\n", tapname);
		device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0);

		if (device_handle != INVALID_HANDLE_VALUE)
		{
			unsigned long	status, info[3] = {0,0,0};

			/* get driver version info */
			if (DeviceIoControl(device_handle, TAP_IOCTL_GET_VERSION, &info, sizeof(info), &info, sizeof(info), &len, NULL))
			{
				D(tun_log(LOG_DEBUG, "start", "TAP-Win32 Driver Version %d.%d %s", (int)info[0], (int)info[1], info[2] ? "(DEBUG)" : ""));
			}

			if (!(info[0] > TAP_WIN32_MIN_MAJOR || (info[0] == TAP_WIN32_MIN_MAJOR && info[1] >= TAP_WIN32_MIN_MINOR)))
			{
				tun_log(LOG_ERR, "start", "A TAP-Win32 driver is required that is at least version %d.%d -- If you recently upgraded your Tap32 driver, a reboot is probably required at this point to get Windows to see the new driver.", TAP_WIN32_MIN_MAJOR, TAP_WIN32_MIN_MINOR);
				CloseHandle(device_handle);
				device_handle = INVALID_HANDLE_VALUE;
				continue;
			}

			/* Note: we use TAP mode on Windows, not TUN */

			/* Try to mark the device as 'up */
			status = true;
			DeviceIoControl(device_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, sizeof(status), &status, sizeof(status), &len, NULL);
		}
	}

	RegCloseKey(key);

	if (device_handle == INVALID_HANDLE_VALUE)
	{
		tun_log(LOG_ERR, "start", "No working Tap device found!\n");
		return false;
	}

#endif /* _WIN32 */

	/* Launch a thread for reader */
#ifndef _WIN32
	pthread_create(&thread, NULL, tun_reader, (void *)tun);
#else
	h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)tun_reader, tun, 0, &pID);
#endif

	/* We now return, the real tunneling tool can call tun_write() when it wants */

	return true;
}
