getifaddrs: implement proper ipv6 netmasks
[musl] / src / network / getifaddrs.c
index d96d109..a4c3178 100644 (file)
 #include <unistd.h>
 #include <sys/ioctl.h>
 
 #include <unistd.h>
 #include <sys/ioctl.h>
 
-static struct ifaddrs* list_add(struct ifaddrs** list, struct ifaddrs** head, char* ifname)
+typedef union {
+       struct sockaddr_in6 v6;
+       struct sockaddr_in v4;
+} soa;
+
+typedef struct ifaddrs_storage {
+       struct ifaddrs ifa;
+       soa addr;
+       soa netmask;
+       soa dst;
+       char name[IFNAMSIZ+1];
+} stor;
+#define next ifa.ifa_next
+
+static stor* list_add(stor** list, stor** head, char* ifname)
 {
 {
-       struct ifaddrs* curr = calloc(1, sizeof(struct ifaddrs));
+       stor* curr = calloc(1, sizeof(stor));
        if(curr) {
        if(curr) {
-               curr->ifa_name = strdup(ifname);
-               if(!curr->ifa_name) {
-                       free(curr);
-                       curr = 0;
-                       goto out;
-               }
-               if(*head) (*head)->ifa_next = curr;
+               strcpy(curr->name, ifname);
+               curr->ifa.ifa_name = curr->name;
+               if(*head) (*head)->next = (struct ifaddrs*) curr;
                *head = curr;
                if(!*list) *list = curr;
        }
                *head = curr;
                if(!*list) *list = curr;
        }
-       out:
        return curr;
 }
 
 void freeifaddrs(struct ifaddrs *ifp)
 {
        return curr;
 }
 
 void freeifaddrs(struct ifaddrs *ifp)
 {
-       struct ifaddrs *head = ifp;
+       stor *head = (stor *) ifp;
        while(head) {
        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;
                void *p = head;
-               head = head->ifa_next;
+               head = (stor *) head->next;
                free(p);
        }
 }
 
                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)
 {
 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));
+       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;
+       }
 }
 
 }
 
-static void dealwithipv6(struct ifaddrs **list, struct ifaddrs** head)
+static void dealwithipv6(stor **list, stor** head)
 {
        FILE* f = fopen("/proc/net/if_inet6", "r");
        /* 00000000000000000000000000000001 01 80 10 80 lo
 {
        FILE* f = fopen("/proc/net/if_inet6", "r");
        /* 00000000000000000000000000000001 01 80 10 80 lo
@@ -94,16 +93,18 @@ static void dealwithipv6(struct ifaddrs **list, struct ifaddrs** head)
                        struct sockaddr_in6 sa = {0};
                        if(1 == inet_pton(AF_INET6, v6conv, &sa.sin6_addr)) {
                                sa.sin6_family = AF_INET6;
                        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);
+                               stor* curr = list_add(list, head, name);
                                if(!curr) goto out;
                                if(!curr) goto out;
-                               curr->ifa_addr = sockaddr_in6_dup(&sa);
+                               curr->addr.v6 = sa;
+                               curr->ifa.ifa_addr = (struct sockaddr*) &curr->addr;
                                ipv6netmask(c, &sa);
                                ipv6netmask(c, &sa);
-                               curr->ifa_netmask = sockaddr_in6_dup(&sa);
+                               curr->netmask.v6 = sa;
+                               curr->ifa.ifa_netmask = (struct sockaddr*) &curr->netmask;
                                /* find ipv4 struct with the same interface name to copy flags */
                                /* 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;
+                               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;
                }
        }
                        } else errno = 0;
                }
        }
@@ -113,79 +114,67 @@ static void dealwithipv6(struct ifaddrs **list, struct ifaddrs** head)
 
 int getifaddrs(struct ifaddrs **ifap)
 {
 
 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;
-                       }
+       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;
                }
        }
                }
        }
-       fclose(f);
+       if_freenameindex(ii);
 
        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;
 
        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;
-                               }
+       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;
+                               break;
                        }
                        }
-                       struct ifreq req;
-                       snprintf(req.ifr_name, sizeof req.ifr_name, "%s", head->ifa_name);
-                       if(-1 == ioctl(sock, SIOCGIFFLAGS, &req)) goto err;
+               }
+               struct ifreq req;
+               snprintf(req.ifr_name, sizeof req.ifr_name, "%s", head->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);
-                               }
+               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;
                        }
                        }
+                       head->ifa.ifa_ifu.ifu_dstaddr = (struct sockaddr*) &head->dst;
                }
        }
        close(sock);
        void* last = 0;
                }
        }
        close(sock);
        void* last = 0;
-       for(head = list; head; head=head->ifa_next) last=head;
+       for(head = list; head; head=(stor*)head->next) last=head;
        head = last;
        dealwithipv6(&list, &head);
        head = last;
        dealwithipv6(&list, &head);
-       *ifap = list;
+       *ifap = (struct ifaddrs*) list;
        return 0;
        err:
        close(sock);
        err2:
        return 0;
        err:
        close(sock);
        err2:
-       freeifaddrs(list);
+       freeifaddrs((struct ifaddrs*) list);
        return -1;
 }
 
        return -1;
 }