Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Mon, 28 Jul 2014 11:13:27 +0300
From: Timo Teras <timo.teras@....fi>
To: Rich Felker <dalias@...c.org>
Cc: musl@...ts.openwall.com
Subject: Re: Reviewing if_nameindex and getifaddrs patch

On Sun, 27 Jul 2014 20:49:06 -0400
Rich Felker <dalias@...c.org> wrote:

> In regards to:
> 
> http://git.alpinelinux.org/cgit/aports/plain/main/musl/1002-reimplement-if_nameindex-and-getifaddrs-using-netlin.patch?id=3227b4ad816f850f655b6f44dc497926cb2cdcd1

Updated patch in the end.

> I don't see any remaining major issues except that it would obviously
> be nice if this could be smaller. A few minor details:
> 
> For __netlink.h, I'd been asking whether everything in this file is
> used. I did some tests just commenting things out and only found a few
> items that could be removed:
> 
> //#define NLM_F_MULTI   2
> //#define NLM_F_ACK     4
> //#define NLM_F_ATOMIC  0x400
> //#define NLMSG_NOOP    0x1
> //#define NLMSG_OVERRUN 0x4
> //#define RTM_NEWADDR   20
> 
> I don't think it makes sense to selectively omit such a small set of
> lines, so I'm fine with just leaving the unneeded ones

Yes, I wanted to add full "groups" of defines in those cases.

> One thing that seems rather out-of-place is:
> 
> #define IFADDRS_HASH_SIZE 64
> 
> This seems to be an implementation detail of the two functions using
> netlink and not part of the netlink API (either the kernel's API or
> musl's internal __rtnetlink_enumerate API for it). So I think it would
> make sense to move this into the files that use it.

Done. The only reason I put it into the header, as it affects the
internal netlink parsing code in two .c files. So I would've preferred
to have one #define instead of two.

> In general, static functions need not/should not have __-prefixed
> names. It's not really a problem but it makes it less obvious that
> they're static. It would be nice to change these and perhaps give them
> slightly more descriptive names (although admittedly that hasn't been
> done elsewhere in musl) just so they make sense in a debugger, etc.)

Names updated.

> I really don't understand the 'hash' logic for getifaddrs yet, but the
> function seems to work. Some general description of what data the
> callback receives and what it's doing with it could be helpful for
> reviewing this part of the patch.

The rtnetlink enumerate does two netlink 'dumps'. One of 'link' type
that dumps the physical interfaces, and one of 'addr' type that lists
all network addresses.

The 'link' dump is used to get the PF_PACKET ifaddrs with mac
addresses, as well as to get the ifindex<->ifname mappings and the
logical flags of each physical interface (SIOCGIFFLAGS equivalent).
These put into the hash based on ifindex.

The 'addr' dump then does the ipv4/ipv6 address (easy to extend if we
need to support something new in future). These lookup the physical
'link' info using ifindex. This is needed to fill ifa_flags with valid
data. It is also used to lookup the interface in non-ipv4 case. (In
ipv4 case it seems that kernel always sends IFA_LABEL containing the
real or aliased interface name. Though this probably might change if
the alias interface system is removed at some point.)

> Finally, I've been trying to reduce the unnecessary usage of
> __-prefixed filenames for implementation-internal purposes, except
> when they implement a function whose name is __-prefixed. So perhaps
> netlink.h and netlink.c, or netlink.h and __rtnetlink_enumerate.c,
> would be better names for these files (I tend to prefer netlink.c I
> think).

Done.

> For if_nameindex, we discussed the 'hash' buckets on IRC and whether
> they are necessary. I noted that it would be nice if there were a way
> to build the result array directly in the callback, but this is
> difficult without breaking up the allocation into lots of small pieces
> since there are strings that need to be located outside of the array,
> and "relocating" them on realloc requires nontrivial code. So I'm not
> sure if there's anything to improve here, but it's an idea someone
> else could look at.

The hash in if_indexname() is used to suppress duplicates
ifindex<->ifname mappings. This could happen for IPv4 addresses due to
kernel reporting IFA_LABEL always. Even if we parsed AF_INET only for
'addr' messages, this would be the case, because an interface can have
multiple address ('ip addr add' or 'ifconfig ethX add') that are not on
alias interface - in that case IFA_LABEL is the interface name.

