2003-03-24 Thorsten Kukuk * sysdeps/unix/sysv/linux/Dist: add ifaddrs.c * sysdeps/unix/sysv/linux/ifaddrs.c: New implementation for Linux * inet/test-ifaddrs.c: Allow AF_PACKET. --- sysdeps/unix/sysv/linux/Dist +++ sysdeps/unix/sysv/linux/Dist 2003/03/24 10:09:13 @@ -5,6 +5,7 @@ exit-thread.S getdirentries.c getdirentries64.c +ifaddrs.c ipc_priv.h kernel-features.h kernel_sigaction.h --- sysdeps/unix/sysv/linux/ifaddrs.c +++ sysdeps/unix/sysv/linux/ifaddrs.c 2003/03/23 21:10:20 @@ -0,0 +1,595 @@ +/* getifaddrs -- get names and addresses of all network interfaces + Copyright (C) 2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +struct netlink_res { + struct netlink_res *next; + struct nlmsghdr *nlh; + size_t size; /* size of response. */ + uint32_t seq; /* sequentiel number we used. */ +}; + +struct netlink_handle { + int fd; /* netlink file descriptor. */ + pid_t pid; /* Process ID. */ + uint32_t seq; /* the sequentiell number we use currently. */ + struct netlink_res *nlm_list; /* pointer to list of responses. */ + struct netlink_res *end_ptr; /* for faster append of new entries. */ +}; +typedef struct netlink_handle netlink_handle; + +static void +free_netlink_handle (netlink_handle *h) +{ + struct netlink_res *ptr; + int saved_errno = errno; + + if (h->nlm_list == NULL) + return; + + ptr = h->nlm_list; + while (ptr) + { + struct netlink_res *tmpptr; + + if (ptr->nlh) + free (ptr->nlh); + tmpptr = ptr->next; + free (ptr); + ptr = tmpptr; + } + + errno = saved_errno; +} + +static int +netlink_sendreq (netlink_handle *h, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + if (h->seq == 0) + h->seq = time (NULL); + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = h->seq; + req.g.rtgen_family = AF_UNSPEC; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto (h->fd, (void*)&req, sizeof (req), 0, + (struct sockaddr*)&nladdr, sizeof (nladdr)); +} + + +static int +netlink_receive (struct netlink_handle *h) +{ + char buf[4096]; + struct iovec iov = {buf, sizeof(buf)}; + struct sockaddr_nl nladdr; + struct nlmsghdr *nh; + int read_len, done = 0; + + while (!done) + { + struct msghdr msg = { + (void*)&nladdr, sizeof (nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + do { + read_len = recvmsg (h->fd, &msg, 0); + } while ((read_len < 0) && (errno == EINTR)); + + if (read_len < 0) + return -1; + + if (msg.msg_flags & MSG_TRUNC) + return -1; + + if (buf) + { + struct netlink_res *nlm_next = + (struct netlink_res *)malloc (sizeof (struct netlink_res)); + + if (nlm_next == NULL) + return -1; + else + { + nlm_next->next = NULL; + nlm_next->nlh = malloc (read_len); + memcpy (nlm_next->nlh, buf, read_len); + nlm_next->size = read_len; + nlm_next->seq = h->seq; + if (h->nlm_list == NULL) + { + h->nlm_list = nlm_next; + h->end_ptr = nlm_next; + } + else + { + h->end_ptr->next = nlm_next; + h->end_ptr = nlm_next; + } + } + } + + for (nh = (struct nlmsghdr *)buf; + NLMSG_OK(nh, (size_t)read_len); + nh = (struct nlmsghdr *)NLMSG_NEXT(nh, read_len)) + { + if ((pid_t)nh->nlmsg_pid != h->pid || + nh->nlmsg_seq != h->seq) + continue; + + if (nh->nlmsg_type == NLMSG_DONE) + { + /* we found the end, leave the loop. */ + ++done; + break; + } + if (nh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *nlerr = (struct nlmsgerr *)NLMSG_DATA(nh); + if (nh->nlmsg_len < NLMSG_LENGTH (sizeof(struct nlmsgerr))) + errno = EIO; + else + errno = -nlerr->error; + return -1; + } + } + } + return 0; +} + +static void +netlink_close (struct netlink_handle *h) +{ + /* save errno, so that netlink_close will not overwrite previous + error code. */ + int saved_errno = errno; + + close (h->fd); + + errno = saved_errno; +} + +static int +netlink_open (struct netlink_handle *h) +{ + struct sockaddr_nl nladdr; + + h->fd = socket (PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (h->fd < 0) + return -1; + + memset (&nladdr, 0, sizeof (nladdr)); + nladdr.nl_family = AF_NETLINK; + if (bind (h->fd, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) + { + netlink_close (h); + return -1; + } + return 0; +} + +/* Create a linked list of `struct ifaddrs' structures, one for each + network interface on the host machine. If successful, store the + list in *IFAP and return 0. On errors, return -1 and set `errno'. */ +int +getifaddrs (struct ifaddrs **ifap) +{ + netlink_handle nh = {0, 0, 0, NULL, NULL}; + struct netlink_res *nlp; + struct ifaddrs *ifas; + uint32_t i, newlink, newaddr, newaddr_idx; + + if (ifap) + *ifap = NULL; + + if (netlink_open (&nh) < 0) + return -1; + + nh.pid = getpid (); + + if (netlink_sendreq (&nh, RTM_GETLINK) < 0) + { + netlink_close (&nh); + return -1; + } + if (netlink_receive (&nh) < 0) + { + netlink_close (&nh); + return -1; + } + + ++nh.seq; + if (netlink_sendreq (&nh, RTM_GETADDR) < 0) + { + netlink_close (&nh); + return -1; + } + if (netlink_receive (&nh) < 0) + { + netlink_close (&nh); + return -1; + } + + /* Count all RTM_NEWLINK entries to allocate enough + memory. */ + newlink = newaddr = 0; + for (nlp = nh.nlm_list; nlp; nlp = nlp->next) + { + struct nlmsghdr *nlh; + size_t size = nlp->size; + + if (nlp->nlh == NULL) + continue; + + for (nlh = nlp->nlh; NLMSG_OK(nlh, size); + nlh = NLMSG_NEXT(nlh, size)) + { + /* check if the message is what we want */ + if ((pid_t)nlh->nlmsg_pid != nh.pid || + nlh->nlmsg_seq != nlp->seq) + continue; + + if (nlh->nlmsg_type == NLMSG_DONE) + break; /* ok */ + + if (nlh->nlmsg_type == RTM_NEWLINK) + ++newlink; + else if (nlh->nlmsg_type == RTM_NEWADDR) + ++newaddr; + } + } + + if ((newlink + newaddr) == 0) + return 0; + + /* allocate memory for all entries we have and set next pointer. */ + ifas = calloc (newlink + newaddr, sizeof (struct ifaddrs)); + for (i = 0; i < newlink + newaddr - 1; i++) + ifas[i].ifa_next = &ifas[i+1]; + + newaddr_idx = 0; /* counter for newaddr index. */ + + for (nlp = nh.nlm_list; nlp; nlp = nlp->next) + { + struct nlmsghdr *nlh; + size_t size = nlp->size; + + if (nlp->nlh == NULL) + continue; + + for (nlh = nlp->nlh; NLMSG_OK(nlh, size); + nlh = NLMSG_NEXT(nlh, size)) + { + uint32_t ifa_index = 0; + + /* check if the message is the one we want */ + if ((pid_t)nlh->nlmsg_pid != nh.pid || + nlh->nlmsg_seq != nlp->seq) + continue; + + if (nlh->nlmsg_type == NLMSG_DONE) + break; /* ok */ + else if (nlh->nlmsg_type == RTM_NEWLINK) + { + struct ifinfomsg *ifim = (struct ifinfomsg *)NLMSG_DATA(nlh); + struct rtattr *rta = IFLA_RTA(ifim); + size_t rtasize = IFLA_PAYLOAD(nlh); + + ifa_index = ifim->ifi_index - 1; + ifas[ifa_index].ifa_flags = ifim->ifi_flags; + + while (RTA_OK (rta, rtasize)) + { + char *rta_data = RTA_DATA(rta); + size_t rta_payload = RTA_PAYLOAD(rta); + + switch (rta->rta_type) + { + case IFLA_ADDRESS: + { + struct sockaddr_ll *sa_ll; + /* struct sockaddr_ll has 8 bytes. */ + sa_ll = calloc (1, sizeof (struct sockaddr_ll)); + ifas[ifa_index].ifa_addr = (struct sockaddr *)sa_ll; + + sa_ll->sll_family = AF_PACKET; + memcpy(sa_ll->sll_addr, (char *)rta_data, rta_payload); + sa_ll->sll_halen = rta_payload; + sa_ll->sll_ifindex = ifim->ifi_index; + sa_ll->sll_hatype = ifim->ifi_type; + } + break; + case IFLA_BROADCAST: + { + struct sockaddr_ll *sa_ll; + /* struct sockaddr_ll has 8 bytes. */ + sa_ll = calloc (1, sizeof (struct sockaddr_ll)); + ifas[ifa_index].ifa_broadaddr = + (struct sockaddr *)sa_ll; + + sa_ll->sll_family = AF_PACKET; + memcpy(sa_ll->sll_addr, (char *)rta_data, rta_payload); + sa_ll->sll_halen = rta_payload; + sa_ll->sll_ifindex = ifim->ifi_index; + sa_ll->sll_hatype = ifim->ifi_type; + } + break; + case IFLA_IFNAME:/* Name of Interface */ + ifas[ifa_index].ifa_name = malloc (rta_payload + 1); + strncpy(ifas[ifa_index].ifa_name, rta_data, rta_payload); + ifas[ifa_index].ifa_name[rta_payload] = '\0'; + break; + case IFLA_STATS:/* Statistics of Interface */ + ifas[ifa_index].ifa_data = malloc (rta_payload); + memcpy (ifas[ifa_index].ifa_data, rta_data, rta_payload); + break; + case IFLA_UNSPEC: + break; + case IFLA_MTU: + break; + case IFLA_LINK: + break; + case IFLA_QDISC: + break; + default: + break; + } + + rta = RTA_NEXT(rta, rtasize); + } + } + else if (nlh->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg *ifam = (struct ifaddrmsg *)NLMSG_DATA(nlh); + struct rtattr *rta = IFA_RTA (ifam); + size_t rtasize = IFA_PAYLOAD(nlh); + + ifa_index = newlink + newaddr_idx; + ifas[ifa_index].ifa_flags = ifas[ifam->ifa_index - 1].ifa_flags; + ++newaddr_idx; + + while (RTA_OK (rta, rtasize)) + { + char *rta_data = RTA_DATA(rta); + size_t rta_payload = RTA_PAYLOAD(rta); + + switch(rta->rta_type) + { + case IFA_ADDRESS: + { + struct sockaddr *sa; + + sa = calloc (1, sizeof (struct sockaddr_storage)); + if (ifas[ifa_index].ifa_addr != NULL) + /* P2P network. */ + ifas[ifa_index].ifa_broadaddr = sa; + else + ifas[ifa_index].ifa_addr = sa; + + sa->sa_family = ifam->ifa_family; + switch (ifam->ifa_family) + { + case AF_INET: + memcpy (&((struct sockaddr_in*)sa)->sin_addr, + rta_data, rta_payload); + break; + case AF_INET6: + memcpy (&((struct sockaddr_in6*)sa)->sin6_addr, + rta_data, rta_payload); + if (IN6_IS_ADDR_LINKLOCAL(rta_data) || + IN6_IS_ADDR_MC_LINKLOCAL(rta_data)) + ((struct sockaddr_in6*)sa)->sin6_scope_id = + ifam->ifa_scope; + break; + default: + memcpy (sa->sa_data, rta_data, rta_payload); + break; + } + } + break; + case IFA_LOCAL: + { + struct sockaddr *sa; + + if (ifas[ifa_index].ifa_addr != NULL) + /* Could be a P2P network. Move address to + correct field. */ + ifas[ifa_index].ifa_broadaddr = + ifas[ifa_index].ifa_addr; + + sa = calloc (1, sizeof (struct sockaddr_storage)); + ifas[ifa_index].ifa_addr = sa; + sa->sa_family = ifam->ifa_family; + switch (ifam->ifa_family) + { + case AF_INET: + memcpy (&((struct sockaddr_in*)sa)->sin_addr, + rta_data, rta_payload); + break; + case AF_INET6: + memcpy (&((struct sockaddr_in6*)sa)->sin6_addr, + rta_data, rta_payload); + if (IN6_IS_ADDR_LINKLOCAL(rta_data) || + IN6_IS_ADDR_MC_LINKLOCAL(rta_data)) + ((struct sockaddr_in6*)sa)->sin6_scope_id = + ifam->ifa_scope; + break; + default: + memcpy (sa->sa_data, rta_data, rta_payload); + break; + } + } + break; + case IFA_BROADCAST: + { + struct sockaddr *sa; + + /* We get IFA_BROADCAST, so IFA_LOCAL was to much. */ + if (ifas[ifa_index].ifa_broadaddr != NULL) + free (ifas[ifa_index].ifa_broadaddr); + + sa = calloc (1, sizeof (struct sockaddr_storage)); + ifas[ifa_index].ifa_broadaddr = sa; + sa->sa_family = ifam->ifa_family; + switch (ifam->ifa_family) + { + case AF_INET: + memcpy (&((struct sockaddr_in*)sa)->sin_addr, + rta_data, rta_payload); + break; + case AF_INET6: + memcpy (&((struct sockaddr_in6*)sa)->sin6_addr, + rta_data, rta_payload); + if (IN6_IS_ADDR_LINKLOCAL(rta_data) || + IN6_IS_ADDR_MC_LINKLOCAL(rta_data)) + ((struct sockaddr_in6*)sa)->sin6_scope_id = + ifam->ifa_scope; + break; + default: + memcpy (sa->sa_data, rta_data, rta_payload); + break; + } + } + break; + case IFA_LABEL: + ifas[ifa_index].ifa_name = malloc (rta_payload + 1); + strncpy (ifas[ifa_index].ifa_name, rta_data, rta_payload); + ifas[ifa_index].ifa_name[rta_payload] = '\0'; + break; + case IFA_UNSPEC: + break; + case IFA_CACHEINFO: + break; + default: + break; + } + + rta = RTA_NEXT(rta, rtasize); + } + + if (ifas[ifa_index].ifa_name == NULL) + ifas[ifa_index].ifa_name = + strdup (ifas[ifam->ifa_index - 1].ifa_name); + + if (ifas[ifa_index].ifa_addr && + ifas[ifa_index].ifa_addr->sa_family != AF_UNSPEC && + ifas[ifa_index].ifa_addr->sa_family != AF_PACKET) + { + uint32_t max_prefixlen = 0; + char *cp = NULL; + + ifas[ifa_index].ifa_netmask = + calloc (1, sizeof (struct sockaddr_storage)); + switch (ifas[ifa_index].ifa_addr->sa_family) + { + case AF_INET: + cp = (char *)&((struct sockaddr_in*)ifas[ifa_index].ifa_netmask)->sin_addr; + max_prefixlen = 32; + break; + case AF_INET6: + cp = (char *)&((struct sockaddr_in6*)ifas[ifa_index].ifa_netmask)->sin6_addr; + max_prefixlen = 128; + break; + } + ifas[ifa_index].ifa_netmask->sa_family = ifas[ifa_index].ifa_addr->sa_family; + if (cp) + { + char c; + unsigned int preflen; + + if ((max_prefixlen > 0) && + (ifam->ifa_prefixlen > max_prefixlen)) + preflen = max_prefixlen; + else + preflen = ifam->ifa_prefixlen; + + for (i = 0; i < (preflen / 8); i++) + *cp++ = 0xff; + c = 0xff; + c <<= (8 - (preflen % 8)); + *cp = c; + } + } + } + } + } + + free_netlink_handle (&nh); + netlink_close (&nh); + if (ifap != NULL) + *ifap = ifas; + + return 0; +} + +void +freeifaddrs (struct ifaddrs *ifa) +{ + struct ifaddrs *ptr; + + ptr = ifa; + while (ptr) + { + if (ptr->ifa_name) + free (ptr->ifa_name); + if (ptr->ifa_addr) + free (ptr->ifa_addr); + if (ptr->ifa_netmask) + free (ptr->ifa_netmask); + if (ptr->ifa_broadaddr) + free (ptr->ifa_broadaddr); + if (ptr->ifa_data) + free (ptr->ifa_data); + ptr = ptr->ifa_next; + } + + free (ifa); +} --- inet/test-ifaddrs.c +++ inet/test-ifaddrs.c 2003/03/24 13:33:14 @@ -69,6 +69,10 @@ #endif case AF_UNSPEC: return "---"; + + case AF_PACKET: + return ""; + default: ++failures; printf ("sa_family=%d %08x\n", sa->sa_family,