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

Daniel P. Berrange berrange at redhat.com
Thu Mar 13 10:48:56 UTC 2014


On Mon, Feb 24, 2014 at 05:27:14PM +0530, Nehal J Wani 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
>     }
> ]

> 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

I'd suggest this should go into the 'network' directory rather than
'util' since this is only for use by the network driver, and also
call the variable NETWORK_LEASES_HELPER_SOURCES 

> +
>  # 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)

This whole block needs to be surround in  'if WITH_NETWORK' and
in the else clause add UTIL_LEASES_HELPER_SOURCES to EXTRA_DIST

> 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

> +/**
> + * 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)

Do you think this is large enough ? Lets imagine a case of
65,000 leases - will they all fit in 2 MB given the JSON
formatting we're doing ?

> +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];

Looks like a '<tab>' character in the source - use 'make syntax-check' to
avoid that.

> +
> +    if (virAsprintf(&custom_lease_file, "%s/%s.status", LOCALSTATEDIR
> +                    "/lib/libvirt/dnsmasq/", interface) < 0)
> +        goto cleanup;

I think that before we do anything to read/write the lease file it is
probably a wise precaution to acquire a lock on a pidfile. Our current
pidfile acquire APis (virPidFileAcquire) will simply return -1 upon
failure to acquire. What we want though is to make it block and wait
for the lock. So you'll need to add an extra parameter to virPidFileAcquire
to say whether to block or not. Then just add

  if ( virPidFileAcquire(....) < 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 {

Put 'else {' on the same line as }.

> +        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;
> +}

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




More information about the libvir-list mailing list