#include "common.h"

unsigned int seqno;

// PKS, pthread_self() = pointer to memory
#define NETLINKID() ((getpid() << 16) | gettid())

typedef int (*netlink_cb_t)(struct nlmsghdr *nh, void *data);

void address_calculate_netmask(struct iface_address *address,
	int ifa_prefixlen);

void iface_entry_append_address(struct iface_entry *iface,
	struct iface_address *address);

/*
 * Open a netlink socket. Maybe in the future we'll support another type.
 */

int netlink_socket(int type)
{
	int fd = -1;
	struct sockaddr_nl snl;

	memset(&snl, 0, sizeof(struct sockaddr_nl));

	dprintf("requesting netlink socket");

	fd = socket(AF_NETLINK, SOCK_RAW, type);
	if(fd == -1) {
		dprintf("failed with %s", strerror(errno));
		return -1;
	}

	snl.nl_family = AF_NETLINK;
	// some systems require pid to 0
	snl.nl_pid = 0;

	if(bind(fd, (void *)&snl, sizeof(struct sockaddr_nl)) == -1) {
		dprintf("Failed to bind to netlink socket: %s", strerror(errno));
		close(fd);
		return -1;
	}
	// let's add some random to seqno
	seqno = time(NULL);
	dprintf("fd %d is a suitable netlink socket", fd);

	return fd;
}

int netlink_request(int fd, int family, int type)
{
	// send at least 16 bytes of data, some old kernels want it
	unsigned char buf[sizeof(struct nlmsghdr) + sizeof(struct rtgenmsg)+15];
	struct nlmsghdr *nh;
	struct rtgenmsg *ng;

	struct sockaddr_nl snl;
	struct iovec iov;

	nh = (struct nlmsghdr *)(buf);
	ng = (struct rtgenmsg *)(buf + sizeof(struct nlmsghdr));

	dprintf("Setting up netlink request");

	memset(&snl, 0, sizeof(struct sockaddr_nl));
	memset(buf, 0, sizeof(buf));

	snl.nl_family = AF_NETLINK;			// I keep auto typing AF_INET :~(

	nh->nlmsg_len =  NLMSG_LENGTH(sizeof(buf) - sizeof(struct nlmsghdr));
	nh->nlmsg_type = type;

	// NLM_F_ROOT     Return the complete table instead of a single entry.

	// Create, remove or receive information about a network route.  These
	// messages contain an rtmsg structure with an optional sequence of
	// rtattr structures following.  For RTM_GETROUTE setting rtm_dst_len
	// and rtm_src_len to 0 means you get all entries for the specified
	// routing table.  For the other fields except rtm_table and
	// rtm_protocol 0 is the wildcard.

	nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;

	// NLM_F_ACK       Request for an acknowledgment on success, maybe an idea.
	// would need to implement re-sending if it fails, etc.
	// for now we will assume it's reliable (even though the docs say it's not)

	//nh->nlmsg_pid = NETLINKID();
	// some systems require pid to 0
	nh->nlmsg_pid = 0;
	nh->nlmsg_seq = __atomic_inc(&seqno);

	ng->rtgen_family = family;

	dprintf("Sending request");

	if(sendto(fd, buf, sizeof(buf), 0, (void *)(&snl), sizeof(struct sockaddr_nl)) == -1) {
		dprintf("Failed to send netlink request. Got %s", strerror(errno));
		return -1;
	}

	dprintf("Request sent");

	return seqno; // XXX, may wrap, etc. just use zero?

}

