X-Git-Url: http://nsz.repo.hu/git/?a=blobdiff_plain;f=src%2Fnetwork%2Fgetifaddrs.c;h=fed75bd8d9293aa51c9e85461ca533dcf1d1ddae;hb=ef137da6428c342baabd3bcf9b5e91f75acefa64;hp=d96d1094d3d6db7f9cc7f4d975848223e47a8ddc;hpb=202db37a6f2cc43c06467473d9970677f6997ce3;p=musl diff --git a/src/network/getifaddrs.c b/src/network/getifaddrs.c index d96d1094..fed75bd8 100644 --- a/src/network/getifaddrs.c +++ b/src/network/getifaddrs.c @@ -1,191 +1,216 @@ -/* (C) 2013 John Spencer. released under musl's standard MIT license. */ -#undef _GNU_SOURCE #define _GNU_SOURCE -#include -#include -#include /* IFNAMSIZ, ifreq, ifconf */ -#include -#include -#include #include -#include /* inet_pton */ +#include +#include #include -#include +#include +#include +#include +#include +#include "netlink.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; -} +#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; + struct sockaddr_in6 v6; +}; + +struct ifaddrs_storage { + struct ifaddrs ifa; + struct ifaddrs_storage *hash_next; + union sockany addr, netmask, ifu; + unsigned int index; + char name[IFNAMSIZ+1]; +}; + +struct ifaddrs_ctx { + struct ifaddrs_storage *first; + struct ifaddrs_storage *last; + struct ifaddrs_storage *hash[IFADDRS_HASH_SIZE]; +}; 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); + struct ifaddrs *n; + while (ifp) { + n = ifp->ifa_next; + free(ifp); + ifp = n; } } -static struct sockaddr *sockaddr_in_dup(struct sockaddr_in *src) +static void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex) { - struct sockaddr_in *nu = malloc(sizeof(struct sockaddr_in)); - if(nu) *nu = *src; - return (struct sockaddr*) nu; + 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 struct sockaddr *sockaddr_in6_dup(struct sockaddr_in6 *src) +static void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen) { - struct sockaddr_in6 *nu = malloc(sizeof(struct sockaddr_in6)); - if(nu) *nu = *src; - return (struct sockaddr*) nu; + 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 ipv6netmask(unsigned prefix_length, struct sockaddr_in6 *sa) +static void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype) { - // FIXME: left for bit-wizard rich - memset(&sa->sin6_addr, -1, sizeof(sa->sin6_addr)); + 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; } -static void dealwithipv6(struct ifaddrs **list, struct ifaddrs** head) +static int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h) { - 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; + 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; } - 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; + 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; } } - } - 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; + 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: + /* If ifa_addr is already set we, received an IFA_LOCAL before + * so treat this as destination address */ + if (ifs->ifa.ifa_addr) + copy_addr(&ifs->ifa.ifa_dstaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); + else + copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); + break; + case IFA_BROADCAST: + copy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); + break; + case IFA_LOCAL: + /* If ifa_addr is set and we get IFA_LOCAL, assume we have + * a point-to-point network. Move address to correct field. */ + if (ifs->ifa.ifa_addr) { + ifs->ifu = ifs->addr; + ifs->ifa.ifa_dstaddr = &ifs->ifu.sa; + memset(&ifs->addr, 0, sizeof(ifs->addr)); } - } - 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); + copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, 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; } } + 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=head->ifa_next) last=head; - head = last; - dealwithipv6(&list, &head); - *ifap = list; return 0; - err: - close(sock); - err2: - freeifaddrs(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; +}