-Timo

Updated patch:

From 4788fd92ed8c83bae2f1b429c6ea370fdf557dff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20Ter=C3=A4s?= <timo.teras@....fi>
Date: Tue, 8 Apr 2014 14:03:16 +0000
Subject: [PATCH] reimplement if_nameindex and getifaddrs using netlink

---
 src/network/getifaddrs.c   | 314 ++++++++++++++++++++++++---------------------
 src/network/if_nameindex.c | 132 +++++++++++++------
 src/network/netlink.c      |  52 ++++++++
 src/network/netlink.h      |  94 ++++++++++++++
 4 files changed, 407 insertions(+), 185 deletions(-)
 create mode 100644 src/network/netlink.c
 create mode 100644 src/network/netlink.h

diff --git a/src/network/getifaddrs.c b/src/network/getifaddrs.c
index 5a94cc7..89a8f72 100644
--- a/src/network/getifaddrs.c
+++ b/src/network/getifaddrs.c
@@ -1,181 +1,203 @@
-/* (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 <string.h>
+#include <stdlib.h>
 #include <unistd.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
+#include <ifaddrs.h>
+#include <syscall.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include "netlink.h"
 
-typedef union {
-	struct sockaddr_in6 v6;
+#define IFADDRS_HASH_SIZE 64
+
+/* getifaddrs() reports hardware addresses with PF_PACKET that implies
+ * struct sockaddr_ll.  But e.g. Infiniband socket address length is
+ * longer than sockaddr_ll.ssl_addr[8] can hold. Use this hack struct
+ * to extend ssl_addr - callers should be able to still use it. */
+struct sockaddr_ll_hack {
+	unsigned short sll_family, sll_protocol;
+	int sll_ifindex;
+	unsigned short sll_hatype;
+	unsigned char sll_pkttype, sll_halen;
+	unsigned char sll_addr[24];
+};
+
+union sockany {
+	struct sockaddr sa;
+	struct sockaddr_ll_hack ll;
 	struct sockaddr_in v4;
-} soa;
+	struct sockaddr_in6 v6;
+};
 
-typedef struct ifaddrs_storage {
+struct ifaddrs_storage {
 	struct ifaddrs ifa;
-	soa addr;
-	soa netmask;
-	soa dst;
+	struct ifaddrs_storage *hash_next;
+	union sockany addr, netmask, ifu;
+	unsigned int index;
 	char name[IFNAMSIZ+1];
-} stor;
-#define next ifa.ifa_next
+};
 
-static stor* list_add(stor** list, stor** head, char* ifname)
+struct ifaddrs_ctx {
+	struct ifaddrs_storage *first;
+	struct ifaddrs_storage *last;
+	struct ifaddrs_storage *hash[IFADDRS_HASH_SIZE];
+};
+
+void freeifaddrs(struct ifaddrs *ifp)
 {
-	stor* curr = calloc(1, sizeof(stor));
-	if(curr) {
-		strcpy(curr->name, ifname);
-		curr->ifa.ifa_name = curr->name;
-		if(*head) (*head)->next = (struct ifaddrs*) curr;
-		*head = curr;
-		if(!*list) *list = curr;
+	struct ifaddrs *n;
+	while (ifp) {
+		n = ifp->ifa_next;
+		free(ifp);
+		ifp = n;
 	}
-	return curr;
 }
 
-void freeifaddrs(struct ifaddrs *ifp)
+static void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex)
 {
-	stor *head = (stor *) ifp;
-	while(head) {
-		void *p = head;
-		head = (stor *) head->next;
-		free(p);
+	uint8_t *dst;
+	int len;
+
+	switch (af) {
+	case AF_INET:
+		dst = (uint8_t*) &sa->v4.sin_addr;
+		len = 4;
+		break;
+	case AF_INET6:
+		dst = (uint8_t*) &sa->v6.sin6_addr;
+		len = 16;
+		if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr))
+			sa->v6.sin6_scope_id = ifindex;
+		break;
+	default:
+		return;
 	}
+	if (addrlen < len) return;
+	sa->sa.sa_family = af;
+	memcpy(dst, addr, len);
+	*r = &sa->sa;
 }
 
