[Libvir] PATCH: Avahi advertisement for libvirtd

Daniel P. Berrange berrange at redhat.com
Tue Sep 18 02:50:18 UTC 2007


This is a serious patch at supporting Avahi advertisement of the libvirtd
service.

 - configure by default will probe to see if avahi is available and if
   found will enable appropriate code.

       --with-avahi    will force error if not found
       --without-avahi   will disable it with no checking

 - HAVE_AVAHI is defined in config.h if avahi is found & used to conditionally
   enable some code in qemud/qemud.c

 - HAVE_AVAHI is also a Makefile conditional to enable compilation of the
   mdns.c and mdns.h files. A little makefile rearrangement was needed to
   make sure variables like EXTRA_DIST were defined before we appended to
   them with +=

 - The code in mdns.c contains all the support for dealing with the Avahi
   APIs. 

 - The primary Avahi API is horribily complicated for day-to-day
   use in application code, exposing far too much of the event loop and
   state machine. So we expose a simplified API to libvirt in mdns.h

 - We fully support the Avahi state machine, so you can start libvirt even
   if Avahi is not running on the machine. If you later start Avahi, then
   libvirt will automatically detect & register with it. Likewise if you
   stop Avahi we handle that gracefully by shutting down our internal mdns
   support & waiting for avahi to restart. 

   NB this does require that the DBus system bus daemon is running. This
   is a limitation of the current Avahi client library & not our use of it.
   It is expected this will be fixed in future avahi releases.

 - The Avahi client library is basically a shim which talks to the Avahi
   daemon using DBus system daemon. The DBus stuff doesn't leak out of 
   the Avahi APIs - it is loosely couple - all we need do is provide Avahi
   with an event loop implementation which was surprisingly easy. The
   libvirtd daemon does now indirectly link with DBus, but I don't see any
   problem with this. Don't want it - then use --without-avahi 

 - We advertise a service name of _libvirt._tcp  The IETF draft recommends
   use of the name from /etc/services associated with your app. There is a
   way to register official Avahi services names. We don't have an /etc/service
   name registered either though.

 - If TLS is *not* enabled, we advertise _libvirt._tcp with a port number
   of 0.  The use of a port number 0 is explicitly allowed by the IETF
   draft docs - see [1] "8. Flagship Naming".  If seeing the _libvirt._tcp
   service with a port of 0, then clients should figure out some way to
   to tunnel to the UNXI domain socket on the advertised machine. The
   tunnel mechanism is undefined, though SSH is the obvious choice.

   If TLS is enabled, then we use getsockname() to fetch the actual port 
   number we're listening on for the TLS enabled socket. This plays nice 
   with admins ability to override the port number. Clients  can still of
   course choose to tunnel instead of use TLS.

   NB. we explicitly refuse to advertise the non-TLS port.

 - The default service name we advertise is 'Virtualization Host %h' where
   '%h' is the short hostname (ie without domain name). This has to be less
   than 63 characters & stripping domain name is usual practice, since this
   is implicit from the mdns domain you are browsing.

   This can be overridden with mdns_name="Blah"  in the libvirtd.conf
   configuration file. Service names *must* be unique in the LAN. If a name
   clashes, then avahi appends junk like #1, #2, #3... until it is unique.

 - Even when compiled in, use of Avahi mdns can be disabled by setting the
   mdns_adv=0 config file parameter in /etc/libvirt/libvirtd.conf

 - This patch does not advertise any per-VM  VNC server instances, but I have
   prepared the APIs in mdns.h to be ready to support that with minimal effort.
   A pre-requisite for this is an extension to the driver API to get async
   signals when VMs start & stop, since making the daemon poll hypervisors 
   will suck big time.

   When implemented each VM will be its own mdns 'group' and the VNC server
   associated with it will be the 'service' advertised in that group.

Having applied this patch & started the daemon, if /etc/init.d/avahi-daemon
is running, you should see the service advertised on the LAN. As mentioned
earlier if you start Avahi daemon after libvirt it should detect this too.

You can verify by running the following on another box on the same local
LAN segment.

  $ avahi-browse --resolve _libvirt._tcp 


When you start libvirtd on the other box the avahi-browse should print out
a record. It should look like this:

+ eth0 IPv4 Virtualization Host avocado          _libvirt._tcp        local
= eth0 IPv4 Virtualization Host avocado          _libvirt._tcp        local
   hostname = [avocado.local]
   address = [10.13.7.48]
   port = [16514]
   txt = ["org.freedesktop.Avahi.cookie=3025088350"]


If you've not configured TLS, then port will be '0' instead of 16514.


So that brings me nicely onto the main outstanding issue.

