return any locally configured IP address if they exist

Fall back to 127.0.0.2/::1 only if there is no proper IP address
configured on any interface.
This commit is contained in:
Lennart Poettering 2011-05-09 14:57:15 +02:00
parent 15ef6d41d6
commit 8041b5bada
6 changed files with 502 additions and 68 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
README
.deps
.libs
*.cache

View file

@ -23,6 +23,8 @@ lib_LTLIBRARIES = libnss_myhostname.la
EXTRA_DIST=bootstrap.sh README LICENSE
SUBDIRS=doc
AM_CPPFLAGS = -include $(top_builddir)/config.h
MAINTAINERCLEANFILES=README
noinst_DATA = README
@ -39,8 +41,16 @@ homepage: all dist
.PHONY: homepage
libnss_myhostname_la_SOURCES = nss-myhostname.c
libnss_myhostname_la_LDFLAGS = -avoid-version -module -export-dynamic -shrext .so.2
libnss_myhostname_la_SOURCES = \
nss-myhostname.c \
netlink.c \
netlink.h
libnss_myhostname_la_LDFLAGS = \
-avoid-version \
-module \
-export-dynamic \
-shrext .so.2
install-exec-hook:
rm -f $(DESTDIR)$(libdir)/libnss_myhostname.la

View file

