blob: 4ee28385de625ded6f1aefbb447ebe2c5ac3169a [file] [log] [blame]
/* $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);
}