Notice the hostname is 'avocado.local' - this is the standard mdns practice
for zero-conf DNS hostnames. If you're /etc/nsswitch.conf is setup correctly
the name avocado.local will actually resolve to the IP address you see there
of '10.13.7.48' (well s/avocado/your hostname/ of course).

Well, x509 certificates include a FQDN in them & the client is expected to
validate the certificate hostname against the hostname it connected to. Now
my FQDN is  avocado.virt.boston.redhat.com which is obviously going to not
validate against avocado.local.

There're a couple of ways around this issue I can come up with so far

  - Recommend that the client try to reverse-DNS the 'address' field from
    the mdns advertisement. If reverse-DNS is working, 10.13.7.48 will be
    transformed into 'avocado.virt.boston.redhat.com' which the client can
    then connect to. 

  - Add TXT record containing the hostname associated with the certificate.
    If the client were to use this record, then obviously any validation of
    it against the resulting certificate is sort-of pointless, since both
    came from the server. The first approach rather had this drawback too.

  - Advertise the records in the real domain 'virt.boston.redhat.com' instead
    of '.local'. Not been able to make this work. 

  - Simply don't bother validating the remote hostname against the server
    certificate cname. ie, take the position that if using zero-conf then
    you already have some implicit level of trust in your LAN and validating
    the cert against the CA cert is sufficient & hostname matching can be
    ignored.

These are all mildy abusing mdns / zeroconf, but then x509 certificates don't
really fit into the model of 'zero conf' in the first place. If people want 
true zero-conf then the (SSH) tunnel is better (and always available), but 
if they've setup certificates they should still be allowed to use zero-conf 
to at least locate hosts. So mildly abusing the rules is reasonable IMHO.

Personally I'm tending towards the latter approach.

Regards,
Dan.

[1] http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt
-- 
|=- Red Hat, Engineering, Emerging Technologies, Boston.  +1 978 392 2496 -=|
|=-           Perl modules: http://search.cpan.org/~danberr/              -=|
|=-               Projects: http://freshmeat.net/~danielpb/               -=|
|=-  GnuPG: 7D3B9505   F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505  -=| 
-------------- next part --------------
diff -r 9f44845c41e1 configure.in
--- a/configure.in	Mon Sep 17 13:30:54 2007 -0400
+++ b/configure.in	Mon Sep 17 14:31:07 2007 -0400
@@ -23,6 +23,7 @@ AC_SUBST(LIBVIRT_VERSION_NUMBER)
 
 LIBXML_REQUIRED="2.5.0"
 GNUTLS_REQUIRED="1.2.0"
+AVAHI_REQUIRED="0.6.0"
 
 dnl Checks for programs.
 AC_PROG_CC
