[libvirt] [PATCH 0/6] NSS module for libvirt

Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need! NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf: $ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt and you're all set. Now you can just: $ ping $mydomain $ ssh user@$mydomain or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname. What's beautiful on this feature is that it helps any users regardless of their systemd attitude. On systemd hosts there already exists a similar module 'mymachines' which takes its data from machined. And libvirt does communicate with machined when creating a domain. But unfortunately at that time we know nothing about guest's IPs and therefore do not tell them to machined, which in turn can't tell anything to mymachines module. To make things worse, machined seems to be lacking an API to tell it the addresses later on when libvirt finds out. Therefore even systemd distros will benefit from this feature. If the feature makes its way in, I'll describe all the steps in a wiki (hence, no documentation in this patch set - although, if you have a bright idea where should I put it, I'm all ears). Enjoy! Michal Privoznik (6): Export virLease* functions for leases file handling virjson: Resolve const correctness Initial support for NSS plugin skeleton nss: Implement _nss_libvirt_gethostbyname3_r Implement _nss_libvirt_gethostbyname4_r nss: Introduce a test cfg.mk | 2 +- configure.ac | 2 + m4/virt-nss.m4 | 51 ++++++ po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 6 + src/network/leaseshelper.c | 271 +-------------------------- src/util/virjson.c | 58 +++--- src/util/virjson.h | 54 +++--- src/util/virlease.c | 304 ++++++++++++++++++++++++++++++ src/util/virlease.h | 44 +++++ tests/Makefile.am | 18 ++ tests/nssdata/virbr0.status | 14 ++ tests/nssmock.c | 140 ++++++++++++++ tests/nsstest.c | 184 +++++++++++++++++++ tools/Makefile.am | 45 +++++ tools/nss/libvirt_nss.c | 436 ++++++++++++++++++++++++++++++++++++++++++++ tools/nss/libvirt_nss.h | 52 ++++++ tools/nss/libvirt_nss.syms | 12 ++ 19 files changed, 1368 insertions(+), 327 deletions(-) create mode 100644 m4/virt-nss.m4 create mode 100644 src/util/virlease.c create mode 100644 src/util/virlease.h create mode 100644 tests/nssdata/virbr0.status create mode 100644 tests/nssmock.c create mode 100644 tests/nsstest.c create mode 100644 tools/nss/libvirt_nss.c create mode 100644 tools/nss/libvirt_nss.h create mode 100644 tools/nss/libvirt_nss.syms -- 2.4.10

