add getifaddrs
authorrofl0r <retnyg@gmx.net>
Fri, 5 Apr 2013 17:26:51 +0000 (19:26 +0200)
committerrofl0r <retnyg@gmx.net>
Fri, 5 Apr 2013 17:36:51 +0000 (19:36 +0200)
supports ipv4 and ipv6, but not the "extended" usage where
usage statistics and other info are assigned to ifa_data members
of duplicate entries with AF_PACKET family.

include/ifaddrs.h [new file with mode: 0644]
src/network/getifaddrs.c [new file with mode: 0644]

diff --git a/include/ifaddrs.h b/include/ifaddrs.h
new file mode 100644 (file)
index 0000000..96b5ae9
--- /dev/null
@@ -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 (file)
index 0000000..d96d109
--- /dev/null
@@ -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;
+}
+