// man 7 netlink
int netlink_parse(int fd, int seq, netlink_cb_t callback, void *data)
{
	int len;
	int status;
    int end = 0;
	unsigned char buf[4096];



	struct sockaddr_nl snl;
	struct msghdr msg;
	struct iovec iov = { buf, sizeof(buf) };
	struct nlmsghdr *nh;

	memset(&snl, 0, sizeof(struct sockaddr_nl));
	snl.nl_family = AF_NETLINK;

	msg.msg_name = (void *)&snl;
	msg.msg_namelen = sizeof(struct sockaddr_nl);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_flags = 0;

	status = 0;

	do {
		len = recvmsg(fd, &msg, 0);
		dprintf("recvmsg returned %d", len);

//debug received data
#if 0
		int i;
		unsigned char buff_str[16192];
		memset(buff_str,0,16192);
		for(i=0;i<len;i++) {
			sprintf(buff_str,"%s%02X ",buff_str,buf[i]);
			if (i%32 == 0 && i!= 0)
				strcat(buff_str,"\n");
		}
		dprintf("\n%s",buff_str);
#endif

		if(len <= 0) {
			status = errno;
			dprintf("socket dead? bailing (%s)", strerror(errno));
			break;
		}

		if(msg.msg_flags & MSG_TRUNC) {
			dprintf("truncated message ? :(");
			status = ERROR_NOT_SUPPORTED;
			break;
		}

		for(nh = (struct nlmsghdr *)(buf); NLMSG_OK(nh, len); nh = (struct nlmsghdr *) NLMSG_NEXT(nh, len)) {
			//dprintf("buf = %p, nh = %p", buf, nh);
			//dprintf("nh->nlmsg_type = %d", nh->nlmsg_type);

			if(nh->nlmsg_type == NLMSG_DONE) {
                end = 1;
                break;
            }

			if(nh->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *me = (struct nlmsgerr *) NLMSG_DATA (nh);
				//dprintf("in NLMSG_ERROR handling.. me = %p", me);
				//dprintf("me->error = %d", me->error);
				if(me->error) {
					//dprintf("so, we have: nlmsg_len: %d, nlmsg_type: %d, nlmsg_flags: %d, nlmsg_seq: %d, nlmsg_pid: %d",
					//	me->msg.nlmsg_len, me->msg.nlmsg_type, me->msg.nlmsg_flags,
					//	me->msg.nlmsg_seq, me->msg.nlmsg_pid);

					if(me->msg.nlmsg_seq == seq) {
						dprintf("Hum. kernel doesn't like our message :~(");
						status = ERROR_NOT_SUPPORTED;
						break;
					}

					dprintf("don't know how to handle this error at the moment. continuing");
				}
				continue; // "yea, whatever"
			}

			//

			dprintf("dispatching into callback");

			status = callback(nh, data);

			if(status) {
				dprintf("callback returned non zero(%d) , stopping process", status);
				break;
			}

		}

	} while(!end);


	return status;
}

// returns 1 if rm seems to be an rtmsg
int likely_rtmsg(struct rtmsg *rm)
{
	if ( (rm->rtm_family == AF_INET  && rm->rtm_dst_len >= 0 && rm->rtm_dst_len <= 32) ||
		 (rm->rtm_family == AF_INET6 && rm->rtm_dst_len >= 0 && rm->rtm_dst_len <= 128)
		)
		return 1;

	else
		return 0;


}

