about summary refs log tree commit diff
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/getifaddrs.c191
1 files changed, 191 insertions, 0 deletions
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;
+}
+