#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#if 0	/* for test */
#define ARS_IPV6
#endif /* ARS_IPV6 */

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <ifaddrs.h>
#endif /* defined(__*BSD__) */

#include "ars.h"

/*===================================================================================
 * Linux code
 *---------------------------------------------------------------------------------*/
#ifdef __linux__
int ars_get_iface_list(struct ars_iface *iface, size_t *isize)
{
	int fd, retval, iface_nr, j;
	struct ifconf	ifc;
	struct ifreq	ibuf[ARS_IF_MAX_IFACE], ifr;
	struct sockaddr_in	*saipv4;
#ifdef ARS_IPV6
	struct sockaddr_in6	*saipv6;
#endif /* ARS_IPV6 */

	/* Open an UDP socket */
	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd == -1)
		return -ARS_ERROR;

	/* Initialize the ifc structure */
	bzero(ibuf, sizeof(ibuf));
	bzero(iface, sizeof(struct ars_iface)*(*isize));
	ifc.ifc_len = sizeof(ibuf);
	ifc.ifc_buf = (caddr_t) ibuf;

	/* Try to get the interfaces list */
	retval = ioctl(fd, SIOCGIFCONF, (char*) &ifc);
	if (retval == -1) {
		close(fd);
		return -ARS_ERROR;
	}

	/* Check if there is at least one interface */
	iface_nr = ifc.ifc_len / sizeof(struct ifreq);
	if (iface_nr == 0) {
		close(fd);
		return -ARS_ERROR;
	}

	*isize = *isize < iface_nr ? *isize : iface_nr;
	for (j = 0; j < *isize; j++) {
		/* Copy the interface name */
		strncpy(iface[j].if_name, ibuf[j].ifr_name, ARS_IF_NAME_SIZE);
		strncpy(ifr.ifr_name, ibuf[j].ifr_name, ARS_IF_NAME_SIZE);

		/* Get interface flags */
		retval = ioctl(fd, SIOCGIFFLAGS, (char*)&ifr);
		if (retval == -1) {
			__D(printf("Warning: Can't get flags for %s\n",
				ibuf[j].ifr_name);)
			iface[j].if_flags |= ARS_IF_MISCONF;
			continue;
		}
		if (ifr.ifr_flags & IFF_UP)
			iface[j].if_flags |= ARS_IF_UP;
		if (ifr.ifr_flags & IFF_LOOPBACK)
			iface[j].if_flags |= ARS_IF_LOOP;

		/* Get interface address */
		retval = ioctl(fd, SIOCGIFADDR, (char*)&ifr);
		if (retval == -1) {
			__D(printf("Warning: Can't get address for %s\n",
				ibuf[j].ifr_name);)
			iface[j].if_flags |= ARS_IF_MISCONF;
			continue;
		}
		if (ifr.ifr_ifru.ifru_addr.sa_family == AF_INET) {
			saipv4 = (struct sockaddr_in *)&ifr.ifr_ifru.ifru_addr;
			strncpy(iface[j].if_ipv4addr,
				inet_ntoa(saipv4->sin_addr),
				ARS_INET_ADDRSTRLEN);
		}
#ifdef ARS_IPV6
		else if (ifr.ifr_ifru.ifru_addr.sa_family == AF_INET6) {
			saipv6 = (struct sockaddr_in6 *)&ifr.ifr_ifru.ifru_addr;
			inet_ntop(AF_INET6, &saipv6->sin6_addr,
				iface[j].if_ipv6addr, ARS_INET6_ADDRSTRLEN);
		}
#endif /* ARS_IPV6 */

		/* Get the interface MTU */
		retval = ioctl (fd, SIOCGIFMTU, (char*)&ifr);
		if (retval == -1) {
			__D(printf("Warning: Can't get MTU for %s\n",
				ibuf[j].ifr_name);)
			iface[j].if_flags |= ARS_IF_MISCONF;
			continue;
		}
	}

	close(fd);
	return -ARS_OK;
}