These functions are going to be reused very shortly. So instead of duplicating the code, lets move them into utils module. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- po/POTFILES.in | 1 + src/Makefile.am | 1 + src/libvirt_private.syms | 6 + src/network/leaseshelper.c | 271 +--------------------------------------- src/util/virlease.c | 304 +++++++++++++++++++++++++++++++++++++++++++++ src/util/virlease.h | 44 +++++++ 6 files changed, 357 insertions(+), 270 deletions(-) create mode 100644 src/util/virlease.c create mode 100644 src/util/virlease.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 4d82a8f..fda5853 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -202,6 +202,7 @@ src/util/viriptables.c src/util/viriscsi.c src/util/virjson.c src/util/virkeyfile.c +src/util/virlease.c src/util/virlockspace.c src/util/virnetdev.c src/util/virnetdevbandwidth.c diff --git a/src/Makefile.am b/src/Makefile.am index f857e59..c48960b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -125,6 +125,7 @@ UTIL_SOURCES = \ util/virkeycode.c util/virkeycode.h \ util/virkeyfile.c util/virkeyfile.h \ util/virkeymaps.h \ + util/virlease.c util/virlease.h \ util/virlockspace.c util/virlockspace.h \ util/virlog.c util/virlog.h \ util/virmacaddr.h util/virmacaddr.c \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 4cfaed5..74f0275 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1715,6 +1715,12 @@ virKModLoad; virKModUnload; +# util/virlease.h +virLeaseNew; +virLeasePrintLeases; +virLeaseReadCustomLeaseFile; + + # util/virlockspace.h virLockSpaceAcquireResource; virLockSpaceCreateResource; diff --git a/src/network/leaseshelper.c b/src/network/leaseshelper.c index 55ddd58..94a6163 100644 --- a/src/network/leaseshelper.c +++ b/src/network/leaseshelper.c @@ -28,34 +28,18 @@ #include <locale.h> #include <stdio.h> #include <stdlib.h> -#include <sys/stat.h> -#include "virutil.h" #include "virthread.h" #include "virfile.h" #include "virpidfile.h" -#include "virbuffer.h" #include "virstring.h" #include "virerror.h" #include "viralloc.h" -#include "virjson.h" +#include "virlease.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_NETWORK -/** - * VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX: - * - * Macro providing the upper limit on the size of leases file - */ -#define VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX (32 * 1024 * 1024) - -/* - * Use this when passing possibly-NULL strings to printf-a-likes. - * Required for unknown parameters during init call. - */ -#define EMPTY_STR(s) ((s) ? (s) : "*") - static const char *program_name; /* Display version information. */ @@ -105,259 +89,6 @@ VIR_ENUM_DECL(virLeaseAction); VIR_ENUM_IMPL(virLeaseAction, VIR_LEASE_ACTION_LAST, "add", "old", "del", "init"); -static int -virLeaseReadCustomLeaseFile(virJSONValuePtr leases_array_new, - const char *custom_lease_file, - const char *ip_to_delete, - char **server_duid) -{ - char *lease_entries = NULL; - virJSONValuePtr leases_array = NULL; - long long currtime = 0; - long long expirytime; - int custom_lease_file_len = 0; - virJSONValuePtr lease_tmp = NULL; - const char *ip_tmp = NULL; - const char *server_duid_tmp = NULL; - size_t i; - int ret = -1; - - currtime = (long long) time(NULL); - - /* Read entire contents */ - if ((custom_lease_file_len = virFileReadAll(custom_lease_file, - VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX, - &lease_entries)) < 0) { - goto cleanup; - } - - /* Check for previous leases */ - if (custom_lease_file_len == 0) { - ret = 0; - goto cleanup; - } - - if (!(leases_array = virJSONValueFromString(lease_entries))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid json in file: %s, rewriting it"), - custom_lease_file); - ret = 0; - goto cleanup; - } - - if (!virJSONValueIsArray(leases_array)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("couldn't fetch array of leases")); - goto cleanup; - } - - i = 0; - while (i < virJSONValueArraySize(leases_array)) { - if (!(lease_tmp = virJSONValueArrayGet(leases_array, i))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to parse json")); - goto cleanup; - } - - if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address")) || - (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", &expirytime) < 0)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to parse json")); - goto cleanup; - } - /* Check whether lease has expired or not */ - if (expirytime < currtime) { - i++; - continue; - } - - /* Check whether lease has to be included or not */ - if (ip_to_delete && STREQ(ip_tmp, ip_to_delete)) { - i++; - continue; - } - - if (strchr(ip_tmp, ':')) { - /* This is an ipv6 lease */ - if ((server_duid_tmp - = virJSONValueObjectGetString(lease_tmp, "server-duid"))) { - if (!*server_duid && VIR_STRDUP(*server_duid, server_duid_tmp) < 0) { - /* Control reaches here when the 'action' is not for an - * ipv6 lease or, for some weird reason the env var - * DNSMASQ_SERVER_DUID wasn't set*/ - goto cleanup; - } - } else { - /* Inject server-duid into those ipv6 leases which - * didn't have it previously, for example, those - * created by leaseshelper from libvirt 1.2.6 */ - if (virJSONValueObjectAppendString(lease_tmp, "server-duid", *server_duid) < 0) - goto cleanup; - } - } - - /* Move old lease to new array */ - if (virJSONValueArrayAppend(leases_array_new, lease_tmp) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to create json")); - goto cleanup; - } - - ignore_value(virJSONValueArraySteal(leases_array, i)); - } - - ret = 0; - - cleanup: - virJSONValueFree(leases_array); - VIR_FREE(lease_entries); - return ret; -} - -static int -virLeasePrintLeases(virJSONValuePtr leases_array_new, - const char *server_duid) -{ - virJSONValuePtr lease_tmp = NULL; - const char *ip_tmp = NULL; - long long expirytime = 0; - int ret = -1; - size_t i; - - /* Man page of dnsmasq says: the script (helper program, in our case) - * should write the saved state of the lease database, in dnsmasq - * leasefile format, to stdout and exit with zero exit code, when - * called with argument init. Format: - * $expirytime $mac $ip $hostname $clientid # For all ipv4 leases - * duid $server-duid # If DHCPv6 is present - * $expirytime $iaid $ip $hostname $clientduid # For all ipv6 leases */ - - /* Traversing the ipv4 leases */ - for (i = 0; i < virJSONValueArraySize(leases_array_new); i++) { - lease_tmp = virJSONValueArrayGet(leases_array_new, i); - if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to parse json")); - goto cleanup; - } - if (!strchr(ip_tmp, ':')) { - if (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", - &expirytime) < 0) - continue; - - printf("%lld %s %s %s %s\n", - expirytime, - virJSONValueObjectGetString(lease_tmp, "mac-address"), - virJSONValueObjectGetString(lease_tmp, "ip-address"), - EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "hostname")), - EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "client-id"))); - } - } - - /* Traversing the ipv6 leases */ - if (server_duid) { - printf("duid %s\n", server_duid); - for (i = 0; i < virJSONValueArraySize(leases_array_new); i++) { - lease_tmp = virJSONValueArrayGet(leases_array_new, i); - if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to parse json")); - goto cleanup; - } - if (strchr(ip_tmp, ':')) { - if (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", - &expirytime) < 0) - continue; - - printf("%lld %s %s %s %s\n", - expirytime, - virJSONValueObjectGetString(lease_tmp, "iaid"), - virJSONValueObjectGetString(lease_tmp, "ip-address"), - EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "hostname")), - EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "client-id"))); - } - } - } - - ret = 0; - - cleanup: - return ret; -} - -static int -virLeaseNew(virJSONValuePtr *lease_ret, - const char *mac, - const char *clientid, - const char *ip, - const char *hostname, - const char *iaid, - const char *server_duid) -{ - virJSONValuePtr lease_new = NULL; - const char *exptime_tmp = virGetEnvAllowSUID("DNSMASQ_LEASE_EXPIRES"); - long long expirytime = 0; - char *exptime = NULL; - int ret = -1; - - /* In case hostname is still unknown, use the last known one */ - if (!hostname) - hostname = virGetEnvAllowSUID("DNSMASQ_OLD_HOSTNAME"); - - if (!mac) { - ret = 0; - goto cleanup; - } - - if (exptime_tmp) { - if (VIR_STRDUP(exptime, exptime_tmp) < 0) - goto cleanup; - - /* Removed extraneous trailing space in DNSMASQ_LEASE_EXPIRES - * (dnsmasq < 2.52) */ - if (exptime[strlen(exptime) - 1] == ' ') - exptime[strlen(exptime) - 1] = '\0'; - } - - if (!exptime || - virStrToLong_ll(exptime, NULL, 10, &expirytime) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Unable to convert lease expiry time to long long: %s"), - NULLSTR(exptime)); - goto cleanup; - } - - /* Create new lease */ - if (!(lease_new = virJSONValueNewObject())) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to create json")); - goto cleanup; - } - - if (iaid && virJSONValueObjectAppendString(lease_new, "iaid", iaid) < 0) - goto cleanup; - if (ip && virJSONValueObjectAppendString(lease_new, "ip-address", ip) < 0) - goto cleanup; - if (mac && virJSONValueObjectAppendString(lease_new, "mac-address", mac) < 0) - goto cleanup; - if (hostname && virJSONValueObjectAppendString(lease_new, "hostname", hostname) < 0) - goto cleanup; - if (clientid && virJSONValueObjectAppendString(lease_new, "client-id", clientid) < 0) - goto cleanup; - if (server_duid && virJSONValueObjectAppendString(lease_new, "server-duid", server_duid) < 0) - goto cleanup; - if (expirytime && virJSONValueObjectAppendNumberLong(lease_new, "expiry-time", expirytime) < 0) - goto cleanup; - - ret = 0; - *lease_ret = lease_new; - lease_new = NULL; - cleanup: - VIR_FREE(exptime); - virJSONValueFree(lease_new); - return ret; -} - int main(int argc, char **argv) { diff --git a/src/util/virlease.c b/src/util/virlease.c new file mode 100644 index 0000000..b8e9d8b --- /dev/null +++ b/src/util/virlease.c @@ -0,0 +1,304 @@ +/* + * virlease.c: Leases file handling + * + * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014 Nehal J Wani + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> + +#include "virlease.h" + +#include <time.h> + +#include "virfile.h" +#include "virstring.h" +#include "virerror.h" +#include "viralloc.h" + +#define VIR_FROM_THIS VIR_FROM_NETWORK + +/** + * VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX: + * + * Macro providing the upper limit on the size of leases file + */ +#define VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX (32 * 1024 * 1024) + + +/* + * Use this when passing possibly-NULL strings to printf-a-likes. + * Required for unknown parameters during init call. + */ +#define EMPTY_STR(s) ((s) ? (s) : "*") + + +int +virLeaseReadCustomLeaseFile(virJSONValuePtr leases_array_new, + const char *custom_lease_file, + const char *ip_to_delete, + char **server_duid) +{ + char *lease_entries = NULL; + virJSONValuePtr leases_array = NULL; + long long currtime = 0; + long long expirytime; + int custom_lease_file_len = 0; + virJSONValuePtr lease_tmp = NULL; + const char *ip_tmp = NULL; + const char *server_duid_tmp = NULL; + size_t i; + int ret = -1; + + currtime = (long long) time(NULL); + + /* Read entire contents */ + if ((custom_lease_file_len = virFileReadAll(custom_lease_file, + VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX, + &lease_entries)) < 0) { + goto cleanup; + } + + /* Check for previous leases */ + if (custom_lease_file_len == 0) { + ret = 0; + goto cleanup; + } + + if (!(leases_array = virJSONValueFromString(lease_entries))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid json in file: %s, rewriting it"), + custom_lease_file); + ret = 0; + goto cleanup; + } + + if (!virJSONValueIsArray(leases_array)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("couldn't fetch array of leases")); + goto cleanup; + } + + i = 0; + while (i < virJSONValueArraySize(leases_array)) { + if (!(lease_tmp = virJSONValueArrayGet(leases_array, i))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse json")); + goto cleanup; + } + + if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address")) || + (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", &expirytime) < 0)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse json")); + goto cleanup; + } + /* Check whether lease has expired or not */ + if (expirytime < currtime) { + i++; + continue; + } + + /* Check whether lease has to be included or not */ + if (ip_to_delete && STREQ(ip_tmp, ip_to_delete)) { + i++; + continue; + } + + if (strchr(ip_tmp, ':')) { + /* This is an ipv6 lease */ + if ((server_duid_tmp + = virJSONValueObjectGetString(lease_tmp, "server-duid"))) { + if (!*server_duid && VIR_STRDUP(*server_duid, server_duid_tmp) < 0) { + /* Control reaches here when the 'action' is not for an + * ipv6 lease or, for some weird reason the env var + * DNSMASQ_SERVER_DUID wasn't set*/ + goto cleanup; + } + } else { + /* Inject server-duid into those ipv6 leases which + * didn't have it previously, for example, those + * created by leaseshelper from libvirt 1.2.6 */ + if (virJSONValueObjectAppendString(lease_tmp, "server-duid", *server_duid) < 0) + goto cleanup; + } + } + + /* Move old lease to new array */ + if (virJSONValueArrayAppend(leases_array_new, lease_tmp) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create json")); + goto cleanup; + } + + ignore_value(virJSONValueArraySteal(leases_array, i)); + } + + ret = 0; + + cleanup: + virJSONValueFree(leases_array); + VIR_FREE(lease_entries); + return ret; +} + + +int +virLeasePrintLeases(virJSONValuePtr leases_array_new, + const char *server_duid) +{ + virJSONValuePtr lease_tmp = NULL; + const char *ip_tmp = NULL; + long long expirytime = 0; + int ret = -1; + size_t i; + + /* Man page of dnsmasq says: the script (helper program, in our case) + * should write the saved state of the lease database, in dnsmasq + * leasefile format, to stdout and exit with zero exit code, when + * called with argument init. Format: + * $expirytime $mac $ip $hostname $clientid # For all ipv4 leases + * duid $server-duid # If DHCPv6 is present + * $expirytime $iaid $ip $hostname $clientduid # For all ipv6 leases */ + + /* Traversing the ipv4 leases */ + for (i = 0; i < virJSONValueArraySize(leases_array_new); i++) { + lease_tmp = virJSONValueArrayGet(leases_array_new, i); + if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse json")); + goto cleanup; + } + if (!strchr(ip_tmp, ':')) { + if (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", + &expirytime) < 0) + continue; + + printf("%lld %s %s %s %s\n", + expirytime, + virJSONValueObjectGetString(lease_tmp, "mac-address"), + virJSONValueObjectGetString(lease_tmp, "ip-address"), + EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "hostname")), + EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "client-id"))); + } + } + + /* Traversing the ipv6 leases */ + if (server_duid) { + printf("duid %s\n", server_duid); + for (i = 0; i < virJSONValueArraySize(leases_array_new); i++) { + lease_tmp = virJSONValueArrayGet(leases_array_new, i); + if (!(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse json")); + goto cleanup; + } + if (strchr(ip_tmp, ':')) { + if (virJSONValueObjectGetNumberLong(lease_tmp, "expiry-time", + &expirytime) < 0) + continue; + + printf("%lld %s %s %s %s\n", + expirytime, + virJSONValueObjectGetString(lease_tmp, "iaid"), + virJSONValueObjectGetString(lease_tmp, "ip-address"), + EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "hostname")), + EMPTY_STR(virJSONValueObjectGetString(lease_tmp, "client-id"))); + } + } + } + + ret = 0; + + cleanup: + return ret; +} + + +int +virLeaseNew(virJSONValuePtr *lease_ret, + const char *mac, + const char *clientid, + const char *ip, + const char *hostname, + const char *iaid, + const char *server_duid) +{ + virJSONValuePtr lease_new = NULL; + const char *exptime_tmp = virGetEnvAllowSUID("DNSMASQ_LEASE_EXPIRES"); + long long expirytime = 0; + char *exptime = NULL; + int ret = -1; + + /* In case hostname is still unknown, use the last known one */ + if (!hostname) + hostname = virGetEnvAllowSUID("DNSMASQ_OLD_HOSTNAME"); + + if (!mac) { + ret = 0; + goto cleanup; + } + + if (exptime_tmp) { + if (VIR_STRDUP(exptime, exptime_tmp) < 0) + goto cleanup; + + /* Removed extraneous trailing space in DNSMASQ_LEASE_EXPIRES + * (dnsmasq < 2.52) */ + if (exptime[strlen(exptime) - 1] == ' ') + exptime[strlen(exptime) - 1] = '\0'; + } + + if (!exptime || + virStrToLong_ll(exptime, NULL, 10, &expirytime) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to convert lease expiry time to long long: %s"), + NULLSTR(exptime)); + goto cleanup; + } + + /* Create new lease */ + if (!(lease_new = virJSONValueNewObject())) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create json")); + goto cleanup; + } + + if (iaid && virJSONValueObjectAppendString(lease_new, "iaid", iaid) < 0) + goto cleanup; + if (ip && virJSONValueObjectAppendString(lease_new, "ip-address", ip) < 0) + goto cleanup; + if (mac && virJSONValueObjectAppendString(lease_new, "mac-address", mac) < 0) + goto cleanup; + if (hostname && virJSONValueObjectAppendString(lease_new, "hostname", hostname) < 0) + goto cleanup; + if (clientid && virJSONValueObjectAppendString(lease_new, "client-id", clientid) < 0) + goto cleanup; + if (server_duid && virJSONValueObjectAppendString(lease_new, "server-duid", server_duid) < 0) + goto cleanup; + if (expirytime && virJSONValueObjectAppendNumberLong(lease_new, "expiry-time", expirytime) < 0) + goto cleanup; + + ret = 0; + *lease_ret = lease_new; + lease_new = NULL; + cleanup: + VIR_FREE(exptime); + virJSONValueFree(lease_new); + return ret; +} diff --git a/src/util/virlease.h b/src/util/virlease.h new file mode 100644 index 0000000..b9f394e --- /dev/null +++ b/src/util/virlease.h @@ -0,0 +1,44 @@ +/* + * virlease.h: Leases file handling + * + * Copyright (C) 2014 Red Hat, Inc. + * Copyright (C) 2014 Nehal J Wani + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef __VIR_LEASE_H_ +# define __VIR_LEASE_H_ + +# include "virjson.h" + +int virLeaseReadCustomLeaseFile(virJSONValuePtr leases_array_new, + const char *custom_lease_file, + const char *ip_to_delete, + char **server_duid); + +int virLeasePrintLeases(virJSONValuePtr leases_array_new, + const char *server_duid); + + +int virLeaseNew(virJSONValuePtr *lease_ret, + const char *mac, + const char *clientid, + const char *ip, + const char *hostname, + const char *iaid, + const char *server_duid); +#endif /* __VIR_LEASE_H */ -- 2.4.10

