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 -=|
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@xxxxxxxxxx> + */ + +#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@xxxxxxxxxx> + */ + +#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);
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list