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