Plenty of our virJSON*() APIs don't modify passed object. They merely get a value stored in it. Note this fact in their definition and enforce const correctness. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/util/virjson.c | 58 +++++++++++++++++++++++++++--------------------------- src/util/virjson.h | 54 +++++++++++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/util/virjson.c b/src/util/virjson.c index ae6362b..0a595b9 100644 --- a/src/util/virjson.c +++ b/src/util/virjson.c @@ -735,7 +735,7 @@ virJSONValueArrayAppend(virJSONValuePtr array, int -virJSONValueObjectHasKey(virJSONValuePtr object, +virJSONValueObjectHasKey(const virJSONValue *object, const char *key) { size_t i; @@ -753,7 +753,7 @@ virJSONValueObjectHasKey(virJSONValuePtr object, virJSONValuePtr -virJSONValueObjectGet(virJSONValuePtr object, +virJSONValueObjectGet(const virJSONValue *object, const char *key) { size_t i; @@ -773,7 +773,7 @@ virJSONValueObjectGet(virJSONValuePtr object, /* Return the value associated with KEY within OBJECT, but return NULL * if the key is missing or if value is not the correct TYPE. */ virJSONValuePtr -virJSONValueObjectGetByType(virJSONValuePtr object, +virJSONValueObjectGetByType(const virJSONValue *object, const char *key, virJSONType type) { @@ -786,7 +786,7 @@ virJSONValueObjectGetByType(virJSONValuePtr object, int -virJSONValueObjectKeysNumber(virJSONValuePtr object) +virJSONValueObjectKeysNumber(const virJSONValue *object) { if (object->type != VIR_JSON_TYPE_OBJECT) return -1; @@ -796,7 +796,7 @@ virJSONValueObjectKeysNumber(virJSONValuePtr object) const char * -virJSONValueObjectGetKey(virJSONValuePtr object, +virJSONValueObjectGetKey(const virJSONValue *object, unsigned int n) { if (object->type != VIR_JSON_TYPE_OBJECT) @@ -844,7 +844,7 @@ virJSONValueObjectRemoveKey(virJSONValuePtr object, virJSONValuePtr -virJSONValueObjectGetValue(virJSONValuePtr object, +virJSONValueObjectGetValue(const virJSONValue *object, unsigned int n) { if (object->type != VIR_JSON_TYPE_OBJECT) @@ -858,7 +858,7 @@ virJSONValueObjectGetValue(virJSONValuePtr object, bool -virJSONValueIsArray(virJSONValuePtr array) +virJSONValueIsArray(const virJSONValue *array) { return array->type == VIR_JSON_TYPE_ARRAY; } @@ -875,7 +875,7 @@ virJSONValueArraySize(const virJSONValue *array) virJSONValuePtr -virJSONValueArrayGet(virJSONValuePtr array, +virJSONValueArrayGet(const virJSONValue *array, unsigned int element) { if (array->type != VIR_JSON_TYPE_ARRAY) @@ -911,7 +911,7 @@ virJSONValueArraySteal(virJSONValuePtr array, const char * -virJSONValueGetString(virJSONValuePtr string) +virJSONValueGetString(const virJSONValue *string) { if (string->type != VIR_JSON_TYPE_STRING) return NULL; @@ -921,7 +921,7 @@ virJSONValueGetString(virJSONValuePtr string) int -virJSONValueGetNumberInt(virJSONValuePtr number, +virJSONValueGetNumberInt(const virJSONValue *number, int *value) { if (number->type != VIR_JSON_TYPE_NUMBER) @@ -932,7 +932,7 @@ virJSONValueGetNumberInt(virJSONValuePtr number, int -virJSONValueGetNumberUint(virJSONValuePtr number, +virJSONValueGetNumberUint(const virJSONValue *number, unsigned int *value) { if (number->type != VIR_JSON_TYPE_NUMBER) @@ -943,7 +943,7 @@ virJSONValueGetNumberUint(virJSONValuePtr number, int -virJSONValueGetNumberLong(virJSONValuePtr number, +virJSONValueGetNumberLong(const virJSONValue *number, long long *value) { if (number->type != VIR_JSON_TYPE_NUMBER) @@ -954,7 +954,7 @@ virJSONValueGetNumberLong(virJSONValuePtr number, int -virJSONValueGetNumberUlong(virJSONValuePtr number, +virJSONValueGetNumberUlong(const virJSONValue *number, unsigned long long *value) { if (number->type != VIR_JSON_TYPE_NUMBER) @@ -965,7 +965,7 @@ virJSONValueGetNumberUlong(virJSONValuePtr number, int -virJSONValueGetNumberDouble(virJSONValuePtr number, +virJSONValueGetNumberDouble(const virJSONValue *number, double *value) { if (number->type != VIR_JSON_TYPE_NUMBER) @@ -976,7 +976,7 @@ virJSONValueGetNumberDouble(virJSONValuePtr number, int -virJSONValueGetBoolean(virJSONValuePtr val, +virJSONValueGetBoolean(const virJSONValue *val, bool *value) { if (val->type != VIR_JSON_TYPE_BOOLEAN) @@ -1077,14 +1077,14 @@ virJSONValueNewArrayFromBitmap(virBitmapPtr bitmap) bool -virJSONValueIsNull(virJSONValuePtr val) +virJSONValueIsNull(const virJSONValue *val) { return val->type == VIR_JSON_TYPE_NULL; } const char * -virJSONValueObjectGetString(virJSONValuePtr object, +virJSONValueObjectGetString(const virJSONValue *object, const char *key) { virJSONValuePtr val = virJSONValueObjectGet(object, key); @@ -1097,7 +1097,7 @@ virJSONValueObjectGetString(virJSONValuePtr object, int -virJSONValueObjectGetNumberInt(virJSONValuePtr object, +virJSONValueObjectGetNumberInt(const virJSONValue *object, const char *key, int *value) { @@ -1111,7 +1111,7 @@ virJSONValueObjectGetNumberInt(virJSONValuePtr object, int -virJSONValueObjectGetNumberUint(virJSONValuePtr object, +virJSONValueObjectGetNumberUint(const virJSONValue *object, const char *key, unsigned int *value) { @@ -1125,7 +1125,7 @@ virJSONValueObjectGetNumberUint(virJSONValuePtr object, int -virJSONValueObjectGetNumberLong(virJSONValuePtr object, +virJSONValueObjectGetNumberLong(const virJSONValue *object, const char *key, long long *value) { @@ -1139,7 +1139,7 @@ virJSONValueObjectGetNumberLong(virJSONValuePtr object, int -virJSONValueObjectGetNumberUlong(virJSONValuePtr object, +virJSONValueObjectGetNumberUlong(const virJSONValue *object, const char *key, unsigned long long *value) { @@ -1153,7 +1153,7 @@ virJSONValueObjectGetNumberUlong(virJSONValuePtr object, int -virJSONValueObjectGetNumberDouble(virJSONValuePtr object, +virJSONValueObjectGetNumberDouble(const virJSONValue *object, const char *key, double *value) { @@ -1167,7 +1167,7 @@ virJSONValueObjectGetNumberDouble(virJSONValuePtr object, int -virJSONValueObjectGetBoolean(virJSONValuePtr object, +virJSONValueObjectGetBoolean(const virJSONValue *object, const char *key, bool *value) { @@ -1195,7 +1195,7 @@ virJSONValueObjectGetArray(virJSONValuePtr object, const char *key) int -virJSONValueObjectIsNull(virJSONValuePtr object, +virJSONValueObjectIsNull(const virJSONValue *object, const char *key) { virJSONValuePtr val = virJSONValueObjectGet(object, key); @@ -1220,7 +1220,7 @@ virJSONValueObjectIsNull(virJSONValuePtr object, * during iteration and -1 on generic errors. */ int -virJSONValueObjectForeachKeyValue(virJSONValuePtr object, +virJSONValueObjectForeachKeyValue(const virJSONValue *object, virJSONValueObjectIteratorFunc cb, void *opaque) { @@ -1241,7 +1241,7 @@ virJSONValueObjectForeachKeyValue(virJSONValuePtr object, virJSONValuePtr -virJSONValueCopy(virJSONValuePtr in) +virJSONValueCopy(const virJSONValue *in) { size_t i; virJSONValuePtr out = NULL; @@ -1683,7 +1683,7 @@ virJSONValueFromString(const char *jsonstring) static int -virJSONValueToStringOne(virJSONValuePtr object, +virJSONValueToStringOne(const virJSONValue *object, yajl_gen g) { size_t i; @@ -1748,7 +1748,7 @@ virJSONValueToStringOne(virJSONValuePtr object, char * -virJSONValueToString(virJSONValuePtr object, +virJSONValueToString(const virJSONValue *object, bool pretty) { yajl_gen g; @@ -1809,7 +1809,7 @@ virJSONValueFromString(const char *jsonstring ATTRIBUTE_UNUSED) char * -virJSONValueToString(virJSONValuePtr object ATTRIBUTE_UNUSED, +virJSONValueToString(const virJSONValue *object ATTRIBUTE_UNUSED, bool pretty ATTRIBUTE_UNUSED) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", diff --git a/src/util/virjson.h b/src/util/virjson.h index 66ed48a..133e976 100644 --- a/src/util/virjson.h +++ b/src/util/virjson.h @@ -108,43 +108,43 @@ virJSONValuePtr virJSONValueNewArrayFromBitmap(virBitmapPtr bitmap); int virJSONValueObjectAppend(virJSONValuePtr object, const char *key, virJSONValuePtr value); int virJSONValueArrayAppend(virJSONValuePtr object, virJSONValuePtr value); -int virJSONValueObjectHasKey(virJSONValuePtr object, const char *key); -virJSONValuePtr virJSONValueObjectGet(virJSONValuePtr object, const char *key); -virJSONValuePtr virJSONValueObjectGetByType(virJSONValuePtr object, +int virJSONValueObjectHasKey(const virJSONValue *object, const char *key); +virJSONValuePtr virJSONValueObjectGet(const virJSONValue *object, const char *key); +virJSONValuePtr virJSONValueObjectGetByType(const virJSONValue *object, const char *key, virJSONType type); -bool virJSONValueIsArray(virJSONValuePtr array); +bool virJSONValueIsArray(const virJSONValue *array); ssize_t virJSONValueArraySize(const virJSONValue *array); -virJSONValuePtr virJSONValueArrayGet(virJSONValuePtr object, unsigned int element); +virJSONValuePtr virJSONValueArrayGet(const virJSONValue *object, unsigned int element); virJSONValuePtr virJSONValueArraySteal(virJSONValuePtr object, unsigned int element); -int virJSONValueObjectKeysNumber(virJSONValuePtr object); -const char *virJSONValueObjectGetKey(virJSONValuePtr object, unsigned int n); -virJSONValuePtr virJSONValueObjectGetValue(virJSONValuePtr object, unsigned int n); +int virJSONValueObjectKeysNumber(const virJSONValue *object); +const char *virJSONValueObjectGetKey(const virJSONValue *object, unsigned int n); +virJSONValuePtr virJSONValueObjectGetValue(const virJSONValue *object, unsigned int n); -const char *virJSONValueGetString(virJSONValuePtr object); -int virJSONValueGetNumberInt(virJSONValuePtr object, int *value); -int virJSONValueGetNumberUint(virJSONValuePtr object, unsigned int *value); -int virJSONValueGetNumberLong(virJSONValuePtr object, long long *value); -int virJSONValueGetNumberUlong(virJSONValuePtr object, unsigned long long *value); -int virJSONValueGetNumberDouble(virJSONValuePtr object, double *value); -int virJSONValueGetBoolean(virJSONValuePtr object, bool *value); +const char *virJSONValueGetString(const virJSONValue *object); +int virJSONValueGetNumberInt(const virJSONValue *object, int *value); +int virJSONValueGetNumberUint(const virJSONValue *object, unsigned int *value); +int virJSONValueGetNumberLong(const virJSONValue *object, long long *value); +int virJSONValueGetNumberUlong(const virJSONValue *object, unsigned long long *value); +int virJSONValueGetNumberDouble(const virJSONValue *object, double *value); +int virJSONValueGetBoolean(const virJSONValue *object, bool *value); int virJSONValueGetArrayAsBitmap(const virJSONValue *val, virBitmapPtr *bitmap) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); -bool virJSONValueIsNull(virJSONValuePtr object); +bool virJSONValueIsNull(const virJSONValue *object); virJSONValuePtr virJSONValueObjectGetObject(virJSONValuePtr object, const char *key); virJSONValuePtr virJSONValueObjectGetArray(virJSONValuePtr object, const char *key); -const char *virJSONValueObjectGetString(virJSONValuePtr object, const char *key); -int virJSONValueObjectGetNumberInt(virJSONValuePtr object, const char *key, int *value); -int virJSONValueObjectGetNumberUint(virJSONValuePtr object, const char *key, unsigned int *value); -int virJSONValueObjectGetNumberLong(virJSONValuePtr object, const char *key, long long *value); -int virJSONValueObjectGetNumberUlong(virJSONValuePtr object, const char *key, unsigned long long *value); -int virJSONValueObjectGetNumberDouble(virJSONValuePtr object, const char *key, double *value); -int virJSONValueObjectGetBoolean(virJSONValuePtr object, const char *key, bool *value); -int virJSONValueObjectIsNull(virJSONValuePtr object, const char *key); +const char *virJSONValueObjectGetString(const virJSONValue *object, const char *key); +int virJSONValueObjectGetNumberInt(const virJSONValue *object, const char *key, int *value); +int virJSONValueObjectGetNumberUint(const virJSONValue *object, const char *key, unsigned int *value); +int virJSONValueObjectGetNumberLong(const virJSONValue *object, const char *key, long long *value); +int virJSONValueObjectGetNumberUlong(const virJSONValue *object, const char *key, unsigned long long *value); +int virJSONValueObjectGetNumberDouble(const virJSONValue *object, const char *key, double *value); +int virJSONValueObjectGetBoolean(const virJSONValue *object, const char *key, bool *value); +int virJSONValueObjectIsNull(const virJSONValue *object, const char *key); int virJSONValueObjectAppendString(virJSONValuePtr object, const char *key, const char *value); int virJSONValueObjectAppendNumberInt(virJSONValuePtr object, const char *key, int number); @@ -160,17 +160,17 @@ int virJSONValueObjectRemoveKey(virJSONValuePtr object, const char *key, ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); virJSONValuePtr virJSONValueFromString(const char *jsonstring); -char *virJSONValueToString(virJSONValuePtr object, +char *virJSONValueToString(const virJSONValue *object, bool pretty); typedef int (*virJSONValueObjectIteratorFunc)(const char *key, const virJSONValue *value, void *opaque); -int virJSONValueObjectForeachKeyValue(virJSONValuePtr object, +int virJSONValueObjectForeachKeyValue(const virJSONValue *object, virJSONValueObjectIteratorFunc cb, void *opaque); -virJSONValuePtr virJSONValueCopy(virJSONValuePtr in); +virJSONValuePtr virJSONValueCopy(const virJSONValue *in); #endif /* __VIR_JSON_H_ */ -- 2.4.10