@ -24,8 +24,10 @@ AC_INIT([nss-myhostname],[0.2],[mzzlubfganzr (at) 0pointer (dot) de])
AC_CONFIG_SRCDIR([nss-myhostname.c])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR(m4)
AC_USE_SYSTEM_EXTENSIONS
AC_SYS_LARGEFILE
AM_INIT_AUTOMAKE([foreign 1.9 -Wall])
AM_INIT_AUTOMAKE([foreign 1.9 -Wall -Wno-portability silent-rules tar-pax subdir-objects dist-bzip2])
AC_SUBST(PACKAGE_URL, [http://0pointer.de/lennart/projects/nss-myhostname/])
@ -34,8 +36,8 @@ ac_default_prefix="/"
# Checks for programs.
AC_PROG_CC
AC_PROG_CC_C99
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CPP
AC_PROG_INSTALL
AC_PROG_LN_S
AC_PROG_MAKE_SET
@ -50,7 +52,8 @@ done
# libtool stuff
AC_DISABLE_STATIC
AC_PROG_LIBTOOL
LT_PREREQ(2.2)
LT_INIT
# Checks for header files.
AC_HEADER_STDC

232
netlink.c Normal file
View file

@ -0,0 +1,232 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of nss-myhostname.
Copyright 2008-2011 Lennart Poettering
nss-myhostname 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.
nss-myhostname 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 nss-myhostname; If not, see
<http://www.gnu.org/licenses/>.
***/
#include <sys/socket.h>
#include <sys/un.h>
#include <asm/types.h>
#include <inttypes.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <inttypes.h>
#include <stdlib.h>
#include "netlink.h"
static int address_compare(const void *_a, const void *_b) {
const struct address *a = _a, *b = _b;
/* Order lowest scope first, IPv4 before IPv6, lowest interface index first */
if (a->scope < b->scope)
return -1;
if (a->scope > b->scope)
return 1;
if (a->family == AF_INET && b->family == AF_INET6)
return -1;
if (a->family == AF_INET6 && b->family == AF_INET)
return 1;
if (a->ifindex < b->ifindex)
return -1;
if (a->ifindex > b->ifindex)
return 1;
return 0;
}
int netlink_acquire_addresses(struct address **_list, unsigned *_n_list) {
struct {
struct nlmsghdr hdr;
struct rtgenmsg gen;
} req;
struct rtgenmsg *gen;
int fd, r, on = 1;
uint32_t seq = 4711;
struct address *list = NULL;
unsigned n_list = 0;
fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0)
return -errno;
if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
r = -errno;
goto finish;
}
memset(&req, 0, sizeof(req));
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
req.hdr.nlmsg_type = RTM_GETADDR;
req.hdr.nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP|NLM_F_ACK;
req.hdr.nlmsg_seq = seq;
req.hdr.nlmsg_pid = 0;
gen = NLMSG_DATA(&req.hdr);
gen->rtgen_family = AF_UNSPEC;
if (send(fd, &req, req.hdr.nlmsg_len, 0) < 0) {
r = -errno;
goto finish;
}
for (;;) {
ssize_t bytes;
struct msghdr msg;
struct cmsghdr *cmsg;
struct ucred *ucred;
struct iovec iov;
struct nlmsghdr *p;
uint8_t cred_buffer[CMSG_SPACE(sizeof(struct ucred))];
struct {
struct nlmsghdr hdr;
struct ifaddrmsg ifaddrmsg;
uint8_t payload[16*1024];
} resp;
memset(&iov, 0, sizeof(iov));
iov.iov_base = &resp;
iov.iov_len = sizeof(resp);
memset(&msg, 0, sizeof(msg));
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cred_buffer;
msg.msg_controllen = sizeof(cred_buffer);
msg.msg_flags = 0;
bytes = recvmsg(fd, &msg, 0);
if (bytes < 0) {
r = -errno;
goto finish;
}
cmsg = CMSG_FIRSTHDR(&msg);
if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
r = -EIO;
goto finish;
}
ucred = (struct ucred*) CMSG_DATA(cmsg);
if (ucred->uid != 0 || ucred->pid != 0)
continue;
for (p = &resp.hdr; bytes > 0; p = NLMSG_NEXT(p, bytes)) {
struct ifaddrmsg *ifaddrmsg;
struct rtattr *a;
size_t l;
void *local = NULL, *address = NULL;
if (!NLMSG_OK(p, (size_t) bytes)) {
r = -EIO;
goto finish;
}
if (p->nlmsg_seq != seq)
continue;
if (p->nlmsg_type == NLMSG_DONE) {
r = 0;
goto finish;
}
if (p->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *nlmsgerr;
nlmsgerr = NLMSG_DATA(p);
r = -nlmsgerr->error;
goto finish;
}
if (p->nlmsg_type != RTM_NEWADDR)
continue;
ifaddrmsg = NLMSG_DATA(p);
if (ifaddrmsg->ifa_family != AF_INET &&
ifaddrmsg->ifa_family != AF_INET6)
continue;
if (ifaddrmsg->ifa_scope == RT_SCOPE_HOST ||
ifaddrmsg->ifa_scope == RT_SCOPE_NOWHERE)
continue;
if (ifaddrmsg->ifa_flags & IFA_F_DEPRECATED)
continue;
l = NLMSG_PAYLOAD(p, sizeof(struct ifaddrmsg));
a = IFA_RTA(ifaddrmsg);
while (RTA_OK(a, l)) {
if (a->rta_type == IFA_ADDRESS)
address = RTA_DATA(a);
else if (a->rta_type == IFA_LOCAL)
local = RTA_DATA(a);
a = RTA_NEXT(a, l);
}
if (local)
address = local;
if (!address)
continue;
list = realloc(list, (n_list+1) * sizeof(struct address));
if (!list) {
r = -ENOMEM;
goto finish;
}
list[n_list].family = ifaddrmsg->ifa_family;
list[n_list].scope = ifaddrmsg->ifa_scope;
memcpy(list[n_list].address, address, ifaddrmsg->ifa_family == AF_INET ? 4 : 16);
list[n_list].ifindex = ifaddrmsg->ifa_index;
n_list++;
}
}
finish:
close(fd);
if (r < 0)
free(list);
else {
qsort(list, n_list, sizeof(struct address), address_compare);
*_list = list;
*_n_list = n_list;
}
return r;
}

45
netlink.h Normal file
View file

@ -0,0 +1,45 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#ifndef foonetlinkhfoo
#define foonetlinkhfoo
/***
This file is part of nss-myhostname.
Copyright 2008-2011 Lennart Poettering
nss-myhostname 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.
nss-myhostname 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 nss-myhostname; If not, see
<http://www.gnu.org/licenses/>.
***/
#include <inttypes.h>
#include <sys/types.h>
#include <assert.h>
struct address {
unsigned char family;
uint8_t address[16];
unsigned char scope;
int ifindex;
};
int netlink_acquire_addresses(struct address **_list, unsigned *_n_list);
static inline size_t PROTO_ADDRESS_SIZE(int proto) {
assert(proto == AF_INET || proto == AF_INET6);
return proto == AF_INET6 ? 16 : 4;
}
#endif

