[libvirt] [PATCH v5] Add helper program to create custom leases

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@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

Ping! On Sat, Apr 26, 2014 at 5:29 AM, Nehal J Wani <nehaljw.kkd1@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@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

On Sat, Apr 26, 2014 at 5:29 AM, Nehal J Wani <nehaljw.kkd1@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
Ping!

On Sat, Apr 26, 2014 at 05:29:16AM +0530, Nehal J Wani wrote:
src/Makefile.am | 22 +++ src/network/bridge_driver.c | 27 ++++ src/network/leaseshelper.c | 360 +++++++++++++++++++++++++++++++++++++++++++
The 'leaseshelper' program also needs to be listed in libvirt.spec.in and in po/POTFILES.in too.
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
typo s/leasehelper/leaseshelper/
+ /* Doesn't hurt to check */ + if (argc > 1) { + if(STREQ(argv[1], "--help"))
Missing space after 'if'
+ usage(EXIT_SUCCESS); + + if (STREQ(argv[1], "--version")) { + helperVersion(argv[0]); + exit(EXIT_SUCCESS); + } + } + rv = EXIT_SUCCESS; + +cleanup:
Missing leading space before 'cleanup' ACK with the nits fixed. In the interests of finally getting this merged, I've corrected the mistakes and pushed the patch. Please remember to use 'make syntax-check' before sending patches. 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 :|
participants (2)
-
Daniel P. Berrange
-
Nehal J Wani