Name Service Switch is a glibc feature responsible for many things. Translating domain names into IP addresses and vice versa is just one of them. However, currently it's the only functionality that this commit is tickling. Well, in this commit the plugin skeleton is introduced. Implementation to come in next patches. Because of the future testing, where the implementation is to be linked with a test, this needs to go into static library. Linking a program with an .so statically is not portable. Therefore a dummy libnss_libvirt_impl library is being introduced too. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- configure.ac | 2 ++ m4/virt-nss.m4 | 51 ++++++++++++++++++++++++++++++++++++++++++++++ tools/Makefile.am | 39 +++++++++++++++++++++++++++++++++++ tools/nss/libvirt_nss.c | 36 ++++++++++++++++++++++++++++++++ tools/nss/libvirt_nss.h | 36 ++++++++++++++++++++++++++++++++ tools/nss/libvirt_nss.syms | 9 ++++++++ 6 files changed, 173 insertions(+) create mode 100644 m4/virt-nss.m4 create mode 100644 tools/nss/libvirt_nss.c create mode 100644 tools/nss/libvirt_nss.h create mode 100644 tools/nss/libvirt_nss.syms diff --git a/configure.ac b/configure.ac index 7d74f53..ba33144 100644 --- a/configure.ac +++ b/configure.ac @@ -257,6 +257,7 @@ LIBVIRT_CHECK_SSH2 LIBVIRT_CHECK_SYSTEMD_DAEMON LIBVIRT_CHECK_UDEV LIBVIRT_CHECK_WIRESHARK +LIBVIRT_CHECK_NSS LIBVIRT_CHECK_YAJL AC_MSG_CHECKING([for CPUID instruction]) @@ -2893,6 +2894,7 @@ LIBVIRT_RESULT_SSH2 LIBVIRT_RESULT_SYSTEMD_DAEMON LIBVIRT_RESULT_UDEV LIBVIRT_RESULT_WIRESHARK +LIBVIRT_RESULT_NSS LIBVIRT_RESULT_YAJL AC_MSG_NOTICE([ libxml: $LIBXML_CFLAGS $LIBXML_LIBS]) AC_MSG_NOTICE([ dlopen: $DLOPEN_LIBS]) diff --git a/m4/virt-nss.m4 b/m4/virt-nss.m4 new file mode 100644 index 0000000..207cd34 --- /dev/null +++ b/m4/virt-nss.m4 @@ -0,0 +1,51 @@ +dnl The libvirt nsswitch plugin +dnl +dnl Copyright (C) 2016 Red Hat, Inc. +dnl +dnl This library is free software; you can redistribute it and/or +dnl modify it under the terms of the GNU Lesser General Public +dnl License as published by the Free Software Foundation; either +dnl version 2.1 of the License, or (at your option) any later version. +dnl +dnl This library is distributed in the hope that it will be useful, +dnl but WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl Lesser General Public License for more details. +dnl +dnl You should have received a copy of the GNU Lesser General Public +dnl License along with this library. If not, see +dnl <http://www.gnu.org/licenses/>. +dnl + +AC_DEFUN([LIBVIRT_CHECK_NSS],[ + AC_ARG_WITH([nss-plugin], + [AS_HELP_STRING([--with-nss-plugin], + [enable Name Servie Switch plugin for resolving guest IP addresses])], + [], [with_nss_plugin=check]) + + fail=0 + if test "x$with_nss_plugin" != "xno" ; then + AC_CHECK_HEADERS([nss.h], [ + with_nss_plugin=yes + ],[ + if test "x$with_nss_plugin" = "xyes" ; then + fail = 1 + fi + ]) + + if test $fail = 1 ; then + AC_MSG_ERROR([Can't build nss plugin without nss.h]) + fi + + if test "x$with_nss_plugin" = "xyes" ; then + AC_DEFINE_UNQUOTED([NSS], 1, [whether nss plugin is enabled]) + fi + + AM_CONDITIONAL(WITH_NSS, [test "x$with_nss_plugin" = "xyes"]) + fi + +]) + +AC_DEFUN([LIBVIRT_RESULT_NSS],[ + LIBVIRT_RESULT([nss], [$with_nss_plugin]) +]) diff --git a/tools/Makefile.am b/tools/Makefile.am index 0be3567..a850adb 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -417,6 +417,45 @@ CLEANFILES += wireshark/src/plugin.c endif WITH_WIRESHARK_DISSECTOR +LIBVIRT_NSS_SYMBOL_FILE = \ + $(srcdir)/nss/libvirt_nss.syms + +LIBVIRT_NSS_SOURCES = \ + nss/libvirt_nss.c \ + nss/libvirt_nss.h + +if WITH_NSS +noinst_LTLIBRARIES += nss/libnss_libvirt_impl.la +nss_libnss_libvirt_impl_la_SOURCES = \ + $(LIBVIRT_NSS_SOURCES) + +nss_libnss_libvirt_impl_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(WARN_CFLAGS) \ + $(PIE_CFLAGS) \ + $(COVERAGE_CFLAGS) + +nss_libnss_libvirt_la_SOURCES = +nss_libnss_libvirt_la_LDFLAGS = \ + $(VERSION_SCRIPT_FLAGS)$(LIBVIRT_NSS_SYMBOL_FILE) \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 + +nss_libnss_libvirt_la_LIBADD = \ + nss/libnss_libvirt_impl.la \ + ../gnulib/lib/libgnu.la + +lib_LTLIBRARIES = \ + nss/libnss_libvirt.la + +endif WITH_NSS + +EXTRA_DIST += $(LIBVIRT_NSS_SYMBOL_FILE) \ + $(LIBVIRT_NSS_SOURCES) clean-local: -rm -rf wireshark/src/libvirt diff --git a/tools/nss/libvirt_nss.c b/tools/nss/libvirt_nss.c new file mode 100644 index 0000000..461d8ca --- /dev/null +++ b/tools/nss/libvirt_nss.c @@ -0,0 +1,36 @@ +/* + * libvirt_nss: Name Service Switch plugin + * + * The aim is to enable users and applications to translate + * domain names into IP addresses. However, this is currently + * available only for those domains which gets their IP addresses + * from a libvirt managed network. + * + * Copyright (C) 2016 Red Hat, Inc. + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Authors: + * Michal Privoznik <mprivozn@redhat.com> + */ +#include <config.h> + +#include "libvirt_nss.h" + +int +blah(int c) +{ + return c; +} diff --git a/tools/nss/libvirt_nss.h b/tools/nss/libvirt_nss.h new file mode 100644 index 0000000..b54e5e3 --- /dev/null +++ b/tools/nss/libvirt_nss.h @@ -0,0 +1,36 @@ +/* + * libvirt_nss: Name Service Switch plugin + * + * The aim is to enable users and applications to translate + * domain names into IP addresses. However, this is currently + * available only for those domains which gets their IP addresses + * from a libvirt managed network. + * + * Copyright (C) 2016 Red Hat, Inc. + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Authors: + * Michal Privoznik <mprivozn@redhat.com> + */ + +#ifndef __LIBVIRT_NSS_H__ +# define __LIBVIRT_NSS_H__ + +# include <nss.h> +# include <netdb.h> + +int blah(int c); +#endif /* __LIBVIRT_NSS_H__ */ diff --git a/tools/nss/libvirt_nss.syms b/tools/nss/libvirt_nss.syms new file mode 100644 index 0000000..3246213 --- /dev/null +++ b/tools/nss/libvirt_nss.syms @@ -0,0 +1,9 @@ +# +# Officially exported symbols. +# + +{ +global: + blah; +local: *; +}; -- 2.4.10

