getifaddrs: one less indent level
[musl] / src / network / getifaddrs.c
1 /* (C) 2013 John Spencer. released under musl's standard MIT license. */
2 #undef _GNU_SOURCE
3 #define _GNU_SOURCE
4 #include <ifaddrs.h>
5 #include <stdlib.h>
6 #include <net/if.h> /* IFNAMSIZ, ifreq, ifconf */
7 #include <stdio.h>
8 #include <ctype.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <arpa/inet.h> /* inet_pton */
12 #include <unistd.h>
13 #include <sys/ioctl.h>
14
15 typedef union {
16         struct sockaddr_in6 v6;
17         struct sockaddr_in v4;
18 } soa;
19
20 typedef struct ifaddrs_storage {
21         struct ifaddrs ifa;
22         soa addr;
23         soa netmask;
24         soa dst;
25         char name[IFNAMSIZ+1];
26 } stor;
27 #define next ifa.ifa_next
28
29 static stor* list_add(stor** list, stor** head, char* ifname)
30 {
31         stor* curr = calloc(1, sizeof(stor));
32         if(curr) {
33                 strcpy(curr->name, ifname);
34                 curr->ifa.ifa_name = curr->name;
35                 if(*head) (*head)->next = (struct ifaddrs*) curr;
36                 *head = curr;
37                 if(!*list) *list = curr;
38         }
39         out:
40         return curr;
41 }
42
43 void freeifaddrs(struct ifaddrs *ifp)
44 {
45         stor *head = (stor *) ifp;
46         while(head) {
47                 void *p = head;
48                 head = (stor *) head->next;
49                 free(p);
50         }
51 }
52
53 static void ipv6netmask(unsigned prefix_length, struct sockaddr_in6 *sa)
54 {
55         // FIXME: left for bit-wizard rich
56         memset(&sa->sin6_addr, -1, sizeof(sa->sin6_addr));
57 }
58
59 static void dealwithipv6(stor **list, stor** head)
60 {
61         FILE* f = fopen("/proc/net/if_inet6", "r");
62         /* 00000000000000000000000000000001 01 80 10 80 lo
63            A                                B  C  D  E  F
64            all numbers in hex
65            A = addr B=netlink device#, C=prefix length,
66            D = scope value (ipv6.h) E = interface flags (rnetlink.h, addrconf.c)
67            F = if name */
68         char v6conv[32 + 7 + 1], *v6;
69         char *line, linebuf[512];
70         if(!f) return;
71         while((line = fgets(linebuf, sizeof linebuf, f))) {
72                 v6 = v6conv;
73                 size_t i = 0;
74                 for(; i < 8; i++) {
75                         memcpy(v6, line, 4);
76                         v6+=4;
77                         *v6++=':';
78                         line+=4;
79                 }
80                 --v6; *v6 = 0;
81                 line++;
82                 unsigned b, c, d, e;
83                 char name[IFNAMSIZ+1];
84                 if(5 == sscanf(line, "%x %x %x %x %s", &b, &c, &d, &e, name)) {
85                         struct sockaddr_in6 sa = {0};
86                         if(1 == inet_pton(AF_INET6, v6conv, &sa.sin6_addr)) {
87                                 sa.sin6_family = AF_INET6;
88                                 stor* curr = list_add(list, head, name);
89                                 if(!curr) goto out;
90                                 curr->addr.v6 = sa;
91                                 curr->ifa.ifa_addr = (struct sockaddr*) &curr->addr;
92                                 ipv6netmask(c, &sa);
93                                 curr->netmask.v6 = sa;
94                                 curr->ifa.ifa_netmask = (struct sockaddr*) &curr->netmask;
95                                 /* find ipv4 struct with the same interface name to copy flags */
96                                 stor* scan = *list;
97                                 for(;scan && strcmp(name, scan->name);scan=(stor*)scan->next);
98                                 if(scan) curr->ifa.ifa_flags = scan->ifa.ifa_flags;
99                                 else curr->ifa.ifa_flags = 0;
100                         } else errno = 0;
101                 }
102         }
103         out:
104         fclose(f);
105 }
106
107 int getifaddrs(struct ifaddrs **ifap)
108 {
109         FILE* f = fopen("/proc/net/dev", "r");
110         if(!f) return -1;
111
112         /* the alternative to parsing /proc.. seems to be iterating
113            through the interfaces using an index number in ifreq.ifr_ifindex
114            until we get some error code back. the kernel will fill ifr_name field
115            for valid ifindices (SIOCGIFINDEX) */
116         stor *list = 0, *head = 0;
117
118         char* line; char linebuf[512];
119         while((line = fgets(linebuf, sizeof linebuf, f))) {
120                 while(isspace(*line) && *line) line++;
121                 char* start = line;
122                 while(*line && isalnum(*line)) line++;
123                 if(line > start && *line == ':') {
124                         // found interface
125                         *line = 0;
126                         stor* curr = list_add(&list, &head, start);
127                         if(!curr) {
128                                 fclose(f);
129                                 goto err2;
130                         }
131                 }
132         }
133         fclose(f);
134
135         int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
136         if(sock == -1) goto err2;
137         struct ifreq reqs[32]; /* arbitrary chosen boundary */
138         struct ifconf conf = {.ifc_len = sizeof reqs, .ifc_req = reqs};
139         if(-1 == ioctl(sock, SIOCGIFCONF, &conf)) goto err;
140         size_t reqitems = conf.ifc_len / sizeof(struct ifreq);
141         for(head = list; head; head = (stor*)head->next) {
142                 size_t i;
143                 for(i = 0; i < reqitems; i++) {
144                         // get SIOCGIFADDR of active interfaces.
145                         if(!strcmp(reqs[i].ifr_name, head->name)) {
146                                 head->addr.v4 = *(struct sockaddr_in*)&reqs[i].ifr_addr;
147                                 head->ifa.ifa_addr = (struct sockaddr*) &head->addr;
148                                 break;
149                         }
150                 }
151                 struct ifreq req;
152                 snprintf(req.ifr_name, sizeof req.ifr_name, "%s", head->name);
153                 if(-1 == ioctl(sock, SIOCGIFFLAGS, &req)) goto err;
154
155                 head->ifa.ifa_flags = req.ifr_flags;
156                 if(head->ifa.ifa_addr) {
157                         /* or'ing flags with IFF_LOWER_UP on active interfaces to mimic glibc */
158                         head->ifa.ifa_flags |= IFF_LOWER_UP; 
159                         if(-1 == ioctl(sock, SIOCGIFNETMASK, &req)) goto err;
160                         head->netmask.v4 = *(struct sockaddr_in*)&req.ifr_netmask;
161                         head->ifa.ifa_netmask = (struct sockaddr*) &head->netmask;
162         
163                         if(head->ifa.ifa_flags & IFF_POINTOPOINT) {
164                                 if(-1 == ioctl(sock, SIOCGIFDSTADDR, &req)) goto err;
165                                 head->dst.v4 = *(struct sockaddr_in*)&req.ifr_dstaddr;
166                         } else {
167                                 if(-1 == ioctl(sock, SIOCGIFBRDADDR, &req)) goto err;
168                                 head->dst.v4 = *(struct sockaddr_in*)&req.ifr_broadaddr;
169                         }
170                         head->ifa.ifa_ifu.ifu_dstaddr = (struct sockaddr*) &head->dst;
171                 }
172         }
173         close(sock);
174         void* last = 0;
175         for(head = list; head; head=(stor*)head->next) last=head;
176         head = last;
177         dealwithipv6(&list, &head);
178         *ifap = (struct ifaddrs*) list;
179         return 0;
180         err:
181         close(sock);
182         err2:
183         freeifaddrs((struct ifaddrs*) list);
184         return -1;
185 }
186