@@ -263,6 +264,27 @@ AC_CHECK_TYPE(gnutls_session,
 	[#include <gnutls/gnutls.h>])
 CFLAGS="$old_cflags"
 LDFLAGS="$old_ldflags"
+
+dnl Avahi library
+AC_ARG_WITH(avahi,
+  [  --with-avahi         use avahi to advertise remote daemon],
+  [],
+  [with_avahi=check])
+
+if test "$with_avahi" = "check"; then
+  PKG_CHECK_EXISTS(avahi-client >= $AVAHI_REQUIRED, [with_avahi=yes], [with_avahi=no])
+fi
+
+if test "$with_avahi" = "yes"; then
+  PKG_CHECK_MODULES(AVAHI, avahi-client >= $AVAHI_REQUIRED)
+  AC_DEFINE_UNQUOTED(HAVE_AVAHI, 1, [whether Avahi is used to broadcast server presense])
+else
+  AVAHI_CFLAGS=
+  AVAHI_LIBS=
+fi
+AM_CONDITIONAL(HAVE_AVAHI, [test "$with_avahi" = "yes"])
+AC_SUBST(AVAHI_CFLAGS)
+AC_SUBST(AVAHI_LIBS)
 
 dnl virsh libraries
 AC_CHECK_LIB(curses, initscr, 
@@ -450,6 +472,11 @@ AC_MSG_NOTICE([])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([  libxml: $LIBXML_CFLAGS $LIBXML_LIBS])
 AC_MSG_NOTICE([  gnutls: $GNUTLS_CFLAGS $GNUTLS_LIBS])
+if test "$with_avahi" = "yes" ; then
+AC_MSG_NOTICE([   avahi: $AVAHI_CFLAGS $AVAHI_LIBS])
+else
+AC_MSG_NOTICE([   avahi: no])
+fi
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Miscellaneous])
 AC_MSG_NOTICE([])
diff -r 9f44845c41e1 qemud/Makefile.am
--- a/qemud/Makefile.am	Mon Sep 17 13:30:54 2007 -0400
+++ b/qemud/Makefile.am	Mon Sep 17 14:31:55 2007 -0400
@@ -4,12 +4,24 @@ UUID=$(shell uuidgen)
 
 sbin_PROGRAMS = libvirtd
 
+# Distribute the generated files so that rpcgen isn't required on the
+# target machine (although almost any Unix machine will have it).
+EXTRA_DIST = libvirtd.init.in libvirtd.sysconf default-network.xml \
+	protocol.x remote_protocol.x \
+	protocol.c protocol.h \
+	remote_protocol.c remote_protocol.h \
+	remote_generate_stubs.pl rpcgen_fix.pl \
+	remote_dispatch_prototypes.h \
+	remote_dispatch_localvars.h \
+	remote_dispatch_proc_switch.h
+
 libvirtd_SOURCES = \
 		qemud.c internal.h \
 		protocol.h protocol.c \
 		remote_protocol.h remote_protocol.c \
 		remote.c \
-                event.c event.h
+		event.c event.h
+
 #-D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D_POSIX_C_SOURCE=199506L
 libvirtd_CFLAGS = \
         -I$(top_srcdir)/include -I$(top_builddir)/include \
@@ -23,6 +35,14 @@ libvirtd_LDFLAGS = $(WARN_CFLAGS) $(LIBX
 libvirtd_LDFLAGS = $(WARN_CFLAGS) $(LIBXML_LIBS) $(GNUTLS_LIBS)
 libvirtd_DEPENDENCIES = ../src/libvirt.la
 libvirtd_LDADD = ../src/libvirt.la
+
+if HAVE_AVAHI
+libvirtd_SOURCES += mdns.c mdns.h
+libvirtd_CFLAGS += $(AVAHI_CFLAGS)
+libvirtd_LDADD += $(AVAHI_LIBS)
+else
+EXTRA_DIST += mdns.c mdns.h
+endif
 
 install-data-local: install-init
 	mkdir -p $(DESTDIR)$(sysconfdir)/libvirt/qemu/networks/autostart
@@ -42,16 +62,6 @@ uninstall-local: uninstall-init
 	rmdir $(DESTDIR)$(localstatedir)/run/libvirt || :
 	rmdir $(DESTDIR)$(localstatedir)/lib/libvirt || :
 
-# Distribute the generated files so that rpcgen isn't required on the
-# target machine (although almost any Unix machine will have it).
-EXTRA_DIST = libvirtd.init.in libvirtd.sysconf default-network.xml \
-	protocol.x remote_protocol.x \
-	protocol.c protocol.h \
-	remote_protocol.c remote_protocol.h \
-	remote_generate_stubs.pl rpcgen_fix.pl \
-	remote_dispatch_prototypes.h \
-	remote_dispatch_localvars.h \
-	remote_dispatch_proc_switch.h
 
 .x.c:
 	rm -f $@
diff -r 9f44845c41e1 qemud/internal.h
--- a/qemud/internal.h	Mon Sep 17 13:30:54 2007 -0400
+++ b/qemud/internal.h	Mon Sep 17 13:32:10 2007 -0400
@@ -111,7 +111,7 @@ struct qemud_socket {
     int readonly;
     /* If set, TLS is required on this socket. */
     int tls;
-
+    int port;
     struct qemud_socket *next;
 };
 
@@ -124,6 +124,9 @@ struct qemud_server {
     int sigread;
     char logDir[PATH_MAX];
     unsigned int shutdown : 1;
+#ifdef HAVE_AVAHI
+    struct libvirtd_mdns *mdns;
+#endif
 };
 
 void qemudLog(int priority, const char *fmt, ...)
diff -r 9f44845c41e1 qemud/mdns.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qemud/mdns.c	Mon Sep 17 13:45:36 2007 -0400
@@ -0,0 +1,504 @@
+/*
+ * mdns.c: advertise libvirt hypervisor connections
+ *
+ * Copyright (C) 2007 Daniel P. Berrange
+ *
+ * Derived from Avahi example service provider code.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+
+#include <avahi-common/alternative.h>
+#include <avahi-common/simple-watch.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-common/timeval.h>
+
+#include "mdns.h"
+#include "event.h"
+#include "../src/remote_internal.h"
+#include "../src/internal.h"
+
+#define AVAHI_DEBUG(fmt, ...) qemudDebug("AVAHI: " fmt, __VA_ARGS__)
+
+struct libvirtd_mdns_entry {
+    char *type;
+    int port;
+    struct libvirtd_mdns_entry *next;
+};
+
+struct libvirtd_mdns_group {
+    struct libvirtd_mdns *mdns;
+    AvahiEntryGroup *handle;
+    char *name;
+    struct libvirtd_mdns_entry *entry;
+    struct libvirtd_mdns_group *next;
+};
+
+struct libvirtd_mdns {
+    AvahiClient *client;
+    AvahiPoll *poller;
+    struct libvirtd_mdns_group *group;
+};
+
+/* Avahi API requires this struct names in the app :-( */
+struct AvahiWatch {
+    int fd;
+    int revents;
+    AvahiWatchCallback callback;
+    void *userdata;
+};
+
+/* Avahi API requires this struct names in the app :-( */
+struct AvahiTimeout {
+    int timer;
+    AvahiTimeoutCallback callback;
+    void  *userdata;
+};
+
+
+static void libvirtd_mdns_create_services(struct libvirtd_mdns_group *group);
+
+/* Called whenever the entry group state changes */
+static void libvirtd_mdns_group_callback(AvahiEntryGroup *g ATTRIBUTE_UNUSED, AvahiEntryGroupState state, void *userdata) {
+    struct libvirtd_mdns_group *group = (struct libvirtd_mdns_group *)userdata;
+
+    switch (state) {
+    case AVAHI_ENTRY_GROUP_ESTABLISHED:
+        /* The entry group has been established successfully */
+        AVAHI_DEBUG("Group '%s' established", group->name);
+        break;
+
+    case AVAHI_ENTRY_GROUP_COLLISION:
+        {
+            char *n;
+
+            /* A service name collision happened. Let's pick a new name */
+            n = avahi_alternative_service_name(group->name);
+            free(group->name);
+            group->name = n;
+
+            AVAHI_DEBUG("Group name collision, renaming service to '%s'", group->name);
+
+            /* And recreate the services */
+            libvirtd_mdns_create_services(group);
+        }
+        break;
+
+    case AVAHI_ENTRY_GROUP_FAILURE :
+        AVAHI_DEBUG("Group failure: %s", avahi_strerror(avahi_client_errno(group->mdns->client)));
+
+        /* Some kind of failure happened while we were registering our services */
+        //avahi_simple_poll_quit(simple_poll);
+        break;
+
+    case AVAHI_ENTRY_GROUP_UNCOMMITED:
+    case AVAHI_ENTRY_GROUP_REGISTERING:
+        ;
+    }
+}
+
+static void libvirtd_mdns_create_services(struct libvirtd_mdns_group *group) {
+    struct libvirtd_mdns *mdns = group->mdns;
+    struct libvirtd_mdns_entry *entry;
+    int ret;
+    AVAHI_DEBUG("Adding services to '%s'", group->name);
+
+    /* If we've no services to advertise, just reset the group to make
+     * sure it is emptied of any previously advertised services */
+    if (!group->entry) {
+        if (group->handle)
+            avahi_entry_group_reset(group->handle);
+        return;
+    }
+
+    /* If this is the first time we're called, let's create a new entry group */
+    if (!group->handle) {
+        AVAHI_DEBUG("Creating initial group %s", group->name);
+        if (!(group->handle = avahi_entry_group_new(mdns->client, libvirtd_mdns_group_callback, group))) {
+            AVAHI_DEBUG("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(mdns->client)));
+            return;
+        }
+    }
+
+    entry = group->entry;
+    while (entry) {
+        if ((ret = avahi_entry_group_add_service(group->handle,
+                                                 AVAHI_IF_UNSPEC,
+                                                 AVAHI_PROTO_UNSPEC,
+                                                 0,
+                                                 group->name,
+                                                 entry->type,
+                                                 NULL,
+                                                 NULL, 
+                                                 entry->port,
+                                                 NULL)) < 0) {
+            AVAHI_DEBUG("Failed to add %s service on port %d: %s", 
+                        entry->type, entry->port, avahi_strerror(ret));
+            avahi_entry_group_reset(group->handle);
+            return;
+        }
+        entry = entry->next;
+    }
+
+    /* Tell the server to register the service */
+    if ((ret = avahi_entry_group_commit(group->handle)) < 0) {
+        avahi_entry_group_reset(group->handle);
+        AVAHI_DEBUG("Failed to commit entry_group: %s", avahi_strerror(ret));
+        return;
+    }
+}
+
+
+static void libvirtd_mdns_client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
+    struct libvirtd_mdns *mdns = (struct libvirtd_mdns *)userdata;
+    struct libvirtd_mdns_group *group = mdns->group;
+    if (!mdns->client)
+        mdns->client = c;
+
+    /* Called whenever the client or server state changes */
+    switch (state) {
+        case AVAHI_CLIENT_S_RUNNING:
+            /* The server has startup successfully and registered its host
+             * name on the network, so it's time to create our services */
+            AVAHI_DEBUG("Client running %p", mdns->client);
+            group = mdns->group;
+            while (group) {
+                libvirtd_mdns_create_services(group);
+                group = group->next;
+            }
+            break;
+
+        case AVAHI_CLIENT_FAILURE:
+            AVAHI_DEBUG("Client failure: %s", avahi_strerror(avahi_client_errno(c)));
+            libvirtd_mdns_stop(mdns);
+            libvirtd_mdns_start(mdns);
+            break;
+
+        case AVAHI_CLIENT_S_COLLISION:
+            /* Let's drop our registered services. When the server is back
+             * in AVAHI_SERVER_RUNNING state we will register them
+             * again with the new host name. */
+
+            /* Fallthrough */
+
+        case AVAHI_CLIENT_S_REGISTERING:
+            /* The server records are now being established. This
+             * might be caused by a host name change. We need to wait
+             * for our own records to register until the host name is
+             * properly esatblished. */
+            AVAHI_DEBUG("Client collision/connecting %p", mdns->client);
+            group = mdns->group;
+            while (group) {
+                if (group->handle)
+                    avahi_entry_group_reset(group->handle);
+                group = group->next;
+            }
+            break;
+
+        case AVAHI_CLIENT_CONNECTING:
+            AVAHI_DEBUG("Client connecting.... %p", mdns->client);
+            ;
+    }
+}
+
+
+static void libvirtd_mdns_watch_dispatch(int fd, int events, void *opaque)
+{
+    AvahiWatch *w = (AvahiWatch*)opaque;
+    AVAHI_DEBUG("Dispatch watch FD %d Event %d", fd, events);
+    w->revents = events;
+    w->callback(w, fd, events, w->userdata);
+}
+
+static AvahiWatch *libvirtd_mdns_watch_new(const AvahiPoll *api ATTRIBUTE_UNUSED,
+                                            int fd, AvahiWatchEvent event, AvahiWatchCallback cb, void *userdata) {
+    AvahiWatch *w = malloc(sizeof(AvahiWatch));
+    if (!w)
+        return NULL;
+
+    w->fd = fd;
+    w->revents = 0;
+    w->callback = cb;
+    w->userdata = userdata;
+
+    AVAHI_DEBUG("New handle %p FD %d Event %d", w, w->fd, event);
+    if (virEventAddHandleImpl(fd, event, libvirtd_mdns_watch_dispatch, w) < 0) {
+        free(w);
+        return NULL;
+    }
+
+    return w;
+}
+
+static void libvirtd_mdns_watch_update(AvahiWatch *w, AvahiWatchEvent event)
+{
+    AVAHI_DEBUG("Update handle %p FD %d Event %d", w, w->fd, event);
+    virEventUpdateHandleImpl(w->fd, event);
+}
+
+static AvahiWatchEvent libvirtd_mdns_watch_get_events(AvahiWatch *w)
+{
+    AVAHI_DEBUG("Get handle events %p %d", w, w->fd);
+    return w->revents;
+}
+
+static void libvirtd_mdns_watch_free(AvahiWatch *w)
+{
+    AVAHI_DEBUG("Free handle %p %d", w, w->fd);
+    virEventRemoveHandleImpl(w->fd);
+    free(w);
+}
+
+static void libvirtd_mdns_timeout_dispatch(int timer ATTRIBUTE_UNUSED, void *opaque)
+{
+    AvahiTimeout *t = (AvahiTimeout*)opaque;
+    AVAHI_DEBUG("Dispatch timeout %p %d", t, timer);
+    virEventUpdateTimeoutImpl(t->timer, -1);
+    t->callback(t, t->userdata);
+}
+
+static AvahiTimeout *libvirtd_mdns_timeout_new(const AvahiPoll *api ATTRIBUTE_UNUSED,
+                                                const struct timeval *tv,
+                                                AvahiTimeoutCallback cb,
+                                                void *userdata)
+{
+    AvahiTimeout *t = malloc(sizeof(AvahiTimeout));
+    struct timeval now;
+    long long nowms, thenms, timeout;
+    AVAHI_DEBUG("Add timeout %p TV %p", t, tv);
+    if (!t)
+        return NULL;
+
+    if (gettimeofday(&now, NULL) < 0) {
+        free(t);
+        return NULL;
+    }
+
+    AVAHI_DEBUG("Trigger timed for %d %d      %d %d",
+               (int)now.tv_sec, (int)now.tv_usec,
+               (int)(tv ? tv->tv_sec : 0), (int)(tv ? tv->tv_usec : 0));
+    nowms = (now.tv_sec * 1000ll) + (now.tv_usec / 1000ll);
+    if (tv) {
+        thenms = (tv->tv_sec * 1000ll) + (tv->tv_usec/1000ll);
+        timeout = thenms > nowms ? nowms - thenms : 0;
+        if (timeout < 0)
+            timeout = 0;
+    } else {
+        timeout = -1;
+    }
+
+    t->timer = virEventAddTimeoutImpl(timeout, libvirtd_mdns_timeout_dispatch, t);
+    t->callback = cb;
+    t->userdata = userdata;
+
+    if (t->timer < 0) {
+        free(t);
+        return NULL;
+    }
+
+    return t;
+}
+
+static void libvirtd_mdns_timeout_update(AvahiTimeout *t, const struct timeval *tv)
+{
+    struct timeval now;
+    long long nowms, thenms, timeout;
+    AVAHI_DEBUG("Update timeout %p TV %p", t, tv);
+    if (gettimeofday(&now, NULL) < 0) {
+        free(t);
+        return;
+    }
+
+    nowms = (now.tv_sec * 1000ll) + (now.tv_usec / 1000ll);
+    if (tv) {
+        thenms = ((tv->tv_sec * 1000ll) + (tv->tv_usec/1000ll));
+        timeout = thenms > nowms ? nowms - thenms : 0;
+        if (timeout < 0)
+            timeout = 0;
+    } else {
+        timeout = -1;
+    }
+
+    virEventUpdateTimeoutImpl(t->timer, timeout);
+}
+
+static void libvirtd_mdns_timeout_free(AvahiTimeout *t)
+{
+    AVAHI_DEBUG("Free timeout %p", t);
+    virEventRemoveTimeoutImpl(t->timer);
+    free(t);
+}
+
+
+static AvahiPoll *libvirtd_create_poll(void)
+{
+    AvahiPoll *p = malloc(sizeof(AvahiPoll));
+    if (!p)
+        return NULL;
+
+    p->userdata = NULL;
+
+    p->watch_new = libvirtd_mdns_watch_new;
+    p->watch_update = libvirtd_mdns_watch_update;
+    p->watch_get_events = libvirtd_mdns_watch_get_events;
+    p->watch_free = libvirtd_mdns_watch_free;
+
+    p->timeout_new = libvirtd_mdns_timeout_new;
+    p->timeout_update = libvirtd_mdns_timeout_update;
+    p->timeout_free = libvirtd_mdns_timeout_free;
+
+    return p;
+}
+
+struct libvirtd_mdns *libvirtd_mdns_new(void)
+{
+    struct libvirtd_mdns *mdns = malloc(sizeof(struct libvirtd_mdns));
+    if (!mdns)
+        return NULL;
+    memset(mdns, 0, sizeof(*mdns));
+
+    /* Allocate main loop object */
+    if (!(mdns->poller = libvirtd_create_poll())) {
+        free(mdns);
+        return NULL;
+    }
+
+    return mdns;
+}
+
+int libvirtd_mdns_start(struct libvirtd_mdns *mdns)
+{
+    int error;
+    AVAHI_DEBUG("Starting client %p", mdns);
+    mdns->client = avahi_client_new(mdns->poller, AVAHI_CLIENT_NO_FAIL, libvirtd_mdns_client_callback, mdns, &error);
+
+    if (!mdns->client) {
+        AVAHI_DEBUG("Failed to create mDNS client: %s", avahi_strerror(error));
+        return -1;
+    }
+
+    return 0;
+}
+
+struct libvirtd_mdns_group *libvirtd_mdns_add_group(struct libvirtd_mdns *mdns, const char *name) {
+    struct libvirtd_mdns_group *group = malloc(sizeof(struct libvirtd_mdns_group));
+
+    AVAHI_DEBUG("Adding group '%s'", name);
+    if (!group)
+        return NULL;
+
+    memset(group, 0, sizeof(*group));
+    if (!(group->name = strdup(name))) {
+        free(group);
+        return NULL;
+    }
+    group->mdns = mdns;
+    group->next = mdns->group;
+    mdns->group = group;
+    return group;
+}
+
+void libvirtd_mdns_remove_group(struct libvirtd_mdns *mdns, struct libvirtd_mdns_group *group) {
+    struct libvirtd_mdns_group *tmp = mdns->group, *prev = NULL;
+
+    while (tmp) {
+        if (tmp == group) {
+            free(group->name);
+            if (prev)
+                prev->next = group->next;
+            else
+                group->mdns->group = group->next;
+            free(group);
+            return;
+        }
+        prev = tmp;
+        tmp = tmp->next;
+    }
+}
+
+struct libvirtd_mdns_entry *libvirtd_mdns_add_entry(struct libvirtd_mdns_group *group, const char *type, int port) {
+    struct libvirtd_mdns_entry *entry = malloc(sizeof(struct libvirtd_mdns_entry));
+
+    AVAHI_DEBUG("Adding entry %s %d to group %s", type, port, group->name);
+    if (!entry)
+        return NULL;
+
+    entry->port = port;
+    if (!(entry->type = strdup(type))) {
+        free(entry);
+        return NULL;
+    }
+    entry->next = group->entry;
+    group->entry = entry;
+    return entry;
+}
+
+void libvirtd_mdns_remove_entry(struct libvirtd_mdns_group *group, struct libvirtd_mdns_entry *entry) {
+    struct libvirtd_mdns_entry *tmp = group->entry, *prev = NULL;
+
+    while (tmp) {
+        if (tmp == entry) {
+            free(entry->type);
+            if (prev)
+                prev->next = entry->next;
+            else
+                group->entry = entry->next;
+            return;
+        }
+        prev = tmp;
+        tmp = tmp->next;
+    }
+}
+
+void libvirtd_mdns_stop(struct libvirtd_mdns *mdns)
+{
+    struct libvirtd_mdns_group *group = mdns->group;
+    while (group) {
+        if (group->handle) {
+            avahi_entry_group_free(group->handle);
+            group->handle = NULL;
+        }
+        group = group->next;
+    }
+    if (mdns->client)
+        avahi_client_free(mdns->client);
+    mdns->client = NULL;
+}
+
+
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
diff -r 9f44845c41e1 qemud/mdns.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qemud/mdns.h	Mon Sep 17 13:32:10 2007 -0400
@@ -0,0 +1,96 @@
+/*
+ * mdns.c: advertise libvirt hypervisor connections
+ *
+ * Copyright (C) 2007 Daniel P. Berrange
+ *
+ * Derived from Avahi example service provider code.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Daniel P. Berrange <berrange at redhat.com>
+ */
+
+#include "internal.h"
+
+#ifndef __VIRTD_MDNS_H__
+#define __VIRTD_MDNS_H__
+
+struct libvirtd_mdns;
+struct libvirtd_mdns_group;
+struct libvirtd_mdns_entry;
+
+/**
+ * Prepares a new mdns manager object for use
+ */
+struct libvirtd_mdns *libvirtd_mdns_new(void);
+
+/**
+ * Starts the mdns client, advertizing any groups/entries currently registered
+ * 
+ * @mdns: manager to start advertizing
+ *
+ * Starts the mdns client. Services may not be immediately visible, since
+ * it may asynchronously wait for the mdns service to startup
+ *
+ * returns -1 upon failure, 0 upon success.
+ */
+int libvirtd_mdns_start(struct libvirtd_mdns *mdns);
+
+/**
+ * Stops the mdns client, removing any advertizements
+ *
+ * @mdns: manager to start advertizing
+ *
+ */
+void libvirtd_mdns_stop(struct libvirtd_mdns *mdns);
+
+/**
+ * Adds a group container for advertizement
+ *
+ * @mdns manager to attach the group to
+ * @name unique human readable service name
+ *
+ * returns the group record, or NULL upon failure
+ */
+struct libvirtd_mdns_group *libvirtd_mdns_add_group(struct libvirtd_mdns *mdns, const char *name);
+
+/**
+ * Removes a group container from advertizement
+ *
+ * @mdns amanger to detatch group from
+ * @group group to remove
+ */
+void libvirtd_mdns_remove_group(struct libvirtd_mdns *mdns, struct libvirtd_mdns_group *group);
+
+/**
+ * Adds a service entry in a group
+ *
+ * @group group to attach the entry to
+ * @type service type string
+ * @port tcp port number
+ *
+ * returns the service record, or NULL upon failure
+ */
+struct libvirtd_mdns_entry *libvirtd_mdns_add_entry(struct libvirtd_mdns_group *group, const char *type, int port);
+
+/**
+ * Removes a service entry from a group
+ *
+ * @group group to deteach service entry from
+ * @entry service entry to remove
+ */
+void libvirtd_mdns_remove_entry(struct libvirtd_mdns_group *group, struct libvirtd_mdns_entry *entry);
+
+#endif /* __VIRTD_MDNS_H__ */
diff -r 9f44845c41e1 qemud/qemud.c
--- a/qemud/qemud.c	Mon Sep 17 13:30:54 2007 -0400
+++ b/qemud/qemud.c	Mon Sep 17 15:12:57 2007 -0400
@@ -56,6 +56,9 @@
 #include "../src/remote_internal.h"
 #include "../src/conf.h"
 #include "event.h"
+#if HAVE_AVAHI
+#include "mdns.h"
+#endif
 
 static int godaemon = 0;        /* -d: Be a daemon */
 static int verbose = 0;         /* -v: Verbose mode */
@@ -68,6 +71,11 @@ static int listen_tcp = 0;
 static int listen_tcp = 0;
 static const char *tls_port = LIBVIRTD_TLS_PORT;
 static const char *tcp_port = LIBVIRTD_TCP_PORT;
+
+#if HAVE_AVAHI
+static int mdns_adv = 1;
+static const char *mdns_name = NULL;
+#endif
 
 static int tls_no_verify_certificate = 0;
 static int tls_no_verify_address = 0;
@@ -448,6 +456,7 @@ static int qemudListenUnix(struct qemud_
     }
 
     sock->readonly = readonly;
+    sock->port = -1;
 
     if ((sock->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
         qemudLog(QEMUD_ERR, "Failed to create socket: %s",
@@ -570,6 +579,9 @@ remoteListenTCP (struct qemud_server *se
         return -1;
 
     for (i = 0; i < nfds; ++i) {
+        struct sockaddr_storage sa;
+        socklen_t salen = sizeof(sa);
+
         sock = calloc (1, sizeof *sock);
 
         if (!sock) {
@@ -585,6 +597,16 @@ remoteListenTCP (struct qemud_server *se
 
         sock->fd = fds[i];
         sock->tls = tls;
+
+        if (getsockname(sock->fd, (struct sockaddr *)(&sa), &salen) < 0)
+            return -1;
+
+        if (sa.ss_family == AF_INET)
+            sock->port = htons(((struct sockaddr_in*)&sa)->sin_port);
+        else if (sa.ss_family == AF_INET6)
+            sock->port = htons(((struct sockaddr_in6*)&sa)->sin6_port);
+        else
+            sock->port = -1;
 
         if (qemudSetCloseExec(sock->fd) < 0 ||
             qemudSetNonBlock(sock->fd) < 0)
@@ -665,6 +687,7 @@ static int qemudInitPaths(struct qemud_s
 
 static struct qemud_server *qemudInitialize(int sigread) {
     struct qemud_server *server;
+    struct qemud_socket *sock;
     char sockname[PATH_MAX];
     char roSockname[PATH_MAX];
 
@@ -709,11 +732,56 @@ static struct qemud_server *qemudInitial
         }
     }
 
+#if HAVE_AVAHI
+    if (getuid() == 0 && mdns_adv) {
+        struct libvirtd_mdns_group *group;
+        int port = 0;
+
+        server->mdns = libvirtd_mdns_new();
+
+        if (!mdns_name) {
+            char groupname[64], localhost[HOST_NAME_MAX+1], *tmp;
+            /* Extract the host part of the potentially FQDN */
+            gethostname(localhost, HOST_NAME_MAX);
+            localhost[HOST_NAME_MAX] = '\0';
+            if ((tmp = strchr(localhost, '.')))
+                *tmp = '\0';
+            snprintf(groupname, sizeof(groupname)-1, "Virtualization Host %s", localhost);
+            groupname[sizeof(groupname)-1] = '\0';
+            qemudDebug("Built group name of '%s'", groupname);
+            group = libvirtd_mdns_add_group(server->mdns, groupname);
+        } else {
+            group = libvirtd_mdns_add_group(server->mdns, mdns_name);
+        }
+
+        /* 
+         * See if there's a TLS enabled port we can advertise. Cowardly
+         * don't bother to advertise TCP since we don't want people using
+         * them for real world apps
+         */
+        sock = server->sockets;
+        while (sock) {
+            if (sock->port != -1 && sock->tls) {
+                port = sock->port;
+                break;
+            }
+            sock = sock->next;
+        }
+    
+        /*
+         * Add the primary entry - we choose SSH because its most likely to always
+         * be available
+         */
+        libvirtd_mdns_add_entry(group, "_libvirt._tcp", port);
+        libvirtd_mdns_start(server->mdns);
+    }
+#endif
+
     return server;
 
  cleanup:
     if (server) {
-        struct qemud_socket *sock = server->sockets;
+        sock = server->sockets;
         while (sock) {
             close(sock->fd);
             sock = sock->next;
@@ -1488,6 +1556,16 @@ remoteReadConfigFile (const char *filena
     p = virConfGetValue (conf, "tcp_port");
     CHECK_TYPE ("tcp_port", VIR_CONF_STRING);
     tcp_port = p ? strdup (p->str) : tcp_port;
+
+#if HAVE_AVAHI
+    p = virConfGetValue (conf, "mdns_adv");
+    CHECK_TYPE ("mdns_adv", VIR_CONF_LONG);
+    mdns_adv = p ? p->l : mdns_adv;
+
+    p = virConfGetValue (conf, "mdns_name");
+    CHECK_TYPE ("mdns_name", VIR_CONF_STRING);
+    mdns_name = p ? strdup (p->str) : NULL;
+#endif
 
     p = virConfGetValue (conf, "tls_no_verify_certificate");
     CHECK_TYPE ("tls_no_verify_certificate", VIR_CONF_LONG);


More information about the libvir-list mailing list