int netlink_parse_routing_table(struct nlmsghdr *nh, void *data)
{
	struct routing_table * rt_table = ( struct routing_table *) data;
	struct ipv4_routing_table **table_v4 = rt_table->table_ipv4;
	struct ipv6_routing_table **table_v6 = rt_table->table_ipv6;
	struct ipv4_routing_table *tmp;
	struct ipv6_routing_table *tmp6;
	struct ipv4_route_entry *re;
	struct ipv6_route_entry *re6;

	struct rtmsg *rm;
	struct rtattr *ra;
	int len;
	int newsize;
	unsigned char is_ipv6;
	unsigned char int_name[IFNAMSIZ+1];
	uint32_t interface_index, metric;

	__u32 dest, netmask, nexthop;
	__u32 *what;

	__u128 dest6, netmask6, nexthop6;
	unsigned char *what6;

	dest = netmask = nexthop = metric = 0;
	memset(&dest6, 0, sizeof(__u128));
	memset(&netmask6, 0, sizeof(__u128));
	memset(&nexthop6, 0, sizeof(__u128));

	memset(int_name, 0, IFNAMSIZ+1);

	if(nh->nlmsg_type != RTM_NEWROUTE) {
		dprintf("got %d instead of RTM_NEWROUTE (%d)", nh->nlmsg_type, RTM_NEWROUTE);
		return 0;
	}

	rm = NLMSG_DATA(nh);
	// stumbled upon an old system with 4 bytes padding of 0 between nlmsghdr and rtmsg, try to detect it
	if(!likely_rtmsg(rm)) {
		rm = (struct rtmsg *)((unsigned char *)rm + 4);
		dprintf("Adjusted rm at +4");
	}

	//dprintf("rtm_family : 0x%x , rtm_dst_len : 0x%x, rtm_src_len : 0x%x",rm->rtm_family, rm->rtm_dst_len, rm->rtm_src_len);
	// print directly connected routes
	if(rm->rtm_type != RTN_UNICAST && rm->rtm_type != RTN_LOCAL) {
		dprintf("got %d instead of RTN_UNICAST (%d) or RTN_LOCAL (%d)", rm->rtm_type, RTN_UNICAST,RTN_LOCAL);
		return 0;
	}

	if(rm->rtm_family != AF_INET && rm->rtm_family != AF_INET6) {
		dprintf("dunno what on earth to do with a rtm_family of %d governor", rm->rtm_family);
		return 0;
	}

	if(rm->rtm_flags & (RTM_F_CLONED | RTM_F_EQUALIZE)) {
		dprintf("cloned / equalized .. doesn't sound good. skipping for now");
		return 0;
	}

	if(rm->rtm_table == RT_TABLE_LOCAL) {
		dprintf("don't want to parse local routing table");
		return 0;
	}

    if (rm->rtm_family == AF_INET)
        is_ipv6 = 0;
    else
        is_ipv6 = 1;

	//dprintf("nh->nlmsg_len: %d, NLMSG_LENGTH(sizeof(..)): %d, is_ipv6 : %d", nh->nlmsg_len, NLMSG_LENGTH(sizeof(struct rtmsg)),is_ipv6);

	len = nh->nlmsg_len - NLMSG_LENGTH (sizeof(struct rtmsg));
	if(len <= 0) {
		dprintf("back to the drawing board it seems");
		return 0;
	}

	//dprintf("RTA_DST=%d, RTA_SRC=%d, RTA_GATEWAY=%d, RTA_PREFSRC=%d, RTA_OIF=%d, RTA_PRIORITY=%d", RTA_DST, RTA_SRC, RTA_GATEWAY, RTA_PREFSRC,RTA_OIF, RTA_PRIORITY);


	//dprintf("rtm_table : %d, RT_TABLE_UNSPEC=%d,  RT_TABLE_DEFAULT =%d, RT_TABLE_MAIN=%d,RT_TABLE_LOCAL=%d", rm->rtm_table,RT_TABLE_UNSPEC,  RT_TABLE_DEFAULT , RT_TABLE_MAIN, RT_TABLE_LOCAL);

	//dprintf("rtm_type : %d, RTN_UNICAST=%d,  RTN_LOCAL=%d", rm->rtm_type,RTN_UNICAST, RTN_LOCAL);
	// okay, so.
	//

	for(ra = (struct rtattr *) RTM_RTA(rm) ; RTA_OK(ra, len); ra = (struct rtattr *) RTA_NEXT(ra, len))
	{
        if (is_ipv6) {
            what6 = (unsigned char *) RTA_DATA(ra);
		    //dprintf("ra @ %p, type = %d, length = %d, payload = %d, payload data = %08x %08x %08x %08x", ra, ra->rta_type, ra->rta_len, RTA_PAYLOAD(ra), *(__u32 *)what6, *(__u32 *)(what6+4), *(__u32 *)(what6+8), *(__u32 *)(what6+12));
        }
        else {
		    what = (__u32 *) RTA_DATA(ra);
		    //dprintf("ra @ %p, type = %d, length = %d, payload = %d, payload data = %08x", ra, ra->rta_type, ra->rta_len, RTA_PAYLOAD(ra), *what);
        }

		switch(ra->rta_type) {
			case RTA_DST:
				if (is_ipv6)
					memcpy(&dest6,what6, sizeof(__u128));
				else
					dest = *what;
				break;
			case RTA_GATEWAY:
				if (is_ipv6)
					memcpy(&nexthop6,what6, sizeof(__u128));
				else
					nexthop = *what;
				break;
			case RTA_OIF:
				interface_index = *(uint32_t *)RTA_DATA(ra);
				if_indextoname(interface_index, int_name);
				break;
            case RTA_PRIORITY:
				// metric is a uint16_t but we must transmit 32bits integers
				metric = (*(uint32_t *)RTA_DATA(ra))&0x0000ffff;
				break;
		}
	}

	//dprintf("and while you're here, rtm_dst_len = %d", rm->rtm_dst_len);

	if (is_ipv6) {
		// if netmask is FFFFFFFF FFFFFFFF 00000000 00000000 (/64), netmask6.a1 and netmask6.a2 == 0xffffffff, and nestmask6.a3 and .a4 == 0
		// netmask6 is set to 0 at the beginning of the function, no need to reset the values to 0 if it is needed
		// XXX really ugly, but works
		if (rm->rtm_dst_len >= 96) {
			netmask6.a4 = (rm->rtm_dst_len == 96) ? 0 : htonl(0xffffffff<<(32-(rm->rtm_dst_len%32)));
			netmask6.a1 = netmask6.a2 = netmask6.a3 =  0xffffffff;
		}
		else if (rm->rtm_dst_len >= 64) {
			netmask6.a3 = (rm->rtm_dst_len == 64) ? 0 : htonl(0xffffffff<<(32-(rm->rtm_dst_len%32)));
			netmask6.a1 = netmask6.a2 =  0xffffffff;
		}
		else if (rm->rtm_dst_len >= 32) {
			netmask6.a2 = (rm->rtm_dst_len == 32) ? 0 : htonl(0xffffffff<<(32-(rm->rtm_dst_len%32)));
			netmask6.a1 = 0xffffffff;
		}
		else
			netmask6.a1 = (rm->rtm_dst_len == 0) ? 0 : htonl(0xffffffff<<(32-rm->rtm_dst_len));
    }
	else {
		netmask = (rm->rtm_dst_len == 0) ? 0 : htonl(0xffffffff<<(32-rm->rtm_dst_len));
	}

    if (is_ipv6) {
        newsize  = sizeof(struct ipv6_routing_table);
	    newsize += ((*table_v6)->entries + 1) * sizeof(struct ipv6_route_entry);

	    tmp6 = realloc(*table_v6, newsize);

        if(tmp6 == NULL) {
		    return ENOMEM;
	    }

	    re6 = &(tmp6->routes[tmp6->entries]);

	    memcpy(&re6->dest6, &dest6, sizeof(__u128));
	    memcpy(&re6->netmask6, &netmask6, sizeof(__u128));
	    memcpy(&re6->nexthop6, &nexthop6, sizeof(__u128));

        strncpy(re6->interface, int_name, IFNAMSIZ);
		re6->metric = metric;
		//dprintf("re6->dest6 = %08x %08x %08x %08x, re6->netmask6 = %08x %08x %08x %08x, re6->nexthop6 = %08x %08x %08x %08x, interface = %s, metric = %d",
		//	re6->dest6.a1,re6->dest6.a2,re6->dest6.a3,re6->dest6.a4,
		//	re6->netmask6.a1,re6->netmask6.a2,re6->netmask6.a3,re6->netmask6.a4,
		//	re6->nexthop6.a1,re6->nexthop6.a2,re6->nexthop6.a3,re6->nexthop6.a4,
		//	re6->interface,re6->metric);
		tmp6->entries++;

		*table_v6 = tmp6;
    }
    else {
	    newsize  = sizeof(struct ipv4_routing_table);
	    newsize += ((*table_v4)->entries + 1) * sizeof(struct ipv4_route_entry);

	    tmp = realloc(*table_v4, newsize);

	    if(tmp == NULL) {
		    return ENOMEM;
	    }

	    re = &(tmp->routes[tmp->entries]);

	    re->dest = dest;
	    re->netmask = netmask;
	    re->nexthop = nexthop;
        strncpy(re->interface, int_name, IFNAMSIZ);
        re->metric = metric;

	    //dprintf("re->dest = %08x, re->netmask = %08x, re->nexthop = %08x, interface = %s, metric = %d", re->dest, re->netmask, re->nexthop,re->interface,re->metric);
	    tmp->entries++;

	    *table_v4 = tmp;
    }

	return 0;

}