The implementation is pretty straightforward. Moreover, because of the nature of things, gethostbyname_r and gethostbyname2_r can be implemented at the same time too. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/Makefile.am | 8 +- tools/nss/libvirt_nss.c | 322 ++++++++++++++++++++++++++++++++++++++++++++- tools/nss/libvirt_nss.h | 14 +- tools/nss/libvirt_nss.syms | 4 +- 4 files changed, 342 insertions(+), 6 deletions(-) diff --git a/tools/Makefile.am b/tools/Makefile.am index a850adb..c6ef201 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -433,7 +433,13 @@ nss_libnss_libvirt_impl_la_CFLAGS = \ $(AM_CFLAGS) \ $(WARN_CFLAGS) \ $(PIE_CFLAGS) \ - $(COVERAGE_CFLAGS) + $(COVERAGE_CFLAGS) \ + $(LIBXML_CFLAGS) + +nss_libnss_libvirt_impl_la_LIBADD = \ + $(LIBXML_LIBS) \ + ../src/libvirt_util.la \ + ../src/libvirt.la nss_libnss_libvirt_la_SOURCES = nss_libnss_libvirt_la_LDFLAGS = \ diff --git a/tools/nss/libvirt_nss.c b/tools/nss/libvirt_nss.c index 461d8ca..f044074 100644 --- a/tools/nss/libvirt_nss.c +++ b/tools/nss/libvirt_nss.c @@ -29,8 +29,324 @@ #include "libvirt_nss.h" -int -blah(int c) +#include <resolv.h> +#include <sys/types.h> +#include <dirent.h> +#include <time.h> +#include <arpa/inet.h> + +#include "virlease.h" +#include "viralloc.h" +#include "virfile.h" +#include "virerror.h" +#include "virstring.h" +#include "virsocketaddr.h" +#include "configmake.h" + +#if 1 +# define ERROR(...) \ +do { \ + char ebuf[1024]; \ + fprintf(stderr, "ERROR %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " : %s\n", virStrerror(errno, ebuf, sizeof(ebuf))); \ + fprintf(stderr, "\n"); \ +} while (0) + +# define DEBUG(...) \ +do { \ + fprintf(stderr, "DEBUG %s:%d : ", __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) +#else +# define ERROR(...) do { } while (0) +# define DEBUG(...) do { } while (0) +#endif + +#define LEASEDIR LOCALSTATEDIR "/lib/libvirt/dnsmasq/" + +#define ALIGN4(l) (((l) + 3) & ~3) +#define ALIGN8(l) (((l) + 7) & ~7) + +#if __SIZEOF_POINTER__ == 8 +# define ALIGN(l) ALIGN8(l) +#elif __SIZEOF_POINTER__ == 4 +# define ALIGN(l) ALIGN4(l) +#else +# error "Wut? Pointers are neither 4 nor 8 bytes long?" +#endif + +/** + * findLease: + * @name: domain name to lookup + * @af: address family + * @addr: address + * @alen: address length + * @errnop: errno pointer + * + * Lookup @name in libvirt's IP database, parse it and store in + * @addr. Callers can choose which address family (@af) should be + * returned, however only AT_INET (IPv4) is currently supported. + * + * Returns -1 on error + * 0 if no matching record was found + * 1 if host found, but no matching IP address found + * 2 on success + */ +static int +findLease(const char *name, + int af, + unsigned char addr[16], + int *alen, + int *errnop) { - return c; + DIR *dir = NULL; + int ret = -1; + const char *leaseDir = LEASEDIR; + struct dirent *entry; + virJSONValuePtr leases_array = NULL; + virJSONValue const *lease = NULL; + char *server_duid = NULL; + ssize_t i, nleases; + const char *ipAddr; + virSocketAddr sa; + + if (af == AF_UNSPEC) + af = AF_INET; + + /* No IPv6 support yet since we don't store those in status file. */ + if (af != AF_INET) { + errno = EAFNOSUPPORT; + goto cleanup; + } + + if (!(dir = opendir(leaseDir))) { + ERROR("Failed to open dir '%s'", leaseDir); + goto cleanup; + } + + if (!(leases_array = virJSONValueNewArray())) { + ERROR("Failed to create json array"); + goto cleanup; + } + + DEBUG("Dir: %s", leaseDir); + while ((ret = virDirRead(dir, &entry, leaseDir)) > 0) { + char *path; + + if (entry->d_name[0] == '.') + continue; + + if (!virFileHasSuffix(entry->d_name, ".status")) + continue; + + if (!(path = virFileBuildPath(leaseDir, entry->d_name, NULL))) + goto cleanup; + + DEBUG("Processing %s", path); + + if (virLeaseReadCustomLeaseFile(leases_array, path, NULL, &server_duid) < 0) { + ERROR("Unable to parse %s", path); + VIR_FREE(path); + goto cleanup; + } + + VIR_FREE(path); + } + + closedir(dir); + dir = NULL; + + nleases = virJSONValueArraySize(leases_array); + DEBUG("Read %zd leases", nleases); + + for (i = 0; i < nleases; i++) { + const char *lease_name; + long long expirytime; + + lease = virJSONValueArrayGet(leases_array, i); + + if (!lease) { + /* This should never happen (TM) */ + ERROR("Unable to get element %zd of %zd", i, nleases); + goto cleanup; + } + + lease_name = virJSONValueObjectGetString(lease, "hostname"); + + if (STRNEQ_NULLABLE(name, lease_name)) + continue; + + DEBUG("Found record for %s", lease_name); + + if (virJSONValueObjectGetNumberLong(lease, "expiry-time", &expirytime) < 0) { + ERROR("Unable to read expiry-time on %s", lease_name); + goto cleanup; + } + + /* Check whether lease has expired or not */ + if (expirytime < (long long) time(NULL)) { + DEBUG("Lease for %s expired", lease_name); + continue; + } + + /* Hooray! Found. */ + break; + } + + if (i == nleases) { + /* NOT found */ + ret = errno = 0; + goto cleanup; + } + + /* found */ + + ret = 1; + DEBUG("Validating record for %s", name); + + if (!(ipAddr = virJSONValueObjectGetString(lease, "ip-address"))) { + ERROR("ip-address field missing for %s", name); + goto cleanup; + } + + if (virSocketAddrParse(&sa, ipAddr, af) < 0) { + ERROR("Unable to parse %s", ipAddr); + goto cleanup; + } + + *alen = 4; /* IPv4 addresses are 4 bytes long */ + memcpy(addr, &sa.data.inet4.sin_addr.s_addr, *alen); + + DEBUG("Validated"); + + /* Explicitly reset errno variable */ + errno = 0; + ret = 2; + + cleanup: + *errnop = errno; + VIR_FREE(server_duid); + virJSONValueFree(leases_array); + if (dir) + closedir(dir); + return ret; +} + + +enum nss_status +_nss_libvirt_gethostbyname_r(const char *name, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop) +{ + int af = ((_res.options & RES_USE_INET6) ? AF_INET6 : AF_INET); + + return _nss_libvirt_gethostbyname3_r(name, af, result, buffer, buflen, + errnop, herrnop, NULL, NULL); +} + +enum nss_status +_nss_libvirt_gethostbyname2_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop) +{ + return _nss_libvirt_gethostbyname3_r(name, af, result, buffer, buflen, + errnop, herrnop, NULL, NULL); +} + +enum nss_status +_nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp, char **canonp) +{ + enum nss_status ret = NSS_STATUS_UNAVAIL; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + unsigned char addr[16]; + int alen; + size_t nameLen, need, idx; + int r; + + if ((r = findLease(name, af, addr, &alen, errnop)) < 0) { + /* Error occurred. Return immediately. */ + if (*errnop == EAGAIN) { + *herrnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } else { + *herrnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + } else if (r == 0) { + /* NOT found */ + *errnop = ESRCH; + *herrnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } else if (r == 1) { + /* Found, but no data */ + *errnop = ENXIO; + *herrnop = NO_DATA; + return NSS_STATUS_UNAVAIL; + } + + /* Found and have data */ + + nameLen = strlen(name); + /* We need space for: + * a) name + * b) alias + * c) address + * d) NULL stem */ + need = ALIGN(nameLen + 1) + ALIGN(alen) + 3 * sizeof(char*); + + if (buflen < need) { + *errnop = ENOMEM; + *herrnop = TRY_AGAIN; + ret = NSS_STATUS_TRYAGAIN; + goto cleanup; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, nameLen + 1); + idx = ALIGN(nameLen + 1); + + /* Second, create aliases array */ + r_aliases = buffer + idx; + ((char**) r_aliases)[0] = NULL; + idx += sizeof(char*); + + /* Third, append address */ + r_addr = buffer + idx; + memcpy(r_addr, addr, alen); + idx += ALIGN(alen); + + /* Third, append address pointer array */ + r_addr_list = buffer + idx; + ((char**) r_addr_list)[0] = r_addr; + ((char**) r_addr_list)[1] = NULL; + idx += 2 * sizeof(char*); + + /* At this point, idx == need */ + DEBUG("Done idx:%zd need:%zd", idx, need); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = alen; + result->h_addr_list = (char**) r_addr_list; + + if (ttlp) + *ttlp = 0; + + if (canonp) + *canonp = r_name; + + /* Explicitly reset all error variables */ + *errnop = 0; + *herrnop = NETDB_SUCCESS; + h_errno = 0; + + ret = NSS_STATUS_SUCCESS; + cleanup: + return ret; } diff --git a/tools/nss/libvirt_nss.h b/tools/nss/libvirt_nss.h index b54e5e3..dd037f5 100644 --- a/tools/nss/libvirt_nss.h +++ b/tools/nss/libvirt_nss.h @@ -32,5 +32,17 @@ # include <nss.h> # include <netdb.h> -int blah(int c); +enum nss_status +_nss_libvirt_gethostbyname_r(const char *name, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop); + +enum nss_status +_nss_libvirt_gethostbyname2_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop); +enum nss_status +_nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp, char **canonp); #endif /* __LIBVIRT_NSS_H__ */ diff --git a/tools/nss/libvirt_nss.syms b/tools/nss/libvirt_nss.syms index 3246213..b88b8be 100644 --- a/tools/nss/libvirt_nss.syms +++ b/tools/nss/libvirt_nss.syms @@ -4,6 +4,8 @@ { global: - blah; + _nss_libvirt_gethostbyname_r; + _nss_libvirt_gethostbyname2_r; + _nss_libvirt_gethostbyname3_r; local: *; }; -- 2.4.10

