Ping! On Sat, Apr 26, 2014 at 5:29 AM, Nehal J Wani <nehaljw.kkd1@xxxxxxxxx> 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@xxxxxxxxx> > + * > + * 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list