int netlink_get_routing_table(struct ipv4_routing_table **table_ipv4, struct ipv6_routing_table **table_ipv6)
{


	int fd;
	int seq;
	int status;
	struct routing_table table;

	*table_ipv4 = NULL;
	*table_ipv6 = NULL;
	table.table_ipv4 = table_ipv4;
	table.table_ipv6 = table_ipv6;


	*table_ipv4 = calloc(sizeof(struct ipv4_routing_table), 1);
	*table_ipv6 = calloc(sizeof(struct ipv6_routing_table), 1);
	if(*table_ipv4 == NULL) {
		return ENOMEM;
	}
	if(*table_ipv6 == NULL) {
        free(*table_ipv4);
		return ENOMEM;
	}

	fd = netlink_socket(NETLINK_ROUTE);
	if(fd == -1) {
		dprintf("failed with netlink");
		return ERROR_NOT_SUPPORTED;
	}

	seq = netlink_request(fd, AF_UNSPEC, RTM_GETROUTE);
	if(seq == -1) {
		dprintf("netlink_request RTM_GETROUTE failed");
		close(fd);
		return ERROR_NOT_SUPPORTED;
	}

	status = netlink_parse(fd, seq, netlink_parse_routing_table, &table);

	close(fd);

	if(status != 0) {
        if (*table_ipv4)
		    free(*table_ipv4);
        if (*table_ipv6)
		    free(*table_ipv6);
		*table_ipv4 = NULL;
		*table_ipv6 = NULL;
	}

	return status;
}