This function is a different beast compared to previous ones. But yet again, nothing surprising is happening here. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/nss/libvirt_nss.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++ tools/nss/libvirt_nss.h | 4 +++ tools/nss/libvirt_nss.syms | 1 + 3 files changed, 89 insertions(+) diff --git a/tools/nss/libvirt_nss.c b/tools/nss/libvirt_nss.c index f044074..a8514f4 100644 --- a/tools/nss/libvirt_nss.c +++ b/tools/nss/libvirt_nss.c @@ -350,3 +350,87 @@ _nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, cleanup: return ret; } + +enum nss_status +_nss_libvirt_gethostbyname4_r(const char *name, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp) +{ + enum nss_status ret = NSS_STATUS_UNAVAIL; + unsigned char addr[16]; + int alen; + const int af = AF_INET; + int r; + size_t nameLen, need, idx; + struct gaih_addrtuple *r_tuple_first = NULL; + char *r_name; + + if ((r = findLease(name, af, addr, &alen, errnop)) < 0) { + /* Error occurred. Return immediately. */ + if (*errnop == EAGAIN) { + *herrnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } else { + *herrnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + } else if (r == 0) { + /* NOT found */ + *errnop = ESRCH; + *herrnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } else if (r == 1) { + /* Found, but no data */ + *errnop = ENXIO; + *herrnop = NO_DATA; + return NSS_STATUS_UNAVAIL; + } + + /* Found and have data */ + + nameLen = strlen(name); + /* We need space for: + * a) name + * b) address */ + need = ALIGN(nameLen + 1) + ALIGN(sizeof(struct gaih_addrtuple)); + + if (buflen < need) { + *errnop = ENOMEM; + *herrnop = TRY_AGAIN; + ret = NSS_STATUS_TRYAGAIN; + goto cleanup; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, nameLen + 1); + idx = ALIGN(nameLen + 1); + + + /* Second, append addresses */ + r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); + r_tuple_first->next = NULL; + r_tuple_first->name = r_name; + r_tuple_first->family = af; + r_tuple_first->scopeid = 0; + memcpy(r_tuple_first->addr, addr, alen); + idx += ALIGN(sizeof(struct gaih_addrtuple)); + + /* At this point, idx == need */ + DEBUG("Done idx:%zd need:%zd", idx, need); + + if (*pat) + **pat = *r_tuple_first; + else + *pat = r_tuple_first; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *herrnop = NETDB_SUCCESS; + ret = NSS_STATUS_SUCCESS; + cleanup: + return ret; +} diff --git a/tools/nss/libvirt_nss.h b/tools/nss/libvirt_nss.h index dd037f5..589c1e6 100644 --- a/tools/nss/libvirt_nss.h +++ b/tools/nss/libvirt_nss.h @@ -45,4 +45,8 @@ enum nss_status _nss_libvirt_gethostbyname3_r(const char *name, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *herrnop, int32_t *ttlp, char **canonp); +enum nss_status +_nss_libvirt_gethostbyname4_r(const char *name, struct gaih_addrtuple **pat, + char *buffer, size_t buflen, int *errnop, + int *herrnop, int32_t *ttlp); #endif /* __LIBVIRT_NSS_H__ */ diff --git a/tools/nss/libvirt_nss.syms b/tools/nss/libvirt_nss.syms index b88b8be..5a793e4 100644 --- a/tools/nss/libvirt_nss.syms +++ b/tools/nss/libvirt_nss.syms @@ -7,5 +7,6 @@ global: _nss_libvirt_gethostbyname_r; _nss_libvirt_gethostbyname2_r; _nss_libvirt_gethostbyname3_r; + _nss_libvirt_gethostbyname4_r; local: *; }; -- 2.4.10

