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:
[
{
"expiry-time": "1390775837",
"mac-address": "52:54:00:93:8c:63",
"iaid": "*",
"ip-address": "192.168.150.209",
"hostname": "iit-ad885e4aa1",
"client-id": "01:52:54:00:44:7c:d7"
},
{
"expiry-time": "1390775950",
"mac-address": "52:54:00:7b:6f:ba",
"iaid": "8089530",
"ip-address": "2001:db8:ca2:2:1::6d",
"hostname": "*",
"client-id":
"00:04:76:00:cf:ae:b3:0b:fc:cd:0e:22:2e:97:76:65:74:ec"
}
]
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
---
v2:
* Changed format to JSON
v1:
* Refer:
https://www.redhat.com/archives/libvir-list/2014-January/msg00626.html
src/Makefile.am | 20 ++++
src/network/bridge_driver.c | 19 ++++
src/util/leaseshelper.c | 271 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 310 insertions(+)
create mode 100644 src/util/leaseshelper.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 7844efa..1fa6263 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -840,6 +840,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 \
@@ -2410,6 +2413,23 @@ libvirt_iohelper_CFLAGS = \
$(NULL)
endif WITH_LIBVIRTD
+if WITH_LIBVIRTD
+libexec_PROGRAMS += libvirt_leaseshelper
+libvirt_leaseshelper_SOURCES = $(UTIL_LEASES_HELPER_SOURCES)
+libvirt_leaseshelper_LDFLAGS = \
+ $(NULL)
+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
if WITH_LIBVIRTD
libexec_PROGRAMS += libvirt_parthelper
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index 0b43a67..5440aef 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -148,6 +148,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;
@@ -183,6 +193,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
virNetworkObjPtr net)
{
char *leasefile = NULL;
+ char *customleasefile = NULL;
char *radvdconfigfile = NULL;
char *configfile = NULL;
char *radvdpidbase = NULL;
@@ -201,6 +212,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;
@@ -217,6 +231,7 @@ networkRemoveInactive(virNetworkDriverStatePtr driver,
/* dnsmasq */
dnsmasqDelete(dctx);
unlink(leasefile);
+ unlink(customleasefile);
unlink(configfile);
/* radvd */
@@ -1063,6 +1078,10 @@ networkBuildDhcpDaemonCommandLine(virNetworkObjPtr network,
cmd = virCommandNew(dnsmasqCapsGetBinaryPath(caps));
virCommandAddArgFormat(cmd, "--conf-file=%s", configfile);
+
+ /* This helper is used to create cutom 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..a9aaf3f
--- /dev/null
+++ b/src/util/leaseshelper.c
@@ -0,0 +1,271 @@
+/*
+ * leasehelper.c: Helper program to create custom leases file
+ *
+ * Copyright (C) 2013 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(a)gmail.com>
+ *
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.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 2097152
+
+/*
+ * Use this when passing possibly-NULL strings to printf-a-likes.
+ */
+# define EMPTY_STR(s) ((s) ? (s) : "*")
+
+int
+main(int argc, char **argv) {
+
+ /* Doesn't hurt to check */
+ if (argc < 4) {
+ /* Refer man page of dnsmasq --dhcp-script for more details */
+ fprintf(stderr, "Usage: $program $action ${mac|clientid} $ip\n");
+ return -1;
+ }
+
+ char *lease_file = NULL;
+ char *lease_entries = NULL;
+ const char *ip = argv[3];
+ const char *mac = argv[2];
+ const char *action = argv[1];
+ const char *program_name = argv[0];
+ const char *iaid = EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_IAID"));
+ const char *clientid = EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_CLIENT_ID"));
+ const char *interface =
EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_INTERFACE"));
+ const char *exptime =
EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_LEASE_EXPIRES"));
+ const char *hostname =
EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_SUPPLIED_HOSTNAME"));
+ const char *leases_str = NULL;
+ size_t i = 0;
+ int rv = -1;
+ int size = 0;
+ int lease_file_len = 0;
+ FILE *fp = NULL;
+ bool add = false;
+ bool delete = false;
+ virJSONValuePtr lease_new;
+ virJSONValuePtr lease_tmp;
+ virJSONValuePtr leases_array;
+ virJSONValuePtr lease_new_tmp;
+ virJSONValuePtr leases_array_new;
+
+ 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);
+ }
+
+ if (virAsprintf(&lease_file, "%s/%s.status", LOCALSTATEDIR
+ "/lib/libvirt/dnsmasq/", interface) < 0)
+ goto cleanup;
+
+ if (virGetEnvAllowSUID("DNSMASQ_IAID")) {
+ mac = EMPTY_STR(virGetEnvAllowSUID("DNSMASQ_MAC"));
+ clientid = argv[2];
+ }
+
+ /* Make sure dnsmasq knows the interface, otherwise something is wrong */
+ if (STREQ(interface, "*"))
+ goto cleanup;
+
+ /* Make sure the file exists. If not, 'touch' it */
+ if (virFileTouch(lease_file, 0644) < 0)
+ goto cleanup;
+
+ /* Read entire contents */
+ if ((lease_file_len = virFileReadAll(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;
+ /* Enter new lease */
+ if (!(lease_new = virJSONValueNewObject())) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create json"));
+ goto cleanup;
+ }
+ if (virJSONValueObjectAppendString(lease_new, "expiry-time",
+ exptime) < 0 ||
+ virJSONValueObjectAppendString(lease_new, "mac-address",
+ mac) < 0 ||
+ virJSONValueObjectAppendString(lease_new, "iaid",
+ iaid) < 0 ||
+ virJSONValueObjectAppendString(lease_new, "ip-address",
+ ip) < 0 ||
+ virJSONValueObjectAppendString(lease_new, "hostname",
+ hostname) < 0 ||
+ virJSONValueObjectAppendString(lease_new, "client-id",
+ clientid) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create json"));
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+ /* Check for previous leases */
+ if (lease_file_len) {
+ if (!(leases_array = virJSONValueFromString(lease_entries))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("invalid json in file: %s"), 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;
+ const char *mac_tmp = NULL;
+ const char *iaid_tmp = NULL;
+ const char *exptime_tmp = NULL;
+ const char *clientid_tmp = NULL;
+ const char *hostname_tmp = NULL;
+ long long expirytime_tmp = 0;
+
+ if (!(lease_tmp = virJSONValueArrayGet(leases_array, i))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to parse json"));
+ goto cleanup;
+ }
+
+ if (!(iaid_tmp = virJSONValueObjectGetString(lease_tmp, "iaid")) ||
+ !(ip_tmp = virJSONValueObjectGetString(lease_tmp, "ip-address"))
||
+ !(mac_tmp = virJSONValueObjectGetString(lease_tmp, "mac-address"))
||
+ !(hostname_tmp = virJSONValueObjectGetString(lease_tmp,
"hostname")) ||
+ !(clientid_tmp = virJSONValueObjectGetString(lease_tmp,
"client-id")) ||
+ !(exptime_tmp = virJSONValueObjectGetString(lease_tmp,
"expiry-time"))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to parse json"));
+ goto cleanup;
+ }
+
+ if (virStrToLong_ll(exptime_tmp, NULL, 10, &expirytime_tmp) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Unable to convert lease expiry time to integer:
%s"),
+ exptime_tmp);
+ goto cleanup;
+ }
+
+ /* Check whether lease has expired or not */
+ if (expirytime_tmp < (long long) time(NULL))
+ continue;
+ else if (delete && STREQ(ip_tmp, ip))
+ continue;
+ else {
+ if (!(lease_new_tmp = virJSONValueNewObject())) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create json"));
+ goto cleanup;
+ }
+ if (virJSONValueObjectAppendString(lease_new_tmp, "expiry-time",
+ exptime_tmp) < 0 ||
+ virJSONValueObjectAppendString(lease_new_tmp, "mac-address",
+ mac_tmp) < 0 ||
+ virJSONValueObjectAppendString(lease_new_tmp, "iaid",
+ iaid_tmp) < 0 ||
+ virJSONValueObjectAppendString(lease_new_tmp, "ip-address",
+ ip_tmp) < 0 ||
+ virJSONValueObjectAppendString(lease_new_tmp, "hostname",
+ hostname_tmp) < 0 ||
+ virJSONValueObjectAppendString(lease_new_tmp, "client-id",
+ clientid_tmp) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to create json"));
+ goto cleanup;
+ }
+ if (virJSONValueArrayAppend(leases_array_new, lease_new_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;
+ }
+ }
+
+ rv = 0;
+
+ /* Write to file */
+ leases_str = virJSONValueToString(leases_array_new, true);
+ if (!leases_str)
+ leases_str = "";
+
+ if (virFileWriteStr(lease_file, leases_str, 0) < 0)
+ rv = -1;
+
+cleanup:
+ VIR_FREE(lease_file);
+ virJSONValueFree(lease_new);
+ virJSONValueFree(leases_array);
+ virJSONValueFree(lease_new_tmp);
+ virJSONValueFree(leases_array_new);
+ return rv;
+}
--
1.8.1.4