void flags_to_string(uint32_t flags, unsigned char * buffer, uint32_t buffer_len)
{
	if ((flags & IFF_UP) == IFF_UP)
		strncat(buffer, "UP ",buffer_len - strlen(buffer));
	if ((flags & IFF_BROADCAST) == IFF_BROADCAST)
		strncat(buffer, "BROADCAST ",buffer_len - strlen(buffer));
	if ((flags & IFF_LOOPBACK) == IFF_LOOPBACK)
		strncat(buffer, "LOOPBACK ",buffer_len - strlen(buffer));
	if ((flags & IFF_POINTOPOINT) == IFF_POINTOPOINT)
		strncat(buffer, "POINTOPOINT ",buffer_len - strlen(buffer));
	if ((flags & IFF_RUNNING) == IFF_RUNNING)
		strncat(buffer, "RUNNING ",buffer_len - strlen(buffer));
	if ((flags & IFF_PROMISC) == IFF_PROMISC)
		strncat(buffer, "PROMISC ",buffer_len - strlen(buffer));
	if ((flags & IFF_MULTICAST) == IFF_MULTICAST)
		strncat(buffer, "MULTICAST ",buffer_len - strlen(buffer));
}


// returns 1 if iface seems to be an ifinfomsg
int likely_ifinfomsg(struct ifinfomsg *iface)
{
	if (iface->ifi_family == 0 && //ifi_family == AF_UNSPEC
		iface->ifi_type > 0 &&
		iface->ifi_index > 0 &&  // iface index should be between 1 and 4096
		iface->ifi_index <= 0x1000 &&
		((iface->ifi_change == 0) || (iface->ifi_change == 0xffffffff))
		)
		return 1;
	else
		return 0;

}


