/* $NetBSD: inet_addr_local.c,v 1.4 2017/02/14 01:16:49 christos Exp $ */
/*++
/* NAME
/* inet_addr_local 3
/* SUMMARY
/* determine if IP address is local
/* SYNOPSIS
/* #include <inet_addr_local.h>
/*
/* int inet_addr_local(addr_list, mask_list, addr_family_list)
/* INET_ADDR_LIST *addr_list;
/* INET_ADDR_LIST *mask_list;
/* unsigned *addr_family;
/* DESCRIPTION
/* inet_addr_local() determines all active IP interface addresses
/* of the local system. Any address found is appended to the
/* specified address list. The result value is the number of
/* active interfaces found.
/*
/* The mask_list is either a null pointer, or it is a list that
/* receives the netmasks of the interface addresses that were found.
/*
/* The addr_family_list specifies one or more of AF_INET or AF_INET6.
/* DIAGNOSTICS
/* Fatal errors: out of memory.
/* SEE ALSO
/* inet_addr_list(3) address list management
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*
/* Dean C. Strik
/* Department ICT
/* Eindhoven University of Technology
/* P.O. Box 513
/* 5600 MB Eindhoven, Netherlands
/* E-mail: <dean@ipnet6.org>
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>
#ifdef USE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#include <errno.h>
#include <string.h>
#ifdef HAS_IPV6 /* Linux only? */
#include <netdb.h>
#include <stdio.h>
#endif
#ifdef HAVE_GETIFADDRS
#include <ifaddrs.h>
#endif
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>
#include <inet_addr_list.h>
#include <inet_addr_local.h>
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <mask_addr.h>
#include <hex_code.h>
/*
* Postfix needs its own interface address information to determine whether
* or not it is an MX host for some destination; without this information,
* mail would loop between MX hosts. Postfix also needs its interface
* addresses to figure out whether or not it is final destination for
* addresses of the form username@[ipaddress].
*
* Postfix needs its own interface netmask information when no explicit
* mynetworks setting is given in main.cf, and "mynetworks_style = subnet".
* The mynetworks parameter controls, among others, what mail clients are
* allowed to relay mail through Postfix.
*
* Different systems have different ways to find out this information. We will
* therefore use OS dependent methods. An overview:
*
* - Use getifaddrs() when available. This supports both IPv4/IPv6 addresses.
* The implementation however is not present in all major operating systems.
*
* - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses.
* With SIOCGLIFNETMASK we can obtain the netmask for either address family.
* Again, this is not present in all major operating systems.
*
* - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some
* time, but IPv6 information was not returned until 2.3.3. With older Linux
* versions we get IPv4 interface information with SIOCGIFCONF, and read
* IPv6 address/prefix information from a file in the /proc filesystem.
*
* - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since
* SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set
* the prefix length to /128 (host), and expect the user to configure a more
* appropriate mynetworks setting if needed.
*
* XXX: Each lookup method is implemented by its own function, so we duplicate
* some code. In this case, I think this is better than really drowning in
* the #ifdefs...
*
* -- Dean Strik (dcs)
*/
#ifndef HAVE_GETIFADDRS
/* ial_socket - make socket for ioctl() operations */
static int ial_socket(int af)
{
const char *myname = "inet_addr_local[socket]";
int sock;
/*
* The host may not be actually configured with IPv6. When IPv6 support
* is not actually in the kernel, don't consider failure to create an
* IPv6 socket as fatal. This could be tuned better though. For other
* families, the error is fatal.
*
* XXX Now that Postfix controls protocol support centrally with the
* inet_proto(3) module, this workaround should no longer be needed.
*/
if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) {
#ifdef HAS_IPV6
if (af == AF_INET6) {
if (msg_verbose)
msg_warn("%s: socket: %m", myname);
return (-1);
}
#endif
msg_fatal("%s: socket: %m", myname);
}
return (sock);
}
#endif
#ifdef HAVE_GETIFADDRS
/*
* The getifaddrs(3) function, introduced by BSD/OS, provides a
* platform-independent way of requesting interface addresses,
* including IPv6 addresses. The implementation however is not
* present in all major operating systems.
*/
/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */
static int ial_getifaddrs(INET_ADDR_LIST *addr_list,
INET_ADDR_LIST *mask_list,
int af)
{
const char *myname = "inet_addr_local[getifaddrs]";
struct ifaddrs *ifap, *ifa;
struct sockaddr *sa, *sam;
if (getifaddrs(&ifap) < 0)
msg_fatal("%s: getifaddrs: %m", myname);
/*
* Get the address of each IP network interface. According to BIND we
* must include interfaces that are down because the machine may still
* receive packets for that address (yes, via some other interface).
* Having no way to verify this claim on every machine, I will give them
* the benefit of the doubt.
*
* FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces;
* fixed by replacing IFF_RUNNING by IFF_UP.
*
* FIX 200501: The IPV6 patch did not skip wild-card interface addresses
* (tested on FreeBSD).
*/
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0)
continue;
sa = ifa->ifa_addr;
if (af != AF_UNSPEC && sa->sa_family != af)
continue;
sam = ifa->ifa_netmask;
if (sam == 0) {
/* XXX In mynetworks, a null netmask would match everyone. */
msg_warn("ignoring interface with null netmask, address family %d",
sa->sa_family);
continue;
}
switch (sa->sa_family) {
case AF_INET:
if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY)
continue;
break;
#ifdef HAS_IPV6
case AF_INET6:
if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))
continue;
break;
#endif
default:
continue;
}
inet_addr_list_append(addr_list, sa);
if (mask_list != 0) {
/*
* Unfortunately, sa_len/sa_family may be broken in the netmask
* sockaddr structure. We must fix this manually to have correct
* addresses. --dcs
*/
#ifdef HAS_SA_LEN
sam->sa_len = sa->sa_family == AF_INET6 ?
sizeof(struct sockaddr_in6) :
sizeof(struct sockaddr_in);
#endif
sam->sa_family = sa->sa_family;
inet_addr_list_append(mask_list, sam);
}
}
freeifaddrs(ifap);
return (0);
}
#elif defined(HAS_SIOCGLIF) /* HAVE_GETIFADDRS */
/*
* The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris
* and HP/UX operating systems. The data is stored in sockaddr_storage
* structure. Both IPv4 and IPv6 addresses are returned though these
* calls.
*/
#define NEXT_INTERFACE(lifr) (lifr + 1)
#define LIFREQ_SIZE(lifr) sizeof(lifr[0])
/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */
static int ial_siocglif(INET_ADDR_LIST *addr_list,
INET_ADDR_LIST *mask_list,
int af)
{
const char *myname = "inet_addr_local[siocglif]";
struct lifconf lifc;
struct lifreq *lifr;
struct lifreq *lifr_mask;
struct lifreq *the_end;
struct sockaddr *sa;
int sock;
VSTRING *buf;
/*
* See also comments in ial_siocgif()
*/
if (af != AF_INET && af != AF_INET6)
msg_fatal("%s: address family was %d, must be AF_INET (%d) or "
"AF_INET6 (%d)", myname, af, AF_INET, AF_INET6);
sock = ial_socket(af);
if (sock < 0)
return (0);
buf = vstring_alloc(1024);
for (;;) {
memset(&lifc, 0, sizeof(lifc));
lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */
lifc.lifc_len = vstring_avail(buf);
lifc.lifc_buf = vstring_str(buf);
if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) {
if (errno != EINVAL)
msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname);
} else if (lifc.lifc_len < vstring_avail(buf) / 2)
break;
VSTRING_SPACE(buf, vstring_avail(buf) * 2);
}
the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len);
for (lifr = lifc.lifc_req; lifr < the_end;) {
sa = (struct sockaddr *) &lifr->lifr_addr;
if (sa->sa_family != af) {
lifr = NEXT_INTERFACE(lifr);
continue;
}
if (af == AF_INET) {
if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) {
lifr = NEXT_INTERFACE(lifr);
continue;
}
}
#ifdef HAS_IPV6
else if (af == AF_INET6) {
if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) {
lifr = NEXT_INTERFACE(lifr);
continue;
}
}
#endif
inet_addr_list_append(addr_list, sa);
if (mask_list) {
lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq));
memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq));
if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0)
msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname);
/* XXX: Check whether sa_len/family are honoured --dcs */
inet_addr_list_append(mask_list,
(struct sockaddr *) &lifr_mask->lifr_addr);
myfree((void *) lifr_mask);
}
lifr = NEXT_INTERFACE(lifr);
}
vstring_free(buf);
(void) close(sock);
return (0);
}
#else /* HAVE_SIOCGLIF */
/*
* The classic SIOCGIF* ioctls. Modern BSD operating systems will
* also return IPv6 addresses through these structure. Note however
* that recent versions of these operating systems have getifaddrs.
*/
#if defined(_SIZEOF_ADDR_IFREQ)
#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr)))
#define IFREQ_SIZE(ifr) _SIZEOF_ADDR_IFREQ(*ifr)
#elif defined(HAS_SA_LEN)
#define NEXT_INTERFACE(ifr) ((struct ifreq *) \
((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len))
#define IFREQ_SIZE(ifr) (sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)
#else
#define NEXT_INTERFACE(ifr) (ifr + 1)
#define IFREQ_SIZE(ifr) sizeof(ifr[0])
#endif
/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */
static int ial_siocgif(INET_ADDR_LIST *addr_list,
INET_ADDR_LIST *mask_list,
int af)
{
const char *myname = "inet_addr_local[siocgif]";
struct in_addr addr;
struct ifconf ifc;
struct ifreq *ifr;
struct ifreq *ifr_mask;
struct ifreq *the_end;
int sock;
VSTRING *buf;
/*
* Get the network interface list. XXX The socket API appears to have no
* function that returns the number of network interfaces, so we have to
* guess how much space is needed to store the result.
*
* On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
* possible, leaving it up to the application to repeat the request with
* a larger buffer if the result caused a tight fit.
*
* Other systems, such as Solaris 2.5, generate an EINVAL error when the
* buffer is too small for the entire result. Workaround: ignore EINVAL
* errors and repeat the request with a larger buffer. The downside is
* that the program can run out of memory due to a non-memory problem,
* making it more difficult than necessary to diagnose the real problem.
*/
sock = ial_socket(af);
if (sock < 0)
return (0);
buf = vstring_alloc(1024);
for (;;) {
ifc.ifc_len = vstring_avail(buf);
ifc.ifc_buf = vstring_str(buf);
if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
if (errno != EINVAL)
msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
} else if (ifc.ifc_len < vstring_avail(buf) / 2)
break;
VSTRING_SPACE(buf, vstring_avail(buf) * 2);
}
the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
for (ifr = ifc.ifc_req; ifr < the_end;) {
if (ifr->ifr_addr.sa_family != af) {
ifr = NEXT_INTERFACE(ifr);
continue;
}
if (af == AF_INET) {
addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
if (addr.s_addr != INADDR_ANY) {
inet_addr_list_append(addr_list, &ifr->ifr_addr);
if (mask_list) {
ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr));
if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);
/*
* Note that this SIOCGIFNETMASK has truly screwed up the
* contents of sa_len/sa_family. We must fix this
* manually to have correct addresses. --dcs
*/
ifr_mask->ifr_addr.sa_family = af;
#ifdef HAS_SA_LEN
ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in);
#endif
inet_addr_list_append(mask_list, &ifr_mask->ifr_addr);
myfree((void *) ifr_mask);
}
}
}
#ifdef HAS_IPV6
else if (af == AF_INET6) {
struct sockaddr *sa;
sa = SOCK_ADDR_PTR(&ifr->ifr_addr);
if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) {
inet_addr_list_append(addr_list, sa);
if (mask_list) {
/* XXX Assume /128 for everything */
struct sockaddr_in6 mask6;
mask6 = *SOCK_ADDR_IN6_PTR(sa);
memset((void *) &mask6.sin6_addr, ~0,
sizeof(mask6.sin6_addr));
inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6));
}
}
}
#endif
ifr = NEXT_INTERFACE(ifr);
}
vstring_free(buf);
(void) close(sock);
return (0);
}
#endif /* HAVE_SIOCGLIF */
#ifdef HAS_PROCNET_IFINET6
/*
* Older Linux versions lack proper calls to retrieve IPv6 interface
* addresses. Instead, the addresses can be read from a file in the
* /proc tree. The most important issue with this approach however
* is that the /proc tree may not always be available, for example
* in a chrooted environment or in "hardened" (sic) installations.
*/
/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */
static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list,
INET_ADDR_LIST *mask_list)
{
const char *myname = "inet_addr_local[procnet_ifinet6]";
FILE *fp;
char buf[BUFSIZ];
unsigned plen;
VSTRING *addrbuf;
struct sockaddr_in6 addr;
struct sockaddr_in6 mask;
/*
* Example: 00000000000000000000000000000001 01 80 10 80 lo
*
* Fields: address, interface index, prefix length, scope value
* (net/ipv6.h), interface flags (linux/rtnetlink.h), device name.
*
* FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected
* input. Use fgets() + sscanf() instead.
*/
if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) {
addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1);
memset((void *) &addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
#ifdef HAS_SA_LEN
addr.sin6_len = sizeof(addr);
#endif
mask = addr;
while (fgets(buf, sizeof(buf), fp) != 0) {
/* 200501 hex_decode() is light-weight compared to getaddrinfo(). */
if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0
|| sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1
|| plen > MAI_V6ADDR_BITS) {
msg_warn("unexpected data in %s - skipping IPv6 configuration",
_PATH_PROCNET_IFINET6);
break;
}
/* vstring_str(addrbuf) has worst-case alignment. */
addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf);
inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr));
memset((void *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr));
mask_addr((unsigned char *) &mask.sin6_addr,
sizeof(mask.sin6_addr), plen);
inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask));
}
vstring_free(addrbuf);
fclose(fp); /* FIX 200501 */
} else {
msg_warn("can't open %s (%m) - skipping IPv6 configuration",
_PATH_PROCNET_IFINET6);
}
return (0);
}
#endif /* HAS_PROCNET_IFINET6 */
/* inet_addr_local - find all IP addresses for this host */
int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list,
unsigned *addr_family_list)
{
const char *myname = "inet_addr_local";
int initial_count = addr_list->used;
unsigned family;
int count;
while ((family = *addr_family_list++) != 0) {
/*
* IP Version 4
*/
if (family == AF_INET) {
count = addr_list->used;
#if defined(HAVE_GETIFADDRS)
ial_getifaddrs(addr_list, mask_list, AF_INET);
#elif defined (HAS_SIOCGLIF)
ial_siocglif(addr_list, mask_list, AF_INET);
#else
ial_siocgif(addr_list, mask_list, AF_INET);
#endif
if (msg_verbose)
msg_info("%s: configured %d IPv4 addresses",
myname, addr_list->used - count);
}
/*
* IP Version 6
*/
#ifdef HAS_IPV6
else if (family == AF_INET6) {
count = addr_list->used;
#if defined(HAVE_GETIFADDRS)
ial_getifaddrs(addr_list, mask_list, AF_INET6);
#elif defined(HAS_PROCNET_IFINET6)
ial_procnet_ifinet6(addr_list, mask_list);
#elif defined(HAS_SIOCGLIF)
ial_siocglif(addr_list, mask_list, AF_INET6);
#else
ial_siocgif(addr_list, mask_list, AF_INET6);
#endif
if (msg_verbose)
msg_info("%s: configured %d IPv6 addresses", myname,
addr_list->used - count);
}
#endif
/*
* Something's not right.
*/
else
msg_panic("%s: unknown address family %d", myname, family);
}
return (addr_list->used - initial_count);
}
#ifdef TEST
#include <string.h>
#include <vstream.h>
#include <msg_vstream.h>
#include <inet_proto.h>
int main(int unused_argc, char **argv)
{
INET_ADDR_LIST addr_list;
INET_ADDR_LIST mask_list;
MAI_HOSTADDR_STR hostaddr;
MAI_HOSTADDR_STR hostmask;
struct sockaddr *sa;
int i;
INET_PROTO_INFO *proto_info;
msg_vstream_init(argv[0], VSTREAM_ERR);
msg_verbose = 1;
proto_info = inet_proto_init(argv[0],
argv[1] ? argv[1] : INET_PROTO_NAME_ALL);
inet_addr_list_init(&addr_list);
inet_addr_list_init(&mask_list);
inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list);
if (addr_list.used == 0)
msg_fatal("cannot find any active network interfaces");
if (addr_list.used == 1)
msg_warn("found only one active network interface");
for (i = 0; i < addr_list.used; i++) {
sa = SOCK_ADDR_PTR(addr_list.addrs + i);
SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
&hostaddr, (MAI_SERVPORT_STR *) 0, 0);
sa = SOCK_ADDR_PTR(mask_list.addrs + i);
SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
&hostmask, (MAI_SERVPORT_STR *) 0, 0);
vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf);
vstream_fflush(VSTREAM_OUT);
}
inet_addr_list_free(&addr_list);
inet_addr_list_free(&mask_list);
return (0);
}
#endif