A small test to see how is the nss module working. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- cfg.mk | 2 +- tests/Makefile.am | 18 +++++ tests/nssdata/virbr0.status | 14 ++++ tests/nssmock.c | 140 +++++++++++++++++++++++++++++++++ tests/nsstest.c | 184 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 tests/nssdata/virbr0.status create mode 100644 tests/nssmock.c create mode 100644 tests/nsstest.c diff --git a/cfg.mk b/cfg.mk index 5b864af..6f28eef 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1139,7 +1139,7 @@ exclude_file_name_regexp--sc_copyright_usage = \ ^COPYING(|\.LESSER)$$ exclude_file_name_regexp--sc_flags_usage = \ - ^(docs/|src/util/virnetdevtap\.c$$|tests/vir(cgroup|pci|usb)mock\.c$$) + ^(docs/|src/util/virnetdevtap\.c$$|tests/(vir(cgroup|pci|usb)|nss)mock\.c$$) exclude_file_name_regexp--sc_libvirt_unmarked_diagnostics = \ ^(src/rpc/gendispatch\.pl$$|tests/) diff --git a/tests/Makefile.am b/tests/Makefile.am index 90981dc..99ef99f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -108,6 +108,7 @@ EXTRA_DIST = \ nodedevschemadata \ nodedevschematest \ nodeinfodata \ + nssdata \ nwfilterschematest \ nwfilterxml2firewalldata \ nwfilterxml2xmlin \ @@ -190,6 +191,7 @@ test_programs = virshtest sockettest \ vircaps2xmltest \ virnetdevtest \ virtypedparamtest \ + nsstest \ $(NULL) if WITH_REMOTE @@ -421,6 +423,7 @@ test_libraries = libshunload.la \ virpcimock.la \ virnetdevmock.la \ nodeinfomock.la \ + nssmock.la \ $(NULL) if WITH_QEMU test_libraries += libqemumonitortestutils.la \ @@ -1067,6 +1070,21 @@ nodeinfomock_la_CFLAGS = $(AM_CFLAGS) nodeinfomock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) nodeinfomock_la_LIBADD = $(MOCKLIBS_LIBS) +nsstest_SOURCES = \ + nsstest.c testutils.h testutils.c +nsstest_CFLAGS = \ + $(AM_CFLAGS) \ + -I$(top_srcdir)/tools/nss +nsstest_LDADD = \ + $(LDADDS) \ + ../tools/nss/libnss_libvirt_impl.la + +nssmock_la_SOURCES = \ + nssmock.c +nssmock_la_CFLAGS = $(AM_CFLAGS) +nssmock_la_LDFLAGS = $(MOCKLIBS_LDFLAGS) +nssmock_la_LIBADD = $(MOCKLIBS_LIBS) + virnetdevtest_SOURCES = \ virnetdevtest.c testutils.h testutils.c virnetdevtest_CFLAGS = $(AM_CFLAGS) $(LIBNL_CFLAGS) diff --git a/tests/nssdata/virbr0.status b/tests/nssdata/virbr0.status new file mode 100644 index 0000000..de5aceb --- /dev/null +++ b/tests/nssdata/virbr0.status @@ -0,0 +1,14 @@ +[ + { + "ip-address": "192.168.122.197", + "mac-address": "52:54:00:a4:6f:91", + "hostname": "fedora", + "expiry-time": 1900000000 + }, + { + "ip-address": "192.168.122.254", + "mac-address": "52:54:00:3a:b5:0c", + "hostname": "gentoo", + "expiry-time": 2000000000 + } +] diff --git a/tests/nssmock.c b/tests/nssmock.c new file mode 100644 index 0000000..b4a4260 --- /dev/null +++ b/tests/nssmock.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Michal Privoznik <mprivozn@redhat.com> + */ + +#include <config.h> + +#ifdef __linux__ +# include <stdio.h> +# include <stdlib.h> +# include <dlfcn.h> +# include <sys/types.h> +# include <dirent.h> +# include <sys/stat.h> +# include <fcntl.h> + +# include "configmake.h" +# include "internal.h" +# include "virstring.h" +# include "viralloc.h" + +static int (*realopen)(const char *path, int flags, ...); +static DIR * (*realopendir)(const char *name); + +# define LEASEDIR LOCALSTATEDIR "/lib/libvirt/dnsmasq/" + +# define STDERR(...) \ + fprintf(stderr, "%s %zu: ", __FUNCTION__, (size_t) __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + +# define ABORT(...) \ + do { \ + STDERR(__VA_ARGS__); \ + abort(); \ + } while (0) + +# define ABORT_OOM() \ + ABORT("Out of memory") + +/* + * Functions to load the symbols and init the environment + */ +static void +init_syms(void) +{ + if (realopen) + return; + +# define LOAD_SYM(name) \ + do { \ + if (!(real ## name = dlsym(RTLD_NEXT, #name))) \ + ABORT("Cannot find real '%s' symbol\n", #name); \ + } while (0) + + LOAD_SYM(open); + LOAD_SYM(opendir); +} + +static int +getrealpath(char **newpath, + const char *path) +{ + if (STRPREFIX(path, LEASEDIR)) { + if (virAsprintfQuiet(newpath, "%s/nssdata/%s", + abs_srcdir, + path + strlen(LEASEDIR)) < 0) { + errno = ENOMEM; + return -1; + } + } else { + if (VIR_STRDUP_QUIET(*newpath, path) < 0) + return -1; + } + + return 0; +} + +int +open(const char *path, int flags, ...) +{ + int ret; + char *newpath = NULL; + + init_syms(); + + if (STRPREFIX(path, LEASEDIR) && + getrealpath(&newpath, path) < 0) + return -1; + + if (flags & O_CREAT) { + va_list ap; + mode_t mode; + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + ret = realopen(newpath ? newpath : path, flags, mode); + } else { + ret = realopen(newpath ? newpath : path, flags); + } + + VIR_FREE(newpath); + return ret; +} + +DIR * +opendir(const char *path) +{ + DIR *ret; + char *newpath = NULL; + + init_syms(); + + if (STRPREFIX(path, LEASEDIR) && + getrealpath(&newpath, path) < 0) + return NULL; + + ret = realopendir(newpath ? newpath : path); + + VIR_FREE(newpath); + return ret; +} +#else +/* Nothing to override on non-__linux__ platforms */ +#endif diff --git a/tests/nsstest.c b/tests/nsstest.c new file mode 100644 index 0000000..932f9a8 --- /dev/null +++ b/tests/nsstest.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2016 Red Hat, Inc. + * + * This 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. + * + * This 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 this library. If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Michal Privoznik <mprivozn@redhat.com> + */ + +#include <config.h> + +#include "testutils.h" + +#ifdef __linux__ + +# include <stdbool.h> +# include <arpa/inet.h> +# include "libvirt_nss.h" + +# define VIR_FROM_THIS VIR_FROM_NONE + +# define BUF_SIZE 1024 + +struct testNSSData { + const char *hostname; + const char *ipAddr; +}; + +static int +testGetHostByName(const void *opaque) +{ + const struct testNSSData *data = opaque; + const bool existent = data->hostname && data->ipAddr; + int ret = -1; + struct hostent resolved; + char buf[BUF_SIZE] = { 0 }; + char **addrList; + int rv, tmp_errno = 0, tmp_herrno = 0; + size_t i; + + if (!data) + goto cleanup; + + memset(&resolved, 0, sizeof(resolved)); + + rv = _nss_libvirt_gethostbyname_r(data->hostname, + &resolved, + buf, sizeof(buf), + &tmp_errno, + &tmp_herrno); + + if (rv == NSS_STATUS_TRYAGAIN || + rv == NSS_STATUS_UNAVAIL || + rv == NSS_STATUS_RETURN) { + /* Resolving failed in unexpected fashion. */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "Resolving of %s failed due to internal error", + data->hostname); + goto cleanup; + } else if (rv == NSS_STATUS_NOTFOUND) { + /* Resolving failed. Should it? */ + if (!existent) + ret = 0; + else + virReportError(VIR_ERR_INTERNAL_ERROR, + "Resolving of %s failed", + data->hostname); + goto cleanup; + } + + /* Resolving succeeded. Should it? */ + if (!existent) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Resolving of %s succeeded but was expected to fail", + data->hostname); + goto cleanup; + } + + /* Now lets see if resolved address match our expectations. */ + + if (!resolved.h_name) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "resolved.h_name empty"); + goto cleanup; + } + + if (resolved.h_addrtype != AF_INET) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected AF_INET (%d) got %d", + AF_INET, resolved.h_addrtype); + goto cleanup; + } + + if (resolved.h_length != 4) { + /* IPv4 addresses are encoded into 4 bytes */ + virReportError(VIR_ERR_INTERNAL_ERROR, + "Expected 4 bytes long address, got %d", + resolved.h_length); + goto cleanup; + } + + if (!resolved.h_addr_list) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "resolved.h_addr_list empty"); + goto cleanup; + } + + addrList = resolved.h_addr_list; + i = 0; + while (*addrList) { + virSocketAddr sa; + char *ipAddr; + + /* For some reason, virSocketAddrSetIPv4Addr does htonl() conversion. + * But the data we already have is in network order. */ + virSocketAddrSetIPv4Addr(&sa, ntohl(*((uint32_t *) *addrList))); + + if (i == 0) { + if (!(ipAddr = virSocketAddrFormat(&sa))) { + /* error reported by helper */ + goto cleanup; + } + if (STRNEQ(data->ipAddr, ipAddr)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Address mismatch. Expected %s got %s", + data->ipAddr, ipAddr); + VIR_FREE(ipAddr); + goto cleanup; + } + VIR_FREE(ipAddr); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Got more addresses than expected (1)"); + goto cleanup; + } + + addrList++; + } + + ret = 0; + cleanup: + return ret; +} + +static int +mymain(void) +{ + int ret = 0; + +# define DO_TEST(name, address) \ + do { \ + struct testNSSData data = { \ + .hostname = name, .ipAddr = address, \ + }; \ + if (virtTestRun(name, testGetHostByName, &data) < 0) \ + ret = -1; \ + } while (0) + + DO_TEST("fedora", "192.168.122.197"); + DO_TEST("gentoo", "192.168.122.254"); + DO_TEST("non-existent", NULL); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/nssmock.so") +#else +int +main(void) +{ + return EXIT_AM_SKIP; +} +#endif -- 2.4.10