int netlink_parse_interface_link(struct nlmsghdr *nh, void *data)
{
	struct ifaces_list ** iface_list = ( struct ifaces_list **) data;
	struct ifaces_list  *tmp;
	struct iface_entry iface_tmp;
	struct iface_entry * iff;

	struct ifinfomsg *iface;
	struct rtattr *attribute;
	uint32_t len;
	uint32_t newsize;

	// stumbled upon an old system with 4 bytes padding between nlmsghdr and ifinfomsg, try to detect it
	iface = NLMSG_DATA(nh);
	if (!likely_ifinfomsg(iface)) {
		iface = (struct ifinfomsg *)((unsigned char *)iface + 4);
		dprintf("Adjusted iface at +4");
	}
	//dprintf("ifi_family : 0x%x , ifi_type : 0x%x, ifi_index : 0x%x",iface->ifi_family, iface->ifi_type, iface->ifi_index);
	len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
	memset(&iface_tmp, 0, sizeof(iface_tmp));

	// index of the interface
	iface_tmp.index = iface->ifi_index;
	//flags of the interface, string version
	flags_to_string(iface->ifi_flags, iface_tmp.flags, FLAGS_LEN);

	for (attribute = IFLA_RTA(iface); RTA_OK(attribute, len); attribute = RTA_NEXT(attribute, len))
	{

		switch(attribute->rta_type)
		{
			case IFLA_IFNAME:
				strncpy(iface_tmp.name, (unsigned char *) RTA_DATA(attribute), IFNAMSIZ);
				break;
			case IFLA_ADDRESS:
				memcpy(iface_tmp.hwaddr, (unsigned char *) RTA_DATA(attribute), 6);
				break;
			case IFLA_MTU:
				iface_tmp.mtu = *(uint32_t *)RTA_DATA(attribute);
				break;
			default:
				break;
	    }
	}

	newsize  = sizeof(struct ifaces_list);
	newsize += ((*iface_list)->entries + 1) * sizeof(struct iface_entry);

	tmp = realloc(*iface_list, newsize);

	if(tmp == NULL) {
		return ENOMEM;
	}

	iff = &(tmp->ifaces[tmp->entries]);
	memset(iff, 0, sizeof(struct iface_entry));

	iff->index = iface_tmp.index;
	strncpy(iff->name, iface_tmp.name, IFNAMSIZ);
	memcpy(iff->hwaddr, iface_tmp.hwaddr, 6);
	strncpy(iff->flags, iface_tmp.flags, FLAGS_LEN);
	iff->mtu = iface_tmp.mtu;

	dprintf("iff->index = %d, iff->name = %s, iff->hwaddr = %02x:%02x:%02x:%02x:%02x:%02x, iff->mtu = %d, iff->flags = %s, real_flags = 0x%08x", iff->index, iff->name,
	*(unsigned char *)(iff->hwaddr), *(unsigned char *)(iff->hwaddr+1),*(unsigned char *)(iff->hwaddr+2),
	*(unsigned char *)(iff->hwaddr+3), *(unsigned char *)(iff->hwaddr+4), *(unsigned char *)(iff->hwaddr+5), iff->mtu, iff->flags, iface->ifi_flags);

	tmp->entries++;

	*iface_list = tmp;

    return 0;


}

struct iface_entry * find_iface_by_index(struct ifaces_list * list, uint32_t index)
{
	struct iface_entry * ret = NULL;
	uint32_t i;
	for(i=0; i<list->entries; i++)
	{
		if (list->ifaces[i].index == index)
		{
			ret = &list->ifaces[i];
			break;
		}

	}
	return ret;
}

struct iface_entry * find_iface_by_index_and_name(struct ifaces_list * list, uint32_t index,unsigned char * name)
{
	struct iface_entry * ret = NULL;
	uint32_t i;
	for(i=0; i<list->entries; i++)
	{
		if (list->ifaces[i].index == index && !strcmp(list->ifaces[i].name, name))
		{
			ret = &list->ifaces[i];
			break;
		}

	}
	return ret;
}


// returns 1 if iaddr seems to be an ifaddrmsg
int likely_ifaddrmsg(struct ifaddrmsg *iaddr)
{
	if ( (iaddr->ifa_family == AF_INET  && iaddr->ifa_prefixlen >= 0 && iaddr->ifa_prefixlen <= 32) || //ifa_family == AF_INET  0 <= and prefix_len <= 32
		 (iaddr->ifa_family == AF_INET6 && iaddr->ifa_prefixlen >= 0 && iaddr->ifa_prefixlen <= 128)   //ifa_family == AF_INET6 and 0 <= prefix_len <= 128
		)
		return 1;

	else
		return 0;


}


