]> xenbits.xensource.com Git - libvirt.git/commitdiff
Add helper program to create custom leases
authorNehal J Wani <nehaljw.kkd1@gmail.com>
Mon, 2 Jun 2014 10:19:26 +0000 (11:19 +0100)
committerDaniel P. Berrange <berrange@redhat.com>
Mon, 2 Jun 2014 10:45:10 +0000 (11:45 +0100)
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

libvirt.spec.in
po/POTFILES.in
src/Makefile.am
src/network/bridge_driver.c
src/network/leaseshelper.c [new file with mode: 0644]

index a16bcb6b57d96fd286865fa0ef3db4a60911f0fe..344748c3adbfdf7e59d2a31e1d7cc3e711805a66 100644 (file)
@@ -1917,6 +1917,7 @@ exit 0
 %dir %attr(0700, root, root) %{_sysconfdir}/libvirt/qemu/networks/autostart
 %dir %attr(0700, root, root) %{_localstatedir}/lib/libvirt/network/
 %dir %attr(0755, root, root) %{_localstatedir}/lib/libvirt/dnsmasq/
+%attr(0755, root, root) %{_libexecdir}/libvirt_leaseshelper
         %endif
         %if %{with_nwfilter}
 %dir %attr(0700, root, root) %{_sysconfdir}/libvirt/nwfilter/
@@ -1992,6 +1993,7 @@ exit 0
 %ghost %dir %{_localstatedir}/run/libvirt/network/
 %dir %attr(0700, root, root) %{_localstatedir}/lib/libvirt/network/
 %dir %attr(0755, root, root) %{_localstatedir}/lib/libvirt/dnsmasq/
+%attr(0755, root, root) %{_libexecdir}/libvirt_leaseshelper
 %{_libdir}/%{name}/connection-driver/libvirt_driver_network.so
         %endif
 
index 6e8d46594d27eab7ccfc88a8eba2df927bfe6ff3..cff92d9d149dbf5a3f941392ea4988364455b4ad 100644 (file)
@@ -76,6 +76,7 @@ src/libxl/libxl_driver.c
 src/libxl/libxl_conf.c
 src/network/bridge_driver.c
 src/network/bridge_driver_linux.c
+src/network/leaseshelper.c
 src/node_device/node_device_driver.c
 src/node_device/node_device_hal.c
 src/node_device/node_device_linux_sysfs.c
index cfb709741bd12f296b9c79c110607a1b02e68388..2397b293de516f9179a2aceba1f7968283ae11d4 100644 (file)
@@ -860,6 +860,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   \
@@ -2468,6 +2471,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
index 1ef1b35a3803c72dbaf9d56bee7c1413b808bb77..eb7f52171a7680f5c1dbf212a3fe80fe97e04227 100644 (file)
@@ -210,6 +210,16 @@ networkDnsmasqLeaseFileNameDefault(const char *netname)
 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)
 {
@@ -246,6 +256,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
                       virNetworkObjPtr net)
 {
     char *leasefile = NULL;
+    char *customleasefile = NULL;
     char *radvdconfigfile = NULL;
     char *configfile = NULL;
     char *radvdpidbase = NULL;
@@ -264,6 +275,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;
 
@@ -280,6 +294,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
     /* dnsmasq */
     dnsmasqDelete(dctx);
     unlink(leasefile);
+    unlink(customleasefile);
     unlink(configfile);
 
     /* radvd */
@@ -297,6 +312,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
  cleanup:
     VIR_FREE(leasefile);
     VIR_FREE(configfile);
+    VIR_FREE(customleasefile);
     VIR_FREE(radvdconfigfile);
     VIR_FREE(radvdpidbase);
     VIR_FREE(statusfile);
@@ -1236,6 +1252,7 @@ networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network,
     int ret = -1;
     char *configfile = NULL;
     char *configstr = NULL;
+    char *leaseshelper_path;
 
     network->dnsmasqPid = -1;
 
@@ -1258,11 +1275,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 (file)
index 0000000..e53a73c
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * leaseshelper.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@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;
+}