| /* $USAGI: ni_ifaddrs.c,v 1.8 2007-10-11 06:25:21 yoshfuji Exp $ */ |
| /* |
| * Copyright (C) 2002 USAGI/WIDE Project. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the project nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| /* reformatted by indent -kr -i8 -l 1000 */ |
| /* USAGI: ifaddrs.c,v 1.18 2002/03/06 01:50:46 yoshfuji Exp */ |
| |
| /************************************************************************** |
| * ifaddrs.c |
| * Copyright (C)2000 Hideaki YOSHIFUJI, All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the author nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #include <string.h> |
| #include <time.h> |
| #include <malloc.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <sys/socket.h> |
| #include <asm/types.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <netpacket/packet.h> |
| #include <net/ethernet.h> /* the L2 protocols */ |
| #include <sys/uio.h> |
| #include <net/if.h> |
| #include <net/if_arp.h> |
| #include "ni_ifaddrs.h" |
| #include <netinet/in.h> |
| |
| #ifdef _USAGI_LIBINET6 |
| #include "libc-compat.h" |
| #endif |
| |
| //#define IFA_LOCAL IFA_LOCAL |
| |
| static const char *RCSID __attribute__ ((unused)) = "$USAGI: ni_ifaddrs.c,v 1.8 2007-10-11 06:25:21 yoshfuji Exp $ based on USAGI: ifaddrs.c,v 1.18 2002/03/06 01:50:46 yoshfuji Exp"; |
| |
| /* ====================================================================== */ |
| struct nlmsg_list { |
| struct nlmsg_list *nlm_next; |
| struct nlmsghdr *nlh; |
| int size; |
| time_t seq; |
| }; |
| |
| #ifndef IFA_LOCAL |
| struct rtmaddr_ifamap { |
| void *address; |
| void *local; |
| void *broadcast; |
| int address_len; |
| int local_len; |
| int broadcast_len; |
| }; |
| #endif |
| |
| /* ====================================================================== */ |
| static int nl_sendreq(int sd, int request, int flags, int *seq) |
| { |
| char reqbuf[NLMSG_ALIGN(sizeof(struct nlmsghdr)) + NLMSG_ALIGN(sizeof(struct rtgenmsg))]; |
| struct sockaddr_nl nladdr; |
| struct nlmsghdr *req_hdr; |
| struct rtgenmsg *req_msg; |
| time_t t = time(NULL); |
| |
| if (seq) |
| *seq = t; |
| memset(&reqbuf, 0, sizeof(reqbuf)); |
| req_hdr = (struct nlmsghdr *) reqbuf; |
| req_msg = (struct rtgenmsg *) NLMSG_DATA(req_hdr); |
| req_hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*req_msg)); |
| req_hdr->nlmsg_type = request; |
| req_hdr->nlmsg_flags = flags | NLM_F_REQUEST; |
| req_hdr->nlmsg_pid = 0; |
| req_hdr->nlmsg_seq = t; |
| req_msg->rtgen_family = AF_UNSPEC; |
| memset(&nladdr, 0, sizeof(nladdr)); |
| nladdr.nl_family = AF_NETLINK; |
| return (sendto(sd, (void *) req_hdr, req_hdr->nlmsg_len, 0, (struct sockaddr *) &nladdr, sizeof(nladdr))); |
| } |
| |
| static int nl_recvmsg(int sd, int request, int seq, void *buf, size_t buflen, int *flags) |
| { |
| struct msghdr msg; |
| struct iovec iov = { buf, buflen }; |
| struct sockaddr_nl nladdr; |
| int read_len; |
| |
| for (;;) { |
| msg.msg_name = (void *) &nladdr; |
| msg.msg_namelen = sizeof(nladdr); |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = NULL; |
| msg.msg_controllen = 0; |
| msg.msg_flags = 0; |
| read_len = recvmsg(sd, &msg, 0); |
| if ((read_len < 0 && errno == EINTR) |
| || (msg.msg_flags & MSG_TRUNC)) |
| continue; |
| if (flags) |
| *flags = msg.msg_flags; |
| break; |
| } |
| return read_len; |
| } |
| |
| static int nl_getmsg(int sd, int request, int seq, struct nlmsghdr **nlhp, int *done) |
| { |
| struct nlmsghdr *nh; |
| size_t bufsize = 65536, lastbufsize = 0; |
| void *buff = NULL; |
| int result = 0, read_size; |
| int msg_flags; |
| pid_t pid = getpid(); |
| for (;;) { |
| void *newbuff = realloc(buff, bufsize); |
| if (newbuff == NULL || bufsize < lastbufsize) { |
| free(newbuff); |
| result = -1; |
| break; |
| } |
| buff = newbuff; |
| result = read_size = nl_recvmsg(sd, request, seq, buff, bufsize, &msg_flags); |
| if (read_size < 0 || (msg_flags & MSG_TRUNC)) { |
| lastbufsize = bufsize; |
| bufsize *= 2; |
| continue; |
| } |
| if (read_size == 0) |
| break; |
| nh = (struct nlmsghdr *) buff; |
| for (nh = (struct nlmsghdr *) buff; NLMSG_OK(nh, read_size); nh = (struct nlmsghdr *) NLMSG_NEXT(nh, read_size)) { |
| if (nh->nlmsg_pid != pid || nh->nlmsg_seq != seq) |
| continue; |
| if (nh->nlmsg_type == NLMSG_DONE) { |
| (*done)++; |
| break; /* ok */ |
| } |
| if (nh->nlmsg_type == NLMSG_ERROR) { |
| struct nlmsgerr *nlerr = (struct nlmsgerr *) NLMSG_DATA(nh); |
| result = -1; |
| if (nh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) |
| errno = EIO; |
| else |
| errno = -nlerr->error; |
| break; |
| } |
| } |
| break; |
| } |
| if (result < 0) |
| if (buff) { |
| int saved_errno = errno; |
| free(buff); |
| buff = NULL; |
| errno = saved_errno; |
| } |
| *nlhp = (struct nlmsghdr *) buff; |
| return result; |
| } |
| |
| static int nl_getlist(int sd, int seq, int request, struct nlmsg_list **nlm_list, struct nlmsg_list **nlm_end) |
| { |
| struct nlmsghdr *nlh = NULL; |
| int status; |
| int done = 0; |
| |
| status = nl_sendreq(sd, request, NLM_F_ROOT | NLM_F_MATCH, &seq); |
| if (status < 0) |
| return status; |
| if (seq == 0) |
| seq = (int) time(NULL); |
| while (!done) { |
| status = nl_getmsg(sd, request, seq, &nlh, &done); |
| if (status < 0) |
| return status; |
| if (nlh) { |
| struct nlmsg_list *nlm_next = (struct nlmsg_list *) malloc(sizeof(struct nlmsg_list)); |
| if (nlm_next == NULL) { |
| int saved_errno = errno; |
| free(nlh); |
| errno = saved_errno; |
| status = -1; |
| } else { |
| nlm_next->nlm_next = NULL; |
| nlm_next->nlh = (struct nlmsghdr *) nlh; |
| nlm_next->size = status; |
| nlm_next->seq = seq; |
| if (*nlm_list == NULL) { |
| *nlm_list = nlm_next; |
| *nlm_end = nlm_next; |
| } else { |
| (*nlm_end)->nlm_next = nlm_next; |
| *nlm_end = nlm_next; |
| } |
| } |
| } |
| } |
| return status >= 0 ? seq : status; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| static void free_nlmsglist(struct nlmsg_list *nlm0) |
| { |
| struct nlmsg_list *nlm, *nlm_next; |
| int saved_errno; |
| if (!nlm0) |
| return; |
| saved_errno = errno; |
| nlm = nlm0; |
| while(nlm) { |
| if(nlm->nlh) |
| free(nlm->nlh); |
| nlm_next = nlm->nlm_next; |
| free(nlm); |
| nlm = nlm_next; |
| } |
| errno = saved_errno; |
| } |
| |
| static void free_data(void *data) |
| { |
| int saved_errno = errno; |
| if (data != NULL) |
| free(data); |
| errno = saved_errno; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| static void nl_close(int sd) |
| { |
| int saved_errno = errno; |
| if (sd >= 0) |
| close(sd); |
| errno = saved_errno; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| static int nl_open(void) |
| { |
| struct sockaddr_nl nladdr; |
| int sd; |
| |
| sd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); |
| if (sd < 0) |
| return -1; |
| memset(&nladdr, 0, sizeof(nladdr)); |
| nladdr.nl_family = AF_NETLINK; |
| if (bind(sd, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) { |
| nl_close(sd); |
| return -1; |
| } |
| return sd; |
| } |
| |
| /* ====================================================================== */ |
| int ni_ifaddrs(struct ni_ifaddrs **ifap, sa_family_t family) |
| { |
| int sd; |
| struct nlmsg_list *nlmsg_list, *nlmsg_end, *nlm; |
| /* - - - - - - - - - - - - - - - */ |
| int icnt; |
| size_t dlen, xlen; |
| uint32_t max_ifindex = 0; |
| |
| pid_t pid = getpid(); |
| int seq = 0; |
| int result; |
| int build; /* 0 or 1 */ |
| |
| /* ---------------------------------- */ |
| /* initialize */ |
| icnt = dlen = xlen = 0; |
| nlmsg_list = nlmsg_end = NULL; |
| |
| if (ifap) |
| *ifap = NULL; |
| |
| /* ---------------------------------- */ |
| /* open socket and bind */ |
| sd = nl_open(); |
| if (sd < 0) |
| return -1; |
| |
| /* ---------------------------------- */ |
| /* gather info */ |
| if ((seq = nl_getlist(sd, seq + 1, RTM_GETADDR, &nlmsg_list, &nlmsg_end)) < 0) { |
| free_nlmsglist(nlmsg_list); |
| nl_close(sd); |
| return -1; |
| } |
| |
| /* ---------------------------------- */ |
| /* Estimate size of result buffer and fill it */ |
| for (build = 0; build <= 1; build++) { |
| struct ni_ifaddrs *ifl = NULL, *ifa = NULL; |
| struct nlmsghdr *nlh, *nlh0; |
| void *data = NULL, *xdata = NULL; |
| uint16_t *ifflist = NULL; |
| #ifndef IFA_LOCAL |
| struct rtmaddr_ifamap ifamap; |
| #endif |
| |
| if (build) { |
| ifa = data = calloc(1, NLMSG_ALIGN(sizeof(struct ni_ifaddrs[icnt])) |
| + dlen + xlen); |
| if (ifap != NULL) |
| *ifap = ifa; |
| else { |
| free_data(data); |
| result = 0; |
| break; |
| } |
| if (data == NULL) { |
| free_data(data); |
| result = -1; |
| break; |
| } |
| ifl = NULL; |
| data += NLMSG_ALIGN(sizeof(struct ni_ifaddrs)) * icnt; |
| xdata = data + dlen; |
| ifflist = xdata + xlen; |
| } |
| |
| for (nlm = nlmsg_list; nlm; nlm = nlm->nlm_next) { |
| int nlmlen = nlm->size; |
| if (!(nlh0 = nlm->nlh)) |
| continue; |
| for (nlh = nlh0; NLMSG_OK(nlh, nlmlen); nlh = NLMSG_NEXT(nlh, nlmlen)) { |
| struct ifaddrmsg *ifam = NULL; |
| struct rtattr *rta; |
| |
| size_t nlm_struct_size = 0; |
| sa_family_t nlm_family = 0; |
| uint32_t nlm_scope = 0, nlm_index = 0; |
| unsigned int nlm_flags; |
| size_t rtasize; |
| |
| #ifndef IFA_LOCAL |
| memset(&ifamap, 0, sizeof(ifamap)); |
| #endif |
| |
| /* check if the message is what we want */ |
| if (nlh->nlmsg_pid != pid || nlh->nlmsg_seq != nlm->seq) |
| continue; |
| if (nlh->nlmsg_type == NLMSG_DONE) { |
| break; /* ok */ |
| } |
| switch (nlh->nlmsg_type) { |
| case RTM_NEWADDR: |
| ifam = (struct ifaddrmsg *) NLMSG_DATA(nlh); |
| nlm_struct_size = sizeof(*ifam); |
| nlm_family = ifam->ifa_family; |
| nlm_scope = ifam->ifa_scope; |
| nlm_index = ifam->ifa_index; |
| nlm_flags = ifam->ifa_flags; |
| if (family && nlm_family != family) |
| continue; |
| if (build) { |
| ifa->ifa_ifindex = nlm_index; |
| ifa->ifa_flags = nlm_flags; |
| } |
| break; |
| default: |
| continue; |
| } |
| |
| if (!build) { |
| if (max_ifindex < nlm_index) |
| max_ifindex = nlm_index; |
| } else { |
| if (ifl != NULL) |
| ifl->ifa_next = ifa; |
| } |
| |
| rtasize = NLMSG_PAYLOAD(nlh, nlmlen) - NLMSG_ALIGN(nlm_struct_size); |
| for (rta = (struct rtattr *) (((char *) NLMSG_DATA(nlh)) + |
| NLMSG_ALIGN(nlm_struct_size)); |
| RTA_OK(rta, rtasize); |
| rta = RTA_NEXT(rta, rtasize)) { |
| void *rtadata = RTA_DATA(rta); |
| size_t rtapayload = RTA_PAYLOAD(rta); |
| |
| switch (nlh->nlmsg_type) { |
| case RTM_NEWADDR: |
| if (nlm_family == AF_PACKET) |
| break; |
| switch (rta->rta_type) { |
| #ifndef IFA_LOCAL |
| case IFA_ADDRESS: |
| ifamap.address = rtadata; |
| ifamap.address_len = rtapayload; |
| break; |
| case IFA_LOCAL: |
| ifamap.local = rtadata; |
| ifamap.local_len = rtapayload; |
| break; |
| case IFA_BROADCAST: |
| ifamap.broadcast = rtadata; |
| ifamap.broadcast_len = rtapayload; |
| break; |
| case IFA_LABEL: |
| break; |
| case IFA_UNSPEC: |
| break; |
| #else |
| case IFA_LOCAL: |
| if (!build) |
| dlen += NLMSG_ALIGN(rtapayload); |
| else { |
| memcpy(data, rtadata, rtapayload); |
| ifa->ifa_addr = data; |
| data += NLMSG_ALIGN(rtapayload); |
| } |
| break; |
| #endif |
| case IFA_CACHEINFO: |
| if (!build) |
| xlen += NLMSG_ALIGN(rtapayload); |
| else { |
| memcpy(xdata, rtadata, rtapayload); |
| ifa->ifa_cacheinfo = xdata; |
| xdata += NLMSG_ALIGN(rtapayload); |
| } |
| break; |
| } |
| } |
| } |
| #ifndef IFA_LOCAL |
| if (nlh->nlmsg_type == RTM_NEWADDR && nlm_family != AF_PACKET) { |
| if (!ifamap.local) { |
| ifamap.local = ifamap.address; |
| ifamap.local_len = ifamap.address_len; |
| } |
| if (!ifamap.address) { |
| ifamap.address = ifamap.local; |
| ifamap.address_len = ifamap.local_len; |
| } |
| if (ifamap.address_len != ifamap.local_len || |
| (ifamap.address != NULL && |
| memcmp(ifamap.address, ifamap.local, ifamap.address_len))) { |
| /* p2p; address is peer and local is ours */ |
| ifamap.broadcast = ifamap.address; |
| ifamap.broadcast_len = ifamap.address_len; |
| ifamap.address = ifamap.local; |
| ifamap.address_len = ifamap.local_len; |
| } |
| if (ifamap.address) { |
| if (!build) |
| dlen += NLMSG_ALIGN(ifamap.address_len); |
| else { |
| ifa->ifa_addr = (struct sockaddr *) data; |
| memcpy(ifa->ifa_addr, ifamap.address, ifamap.address_len); |
| data += NLMSG_ALIGN(ifamap.address_len); |
| } |
| } |
| } |
| #endif |
| if (!build) { |
| icnt++; |
| } else { |
| ifl = ifa++; |
| } |
| } |
| } |
| if (!build) { |
| if (icnt == 0 && (dlen + xlen == 0)) { |
| if (ifap != NULL) |
| *ifap = NULL; |
| break; /* cannot found any addresses */ |
| } |
| } |
| } |
| |
| /* ---------------------------------- */ |
| /* Finalize */ |
| free_nlmsglist(nlmsg_list); |
| nl_close(sd); |
| return 0; |
| } |
| |
| /* ---------------------------------------------------------------------- */ |
| void ni_freeifaddrs(struct ni_ifaddrs *ifa) |
| { |
| free(ifa); |
| } |
| |