/*
 * (c) BayCom GmbH, http://www.baycom.de, info@baycom.de
 *
 * See the COPYING file for copyright information and
 * how to reach the author.
 *
 */

#include "headers.h"

void int_destroy (struct intnode *intn)
{
	dbg ("Destroying interface %s\n", intn->name);

	/* Resetting the MTU to zero disabled the interface */
	intn->mtu = 0;
}

struct intnode *int_find (unsigned int ifindex)
{
	unsigned int i;	
	for (i = 0; i < g_conf->maxinterfaces; i++) {
		if(g_conf->ints[i].ifindex == ifindex) {
			return g_conf->ints+i;
		}
	}
	return NULL;
}

struct intnode *int_find_name (char *ifname)
{
	unsigned int i;
	for (i = 0; i < g_conf->maxinterfaces; i++) {
		if (!strcmp (ifname, g_conf->ints[i].name) && g_conf->ints[i].mtu != 0) {
			return g_conf->ints+i;
		}
	}
	return NULL;
}


struct intnode *int_find_first (void)
{
	unsigned int i;
	for (i = 0; i < g_conf->maxinterfaces; i++) {
		dbg("int: %d %s\n",i, g_conf->ints[i].name);
		if (g_conf->ints[i].mtu != 0) {
			return g_conf->ints+i;
		}
	}
	return NULL;
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#if ! (defined WIN32 || defined APPLE) || defined __CYGWIN__

/* Initiliaze interfaces */
void update_interfaces (struct intnode *intn)
{
	struct in6_addr addr;

	FILE *file;
	unsigned int prefixlen, scope, flags, ifindex;
	char devname[IFNAMSIZ];

	/* Only update every 5 seconds to avoid rerunning it every packet */
	if (g_conf->maxinterfaces)
		return;

	dbg ("Updating Interfaces\n");

	/* Get link local addresses from /proc/net/if_inet6 */
	file = fopen ("/proc/net/if_inet6", "r");

	/* We can live without it though */
	if (!file) {
		err ("Cannot open /proc/net/if_inet6\n");
		return;
	}

	char buf[255];
	/* Format "fe80000000000000029027fffe24bbab 02 0a 20 80     eth0" */
	while (fgets (buf, sizeof (buf), file)) {
		if (21 != sscanf (buf, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx %x %x %x %x %s", &addr.s6_addr[0], &addr.s6_addr[1], &addr.s6_addr[2], &addr.s6_addr[3], &addr.s6_addr[4], &addr.s6_addr[5], &addr.s6_addr[6], &addr.s6_addr[7], &addr.s6_addr[8], &addr.s6_addr[9], &addr.s6_addr[10], &addr.s6_addr[11], &addr.s6_addr[12], &addr.s6_addr[13], &addr.s6_addr[14], &addr.s6_addr[15], &ifindex, &prefixlen, &scope, &flags, devname)) {

			warn ("/proc/net/if_inet6 in wrong format!\n");
			continue;
		}
		if (!IN6_IS_ADDR_LINKLOCAL (&addr) && (IN6_IS_ADDR_UNSPECIFIED (&addr) || IN6_IS_ADDR_LOOPBACK (&addr) || IN6_IS_ADDR_MULTICAST (&addr))) {
			continue;
		}

		if((intn=int_find(ifindex))==NULL) {
			g_conf->ints=(struct intnode*)realloc(g_conf->ints, sizeof(struct intnode)*(++g_conf->maxinterfaces));
			if (!g_conf->ints) {
				err ("Cannot get memory for interface structures.\n");
			}
			intn=g_conf->ints+g_conf->maxinterfaces-1;
			memset(intn, 0, sizeof(struct intnode));
		}
#ifdef WIN32
		// Ugly WINXP workaround
		if(scope==0x20 && flags==0x80) {
			intn->mtu=1480;
		} else {
			intn->mtu=0;
		}
#else
		intn->ifindex = ifindex;
		strcpy(intn->name, devname);

		struct ifreq ifreq;
		int sock;
		sock = socket (AF_INET6, SOCK_DGRAM, 0);
		if (sock < 0) {
			err ("Cannot get socket for setup\n");
		}

		memcpy (&ifreq.ifr_name, &intn->name, sizeof (ifreq.ifr_name));
		/* Get the MTU size of this interface */
		/* We will use that for fragmentation */
		if (ioctl (sock, SIOCGIFMTU, &ifreq) != 0) {
			warn ("Cannot get MTU size for %s index %d: %s\n", intn->name, intn->ifindex, strerror (errno));
		}
		intn->mtu = ifreq.ifr_mtu;

		/* Get hardware address + type */
		if (ioctl (sock, SIOCGIFHWADDR, &ifreq) != 0) {
			warn ("Cannot get hardware address for %s, interface index %d : %s\n", intn->name, intn->ifindex, strerror (errno));
		}
		intn->hwaddr = ifreq.ifr_hwaddr;
		close (sock);
#endif
		/* Link Local IPv6 address ? */
		if (IN6_IS_ADDR_LINKLOCAL (&addr)) {
			/* Update the linklocal address */
			intn->linklocal = addr;
		} else {
			intn->global = addr;
		}

		dbg ("Available interface %s index %u hardware %s/%u MTU %d\n", intn->name, intn->ifindex, (intn->hwaddr.sa_family == ARPHRD_ETHER ? "Ethernet" : (intn->hwaddr.sa_family == ARPHRD_SIT ? "sit" : "Unknown")), intn->hwaddr.sa_family, intn->mtu);
	}

	fclose (file);
}
#endif 
#if defined WIN32 && ! defined __CYGWIN__

unsigned int if_nametoindex (const char *ifname)
{
	unsigned int ifindex;
	for (ifindex = 0; ifindex < g_conf->maxinterfaces; ifindex++) {
		if (!strcmp (ifname, g_conf->ints[ifindex].name) && g_conf->ints[ifindex].mtu != 0) {
			return g_conf->ints[ifindex].ifindex;
		}
	}
	return 0;
}

void update_interfaces (struct intnode *intn)
{

	/* Declare and initialize variables */

	DWORD dwRetVal = 0;

	int i = 0;

	// Set the flags to pass to GetAdaptersAddresses
	ULONG flags = GAA_FLAG_INCLUDE_PREFIX;

	// default to unspecified address family (both)
	ULONG family = AF_INET6;

	PIP_ADAPTER_ADDRESSES pAddresses = NULL;
	ULONG outBufLen = 0;

	PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL;
	PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL;

	outBufLen = sizeof (IP_ADAPTER_ADDRESSES);
	pAddresses = (IP_ADAPTER_ADDRESSES *) malloc (outBufLen);
	if (pAddresses == NULL) {
		printf ("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");
		exit (1);
	}
	// Make an initial call to GetAdaptersAddresses to get the 
	// size needed into the outBufLen variable
	if (GetAdaptersAddresses (family, flags, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
		free (pAddresses);
		pAddresses = (IP_ADAPTER_ADDRESSES *) malloc (outBufLen);
	}

	if (pAddresses == NULL) {
		printf ("Memory allocation failed for IP_ADAPTER_ADDRESSES struct\n");
		exit (1);
	}

	dwRetVal = GetAdaptersAddresses (family, flags, NULL, pAddresses, &outBufLen);

	if (dwRetVal == NO_ERROR) {
		// If successful, output some information from the data we received
		pCurrAddresses = pAddresses;
		g_conf->maxinterfaces=0;
		
		while (pCurrAddresses) {
			
			if( /* pCurrAddresses->Flags & IP_ADAPTER_IPV6_ENABLED && */ (pCurrAddresses->IfType == IF_TYPE_ETHERNET_CSMACD || pCurrAddresses->IfType == IF_TYPE_IEEE80211) && pCurrAddresses->OperStatus == IfOperStatusUp ) {
				g_conf->ints=(struct intnode*)realloc(g_conf->ints, sizeof(struct intnode)*(g_conf->maxinterfaces+1));
				if (!g_conf->ints) {
					err ("update_interfaces: out of memory\n");
				}
				intn=g_conf->ints+g_conf->maxinterfaces;
				memset(intn, 0, sizeof(struct intnode));
				
#ifndef __MINGW32__
				printf ("Interface: %s (%wS)\n", pCurrAddresses->AdapterName, pCurrAddresses->Description);
				dbg ("\tFriendly name: %wS\n", pCurrAddresses->FriendlyName);
#else
				printf ("Interface: %s (%ls)\n", pCurrAddresses->AdapterName, pCurrAddresses->Description);
				dbg ("\tFriendly name: %ls\n", pCurrAddresses->FriendlyName);
#endif
				dbg ("\tFlags: %x\n", pCurrAddresses->Flags);
				dbg ("\tIfType: %ld\n", pCurrAddresses->IfType);
				dbg ("\tOperStatus: %ld\n", pCurrAddresses->OperStatus);
				dbg ("\tMtu: %lu\n", pCurrAddresses->Mtu);
				dbg ("\tIpv6IfIndex (IPv6 interface): %u\n", pCurrAddresses->Ipv6IfIndex);
				
				strncpy(intn->name, pCurrAddresses->AdapterName, IFNAMSIZ-1);
			
				intn->mtu =  pCurrAddresses->Mtu;
				intn->ifindex= pCurrAddresses->Ipv6IfIndex;

				pUnicast = pCurrAddresses->FirstUnicastAddress;
				if (pUnicast != NULL) {
					for (i = 0; pUnicast != NULL; i++) {
						char host[80];
						inet_ntop (AF_INET6, ((struct sockaddr_in6 *)(pUnicast->Address.lpSockaddr))->sin6_addr.s6_addr, host, sizeof(host));
						dbg("\tIP:%s LL:%d\n",host, IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)(pUnicast->Address.lpSockaddr))->sin6_addr));
						if(IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)(pUnicast->Address.lpSockaddr))->sin6_addr)) {
							intn->linklocal=((struct sockaddr_in6 *)(pUnicast->Address.lpSockaddr))->sin6_addr;
						} else {
							intn->global=((struct sockaddr_in6 *)(pUnicast->Address.lpSockaddr))->sin6_addr;
						}
						pUnicast = pUnicast->Next;
					}
					dbg ("\tNumber of Unicast Addresses: %d\n", i);
				} 
#ifdef DEBUG
				if (pCurrAddresses->PhysicalAddressLength != 0) {
					dbg ("\tPhysical address: ");
					for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) {
						if (i == (pCurrAddresses->PhysicalAddressLength - 1))
							printf ("%.2X\n", (int) pCurrAddresses->PhysicalAddress[i]);
						else
							printf ("%.2X:", (int) pCurrAddresses->PhysicalAddress[i]);
					}
				}