int netlink_parse_interface_address(struct nlmsghdr *nh, void *data)
{
	struct ifaces_list ** iface_list = ( struct ifaces_list **) data;
	struct iface_entry * iff;
	struct ifaces_list * iface_list_tmp;
	struct iface_entry iface_tmp;

	struct ifaddrmsg *iaddr;
	struct rtattr *attribute;
	uint32_t len;
	uint32_t newsize;
	unsigned char is_ipv6;

	struct iface_address *addr_tmp;

	// strictly for debugging
	char addr_str[64];

	iaddr = NLMSG_DATA(nh);
	// stumbled upon an old system with 4 bytes padding between nlmsghdr and ifaddrmsg, try to detect it
	if (!likely_ifaddrmsg(iaddr)) {
		iaddr = (struct ifaddrmsg *)((unsigned char *)iaddr + 4);
		dprintf("Adjusted iaddr at +4");
	}

	len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*iaddr));

	if (iaddr->ifa_family == AF_INET6)
		is_ipv6 = 1;
	else if (iaddr->ifa_family == AF_INET)
		is_ipv6 = 0;
	else {
		//dprintf("Got iaddr->ifa_family : %d which is unknown (iaddr->ifa_index : %d)", iaddr->ifa_family, iaddr->ifa_index);
		return 0;
	}

	memset(&iface_tmp, 0, sizeof(iface_tmp));
	iface_tmp.index = iaddr->ifa_index;

	for (attribute = IFA_RTA(iaddr); RTA_OK(attribute, len); attribute = RTA_NEXT(attribute, len))
	{
		switch(attribute->rta_type)
		{
			case IFA_ADDRESS:
				// Make room for a new address
				iface_tmp.addr_count++;
				iface_tmp.addr_list = realloc(iface_tmp.addr_list, sizeof(struct iface_address) * iface_tmp.addr_count);
				addr_tmp = &iface_tmp.addr_list[iface_tmp.addr_count-1];
				if (is_ipv6)
				{
					addr_tmp->family = AF_INET6;
					memcpy(&addr_tmp->ip.addr6, (unsigned char *) RTA_DATA(attribute), sizeof(__u128));
				} else {
					addr_tmp->family = AF_INET;
					addr_tmp->ip.addr = *(__u32 *) RTA_DATA(attribute);
				}
				address_calculate_netmask(addr_tmp, iaddr->ifa_prefixlen);

				inet_ntop(addr_tmp->family, &addr_tmp->ip, addr_str, sizeof(addr_str));
				dprintf("Interface: %s", addr_str);
				inet_ntop(addr_tmp->family, &addr_tmp->nm, addr_str, sizeof(addr_str));
				dprintf("Netmask: %s", addr_str);
				break;

			case IFA_LABEL:
				strncpy(iface_tmp.name, (unsigned char *) RTA_DATA(attribute), IFNAMSIZ);
				dprintf("Copied name %s", iface_tmp.name);
	      		break;
			default:
				break;
		}
	}

	/*
 	 * try to find the iface by index and name
	 * An IP alias (eth0:0 for instance) will have the same index but not the
	 * same name/label.  There are no aliases when getting IPv6 address, so
	 * just search using the index.
	 */
	if (is_ipv6) {
		iff = find_iface_by_index(*iface_list, iface_tmp.index);
		if (iff == NULL) {
			dprintf("Cannot find iface with index %d", iface_tmp.index);
			return 0;
		}
	}
	else
		iff = find_iface_by_index_and_name(*iface_list, iface_tmp.index, iface_tmp.name);

	if (iff == NULL) {
		/* Now we're dealing with an IPv4 alias such as eth0:0.  With a regular
		 * interface, the mac address, mtu, flags, etc. would already have been
		 * initialized when we did the RTM_GETLINK request.  Since an alias
		 * doesn't count as a physical interface, that didn't happen, so copy
		 * all of the parent interface's info to this one.
		 */
		dprintf("%s an alias?", iface_tmp.name);
		iff = find_iface_by_index(*iface_list, iface_tmp.index);
		if (iff == NULL) {
			dprintf("Cannot find iface with index %d", iface_tmp.index);
			return 0;
		}
		memcpy(iface_tmp.hwaddr, iff->hwaddr, 6);
		iface_tmp.mtu = iff->mtu;
		strncpy(iface_tmp.flags, iff->flags, FLAGS_LEN);

		// expand the list to accomodate the new one
		newsize  = sizeof(struct ifaces_list);
		newsize += ((*iface_list)->entries + 1) * sizeof(struct iface_entry);
		iface_list_tmp = realloc(*iface_list, newsize);

		if(iface_list_tmp == NULL) {
			return ENOMEM;
		}

		iff = &(iface_list_tmp->ifaces[iface_list_tmp->entries]);
		memset(iff, 0, sizeof(struct iface_entry));
		// copy back saved data in new iface_entry
		memcpy(iff->hwaddr, iface_tmp.hwaddr, 6);
		iff->mtu = iface_tmp.mtu;
		iff->index = iface_tmp.index;

		strncpy(iff->flags, iface_tmp.flags, FLAGS_LEN);
		strncpy(iff->name, iface_tmp.name, IFNAMSIZ);

		iface_list_tmp->entries++;
		*iface_list = iface_list_tmp;
	}

	inet_ntop(addr_tmp->family, &addr_tmp->ip, addr_str, sizeof(addr_str));
	dprintf("Appending: %s", addr_str);
	iface_entry_append_address(iff, &iface_tmp.addr_list[0]);
	dprintf("iff->addr_count = %d; iface_tmp.addr_count = %d", iff->addr_count, iface_tmp.addr_count);

	return 0;
}