On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
What's beautiful on this feature is that it helps any users regardless of their systemd attitude. On systemd hosts there already exists a similar module 'mymachines' which takes its data from machined. And libvirt does communicate with machined when creating a domain. But unfortunately at that time we know nothing about guest's IPs and therefore do not tell them to machined, which in turn can't tell anything to mymachines module. To make things worse, machined seems to be lacking an API to tell it the addresses later on when libvirt finds out. Therefore even systemd distros will benefit from this feature.
Nice. For a similar purpose I hacked up simplec a while ago: https://github.com/agx/simplec it works by fetching domain IPs using our APIs and stores them in a file for a dnsmasq instance to read. This allows to even collect IPs from remote URIs. Cheers, -- Guido

On 16.02.2016 17:48, Guido Günther wrote:
On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
What's beautiful on this feature is that it helps any users regardless of their systemd attitude. On systemd hosts there already exists a similar module 'mymachines' which takes its data from machined. And libvirt does communicate with machined when creating a domain. But unfortunately at that time we know nothing about guest's IPs and therefore do not tell them to machined, which in turn can't tell anything to mymachines module. To make things worse, machined seems to be lacking an API to tell it the addresses later on when libvirt finds out. Therefore even systemd distros will benefit from this feature.
Nice. For a similar purpose I hacked up simplec a while ago:
https://github.com/agx/simplec
it works by fetching domain IPs using our APIs and stores them in a file for a dnsmasq instance to read. This allows to even collect IPs from remote URIs.
Interesting. Esp. the remote URIs part. That's what I was wondering when writing my module, whether I should actually open a libvirt connection and use public API to retrieve IPs or parsing an internal file is just enough. But I could not think of any useful use case where I'd need to resolve remote IPs. I mean, either those IPs are in a private network so they are useless, or they come from the same subnet as host ones and in that case external DHCP server has assigned them and hopefully set DNS records too. What's your use case? My argumentation for NSS module is that one does not need yet another dnsmasq process running just to do a name translation. Michal

On Wed, Feb 17, 2016 at 12:59:58PM +0100, Michal Privoznik wrote:
On 16.02.2016 17:48, Guido Günther wrote:
On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
What's beautiful on this feature is that it helps any users regardless of their systemd attitude. On systemd hosts there already exists a similar module 'mymachines' which takes its data from machined. And libvirt does communicate with machined when creating a domain. But unfortunately at that time we know nothing about guest's IPs and therefore do not tell them to machined, which in turn can't tell anything to mymachines module. To make things worse, machined seems to be lacking an API to tell it the addresses later on when libvirt finds out. Therefore even systemd distros will benefit from this feature.
Nice. For a similar purpose I hacked up simplec a while ago:
https://github.com/agx/simplec
it works by fetching domain IPs using our APIs and stores them in a file for a dnsmasq instance to read. This allows to even collect IPs from remote URIs.
Interesting. Esp. the remote URIs part. That's what I was wondering when writing my module, whether I should actually open a libvirt connection and use public API to retrieve IPs or parsing an internal file is just enough. But I could not think of any useful use case where I'd need to resolve remote IPs. I mean, either those IPs are in a private network so they are useless, or they come from the same subnet as host ones and in that case external DHCP server has assigned them and hopefully set DNS records too. What's your use case?
IMHO opening a libvirt connection would be a pretty bad idea - this code runs in pretty much any process on the host so you want to keep the code in the NSS module small and simple with as little performance overhead, nor potential with slow response. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 17.02.2016 13:02, Daniel P. Berrange wrote:
On Wed, Feb 17, 2016 at 12:59:58PM +0100, Michal Privoznik wrote:
On 16.02.2016 17:48, Guido Günther wrote:
On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
What's beautiful on this feature is that it helps any users regardless of their systemd attitude. On systemd hosts there already exists a similar module 'mymachines' which takes its data from machined. And libvirt does communicate with machined when creating a domain. But unfortunately at that time we know nothing about guest's IPs and therefore do not tell them to machined, which in turn can't tell anything to mymachines module. To make things worse, machined seems to be lacking an API to tell it the addresses later on when libvirt finds out. Therefore even systemd distros will benefit from this feature.
Nice. For a similar purpose I hacked up simplec a while ago:
https://github.com/agx/simplec
it works by fetching domain IPs using our APIs and stores them in a file for a dnsmasq instance to read. This allows to even collect IPs from remote URIs.
Interesting. Esp. the remote URIs part. That's what I was wondering when writing my module, whether I should actually open a libvirt connection and use public API to retrieve IPs or parsing an internal file is just enough. But I could not think of any useful use case where I'd need to resolve remote IPs. I mean, either those IPs are in a private network so they are useless, or they come from the same subnet as host ones and in that case external DHCP server has assigned them and hopefully set DNS records too. What's your use case?
IMHO opening a libvirt connection would be a pretty bad idea - this code runs in pretty much any process on the host so you want to keep the code in the NSS module small and simple with as little performance overhead, nor potential with slow response.
Exactly! That's why I even hesitated to link libvirt.so in. But unfortunately, due to uncleanliness of our code, libvirt_util requires some symbols from other areas of the code. One day I'm gonna change that. On the other hand, if you want the NSS module, you probably have libvirt in the system anyway so libvirt.so is already loaded in your ram anyway. Michal

On Wed, Feb 17, 2016 at 12:59:58PM +0100, Michal Privoznik wrote: [..snip..]
Nice. For a similar purpose I hacked up simplec a while ago:
https://github.com/agx/simplec
it works by fetching domain IPs using our APIs and stores them in a file for a dnsmasq instance to read. This allows to even collect IPs from remote URIs.
Interesting. Esp. the remote URIs part. That's what I was wondering when writing my module, whether I should actually open a libvirt connection and use public API to retrieve IPs or parsing an internal file is just enough. But I could not think of any useful use case where I'd need to resolve remote IPs. I mean, either those IPs are in a private network so they are useless, or they come from the same subnet as host ones and in that case external DHCP server has assigned them and hopefully set DNS records too. What's your use case?
The use case is IPv6 connections assigned via e.g. radvd. I'm not using simplec for that yet (and it will need some minor tweaks to support AAAA) but that was the reason for using dnsmasq instead of a new nss module that proxies to another service. The other reason is that I want it to handle more in the future like settings up .ssh/config (for tab completion and jump hosts). Cheers, -- Guido
My argumentation for NSS module is that one does not need yet another dnsmasq process running just to do a name translation.
Michal

On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
So the 'nss' modules are loaded by any process on the host which does dns lookups. This in turns implies that any process has to have permission to read the dnsmasq lease files directly. I don't think this is very desirable, particularly from an SELinux POV - I'm not convinced we want to grant every process perm to read the virt_var_lib_t. I'm wondering if we shouldn't have a separate file(s) recording the hostname/IP address mappings for the NSS module to read, that we place somewhere dedicated to this purpose, so we can grant permission to just the data NSS needs. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Tue, Feb 16, 2016 at 04:59:28PM +0000, Daniel P. Berrange wrote: [..snip..]
I'm wondering if we shouldn't have a separate file(s) recording the hostname/IP address mappings for the NSS module to read, that we place somewhere dedicated to this purpose, so we can grant permission to just the data NSS needs.
Or rather maintain the entries in a separate process that can be talked to over a socket like nss-ldapd does? Cheers, -- Guido

On 16.02.2016 17:59, Daniel P. Berrange wrote:
On Mon, Feb 15, 2016 at 05:38:37PM +0100, Michal Privoznik wrote:
Are you tired of remembering IP addresses for your domains? Do you have enough of configuring static IPs so that you can add them to your hosts file? Then libvirt NSS module is exactly what you need!
NSS does a lot in a Linux host. These patches aim at translating domain names into IP addresses. All you need to do, is install libnss_libvirt.so.2 (e.g. via 'make install' ran from source dir), enable the module in nsswitch.conf:
$ grep libvirt /etc/nsswitch.conf hosts: files dns libvirt
and you're all set. Now you can just:
$ ping $mydomain $ ssh user@$mydomain
or anything you'd like. The only limitation is that it has to be libvirt who has assigned the domain IP address. The limitation comes from implementation in which '/var/lib/libvirt/dnsmasq/*.status' files are parsed when looking up a hostname.
So the 'nss' modules are loaded by any process on the host which does dns lookups. This in turns implies that any process has to have permission to read the dnsmasq lease files directly. I don't think this is very desirable, particularly from an SELinux POV - I'm not convinced we want to grant every process perm to read the virt_var_lib_t.
Okay, I haven't thought of that. What if, *.status file under /var/lib/libvirt/dnsmasq would have virt_nss_var_lib_t and we have new selinux boolean. Anybody who could read virt_var_lib_t could read virt_nss_var_lib_t too. Moreover, if the boolean would be set, everybody else, who would be denied on virt_var_lib_t would be granted access on virt_nss_var_lib_t.
I'm wondering if we shouldn't have a separate file(s) recording the hostname/IP address mappings for the NSS module to read, that we place somewhere dedicated to this purpose, so we can grant permission to just the data NSS needs.
I'd like to avoid that if possible. Michal
participants (3)
-
Daniel P. Berrange
-
Guido Günther
-
Michal Privoznik