#endif				
				g_conf->maxinterfaces++;
			}
			pCurrAddresses = pCurrAddresses->Next;
		}
	} 
	
	free (pAddresses);
}

#endif
#ifdef APPLE
void update_interfaces (struct intnode *intn)
{
	struct ifaddrs *myaddrs, *ifa;
	struct sockaddr_in *s4;
	struct sockaddr_in6 *s6;
	int if_index;
	/*
	 * buf must be big enough for an IPv6 address (e.g.
	 * 3ffe:2fa0:1010:ca22:020a:95ff:fe8a:1cf8)
	 */
	char buf[64];

	if (getifaddrs(&myaddrs)) {
		err ("getifaddrs");
	}
	
	for (ifa = myaddrs; ifa != NULL; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL)
			continue;
		if ((ifa->ifa_flags & IFF_UP) == 0)
			continue;

		if_index=if_nametoindex(ifa->ifa_name);
		dbg("%s(%d): ", ifa->ifa_name,if_index);
		
		if(!if_index) {
			warn("cannot get interface index for %s\n",ifa->ifa_name);
			continue;
		}

		if((intn=int_find(if_index))==NULL) {
			g_conf->ints=(struct intnode*)realloc(g_conf->ints, sizeof(struct intnode)*(++g_conf->maxinterfaces));
			if (!g_conf->ints) {
				err ("Cannot get memory for interface structures.\n");
			}
			intn=g_conf->ints+g_conf->maxinterfaces-1;
			memset(intn, 0, sizeof(struct intnode));
		}

		intn->ifindex=if_index;
		strcpy(intn->name,ifa->ifa_name);

		if(ifa->ifa_addr->sa_family == AF_LINK && ((struct if_data *)ifa->ifa_data)->ifi_type != IFT_LOOP && ifa->ifa_data) {
			dbg("MTU: %d\n", ((struct if_data *)ifa->ifa_data)->ifi_mtu);
			intn->mtu=((struct if_data *)ifa->ifa_data)->ifi_mtu;
			memcpy(&intn->hwaddr, ifa->ifa_addr, sizeof(struct sockaddr_in));
		}

		if (ifa->ifa_addr->sa_family == AF_INET) {
			s4 = (struct sockaddr_in *) (ifa->ifa_addr);
			if (inet_ntop(ifa->ifa_addr->sa_family, (void *) &(s4->sin_addr), buf, sizeof(buf)) == NULL) {
				warn("%s: inet_ntop failed!\n", ifa->ifa_name);
			} else {
				dbg("%s\n", buf);
			}
		} else if (ifa->ifa_addr->sa_family == AF_INET6) {
			s6 = (struct sockaddr_in6 *) (ifa->ifa_addr);
			/* Link Local IPv6 address ? */
			if (IN6_IS_ADDR_LINKLOCAL (&s6->sin6_addr)) {
				/* Update the linklocal address */
				intn->linklocal = s6->sin6_addr;
			} else {
				intn->global = s6->sin6_addr;
			}
			if (inet_ntop(ifa->ifa_addr->sa_family, (void *) &(s6->sin6_addr), buf, sizeof(buf)) == NULL) {
				warn("%s: inet_ntop failed!\n", ifa->ifa_name);
			} else {
				dbg("%s\n", buf);
			}
		}
	}

	freeifaddrs(myaddrs);
}

#endif