int netlink_get_interfaces(struct ifaces_list **iface_list)
{

	int fd;
	int seq;
	int status;

	*iface_list = NULL;

	*iface_list = calloc(sizeof(struct ifaces_list), 1);
	if(*iface_list == NULL) {
		return ENOMEM;
	}

	fd = netlink_socket(NETLINK_ROUTE);
	if(fd == -1) {
		dprintf("failed with netlink");
		return ERROR_NOT_SUPPORTED;
	}

	seq = netlink_request(fd, AF_UNSPEC, RTM_GETLINK);
	if(seq == -1) {
		dprintf("netlink_request RTM_GETLINK failed");
		close(fd);
		return ERROR_NOT_SUPPORTED;
	}

	// will create one iface_entry for each interface
	status = netlink_parse(fd, seq, netlink_parse_interface_link, iface_list);
	if(status != 0) {
        if (*iface_list)
		    free(*iface_list);
		*iface_list = NULL;
		return status;
	}

	seq = netlink_request(fd, AF_UNSPEC, RTM_GETADDR);
	if(seq == -1) {
		dprintf("netlink_request RTM_GETADDR failed");
		close(fd);
		return ERROR_NOT_SUPPORTED;
	}
	// for each interface created before, will get the IPv4 / IPv6 addr
	status = netlink_parse(fd, seq, netlink_parse_interface_address, iface_list);
	close(fd);
	if(status != 0) {
        if (*iface_list)
		    free(*iface_list);
		*iface_list = NULL;

	}

	return status;

}

void address_calculate_netmask(struct iface_address *address, int ifa_prefixlen) {

	if (address->family == AF_INET6) {
		// if netmask is FFFFFFFF FFFFFFFF 00000000 00000000 (/64), netmask6.a1 and netmask6.a2 == 0xffffffff, and nestmask6.a3 and .a4 == 0
		// netmask6 is no longer set to 0 at the beginning of the function,  need to reset the values to 0
		// XXX really ugly, but works
		memset(&address->nm.netmask6, 0, sizeof(__u128));
		if (ifa_prefixlen >= 96) {
			address->nm.netmask6.a4 = (ifa_prefixlen == 96) ? 0 : htonl(0xffffffff<<(32-(ifa_prefixlen%32)));
			address->nm.netmask6.a1 = address->nm.netmask6.a2 = address->nm.netmask6.a3 =  0xffffffff;
		}
		else if (ifa_prefixlen >= 64) {
			address->nm.netmask6.a3 = (ifa_prefixlen == 64) ? 0 : htonl(0xffffffff<<(32-(ifa_prefixlen%32)));
			address->nm.netmask6.a1 = address->nm.netmask6.a2 =  0xffffffff;
		}
		else if (ifa_prefixlen >= 32) {
			address->nm.netmask6.a2 = (ifa_prefixlen == 32) ? 0 : htonl(0xffffffff<<(32-(ifa_prefixlen%32)));
			address->nm.netmask6.a1 = 0xffffffff;
		}
		else
			address->nm.netmask6.a1 = (ifa_prefixlen == 0) ? 0 : htonl(0xffffffff<<(32-ifa_prefixlen));
	}
	else {
		address->nm.netmask = (ifa_prefixlen == 0) ? 0 : htonl(0xffffffff<<(32-ifa_prefixlen));
	}
}


void iface_entry_append_address(struct iface_entry *iface, struct iface_address *address) {
	iface->addr_count++;
	iface->addr_list = realloc(iface->addr_list, sizeof(struct iface_address) * iface->addr_count);
	dprintf("Realloc'd %p", iface->addr_list);

	memcpy(&iface->addr_list[iface->addr_count-1], address, sizeof(struct iface_address));
}