-static void ipv6netmask(unsigned prefix_length, struct sockaddr_in6 *sa)
+static void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen)
 {
-	unsigned char* hb = sa->sin6_addr.s6_addr;
-	unsigned onebytes = prefix_length / 8;
-	unsigned bits = prefix_length % 8;
-	unsigned nullbytes = 16 - onebytes;
-	memset(hb, -1, onebytes);
-	memset(hb+onebytes, 0, nullbytes);
-	if(bits) {
-		unsigned char x = -1;
-		x <<= 8 - bits;
-		hb[onebytes] = x;
-	}
+	uint8_t addr[16] = {0};
+	int i;
+
+	if (prefixlen > 8*sizeof(addr)) prefixlen = 8*sizeof(addr);
+	i = prefixlen / 8;
+	memset(addr, 0xff, i);
+	if (i < sizeof(addr)) addr[i++] = 0xff << (8 - (prefixlen % 8));
+	copy_addr(r, af, sa, addr, sizeof(addr), 0);
 }
 
-static void dealwithipv6(stor **list, stor** head)
+static void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype)
 {
-	FILE* f = fopen("/proc/net/if_inet6", "rbe");
-	/* 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;
-				stor* curr = list_add(list, head, name);
-				if(!curr) goto out;
-				curr->addr.v6 = sa;
-				curr->ifa.ifa_addr = (struct sockaddr*) &curr->addr;
-				ipv6netmask(c, &sa);
-				curr->netmask.v6 = sa;
-				curr->ifa.ifa_netmask = (struct sockaddr*) &curr->netmask;
-				/* find ipv4 struct with the same interface name to copy flags */
-				stor* scan = *list;
-				for(;scan && strcmp(name, scan->name);scan=(stor*)scan->next);
-				if(scan) curr->ifa.ifa_flags = scan->ifa.ifa_flags;
-				else curr->ifa.ifa_flags = 0;
-			} else errno = 0;
-		}
-	}
-	out:
-	fclose(f);
+	if (addrlen > sizeof(sa->ll.sll_addr)) return;
+	sa->ll.sll_family = AF_PACKET;
+	sa->ll.sll_ifindex = ifindex;
+	sa->ll.sll_hatype = hatype;
+	sa->ll.sll_halen = addrlen;
+	memcpy(sa->ll.sll_addr, addr, addrlen);
+	*r = &sa->sa;
 }
 
