1#define _GNU_SOURCE 2#include <errno.h> 3#include <string.h> 4#include <stdlib.h> 5#include <unistd.h> 6#include <ifaddrs.h> 7#include <syscall.h> 8#include <net/if.h> 9#include <netinet/in.h> 10#include "netlink.h" 11 12#define IFADDRS_HASH_SIZE 64 13 14/* getifaddrs() reports hardware addresses with PF_PACKET that implies 15 * struct sockaddr_ll. But e.g. Infiniband socket address length is 16 * longer than sockaddr_ll.ssl_addr[8] can hold. Use this hack struct 17 * to extend ssl_addr - callers should be able to still use it. */ 18struct sockaddr_ll_hack { 19 unsigned short sll_family, sll_protocol; 20 int sll_ifindex; 21 unsigned short sll_hatype; 22 unsigned char sll_pkttype, sll_halen; 23 unsigned char sll_addr[24]; 24}; 25 26union sockany { 27 struct sockaddr sa; 28 struct sockaddr_ll_hack ll; 29 struct sockaddr_in v4; 30 struct sockaddr_in6 v6; 31}; 32 33struct ifaddrs_storage { 34 struct ifaddrs ifa; 35 struct ifaddrs_storage *hash_next; 36 union sockany addr, netmask, ifu; 37 unsigned int index; 38 char name[IFNAMSIZ+1]; 39}; 40 41struct ifaddrs_ctx { 42 struct ifaddrs_storage *first; 43 struct ifaddrs_storage *last; 44 struct ifaddrs_storage *hash[IFADDRS_HASH_SIZE]; 45}; 46 47void freeifaddrs(struct ifaddrs *ifp) 48{ 49 struct ifaddrs *n; 50 while (ifp) { 51 n = ifp->ifa_next; 52 free(ifp); 53 ifp = n; 54 } 55} 56 57static void copy_addr(struct sockaddr **r, int af, union sockany *sa, void *addr, size_t addrlen, int ifindex) 58{ 59 uint8_t *dst; 60 int len; 61 62 switch (af) { 63 case AF_INET: 64 dst = (uint8_t*) &sa->v4.sin_addr; 65 len = 4; 66 break; 67 case AF_INET6: 68 dst = (uint8_t*) &sa->v6.sin6_addr; 69 len = 16; 70 if (IN6_IS_ADDR_LINKLOCAL(addr) || IN6_IS_ADDR_MC_LINKLOCAL(addr)) 71 sa->v6.sin6_scope_id = ifindex; 72 break; 73 default: 74 return; 75 } 76 if (addrlen < len) return; 77 sa->sa.sa_family = af; 78 memcpy(dst, addr, len); 79 *r = &sa->sa; 80} 81 82static void gen_netmask(struct sockaddr **r, int af, union sockany *sa, int prefixlen) 83{ 84 uint8_t addr[16] = {0}; 85 int i; 86 87 if (prefixlen > 8*sizeof(addr)) prefixlen = 8*sizeof(addr); 88 i = prefixlen / 8; 89 memset(addr, 0xff, i); 90 if (i < sizeof(addr)) addr[i++] = 0xff << (8 - (prefixlen % 8)); 91 copy_addr(r, af, sa, addr, sizeof(addr), 0); 92} 93 94static void copy_lladdr(struct sockaddr **r, union sockany *sa, void *addr, size_t addrlen, int ifindex, unsigned short hatype) 95{ 96 if (addrlen > sizeof(sa->ll.sll_addr)) return; 97 sa->ll.sll_family = AF_PACKET; 98 sa->ll.sll_ifindex = ifindex; 99 sa->ll.sll_hatype = hatype; 100 sa->ll.sll_halen = addrlen; 101 memcpy(sa->ll.sll_addr, addr, addrlen); 102 *r = &sa->sa; 103} 104 105static int netlink_msg_to_ifaddr(void *pctx, struct nlmsghdr *h) 106{ 107 struct ifaddrs_ctx *ctx = pctx; 108 struct ifaddrs_storage *ifs, *ifs0; 109 struct ifinfomsg *ifi = NLMSG_DATA(h); 110 struct ifaddrmsg *ifa = NLMSG_DATA(h); 111 struct rtattr *rta; 112 int stats_len = 0; 113 114 if (h->nlmsg_type == RTM_NEWLINK) { 115 for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { 116 if (rta->rta_type != IFLA_STATS) continue; 117 stats_len = RTA_DATALEN(rta); 118 break; 119 } 120 } else { 121 for (ifs0 = ctx->hash[ifa->ifa_index % IFADDRS_HASH_SIZE]; ifs0; ifs0 = ifs0->hash_next) 122 if (ifs0->index == ifa->ifa_index) 123 break; 124 if (!ifs0) return 0; 125 } 126 127 ifs = calloc(1, sizeof(struct ifaddrs_storage) + stats_len); 128 if (ifs == 0) return -1; 129 130 if (h->nlmsg_type == RTM_NEWLINK) { 131 ifs->index = ifi->ifi_index; 132 ifs->ifa.ifa_flags = ifi->ifi_flags; 133 134 for (rta = NLMSG_RTA(h, sizeof(*ifi)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { 135 switch (rta->rta_type) { 136 case IFLA_IFNAME: 137 if (RTA_DATALEN(rta) < sizeof(ifs->name)) { 138 memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); 139 ifs->ifa.ifa_name = ifs->name; 140 } 141 break; 142 case IFLA_ADDRESS: 143 copy_lladdr(&ifs->ifa.ifa_addr, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); 144 break; 145 case IFLA_BROADCAST: 146 copy_lladdr(&ifs->ifa.ifa_broadaddr, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifi->ifi_index, ifi->ifi_type); 147 break; 148 case IFLA_STATS: 149 ifs->ifa.ifa_data = (void*)(ifs+1); 150 memcpy(ifs->ifa.ifa_data, RTA_DATA(rta), RTA_DATALEN(rta)); 151 break; 152 } 153 } 154 if (ifs->ifa.ifa_name) { 155 unsigned int bucket = ifs->index % IFADDRS_HASH_SIZE; 156 ifs->hash_next = ctx->hash[bucket]; 157 ctx->hash[bucket] = ifs; 158 } 159 } else { 160 ifs->ifa.ifa_name = ifs0->ifa.ifa_name; 161 ifs->ifa.ifa_flags = ifs0->ifa.ifa_flags; 162 for (rta = NLMSG_RTA(h, sizeof(*ifa)); NLMSG_RTAOK(rta, h); rta = RTA_NEXT(rta)) { 163 switch (rta->rta_type) { 164 case IFA_ADDRESS: 165 /* If ifa_addr is already set we, received an IFA_LOCAL before 166 * so treat this as destination address */ 167 if (ifs->ifa.ifa_addr) 168 copy_addr(&ifs->ifa.ifa_dstaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); 169 else 170 copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); 171 break; 172 case IFA_BROADCAST: 173 copy_addr(&ifs->ifa.ifa_broadaddr, ifa->ifa_family, &ifs->ifu, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); 174 break; 175 case IFA_LOCAL: 176 /* If ifa_addr is set and we get IFA_LOCAL, assume we have 177 * a point-to-point network. Move address to correct field. */ 178 if (ifs->ifa.ifa_addr) { 179 ifs->ifu = ifs->addr; 180 ifs->ifa.ifa_dstaddr = &ifs->ifu.sa; 181 memset(&ifs->addr, 0, sizeof(ifs->addr)); 182 } 183 copy_addr(&ifs->ifa.ifa_addr, ifa->ifa_family, &ifs->addr, RTA_DATA(rta), RTA_DATALEN(rta), ifa->ifa_index); 184 break; 185 case IFA_LABEL: 186 if (RTA_DATALEN(rta) < sizeof(ifs->name)) { 187 memcpy(ifs->name, RTA_DATA(rta), RTA_DATALEN(rta)); 188 ifs->ifa.ifa_name = ifs->name; 189 } 190 break; 191 } 192 } 193 if (ifs->ifa.ifa_addr) 194 gen_netmask(&ifs->ifa.ifa_netmask, ifa->ifa_family, &ifs->netmask, ifa->ifa_prefixlen); 195 } 196 197 if (ifs->ifa.ifa_name) { 198 if (!ctx->first) ctx->first = ifs; 199 if (ctx->last) ctx->last->ifa.ifa_next = &ifs->ifa; 200 ctx->last = ifs; 201 } else { 202 free(ifs); 203 } 204 return 0; 205} 206 207int getifaddrs(struct ifaddrs **ifap) 208{ 209 struct ifaddrs_ctx _ctx, *ctx = &_ctx; 210 int r; 211 memset(ctx, 0, sizeof *ctx); 212 r = __rtnetlink_enumerate(AF_UNSPEC, AF_UNSPEC, netlink_msg_to_ifaddr, ctx); 213 if (r == 0) *ifap = &ctx->first->ifa; 214 else freeifaddrs(&ctx->first->ifa); 215 return r; 216} 217