[libvirt] [PATCH v5] Add helper program to create custom leases
Nehal J Wani
nehaljw.kkd1 at gmail.com
Tue May 6 19:25:54 UTC 2014
Ping!
On Sat, Apr 26, 2014 at 5:29 AM, Nehal J Wani <nehaljw.kkd1 at gmail.com> wrote:
> Introduce helper program to catch events from dnsmasq and maintain a custom
> lease file per network. It supports dhcpv4 and dhcpv6. The file is saved as
> "<interface-name>.status".
>
> Each lease contains the following info:
> <expiry-time (epoch time)> <mac> <iaid> <ip-address> <hostname> <clientid>
>
> Example of custom leases file content:
> [
> {
> "iaid": "1221229",
> "ip-address": "2001:db8:ca2:2:1::95",
> "mac-address": "52:54:00:12:a2:6d",
> "hostname": "Fedora20",
> "client-id": "00:04:1a:c1:d9:6b:5a:0a:e2:bc:f8:4b:1e:37:2e:38:22:55",
> "expiry-time": 1393244216
> },
> {
> "ip-address": "192.168.150.208",
> "mac-address": "52:54:00:11:56:b3",
> "hostname": "Wani-PC",
> "client-id": "01:52:54:00:11:56:b3",
> "expiry-time": 1393244248
> }
> ]
>
> src/Makefile.am:
> * Add options to compile the helper program
>
> src/network/bridge_driver.c:
> * Introduce networkDnsmasqLeaseFileNameCustom()
> * Invoke helper program along with dnsmasq
> * Delete the .status file when corresponding n/w is destroyed.
>
> src/network/leaseshelper.c
> * Helper program to create the custom lease file
>
> ---
> v5:
> * More comments added, for better explanation
> * Use of virFileFindResource() to identify proper path to helper binary
> * Use of VIR_ENUM_IMPL for handling action events added
>
> v4:
> * Addition of pidfile and a corresponding lock for it
> * Make correction for dnsmasq < 2.52 (Only IPv4)
> * Move helper file from src/util to src/network
> * Increase limit on max size of leases file
> * Refer: https://www.redhat.com/archives/libvir-list/2014-March/msg01038.html
>
> v3:
> * Improved file handling, removed redundant copying, introduced --help and --version
> * Refer: https://www.redhat.com/archives/libvir-list/2014-February/msg01431.html
>
> v2:
> * Changed format to JSON
> * Refer: https://www.redhat.com/archives/libvir-list/2014-January/msg01234.html
>
> v1:
> * Refer: https://www.redhat.com/archives/libvir-list/2014-January/msg00626.html
>
> src/Makefile.am | 22 +++
> src/network/bridge_driver.c | 27 ++++
> src/network/leaseshelper.c | 360 +++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 409 insertions(+), 0 deletions(-)
> create mode 100644 src/network/leaseshelper.c
>
> diff --git a/src/Makefile.am b/src/Makefile.am
> index dd0abe7..fbe72c4 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -859,6 +859,9 @@ STORAGE_HELPER_DISK_SOURCES = \
> UTIL_IO_HELPER_SOURCES = \
> util/iohelper.c
>
> +NETWORK_LEASES_HELPER_SOURCES = \
> + network/leaseshelper.c
> +
> # Network filters
> NWFILTER_DRIVER_SOURCES = \
> nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \
> @@ -2466,6 +2469,25 @@ libvirt_iohelper_CFLAGS = \
> $(AM_CFLAGS) \
> $(PIE_CFLAGS) \
> $(NULL)
> +
> +if WITH_NETWORK
> +libexec_PROGRAMS += libvirt_leaseshelper
> +libvirt_leaseshelper_SOURCES = $(NETWORK_LEASES_HELPER_SOURCES)
> +libvirt_leaseshelper_LDADD = \
> + libvirt_util.la \
> + ../gnulib/lib/libgnu.la
> +if WITH_DTRACE_PROBES
> +libvirt_leaseshelper_LDADD += libvirt_probes.lo
> +endif WITH_DTRACE_PROBES
> +
> +libvirt_leaseshelper_CFLAGS = \
> + $(AM_CFLAGS) \
> + $(PIE_CFLAGS) \
> + $(NULL)
> +else ! WITH_NETWORK
> +EXTRA_DIST += $(NETWORK_LEASES_HELPER_SOURCES)
> +endif ! WITH_NETWORK
> +
> endif WITH_LIBVIRTD
>
> if WITH_STORAGE_DISK
> diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
> index 201b22f..ade664d 100644
> --- a/src/network/bridge_driver.c
> +++ b/src/network/bridge_driver.c
> @@ -210,6 +210,16 @@ networkDnsmasqLeaseFileNameFunc networkDnsmasqLeaseFileName =
> networkDnsmasqLeaseFileNameDefault;
>
> static char *
> +networkDnsmasqLeaseFileNameCustom(const char *bridge)
> +{
> + char *leasefile;
> +
> + ignore_value(virAsprintf(&leasefile, "%s/%s.status",
> + driverState->dnsmasqStateDir, bridge));
> + return leasefile;
> +}
> +
> +static char *
> networkDnsmasqConfigFileName(const char *netname)
> {
> char *conffile;
> @@ -245,6 +255,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
> virNetworkObjPtr net)
> {
> char *leasefile = NULL;
> + char *customleasefile = NULL;
> char *radvdconfigfile = NULL;
> char *configfile = NULL;
> char *radvdpidbase = NULL;
> @@ -263,6 +274,9 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
> if (!(leasefile = networkDnsmasqLeaseFileName(def->name)))
> goto cleanup;
>
> + if (!(customleasefile = networkDnsmasqLeaseFileNameCustom(def->bridge)))
> + goto cleanup;
> +
> if (!(radvdconfigfile = networkRadvdConfigFileName(def->name)))
> goto cleanup;
>
> @@ -279,6 +293,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
> /* dnsmasq */
> dnsmasqDelete(dctx);
> unlink(leasefile);
> + unlink(customleasefile);
> unlink(configfile);
>
> /* radvd */
> @@ -296,6 +311,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
> cleanup:
> VIR_FREE(leasefile);
> VIR_FREE(configfile);
> + VIR_FREE(customleasefile);
> VIR_FREE(radvdconfigfile);
> VIR_FREE(radvdpidbase);
> VIR_FREE(statusfile);
> @@ -1120,6 +1136,7 @@ networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network,
> int ret = -1;
> char *configfile = NULL;
> char *configstr = NULL;
> + char *leaseshelper_path;
>
> network->dnsmasqPid = -1;
>
> @@ -1142,11 +1159,21 @@ networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network,
>
> cmd = virCommandNew(dnsmasqCapsGetBinaryPath(caps));
> virCommandAddArgFormat(cmd, "--conf-file=%s", configfile);
> +
> + /* This helper is used to create custom leases file for libvirt */
> + if (!(leaseshelper_path = virFileFindResource("libvirt_leaseshelper",
> + "src",
> + LIBEXECDIR)))
> + goto cleanup;
> +
> + virCommandAddArgFormat(cmd, "--dhcp-script=%s", leaseshelper_path);
> +
> *cmdout = cmd;
> ret = 0;
> cleanup:
> VIR_FREE(configfile);
> VIR_FREE(configstr);
> + VIR_FREE(leaseshelper_path);
> return ret;
> }
>
> diff --git a/src/network/leaseshelper.c b/src/network/leaseshelper.c
> new file mode 100644
> index 0000000..d580369
> --- /dev/null
> +++ b/src/network/leaseshelper.c
> @@ -0,0 +1,360 @@
> +/*
> + * leasehelper.c: Helper program to create custom leases file
> + *
> + * 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/>.
> + *
> + * Author: Nehal J Wani <nehaljw.kkd1 at gmail.com>
> + *
> + * For IPv6 support, use dnsmasq >= 2.67
> + */
> +
> +#include <config.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 "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)
> +
> +static const char *program_name;
> +
> +/* Display version information. */
> +static void
> +helperVersion(const char *argv0)
> +{
> + printf("%s (%s) %s\n", argv0, PACKAGE_NAME, PACKAGE_VERSION);
> +}
> +
> +ATTRIBUTE_NORETURN static void
> +usage(int status)
> +{
> + if (status) {
> + fprintf(stderr, _("%s: try --help for more details\n"), program_name);
> + } else {
> + printf(_("Usage: %s add|old|del mac|clientid ip [hostname]\n"
> + "Designed for use with 'dnsmasq --dhcp-script'\n"
> + "Refer to man page of dnsmasq for more details'\n"),
> + program_name);
> + }
> + exit(status);
> +}
> +
> +static int
> +customLeaseRewriteFile(int fd, void *opaque)
> +{
> + char **data = opaque;
> +
> + if (safewrite(fd, *data, strlen(*data)) < 0)
> + return -1;
> +
> + return 0;
> +}
> +
> +/* Flags denoting actions for a lease */
> +enum virLeaseActionFlags {
> + VIR_LEASE_ACTION_ADD, /* Create new lease */
> + VIR_LEASE_ACTION_OLD, /* Lease already exists, renew it */
> + VIR_LEASE_ACTION_DEL, /* Delete the lease */
> +
> + VIR_LEASE_ACTION_LAST
> +};
> +
> +VIR_ENUM_DECL(virLeaseAction);
> +
> +VIR_ENUM_IMPL(virLeaseAction, VIR_LEASE_ACTION_LAST,
> + "add", "old", "del");
> +
> +int
> +main(int argc, char **argv)
> +{
> + char *exptime = NULL;
> + char *pid_file = NULL;
> + char *lease_entries = NULL;
> + char *custom_lease_file = NULL;
> + const char *ip = NULL;
> + const char *mac = NULL;
> + const char *iaid = virGetEnvAllowSUID("DNSMASQ_IAID");
> + const char *clientid = virGetEnvAllowSUID("DNSMASQ_CLIENT_ID");
> + const char *interface = virGetEnvAllowSUID("DNSMASQ_INTERFACE");
> + const char *exptime_tmp = virGetEnvAllowSUID("DNSMASQ_LEASE_EXPIRES");
> + const char *hostname = virGetEnvAllowSUID("DNSMASQ_SUPPLIED_HOSTNAME");
> + const char *leases_str = NULL;
> + long long currtime = 0;
> + long long expirytime = 0;
> + size_t i = 0;
> + int size = 0;
> + int action = -1;
> + int pid_file_fd = -1;
> + int rv = EXIT_FAILURE;
> + int custom_lease_file_len = 0;
> + bool add = false;
> + bool delete = false;
> + virJSONValuePtr lease_new = NULL;
> + virJSONValuePtr lease_tmp = NULL;
> + virJSONValuePtr leases_array = NULL;
> + virJSONValuePtr leases_array_new = NULL;
> +
> + virSetErrorFunc(NULL, NULL);
> + virSetErrorLogPriorityFunc(NULL);
> +
> + program_name = argv[0];
> +
> + if (setlocale(LC_ALL, "") == NULL ||
> + bindtextdomain(PACKAGE, LOCALEDIR) == NULL ||
> + textdomain(PACKAGE) == NULL) {
> + fprintf(stderr, _("%s: initialization failed\n"), program_name);
> + exit(EXIT_FAILURE);
> + }
> +
> + if (virThreadInitialize() < 0 ||
> + virErrorInitialize() < 0) {
> + fprintf(stderr, _("%s: initialization failed\n"), program_name);
> + exit(EXIT_FAILURE);
> + }
> +
> + /* Doesn't hurt to check */
> + if (argc > 1) {
> + if(STREQ(argv[1], "--help"))
> + usage(EXIT_SUCCESS);
> +
> + if (STREQ(argv[1], "--version")) {
> + helperVersion(argv[0]);
> + exit(EXIT_SUCCESS);
> + }
> + }
> +
> + if (argc != 4 && argc != 5) {
> + /* Refer man page of dnsmasq --dhcp-script for more details */
> + usage(EXIT_FAILURE);
> + }
> +
> + /* Make sure dnsmasq knows the interface. The interface name is not known
> + * when dnsmasq (re)starts and throws 'del' events for expired leases.
> + * So, if any old lease has expired, it will be automatically removed the
> + * next time this program is invoked */
> + if (!interface)
> + goto cleanup;
> +
> + ip = argv[3];
> + mac = argv[2];
> + action = virLeaseActionTypeFromString(argv[1]);
> +
> + /* In case hostname is known, it is the 5th argument */
> + if (argc == 5)
> + hostname = argv[4];
> +
> + 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';
> +
> + /* Check if it is an IPv6 lease */
> + if (virGetEnvAllowSUID("DNSMASQ_IAID")) {
> + mac = virGetEnvAllowSUID("DNSMASQ_MAC");
> + clientid = argv[2];
> + }
> +
> + if (virAsprintf(&custom_lease_file,
> + LOCALSTATEDIR "/lib/libvirt/dnsmasq/%s.status",
> + interface) < 0)
> + goto cleanup;
> +
> + if (VIR_STRDUP(pid_file, LOCALSTATEDIR "/run/leaseshelper.pid") < 0)
> + goto cleanup;
> +
> + /* Try to claim the pidfile, exiting if we can't */
> + if ((pid_file_fd = virPidFileAcquirePath(pid_file, true, getpid())) < 0)
> + goto cleanup;
> +
> + /* Since interfaces can be hot plugged, we need to make sure that the
> + * corresponding custom lease file exists. If not, 'touch' it */
> + if (virFileTouch(custom_lease_file, 0644) < 0)
> + goto cleanup;
> +
> + /* Read entire contents */
> + if ((custom_lease_file_len = virFileReadAll(custom_lease_file,
> + VIR_NETWORK_DHCP_LEASE_FILE_SIZE_MAX,
> + &lease_entries)) < 0) {
> + goto cleanup;
> + }
> +
> + if (action == VIR_LEASE_ACTION_ADD ||
> + action == VIR_LEASE_ACTION_OLD ||
> + action == VIR_LEASE_ACTION_DEL) {
> + /* Custom ipv6 leases *will not* be created if the env-var DNSMASQ_MAC
> + * is not set. In the special case, when the $(interface).status file
> + * is not already present and dnsmasq is (re)started, the corresponding
> + * ipv6 custom lease will be created only when the guest sends the
> + * 'old' action for its existing ipv6 interfaces.
> + *
> + * According to rfc3315, the combination of DUID and IAID can be used
> + * to uniquely identify each ipv6 guest interface. So, in future, if
> + * we introduce virNetworkGetDHCPLeaseBy(IAID|DUID|IAID+DUID) for ipv6
> + * interfaces, then, the following if condition won't be required, as
> + * the new lease will be created irrespective of whether the MACID is
> + * known or not.
> + */
> + if (mac || action == VIR_LEASE_ACTION_DEL) {
> + /* Delete the corresponding lease, if it already exists */
> + delete = true;
> + if (action == VIR_LEASE_ACTION_ADD ||
> + action == VIR_LEASE_ACTION_OLD) {
> + add = true;
> + /* Create new lease */
> + if (!(lease_new = virJSONValueNewObject())) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("failed to create json"));
> + goto cleanup;
> + }
> +
> + if (virStrToLong_ll(exptime, NULL, 10, &expirytime) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Unable to convert lease expiry time to long long: %s"),
> + exptime);
> + 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 (expirytime && virJSONValueObjectAppendNumberLong(lease_new, "expiry-time", expirytime) < 0)
> + goto cleanup;
> + }
> + }
> + } else {
> + fprintf(stderr, _("Unsupported action: %s\n"),
> + virLeaseActionTypeToString(action));
> + exit(EXIT_FAILURE);
> + }
> + /* Check for previous leases */
> + if (custom_lease_file_len) {
> + if (!(leases_array = virJSONValueFromString(lease_entries))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("invalid json in file: %s"), custom_lease_file);
> + goto cleanup;
> + }
> +
> + if ((size = virJSONValueArraySize(leases_array)) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("couldn't fetch array of leases"));
> + goto cleanup;
> + }
> + }
> +
> + if (!(leases_array_new = virJSONValueNewArray())) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("failed to create json"));
> + goto cleanup;
> + }
> +
> + currtime = (long long) time(NULL);
> +
> + for (i = 0; i < size; i++) {
> + const char *ip_tmp = NULL;
> + long long expirytime_tmp = -1;
> +
> + 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_tmp) < 0)) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("failed to parse json"));
> + goto cleanup;
> + }
> +
> + /* Check whether lease has expired or not */
> + if (expirytime_tmp < currtime)
> + continue;
> +
> + /* Check whether lease has to be included or not */
> + if (delete && STREQ(ip_tmp, ip))
> + continue;
> +
> + /* Add 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;
> + }
> + }
> +
> + if (add) {
> + if (virJSONValueArrayAppend(leases_array_new, lease_new) < 0) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("failed to create json"));
> + goto cleanup;
> + }
> + }
> +
> + if (!(leases_str = virJSONValueToString(leases_array_new, true))) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("empty json array"));
> + goto cleanup;
> + }
> +
> + /* Write to file */
> + if (virFileRewrite(custom_lease_file, 0644,
> + customLeaseRewriteFile, &leases_str) < 0)
> + goto cleanup;
> +
> + rv = EXIT_SUCCESS;
> +
> +cleanup:
> + if (pid_file_fd != -1)
> + virPidFileReleasePath(pid_file, pid_file_fd);
> +
> + VIR_FREE(pid_file);
> + VIR_FREE(exptime_tmp);
> + VIR_FREE(custom_lease_file);
> + virJSONValueFree(lease_new);
> + virJSONValueFree(leases_array);
> + virJSONValueFree(leases_array_new);
> +
> + return rv;
> +}
> --
> 1.7.1
>
--
Nehal J Wani
More information about the libvir-list
mailing list