/*===================================================================================
 * *BSD code
 *---------------------------------------------------------------------------------*/
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
/* and BSDI ? */
int ars_get_iface_list(struct ars_iface *iface, size_t *isize)
{
        int 			iface_nr, j;
	struct 	ifaddrs 	*ifap, *ifa;
	char			current_if_name[ARS_IF_NAME_SIZE];

	/* get the list */
	if(getifaddrs(&ifap) < 0) {
		perror("getifaddrs");
		return -ARS_ERROR;
	}

	/* check if there is at least one interface */
	if(!ifap) { return -ARS_ERROR; }

	iface_nr = 0;
	for(ifa = ifap; ifa; ifa = ifa->ifa_next) 
		if(ifa->ifa_addr->sa_family == AF_LINK) iface_nr++;
		
	if (iface_nr == 0) { return -ARS_ERROR; }

	*isize = *isize < iface_nr ? *isize : iface_nr;

	/* save the first interface name */
	strncpy(current_if_name,ifap->ifa_name,ARS_IF_NAME_SIZE);

	for(ifa = ifap, j = 0; ifa; ifa = ifa->ifa_next) {

	if (j > *isize) break;	
	switch(ifa->ifa_addr->sa_family) {

	case AF_LINK:
		if(strncmp(ifa->ifa_name,current_if_name,ARS_IF_NAME_SIZE)) j++;
		/*printf("if n° %d : %s AF_LINK\n",j,ifa->ifa_name);*/

		/* copy the interface name */
		strncpy(iface[j].if_name,ifa->ifa_name,ARS_IF_NAME_SIZE);
		strncpy(current_if_name,ifa->ifa_name,ARS_IF_NAME_SIZE);

		/* Get interface flags */
		iface[j].if_flags = 0;
		strncpy(iface[j].if_ipv4addr,"",1);
#ifdef ARS_IPV6
		strncpy(iface[j].if_ipv6addr,"",1);
#endif /* ARS_IPV6 */
		if (ifa->ifa_flags & IFF_UP) iface[j].if_flags |= ARS_IF_UP;
                if (ifa->ifa_flags & IFF_LOOPBACK) iface[j].if_flags |= ARS_IF_LOOP;

		/* Get the interface MTU */
		iface[j].if_mtu = ((struct if_data *)ifa->ifa_data)->ifi_mtu;
		break;

	case AF_INET:
		if(strncmp(ifa->ifa_name,current_if_name,ARS_IF_NAME_SIZE)) {
			return -ARS_ERROR; /* strange error */
		}

		/* Get the v4 address */
		strncpy(iface[j].if_ipv4addr,
			inet_ntoa(((struct sockaddr_in *)ifa->ifa_addr)->sin_addr),
			ARS_INET_ADDRSTRLEN);
		break;

#ifdef ARS_IPV6
	case AF_INET6:
		if(strncmp(ifa->ifa_name,current_if_name,ARS_IF_NAME_SIZE)) {
			return -ARS_ERROR; /* strange error */
		}
		inet_ntop(AF_INET6,
			  &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr,
			  iface[j].if_ipv6addr,
			  ARS_INET6_ADDRSTRLEN);
		break;
#endif /* ARS_IPV6 */
	
	default: 
	}
	}
	return -ARS_OK;
}
#else
#error "Please, implement ars_get_iface_list() for your arch"
#endif /* OS type */

int ars_get_iface(char *name, struct ars_iface *i)
{
	struct ars_iface iface[16];
	int isize = 16, j, error;

	if ((error = ars_get_iface_list(iface, &isize)) != -ARS_OK)
		return -ARS_ERROR;

	if (name == NULL) { /* Get the first good interface */
		for (j = 0; j < isize; j++) {
			if (iface[j].if_flags & ARS_IF_MISCONF ||
			    iface[j].if_flags & ARS_IF_LOOP ||
			    (iface[j].if_flags & ARS_IF_UP) == 0)
				continue;
			*i = iface[j];
			return -ARS_OK;
		}
	} else { /* Get the specified interface, even if loopback */
        	for (j = 0; j < isize; j++) {
			if (strcasecmp(iface[j].if_name, name) == 0) {
                        	if (!(iface[j].if_flags & ARS_IF_MISCONF) &&
                          	    (iface[j].if_flags & ARS_IF_UP)) {
                                    *i = iface[j];
                                    return -ARS_OK;
                        	}
                        	return -ARS_ERROR;
                	}
        	}
		return -ARS_ERROR; /* nothing found */
	}
	return -ARS_ERROR;
}

/*===================================================================================
 * Arch undependent code
 *---------------------------------------------------------------------------------*/
#ifdef ARS_IF_TESTMAIN
int main(int argc, char **argv)
{
	struct ars_iface iface[16];
	int isize = 16, j;
	struct ars_iface test;

	if (ars_get_iface_list(iface, &isize) != -ARS_OK) {
		printf("Can't get interfaces\n");
		exit(1);
	}

	printf("Found %d interfaces\n", isize);
	for (j = 0; j < isize; j++) {
		if (iface[j].if_flags & ARS_IF_MISCONF) {
			printf("%s MISCONFIGURED!\n", iface[j].if_name);
			continue;
		}
		printf("%s(%s)(%s) mtu(%d) up(%d) loopback(%d)\n",
			iface[j].if_name,
			iface[j].if_ipv4addr,
			iface[j].if_ipv6addr,
			iface[j].if_mtu,
			(iface[j].if_flags & ARS_IF_UP) ? 1 : 0,
			(iface[j].if_flags & ARS_IF_LOOP) ? 1 : 0);
	}
	return 0;
}
#endif