-int getifaddrs(struct ifaddrs **ifap)
+static int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h)
 {
-	stor *list = 0, *head = 0;
-	struct if_nameindex* ii = if_nameindex();
-	if(!ii) return -1;
-	size_t i;
-	for(i = 0; ii[i].if_index || ii[i].if_name; i++) {
-		stor* curr = list_add(&list, &head, ii[i].if_name);
-		if(!curr) {
-			if_freenameindex(ii);
-			goto err2;
+	struct ifaddrs_ctx *ctx = pctx;
+	struct ifaddrs_storage *ifs, *ifs0;
+	struct ifinfomsg *ifi = NLMSG_DATA(h);
+	struct ifaddrmsg *ifa = NLMSG_DATA(h);
+	struct rtattr *rta;
+	int stats_len = 0;
+
+	if (h->nlmsg_type == RTM_NEWLINK) {
+		for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {
+			if (rta->rta_type != IFLA_STATS) continue;
+			stats_len = RTA_DATALEN(rta);
+			break;
 		}
+	} else {
+		for (ifs0 = ctx->hash[ifa->ifa_index % IFADDRS_HASH_SIZE]; ifs0; ifs0 = ifs0->hash_next)
+			if (ifs0->index == ifa->ifa_index)
+				break;
+		if (!ifs0) return 0;
 	}
-	if_freenameindex(ii);
-
-	int sock = socket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 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;
-	size_t reqitems = conf.ifc_len / sizeof(struct ifreq);
-	for(head = list; head; head = (stor*)head->next) {
-		for(i = 0; i < reqitems; i++) {
-			// get SIOCGIFADDR of active interfaces.
-			if(!strcmp(reqs[i].ifr_name, head->name)) {
-				head->addr.v4 = *(struct sockaddr_in*)&reqs[i].ifr_addr;
-				head->ifa.ifa_addr = (struct sockaddr*) &head->addr;
+
+	ifs = calloc(1, sizeof(struct ifaddrs_storage) + stats_len);
+	if (ifs == 0) return -1;
+
+	if (h->nlmsg_type == RTM_NEWLINK) {
+		ifs->index = ifi->ifi_index;
+		ifs->ifa.ifa_flags = ifi->ifi_flags;
+
+		for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {
+			switch (rta->rta_type) {
+			case IFLA_IFNAME:
+				if (RTA_DATALEN(rta) < sizeof(ifs->name)) {
+					memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta));
+					ifs->ifa.ifa_name = ifs->name;
+				}
+				break;
+			case IFLA_ADDRESS:
+				copy_lladdr(&ifs->ifa.ifa_addr, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type);
+				break;
+			case IFLA_BROADCAST:
+				copy_lladdr(&ifs->ifa.ifa_broadaddr, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type);
+				break;
+			case IFLA_STATS:
+				ifs->ifa.ifa_data = (void*)(ifs+1);
+				memcpy(ifs->ifa.ifa_data, RTA_DATA(rta), RTA_DATALEN(rta));
 				break;
 			}
 		}
-		struct ifreq req;
-		snprintf(req.ifr_name, sizeof req.ifr_name, "%s", head->name);
-		if(-1 == ioctl(sock, SIOCGIFFLAGS, &req)) goto err;
-
-		head->ifa.ifa_flags = req.ifr_flags;
-		if(head->ifa.ifa_addr) {
-			/* or'ing flags with IFF_LOWER_UP on active interfaces to mimic glibc */
-			head->ifa.ifa_flags |= IFF_LOWER_UP; 
-			if(-1 == ioctl(sock, SIOCGIFNETMASK, &req)) goto err;
-			head->netmask.v4 = *(struct sockaddr_in*)&req.ifr_netmask;
-			head->ifa.ifa_netmask = (struct sockaddr*) &head->netmask;
-	
-			if(head->ifa.ifa_flags & IFF_POINTOPOINT) {
-				if(-1 == ioctl(sock, SIOCGIFDSTADDR, &req)) goto err;
-				head->dst.v4 = *(struct sockaddr_in*)&req.ifr_dstaddr;
-			} else {
-				if(-1 == ioctl(sock, SIOCGIFBRDADDR, &req)) goto err;
-				head->dst.v4 = *(struct sockaddr_in*)&req.ifr_broadaddr;
+		if (ifs->ifa.ifa_name) {
+			unsigned int bucket = ifs->index % IFADDRS_HASH_SIZE;
+			ifs->hash_next = ctx->hash[bucket];
+			ctx->hash[bucket] = ifs;
+		}
+	} else {
+		ifs->ifa.ifa_name = ifs0->ifa.ifa_name;
+		ifs->ifa.ifa_flags = ifs0->ifa.ifa_flags;
+		for (rta = NLMSG_RTA(h, sizeof(*ifa)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {
+			switch (rta->rta_type) {
+			case IFA_ADDRESS:
+				copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);
+				break;
+			case IFA_BROADCAST:
+				/* For point-to-point links this is peer, but ifa_broadaddr
+				 * and ifa_dstaddr are union, so this works for both.  */
+				copy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index);
+				break;
+			case IFA_LABEL:
+				if (RTA_DATALEN(rta) < sizeof(ifs->name)) {
+					memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta));
+					ifs->ifa.ifa_name = ifs->name;
+				}
+				break;
 			}
-			head->ifa.ifa_ifu.ifu_dstaddr = (struct sockaddr*) &head->dst;
 		}
+		if (ifs->ifa.ifa_addr)
+			gen_netmask(&ifs->ifa.ifa_netmask, ifa->ifa_family, &ifs->netmask, ifa->ifa_prefixlen);
+	}
+
+	if (ifs->ifa.ifa_name) {
+		if (!ctx->first) ctx->first = ifs;
+		if (ctx->last) ctx->last->ifa.ifa_next = &ifs->ifa;
+		ctx->last = ifs;
+	} else {
+		free(ifs);
 	}
-	close(sock);
-	void* last = 0;
-	for(head = list; head; head=(stor*)head->next) last=head;
-	head = last;
-	dealwithipv6(&list, &head);
-	*ifap = (struct ifaddrs*) list;
 	return 0;
-	err:
-	close(sock);
-	err2:
-	freeifaddrs((struct ifaddrs*) list);
-	return -1;
 }
 
+int getifaddrs(struct ifaddrs **ifap)
+{
+	struct ifaddrs_ctx _ctx, *ctx = &_ctx;
+	int r;
+	memset(ctx, 0, sizeof *ctx);
+	r = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx);
+	if (r == 0) *ifap = &ctx->first->ifa;
+	else freeifaddrs(&ctx->first->ifa);
+	return r;
+}
diff --git a/src/network/if_nameindex.c b/src/network/if_nameindex.c
index 53b80b2..f8dda54 100644
--- a/src/network/if_nameindex.c
+++ b/src/network/if_nameindex.c
@@ -1,55 +1,109 @@
 #define _GNU_SOURCE
 #include <net/if.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
 #include <errno.h>
-#include "syscall.h"
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include "netlink.h"
 
-static void *do_nameindex(int s, size_t n)
-{
-	size_t i, len, k;
-	struct ifconf conf;
-	struct if_nameindex *idx;
+#define IFADDRS_HASH_SIZE 64
 
-	idx = malloc(n * (sizeof(struct if_nameindex)+sizeof(struct ifreq)));
-	if (!idx) return 0;
+struct ifnamemap {
+	unsigned int hash_next;
+	unsigned int index;
+	unsigned char namelen;
+	char name[IFNAMSIZ];
+};
 
-	conf.ifc_buf = (void *)&idx[n];
-	conf.ifc_len = len = n * sizeof(struct ifreq);
-	if (ioctl(s, SIOCGIFCONF, &conf) < 0) {
-		free(idx);
-		return 0;
-	}
-	if (conf.ifc_len == len) {
-		free(idx);
-		return (void *)-1;
+struct ifnameindexctx {
+	unsigned int num, allocated, str_bytes;
+	struct ifnamemap *list;
+	unsigned int hash[IFADDRS_HASH_SIZE];
+};
+
+static int netlink_msg_to_nameindex(void *pctx, struct nlmsghdr *h)
+{
+	struct ifnameindexctx *ctx = pctx;
+	struct ifnamemap *map;
+	struct rtattr *rta;
+	unsigned int i;
+	int index, type, namelen, bucket;
+
+	if (h->nlmsg_type == RTM_NEWLINK) {
+		struct ifinfomsg *ifi = NLMSG_DATA(h);
+		index = ifi->ifi_index;
+		type = IFLA_IFNAME;
+		rta = NLMSG_RTA(h, sizeof(*ifi));
+	} else {
+		struct ifaddrmsg *ifa = NLMSG_DATA(h);
+		index = ifa->ifa_index;
+		type = IFA_LABEL;
+		rta = NLMSG_RTA(h, sizeof(*ifa));
 	}
+	for (; NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) {
+		if (rta->rta_type != type) continue;
 
-	n = conf.ifc_len / sizeof(struct ifreq);
-	for (i=k=0; i<n; i++) {
-		if (ioctl(s, SIOCGIFINDEX, &conf.ifc_req[i]) < 0) {
-			k++;
-			continue;
+		namelen = RTA_DATALEN(rta) - 1;
+		if (namelen > IFNAMSIZ) return 0;
+
+		/* suppress duplicates */
+		bucket = index % IFADDRS_HASH_SIZE;
+		i = ctx->hash[bucket];
+		while (i) {
+			map = &ctx->list[i-1];
+			if (map->index == index &&
+			    map->namelen == namelen &&
+			    memcmp(map->name, RTA_DATA(rta), namelen) == 0)
+				return 0;
+			i = map->hash_next;
 		}
-		idx[i-k].if_index = conf.ifc_req[i].ifr_ifindex;
-		idx[i-k].if_name = conf.ifc_req[i].ifr_name;
-	}
-	idx[i-k].if_name = 0;
-	idx[i-k].if_index = 0;
 
-	return idx;
+		if (ctx->num >= ctx->allocated) {
+			size_t a = ctx->allocated ? ctx->allocated * 2 + 1 : 8;
+			if (a > SIZE_MAX/sizeof *map) return -1;
+			map = realloc(ctx->list, a * sizeof *map);
+			if (!map) return -1;
+			ctx->list = map;
+			ctx->allocated = a;
+		}
+		map = &ctx->list[ctx->num];
+		map->index = index;
+		map->namelen = namelen;
+		memcpy(map->name, RTA_DATA(rta), namelen);
+		ctx->str_bytes += namelen + 1;
+		ctx->num++;
+		map->hash_next = ctx->hash[bucket];
+		ctx->hash[bucket] = ctx->num;
+		return 0;
+	}
+	return 0;
 }
 
 struct if_nameindex *if_nameindex()
 {
-	size_t n;
-	void *p = 0;
-	int s = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
-	if (s>=0) {
-		for (n=0; (p=do_nameindex(s, n)) == (void *)-1; n++);
-		__syscall(SYS_close, s);
+	struct ifnameindexctx _ctx, *ctx = &_ctx;
+	struct if_nameindex *ifs = 0, *d;
+	struct ifnamemap *s;
+	char *p;
+	int i;
+
+	memset(ctx, 0, sizeof(*ctx));
+	if (__rtnetlink_enumerate(AF_UNSPEC, AF_INET, netlink_msg_to_nameindex, ctx) < 0) goto err;
+
+	ifs = malloc(sizeof(struct if_nameindex[ctx->num+1]) + ctx->str_bytes);
+	if (!ifs) goto err;
+
+	p = (char*)(ifs + ctx->num + 1);
+	for (i = ctx->num, d = ifs, s = ctx->list; i; i--, s++, d++) {
+		d->if_index = s->index;
+		d->if_name = p;
+		memcpy(p, s->name, s->namelen);
+		p += s->namelen;
+		*p++ = 0;
 	}
-	errno = ENOBUFS;
-	return p;
+	d->if_index = 0;
+	d->if_name = 0;
+err:
+	free(ctx->list);
+	return ifs;
 }
diff --git a/src/network/netlink.c b/src/network/netlink.c
new file mode 100644
index 0000000..94dba7f
--- /dev/null
+++ b/src/network/netlink.c
@@ -0,0 +1,52 @@
+#include <errno.h>
+#include <string.h>
+#include <syscall.h>
+#include <sys/socket.h>
+#include "netlink.h"
+
+static int __netlink_enumerate(int fd, unsigned int seq, int type, int af,
+	int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx)
+{
+	struct nlmsghdr *h;
+	union {
+		uint8_t buf[8192];
+		struct {
+			struct nlmsghdr nlh;
+			struct rtgenmsg g;
+		} req;
+		struct nlmsghdr reply;
+	} u;
+	int r, ret;
+
+	memset(&u.req, 0, sizeof(u.req));
+	u.req.nlh.nlmsg_len = sizeof(u.req);
+	u.req.nlh.nlmsg_type = type;
+	u.req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+	u.req.nlh.nlmsg_seq = seq;
+	u.req.g.rtgen_family = af;
+	r = send(fd, &u.req, sizeof(u.req), 0);
+	if (r < 0) return r;
+
+	while (1) {
+		r = recv(fd, u.buf, sizeof(u.buf), MSG_DONTWAIT);
+		if (r <= 0) return -1;
+		for (h = &u.reply; NLMSG_OK(h, (void*)&u.buf[r]); h = NLMSG_NEXT(h)) {
+			if (h->nlmsg_type == NLMSG_DONE) return 0;
+			if (h->nlmsg_type == NLMSG_ERROR) return -1;
+			ret = cb(ctx, h);
+			if (ret) return ret;
+		}
+	}
+}
+
+int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx)
+{
+	int fd, r;
+
+	fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
+	if (fd < 0) return -1;
+	r = __netlink_enumerate(fd, 1, RTM_GETLINK, link_af, cb, ctx);
+	if (!r) r = __netlink_enumerate(fd, 2, RTM_GETADDR, addr_af, cb, ctx);
+	__syscall(SYS_close,fd);
+	return r;
+}
diff --git a/src/network/netlink.h b/src/network/netlink.h
new file mode 100644
index 0000000..20700ac
--- /dev/null
+++ b/src/network/netlink.h
@@ -0,0 +1,94 @@
+#include <stdint.h>
+
+/* linux/netlink.h */
+
+#define NETLINK_ROUTE 0
+
+struct nlmsghdr {
+	uint32_t	nlmsg_len;
+	uint16_t	nlmsg_type;
+	uint16_t	nlmsg_flags;
+	uint32_t	nlmsg_seq;
+	uint32_t	nlmsg_pid;
+};
+
+#define NLM_F_REQUEST	1
+#define NLM_F_MULTI	2
+#define NLM_F_ACK	4
+
+#define NLM_F_ROOT	0x100
+#define NLM_F_MATCH	0x200
+#define NLM_F_ATOMIC	0x400
+#define NLM_F_DUMP	(NLM_F_ROOT|NLM_F_MATCH)
+
+#define NLMSG_NOOP	0x1
+#define NLMSG_ERROR	0x2
+#define NLMSG_DONE	0x3
+#define NLMSG_OVERRUN	0x4
+
+/* linux/rtnetlink.h */
+
+#define RTM_NEWLINK	16
+#define RTM_GETLINK	18
+#define RTM_NEWADDR	20
+#define RTM_GETADDR	22
+
+struct rtattr {
+	unsigned short	rta_len;
+	unsigned short	rta_type;
+};
+
+struct rtgenmsg {
+	unsigned char	rtgen_family;
+};
+
+struct ifinfomsg {
+	unsigned char	ifi_family;
+	unsigned char	__ifi_pad;
+	unsigned short	ifi_type;
+	int		ifi_index;
+	unsigned	ifi_flags;
+	unsigned	ifi_change;
+};
+
+/* linux/if_link.h */
+
+#define IFLA_ADDRESS	1
+#define IFLA_BROADCAST	2
+#define IFLA_IFNAME	3
+#define IFLA_STATS	7
+
+/* linux/if_addr.h */
+
+struct ifaddrmsg {
+	uint8_t		ifa_family;
+	uint8_t		ifa_prefixlen;
+	uint8_t		ifa_flags;
+	uint8_t		ifa_scope;
+	uint32_t	ifa_index;
+};
+
+#define IFA_ADDRESS	1
+#define IFA_LOCAL	2
+#define IFA_LABEL	3
+#define IFA_BROADCAST	4
+
+/* musl */
+
+#define NETLINK_ALIGN(len)	(((len)+3) & ~3)
+#define NLMSG_DATA(nlh)		((void*)((char*)(nlh)+sizeof(struct nlmsghdr)))
+#define NLMSG_DATALEN(nlh)	((nlh)->nlmsg_len-sizeof(struct nlmsghdr))
+#define NLMSG_DATAEND(nlh)	((char*)(nlh)+(nlh)->nlmsg_len)
+#define NLMSG_NEXT(nlh)		(struct nlmsghdr*)((char*)(nlh)+NETLINK_ALIGN((nlh)->nlmsg_len))
+#define NLMSG_OK(nlh,end)	((char*)(end)-(char*)(nlh) >= sizeof(struct nlmsghdr))
+
+#define RTA_DATA(rta)		((void*)((char*)(rta)+sizeof(struct rtattr)))
+#define RTA_DATALEN(rta)	((rta)->rta_len-sizeof(struct rtattr))
+#define RTA_DATAEND(rta)	((char*)(rta)+(rta)->rta_len)
+#define RTA_NEXT(rta)		(struct rtattr*)((char*)(rta)+NETLINK_ALIGN((rta)->rta_len))
+#define RTA_OK(nlh,end)		((char*)(end)-(char*)(rta) >= sizeof(struct rtattr))
+
+#define NLMSG_RTA(nlh,len)	((void*)((char*)(nlh)+sizeof(struct nlmsghdr)+NETLINK_ALIGN(len)))
+#define NLMSG_RTAOK(rta,nlh)	RTA_OK(rta,NLMSG_DATAEND(nlh))
+
+int __rtnetlink_enumerate(int link_af, int addr_af, int (*cb)(void *ctx, struct nlmsghdr *h), void *ctx);
-- 
2.0.3

Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.