diff options
| -rw-r--r-- | include/ifaddrs.h | 31 | ||||
| -rw-r--r-- | src/network/getifaddrs.c | 191 | 
2 files changed, 222 insertions, 0 deletions
| diff --git a/include/ifaddrs.h b/include/ifaddrs.h new file mode 100644 index 00000000..96b5ae90 --- /dev/null +++ b/include/ifaddrs.h @@ -0,0 +1,31 @@ +#ifndef _IFADDRS_H +#define _IFADDRS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <features.h> +#include <netinet/in.h> + +struct ifaddrs { +	struct ifaddrs *ifa_next; +	char *ifa_name; +	unsigned ifa_flags; +	struct sockaddr *ifa_addr; +	struct sockaddr *ifa_netmask; +	union { +		struct sockaddr *ifu_broadaddr; +		struct sockaddr *ifu_dstaddr; +	} ifa_ifu; +	void *ifa_data; +}; +#define ifa_broadaddr ifa_ifu.ifu_broadaddr +#define ifa_dstaddr ifa_ifu.ifu_dstaddr + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/network/getifaddrs.c b/src/network/getifaddrs.c new file mode 100644 index 00000000..d96d1094 --- /dev/null +++ b/src/network/getifaddrs.c @@ -0,0 +1,191 @@ +/* (C) 2013 John Spencer. released under musl's standard MIT license. */ +#undef _GNU_SOURCE +#define _GNU_SOURCE +#include <ifaddrs.h> +#include <stdlib.h> +#include <net/if.h> /* IFNAMSIZ, ifreq, ifconf */ +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> /* inet_pton */ +#include <unistd.h> +#include <sys/ioctl.h> + +static struct ifaddrs* list_add(struct ifaddrs** list, struct ifaddrs** head, char* ifname) +{ +	struct ifaddrs* curr = calloc(1, sizeof(struct ifaddrs)); +	if(curr) { +		curr->ifa_name = strdup(ifname); +		if(!curr->ifa_name) { +			free(curr); +			curr = 0; +			goto out; +		} +		if(*head) (*head)->ifa_next = curr; +		*head = curr; +		if(!*list) *list = curr; +	} +	out: +	return curr; +} + +void freeifaddrs(struct ifaddrs *ifp) +{ +	struct ifaddrs *head = ifp; +	while(head) { +		free(head->ifa_name); +		free(head->ifa_addr); +		free(head->ifa_netmask); +		free(head->ifa_ifu.ifu_dstaddr); +		free(head->ifa_data); +		void *p = head; +		head = head->ifa_next; +		free(p); +	} +} + +static struct sockaddr *sockaddr_in_dup(struct sockaddr_in *src) +{ +	struct sockaddr_in *nu = malloc(sizeof(struct sockaddr_in)); +	if(nu) *nu = *src; +	return (struct sockaddr*) nu; +} + +static struct sockaddr *sockaddr_in6_dup(struct sockaddr_in6 *src) +{ +	struct sockaddr_in6 *nu = malloc(sizeof(struct sockaddr_in6)); +	if(nu) *nu = *src; +	return (struct sockaddr*) nu; +} + +static void ipv6netmask(unsigned prefix_length, struct sockaddr_in6 *sa) +{ +	// FIXME: left for bit-wizard rich +	memset(&sa->sin6_addr, -1, sizeof(sa->sin6_addr)); +} + +static void dealwithipv6(struct ifaddrs **list, struct ifaddrs** head) +{ +	FILE* f = fopen("/proc/net/if_inet6", "r"); +	/* 00000000000000000000000000000001 01 80 10 80 lo +	   A                                B  C  D  E  F +	   all numbers in hex +	   A = addr B=netlink device#, C=prefix length, +	   D = scope value (ipv6.h) E = interface flags (rnetlink.h, addrconf.c) +	   F = if name */ +	char v6conv[32 + 7 + 1], *v6; +	char *line, linebuf[512]; +	if(!f) return; +	while((line = fgets(linebuf, sizeof linebuf, f))) { +		v6 = v6conv; +		size_t i = 0; +		for(; i < 8; i++) { +			memcpy(v6, line, 4); +			v6+=4; +			*v6++=':'; +			line+=4; +		} +		--v6; *v6 = 0; +		line++; +		unsigned b, c, d, e; +		char name[IFNAMSIZ+1]; +		if(5 == sscanf(line, "%x %x %x %x %s", &b, &c, &d, &e, name)) { +			struct sockaddr_in6 sa = {0}; +			if(1 == inet_pton(AF_INET6, v6conv, &sa.sin6_addr)) { +				sa.sin6_family = AF_INET6; +				struct ifaddrs* curr = list_add(list, head, name); +				if(!curr) goto out; +				curr->ifa_addr = sockaddr_in6_dup(&sa); +				ipv6netmask(c, &sa); +				curr->ifa_netmask = sockaddr_in6_dup(&sa); +				/* find ipv4 struct with the same interface name to copy flags */ +				struct ifaddrs* scan = *list; +				for(;scan && strcmp(name, scan->ifa_name);scan=scan->ifa_next); +				if(scan) curr->ifa_flags=scan->ifa_flags; +				else curr->ifa_flags = 0; +			} else errno = 0; +		} +	} +	out: +	fclose(f); +} + +int getifaddrs(struct ifaddrs **ifap) +{ +	FILE* f = fopen("/proc/net/dev", "r"); +	/* the alternative to parsing /proc.. seems to be iterating +	   through the interfaces using an index number in ifreq.ifr_ifindex +	   until we get some error code back. the kernel will fill ifr_name field +	   for valid ifindices (SIOCGIFINDEX) */ +	if(!f) return -1; +	struct ifaddrs *list = 0, *head = 0; + +	char* line; char linebuf[512]; +	while((line = fgets(linebuf, sizeof linebuf, f))) { +		while(isspace(*line) && *line) line++; +		char* start = line; +		while(*line && isalnum(*line)) line++; +		if(line > start && *line == ':') { +			// found interface +			*line = 0; +			struct ifaddrs* curr = list_add(&list, &head, start); +			if(!curr) { +				fclose(f); +				goto err2; +			} +		} +	} +	fclose(f); + +	int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); +	if(sock == -1) goto err2; +	struct ifreq reqs[32]; /* arbitrary chosen boundary */ +	struct ifconf conf = {.ifc_len = sizeof reqs, .ifc_req = reqs}; +	if(-1 == ioctl(sock, SIOCGIFCONF, &conf)) goto err; +	else { +		size_t reqitems = conf.ifc_len / sizeof(struct ifreq); +		for(head = list; head; head=head->ifa_next) { +			size_t i; +			for(i = 0; i < reqitems; i++) { +				// get SIOCGIFADDR of active interfaces. +				if(!strcmp(reqs[i].ifr_name, head->ifa_name)) { +					head->ifa_addr = sockaddr_in_dup((struct sockaddr_in*) &reqs[i].ifr_addr); +					break; +				} +			} +			struct ifreq req; +			snprintf(req.ifr_name, sizeof req.ifr_name, "%s", head->ifa_name); +			if(-1 == ioctl(sock, SIOCGIFFLAGS, &req)) goto err; + +			head->ifa_flags = req.ifr_flags; +			if(head->ifa_addr) { +				/* or'ing flags with IFF_LOWER_UP on active interfaces to mimic glibc */ +				head->ifa_flags |= IFF_LOWER_UP;  +				if(-1 == ioctl(sock, SIOCGIFNETMASK, &req)) goto err; +				head->ifa_netmask = sockaddr_in_dup((struct sockaddr_in*) &req.ifr_netmask); +		 +				if(head->ifa_flags & IFF_POINTOPOINT) { +					if(-1 == ioctl(sock, SIOCGIFDSTADDR, &req)) goto err; +					head->ifa_ifu.ifu_dstaddr = sockaddr_in_dup((struct sockaddr_in*) &req.ifr_dstaddr); +				} else { +					if(-1 == ioctl(sock, SIOCGIFBRDADDR, &req)) goto err; +					head->ifa_ifu.ifu_broadaddr = sockaddr_in_dup((struct sockaddr_in*) &req.ifr_broadaddr); +				} +			} +		} +	} +	close(sock); +	void* last = 0; +	for(head = list; head; head=head->ifa_next) last=head; +	head = last; +	dealwithipv6(&list, &head); +	*ifap = list; +	return 0; +	err: +	close(sock); +	err2: +	freeifaddrs(list); +	return -1; +} + | 
