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/util/leaseshelper.c * Helper program to create the custom lease file --- v3: * Improved file handling, removed redundant copying, introduced --help and --version 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 | 16 +++ src/network/bridge_driver.c | 19 +++ src/util/leaseshelper.c | 303 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 0 deletions(-) create mode 100644 src/util/leaseshelper.c diff --git a/src/Makefile.am b/src/Makefile.am index 6d21e5d..b8e1993 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -849,6 +849,9 @@ STORAGE_HELPER_DISK_SOURCES = \ UTIL_IO_HELPER_SOURCES = \ util/iohelper.c +UTIL_LEASES_HELPER_SOURCES = \ + util/leaseshelper.c + # Network filters NWFILTER_DRIVER_SOURCES = \ nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \ @@ -2444,6 +2447,19 @@ libvirt_iohelper_CFLAGS = \ $(AM_CFLAGS) \ $(PIE_CFLAGS) \ $(NULL) + +libexec_PROGRAMS += libvirt_leaseshelper +libvirt_leaseshelper_SOURCES = $(UTIL_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 = \ + $(PIE_CFLAGS) \ + $(NULL) endif WITH_LIBVIRTD if WITH_STORAGE_DISK diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c index a6c719d..9fb750f 100644 --- a/src/network/bridge_driver.c +++ b/src/network/bridge_driver.c @@ -205,6 +205,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; @@ -240,6 +250,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver, virNetworkObjPtr net) { char *leasefile = NULL; + char *customleasefile = NULL; char *radvdconfigfile = NULL; char *configfile = NULL; char *radvdpidbase = NULL; @@ -258,6 +269,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; @@ -274,6 +288,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver, /* dnsmasq */ dnsmasqDelete(dctx); unlink(leasefile); + unlink(customleasefile); unlink(configfile); /* radvd */ @@ -1117,6 +1132,10 @@ networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network, cmd = virCommandNew(dnsmasqCapsGetBinaryPath(caps)); virCommandAddArgFormat(cmd, "--conf-file=%s", configfile); + + /* This helper is used to create custom leases file for libvirt */ + virCommandAddArgFormat(cmd, "--dhcp-script=%s", LIBEXECDIR "/libvirt_leaseshelper"); + *cmdout = cmd; ret = 0; cleanup: diff --git a/src/util/leaseshelper.c b/src/util/leaseshelper.c new file mode 100644 index 0000000..bd8110f --- /dev/null +++ b/src/util/leaseshelper.c @@ -0,0 +1,303 @@ +/* + * leasehelper.c: Helper program to create custom leases file + * + * Copyright (C) 2014 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: Nehal J Wani <nehaljw.kkd1@xxxxxxxxx> + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include "virutil.h" +#include "virthread.h" +#include "virfile.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 (2 * 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 ACTION MAC|CLIENTID IP HOSTNAME\n" + " or: %s ACTION MAC|CLIENTID IP\n"), + program_name, 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; +} + +int +main(int argc, char **argv) +{ + char *lease_entries = NULL; + char *custom_lease_file = NULL; + const char *ip = NULL; + const char *mac = NULL; + const char *action = NULL; + const char *iaid = virGetEnvAllowSUID("DNSMASQ_IAID"); + const char *clientid = virGetEnvAllowSUID("DNSMASQ_CLIENT_ID"); + const char *interface = virGetEnvAllowSUID("DNSMASQ_INTERFACE"); + const char *exptime = virGetEnvAllowSUID("DNSMASQ_LEASE_EXPIRES"); + const char *hostname = virGetEnvAllowSUID("DNSMASQ_SUPPLIED_HOSTNAME"); + const char *leases_str = NULL; + long long expirytime = 0; + size_t i = 0; + int rv = EXIT_FAILURE; + int size = 0; + 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 = argv[1]; + + /* In case hostname is known, it is the 5th argument */ + if (argc == 5) + hostname = argv[4]; + + if (virAsprintf(&custom_lease_file, "%s/%s.status", LOCALSTATEDIR + "/lib/libvirt/dnsmasq/", interface) < 0) + goto cleanup; + + /* Check if it is an IPv6 lease */ + if (virGetEnvAllowSUID("DNSMASQ_IAID")) { + mac = virGetEnvAllowSUID("DNSMASQ_MAC"); + clientid=argv[2]; + } + + /* 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 (STREQ(action, "add") || STREQ(action, "old") || STREQ(action, "del")) { + if (mac || STREQ(action, "del")) { + /* Delete the corresponding lease */ + delete = true; + if (STREQ(action, "add") || STREQ(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) || + (ip && virJSONValueObjectAppendString(lease_new, "ip-address", + ip) < 0) || + (mac && virJSONValueObjectAppendString(lease_new, "mac-address", + mac) < 0) || + (hostname && virJSONValueObjectAppendString(lease_new, "hostname", + hostname) < 0) || + (clientid && virJSONValueObjectAppendString(lease_new, "client-id", + clientid) < 0) || + (expirytime && virJSONValueObjectAppendNumberLong(lease_new, "expiry-time", + expirytime) < 0)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to create json")); + goto cleanup; + } + } + } + } + else { + fprintf(stderr, _("Unsupported action: %s\n"), 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; + } + + for (i = 0; i < size; i++) { + const char *ip_tmp = NULL; + long long exptime_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", &exptime_tmp) < 0)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to parse json")); + goto cleanup; + } + + /* Check whether lease has expired or not */ + if (exptime_tmp < (long long) time(NULL)) + 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: + VIR_FREE(custom_lease_file); + virJSONValueFree(lease_new); + virJSONValueFree(leases_array); + virJSONValueFree(leases_array_new); + return rv; +} -- 1.7.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list