View file

@ -1,39 +1,38 @@
/*-*- Mode: C; c-basic-offset: 8 -*-*/
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of nss-myhostname.
This file is part of nss-myhostname.
Copyright 2008 Lennart Poettering
Copyright 2008-2011 Lennart Poettering
nss-myhostname 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.
nss-myhostname 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.
nss-myhostname 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.
nss-myhostname 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 nss-myhostname. If not, If not, see
<http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Lesser General Public
License along with nss-myhostname; If not, see
<http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <limits.h>
#include <nss.h>
#include <sys/types.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <net/if.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include "netlink.h"
/* We use 127.0.0.2 as IPv4 address. This has the advantage over
* 127.0.0.1 that it can be translated back to the local hostname. For
@ -46,6 +45,50 @@
#define ALIGN(a) (((a+sizeof(void*)-1)/sizeof(void*))*sizeof(void*))
enum nss_status _nss_myhostname_gethostbyname4_r(
const char *name,
struct gaih_addrtuple **pat,
char *buffer, size_t buflen,
int *errnop, int *h_errnop,
int32_t *ttlp);
enum nss_status _nss_myhostname_gethostbyname3_r(
const char *name,
int af,
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop,
int32_t *ttlp,
char **canonp);
enum nss_status _nss_myhostname_gethostbyname2_r(
const char *name,
int af,
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop);
enum nss_status _nss_myhostname_gethostbyname_r(
const char *name,
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop);
enum nss_status _nss_myhostname_gethostbyaddr2_r(
const void* addr, socklen_t len,
int af,
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop,
int32_t *ttlp);
enum nss_status _nss_myhostname_gethostbyaddr_r(
const void* addr, socklen_t len,
int af,
struct hostent *host,
char *buffer, size_t buflen,
int *errnop, int *h_errnop);
enum nss_status _nss_myhostname_gethostbyname4_r(
const char *name,
struct gaih_addrtuple **pat,
@ -53,11 +96,13 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
int *errnop, int *h_errnop,
int32_t *ttlp) {
unsigned ifi;
unsigned lo_ifi;
char hn[HOST_NAME_MAX+1];
size_t l, idx, ms;
char *r_name;
struct gaih_addrtuple *r_tuple1, *r_tuple2;
struct gaih_addrtuple *r_tuple, *r_tuple_prev = NULL;
struct address *addresses = NULL, *a;
unsigned n_addresses = 0, n;
memset(hn, 0, sizeof(hn));
if (gethostname(hn, sizeof(hn)-1) < 0) {
@ -72,14 +117,18 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
return NSS_STATUS_NOTFOUND;
}
/* If this fails, n_addresses is 0. Which is fine */
netlink_acquire_addresses(&addresses, &n_addresses);
/* If this call fails we fill in 0 as scope. Which is fine */
ifi = if_nametoindex(LOOPBACK_INTERFACE);
lo_ifi = if_nametoindex(LOOPBACK_INTERFACE);
l = strlen(hn);
ms = ALIGN(l+1)+ALIGN(sizeof(struct gaih_addrtuple))*2;
ms = ALIGN(l+1)+ALIGN(sizeof(struct gaih_addrtuple))*(n_addresses > 0 ? n_addresses : 2);
if (buflen < ms) {
*errnop = ENOMEM;
*h_errnop = NO_RECOVERY;
free(addresses);
return NSS_STATUS_TRYAGAIN;
}
@ -88,32 +137,53 @@ enum nss_status _nss_myhostname_gethostbyname4_r(
memcpy(r_name, hn, l+1);
idx = ALIGN(l+1);
/* Second, fill in IPv4 tuple */
r_tuple1 = (struct gaih_addrtuple*) (buffer + idx);
r_tuple1->next = NULL;
r_tuple1->name = r_name;
r_tuple1->family = AF_INET;
*(uint32_t*) r_tuple1->addr = LOCALADDRESS_IPV4;
r_tuple1->scopeid = (uint32_t) ifi;
idx += ALIGN(sizeof(struct gaih_addrtuple));
if (n_addresses <= 0) {
/* Second, fill in IPv6 tuple */
r_tuple = (struct gaih_addrtuple*) (buffer + idx);
r_tuple->next = r_tuple_prev;
r_tuple->name = r_name;
r_tuple->family = AF_INET6;
memcpy(r_tuple->addr, LOCALADDRESS_IPV6, 16);
r_tuple->scopeid = (uint32_t) lo_ifi;
/* Third, fill in IPv6 tuple */
r_tuple2 = (struct gaih_addrtuple*) (buffer + idx);
r_tuple2->next = r_tuple1;
r_tuple2->name = r_name;
r_tuple2->family = AF_INET6;
memcpy(r_tuple2->addr, LOCALADDRESS_IPV6, 16);
r_tuple2->scopeid = (uint32_t) ifi;
idx += ALIGN(sizeof(struct gaih_addrtuple));
idx += ALIGN(sizeof(struct gaih_addrtuple));
r_tuple_prev = r_tuple;
/* Third, fill in IPv4 tuple */
r_tuple = (struct gaih_addrtuple*) (buffer + idx);
r_tuple->next = r_tuple_prev;
r_tuple->name = r_name;
r_tuple->family = AF_INET;
*(uint32_t*) r_tuple->addr = LOCALADDRESS_IPV4;
r_tuple->scopeid = (uint32_t) lo_ifi;
idx += ALIGN(sizeof(struct gaih_addrtuple));
r_tuple_prev = r_tuple;
}
/* Fourth, fill actual addresses in, but in backwards order */
for (a = addresses + n_addresses - 1, n = 0; n < n_addresses; n++, a--) {
r_tuple = (struct gaih_addrtuple*) (buffer + idx);
r_tuple->next = r_tuple_prev;
r_tuple->name = r_name;
r_tuple->family = a->family;
r_tuple->scopeid = a->ifindex;
memcpy(r_tuple->addr, a->address, 16);
idx += ALIGN(sizeof(struct gaih_addrtuple));
r_tuple_prev = r_tuple;
}
/* Verify the size matches */
assert(idx == ms);
*pat = r_tuple2;
*pat = r_tuple_prev;
if (ttlp)
*ttlp = 0;
free(addresses);
return NSS_STATUS_SUCCESS;
}
@ -129,14 +199,27 @@ static enum nss_status fill_in_hostent(
size_t l, idx, ms;
char *r_addr, *r_name, *r_aliases, *r_addr_list;
size_t alen;
struct address *addresses = NULL, *a;
unsigned n_addresses = 0, n, c;
alen = af == AF_INET ? 4 : 16;
alen = PROTO_ADDRESS_SIZE(af);
netlink_acquire_addresses(&addresses, &n_addresses);
for (a = addresses, n = 0, c = 0; n < n_addresses; a++, n++)
if (af == a->family)
c++;
l = strlen(hn);
ms = ALIGN(l+1)+sizeof(char*)+ALIGN(alen)+sizeof(char*)*2;
ms = ALIGN(l+1)+
sizeof(char*)+
(c > 0 ? c : 1)*ALIGN(alen)+
(c > 0 ? c+1 : 2)*sizeof(char*);
if (buflen < ms) {
*errnop = ENOMEM;
*h_errnop = NO_RECOVERY;
free(addresses);
return NSS_STATUS_TRYAGAIN;
}
@ -145,24 +228,57 @@ static enum nss_status fill_in_hostent(
memcpy(r_name, hn, l+1);
idx = ALIGN(l+1);
/* Second, create aliases array */
/* Second, create (empty) aliases array */
r_aliases = buffer + idx;
*(char**) r_aliases = NULL;
idx += sizeof(char*);
/* Third, add address */
/* Third, add addresses */
r_addr = buffer + idx;
if (af == AF_INET)
*(uint32_t*) r_addr = LOCALADDRESS_IPV4;
else
memcpy(r_addr, LOCALADDRESS_IPV6, 16);
idx += ALIGN(alen);
if (c > 0) {
unsigned i = 0;
for (a = addresses, n = 0; n < n_addresses; a++, n++) {
if (af != a->family)
continue;
memcpy(r_addr + i*ALIGN(alen), a->address, alen);
i++;
}
assert(i == c);
idx += c*ALIGN(alen);
} else {
if (af == AF_INET)
*(uint32_t*) r_addr = LOCALADDRESS_IPV4;
else
memcpy(r_addr, LOCALADDRESS_IPV6, 16);
idx += ALIGN(alen);
}
/* Fourth, add address pointer array */
r_addr_list = buffer + idx;
((char**) r_addr_list)[0] = r_addr;
((char**) r_addr_list)[1] = NULL;
idx += sizeof(char*)*2;
if (c > 0) {
unsigned i = 0;
for (a = addresses, n = 0; n < n_addresses; a++, n++) {
if (af != a->family)
continue;
((char**) r_addr_list)[i] = (r_addr + i*ALIGN(alen));
i++;
}
assert(i == c);
((char**) r_addr_list)[c] = NULL;
idx += (c+1)*sizeof(char*);
} else {
((char**) r_addr_list)[0] = r_addr;
((char**) r_addr_list)[1] = NULL;
idx += 2*sizeof(char*);
}
/* Verify the size matches */
assert(idx == ms);
@ -179,6 +295,8 @@ static enum nss_status fill_in_hostent(
if (canonp)
*canonp = r_name;
free(addresses);
return NSS_STATUS_SUCCESS;
}
@ -235,7 +353,7 @@ enum nss_status _nss_myhostname_gethostbyname2_r(
NULL);
}
enum nss_status _nss_myhostname_gethostbyname_r (
enum nss_status _nss_myhostname_gethostbyname_r(
const char *name,
struct hostent *host,
char *buffer, size_t buflen,
@ -260,35 +378,60 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r(
int32_t *ttlp) {
char hn[HOST_NAME_MAX+1];
struct address *addresses = NULL, *a;
unsigned n_addresses = 0, n;
if (len != PROTO_ADDRESS_SIZE(af)) {
*errnop = EINVAL;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL;
}
if (af == AF_INET) {
if (len != 4 ||
(*(uint32_t*) addr) != LOCALADDRESS_IPV4)
goto not_found;
if ((*(uint32_t*) addr) == LOCALADDRESS_IPV4)
goto found;
} else if (af == AF_INET6) {
if (len != 16 ||
memcmp(addr, LOCALADDRESS_IPV6, 16) != 0)
goto not_found;
if (memcmp(addr, LOCALADDRESS_IPV6, 16) == 0)
goto found;
} else {
*errnop = EAFNOSUPPORT;
*h_errnop = NO_DATA;
return NSS_STATUS_UNAVAIL;
}
netlink_acquire_addresses(&addresses, &n_addresses);
for (a = addresses, n = 0; n < n_addresses; n++, a++) {
if (af != a->family)
continue;
if (memcmp(addr, a->address, PROTO_ADDRESS_SIZE(af)) == 0)
goto found;
}
*errnop = ENOENT;
*h_errnop = HOST_NOT_FOUND;
free(addresses);
return NSS_STATUS_NOTFOUND;
found:
free(addresses);
memset(hn, 0, sizeof(hn));
if (gethostname(hn, sizeof(hn)-1) < 0) {
*errnop = errno;
*h_errnop = NO_RECOVERY;
return NSS_STATUS_UNAVAIL;
}
return fill_in_hostent(hn, af, host, buffer, buflen, errnop, h_errnop, ttlp, NULL);
not_found:
*errnop = ENOENT;
*h_errnop = HOST_NOT_FOUND;
return NSS_STATUS_NOTFOUND;
}
enum nss_status _nss_myhostname_gethostbyaddr_r(