diff options
Diffstat (limited to 'trunk/avahi-core')
76 files changed, 19188 insertions, 0 deletions
diff --git a/trunk/avahi-core/Makefile.am b/trunk/avahi-core/Makefile.am new file mode 100644 index 0000000..cb2d8f9 --- /dev/null +++ b/trunk/avahi-core/Makefile.am @@ -0,0 +1,160 @@ +# $Id$ +# +# This file is part of avahi. +# +# avahi 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 of the +# License, or (at your option) any later version. +# +# avahi 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 General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with avahi; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA. + +AM_CFLAGS=-I$(top_srcdir) + +# This cool debug trap works on i386/gcc only +AM_CFLAGS+='-DDEBUG_TRAP=__asm__("int $$3")' + +avahiincludedir=$(includedir)/avahi-core + +avahiinclude_HEADERS = \ + core.h \ + log.h \ + rr.h \ + publish.h \ + lookup.h + +lib_LTLIBRARIES = \ + libavahi-core.la + +if ENABLE_TESTS +noinst_PROGRAMS = \ + prioq-test \ + avahi-test \ + conformance-test \ + avahi-reflector \ + dns-test \ + timeeventq-test \ + hashmap-test \ + querier-test \ + update-test +endif + +libavahi_core_la_SOURCES = \ + timeeventq.c timeeventq.h\ + iface.c iface.h \ + server.c internal.h entry.c \ + prioq.c prioq.h \ + cache.c cache.h \ + socket.c socket.h \ + response-sched.c response-sched.h \ + query-sched.c query-sched.h \ + probe-sched.c probe-sched.h \ + announce.c announce.h \ + browse.c browse.h \ + rrlist.c rrlist.h \ + resolve-host-name.c \ + resolve-address.c \ + browse-domain.c \ + browse-service-type.c \ + browse-service.c \ + resolve-service.c \ + dns.c dns.h \ + rr.c rr.h rr-util.h \ + core.h lookup.h publish.h \ + log.c log.h \ + browse-dns-server.c \ + fdutil.h fdutil.c \ + util.c util.h \ + hashmap.c hashmap.h \ + wide-area.c wide-area.h \ + multicast-lookup.c multicast-lookup.h \ + querier.c querier.h \ + addr-util.h addr-util.c \ + domain-util.h domain-util.c \ + dns-srv-rr.h + +if HAVE_NETLINK +libavahi_core_la_SOURCES += \ + iface-linux.c iface-linux.h \ + netlink.c netlink.h +else +if HAVE_PF_ROUTE +libavahi_core_la_SOURCES += \ + iface-pfroute.c iface-pfroute.h +endif +endif + +libavahi_core_la_CFLAGS = $(AM_CFLAGS) +libavahi_core_la_LIBADD = $(AM_LDADD) ../avahi-common/libavahi-common.la +libavahi_core_la_LDFLAGS = $(AM_LDFLAGS) -export-dynamic -version-info $(LIBAVAHI_CORE_VERSION_INFO) + +prioq_test_SOURCES = \ + prioq-test.c \ + prioq.c prioq.h +prioq_test_CFLAGS = $(AM_CFLAGS) +prioq_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la + +avahi_test_SOURCES = \ + avahi-test.c +avahi_test_CFLAGS = $(AM_CFLAGS) +avahi_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la libavahi-core.la + +update_test_SOURCES = \ + update-test.c +update_test_CFLAGS = $(AM_CFLAGS) +update_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la libavahi-core.la + +querier_test_SOURCES = \ + querier-test.c +querier_test_CFLAGS = $(AM_CFLAGS) +querier_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la libavahi-core.la + +conformance_test_SOURCES = \ + conformance-test.c +conformance_test_CFLAGS = $(AM_CFLAGS) +conformance_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la libavahi-core.la + +avahi_reflector_SOURCES = \ + avahi-reflector.c +avahi_reflector_CFLAGS = $(AM_CFLAGS) +avahi_reflector_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la libavahi-core.la + +dns_test_SOURCES = \ + dns.c dns.h \ + dns-test.c \ + log.c log.h \ + util.c util.h \ + rr.c rr.h \ + hashmap.c hashmap.h \ + domain-util.c domain-util.h +dns_test_CFLAGS = $(AM_CFLAGS) +dns_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la + +timeeventq_test_SOURCES = \ + timeeventq-test.c \ + timeeventq.h timeeventq.c \ + prioq.h prioq.c \ + log.c log.h +timeeventq_test_CFLAGS = $(AM_CFLAGS) +timeeventq_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la + +hashmap_test_SOURCES = \ + hashmap-test.c \ + hashmap.h hashmap.c \ + util.h util.c +hashmap_test_CFLAGS = $(AM_CFLAGS) +hashmap_test_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la + +valgrind: avahi-test + libtool --mode=execute valgrind ./avahi-test + +gdb: avahi-test + libtool --mode=execute gdb ./avahi-test diff --git a/trunk/avahi-core/addr-util.c b/trunk/avahi-core/addr-util.c new file mode 100644 index 0000000..9e2d1e9 --- /dev/null +++ b/trunk/avahi-core/addr-util.c @@ -0,0 +1,79 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <string.h> +#include <assert.h> + +#include "addr-util.h" + +AvahiAddress *avahi_address_from_sockaddr(const struct sockaddr* sa, AvahiAddress *ret_addr) { + assert(sa); + assert(ret_addr); + + assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + + ret_addr->proto = avahi_af_to_proto(sa->sa_family); + + if (sa->sa_family == AF_INET) + memcpy(&ret_addr->data.ipv4, &((const struct sockaddr_in*) sa)->sin_addr, sizeof(ret_addr->data.ipv4)); + else + memcpy(&ret_addr->data.ipv6, &((const struct sockaddr_in6*) sa)->sin6_addr, sizeof(ret_addr->data.ipv6)); + + return ret_addr; +} + +uint16_t avahi_port_from_sockaddr(const struct sockaddr* sa) { + assert(sa); + + assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + + if (sa->sa_family == AF_INET) + return ntohs(((const struct sockaddr_in*) sa)->sin_port); + else + return ntohs(((const struct sockaddr_in6*) sa)->sin6_port); +} + +int avahi_address_is_ipv4_in_ipv6(const AvahiAddress *a) { + + static const uint8_t ipv4_in_ipv6[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF + }; + + assert(a); + + if (a->proto != AVAHI_PROTO_INET6) + return 0; + + return memcmp(a->data.ipv6.address, ipv4_in_ipv6, sizeof(ipv4_in_ipv6)) == 0; +} + + + diff --git a/trunk/avahi-core/addr-util.h b/trunk/avahi-core/addr-util.h new file mode 100644 index 0000000..4134de1 --- /dev/null +++ b/trunk/avahi-core/addr-util.h @@ -0,0 +1,45 @@ +#ifndef fooaddrutilhfoo +#define fooaddrutilhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <inttypes.h> +#include <sys/socket.h> + +#include <avahi-common/cdecl.h> +#include <avahi-common/address.h> + +AVAHI_C_DECL_BEGIN + +/** Make an address structture of a sockaddr structure */ +AvahiAddress *avahi_address_from_sockaddr(const struct sockaddr* sa, AvahiAddress *ret_addr); + +/** Return the port number of a sockaddr structure (either IPv4 or IPv6) */ +uint16_t avahi_port_from_sockaddr(const struct sockaddr* sa); + +/** Check whether the specified IPv6 address is in fact an + * encapsulated IPv4 address, returns 1 if yes, 0 otherwise */ +int avahi_address_is_ipv4_in_ipv6(const AvahiAddress *a); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/announce.c b/trunk/avahi-core/announce.c new file mode 100644 index 0000000..4a87e1d --- /dev/null +++ b/trunk/avahi-core/announce.c @@ -0,0 +1,526 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "announce.h" +#include "log.h" +#include "rr-util.h" + +#define AVAHI_ANNOUNCEMENT_JITTER_MSEC 250 +#define AVAHI_PROBE_JITTER_MSEC 250 +#define AVAHI_PROBE_INTERVAL_MSEC 250 + +static void remove_announcer(AvahiServer *s, AvahiAnnouncer *a) { + assert(s); + assert(a); + + if (a->time_event) + avahi_time_event_free(a->time_event); + + AVAHI_LLIST_REMOVE(AvahiAnnouncer, by_interface, a->interface->announcers, a); + AVAHI_LLIST_REMOVE(AvahiAnnouncer, by_entry, a->entry->announcers, a); + + avahi_free(a); +} + +static void elapse_announce(AvahiTimeEvent *e, void *userdata); + +static void set_timeout(AvahiAnnouncer *a, const struct timeval *tv) { + assert(a); + + if (!tv) { + if (a->time_event) { + avahi_time_event_free(a->time_event); + a->time_event = NULL; + } + } else { + + if (a->time_event) + avahi_time_event_update(a->time_event, tv); + else + a->time_event = avahi_time_event_new(a->server->time_event_queue, tv, elapse_announce, a); + } +} + +static void next_state(AvahiAnnouncer *a); + +void avahi_s_entry_group_check_probed(AvahiSEntryGroup *g, int immediately) { + AvahiEntry *e; + assert(g); + assert(!g->dead); + + /* Check whether all group members have been probed */ + + if (g->state != AVAHI_ENTRY_GROUP_REGISTERING || g->n_probing > 0) + return; + + avahi_s_entry_group_change_state(g, AVAHI_ENTRY_GROUP_ESTABLISHED); + + if (g->dead) + return; + + for (e = g->entries; e; e = e->by_group_next) { + AvahiAnnouncer *a; + + for (a = e->announcers; a; a = a->by_entry_next) { + + if (a->state != AVAHI_WAITING) + continue; + + a->state = AVAHI_ANNOUNCING; + + if (immediately) { + /* Shortcut */ + + a->n_iteration = 1; + next_state(a); + } else { + struct timeval tv; + a->n_iteration = 0; + avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC); + set_timeout(a, &tv); + } + } + } +} + +static void next_state(AvahiAnnouncer *a) { + assert(a); + + if (a->state == AVAHI_WAITING) { + + assert(a->entry->group); + + avahi_s_entry_group_check_probed(a->entry->group, 1); + + } else if (a->state == AVAHI_PROBING) { + + if (a->n_iteration >= 4) { + /* Probing done */ + + if (a->entry->group) { + assert(a->entry->group->n_probing); + a->entry->group->n_probing--; + } + + if (a->entry->group && a->entry->group->state == AVAHI_ENTRY_GROUP_REGISTERING) + a->state = AVAHI_WAITING; + else { + a->state = AVAHI_ANNOUNCING; + a->n_iteration = 1; + } + + set_timeout(a, NULL); + next_state(a); + } else { + struct timeval tv; + + avahi_interface_post_probe(a->interface, a->entry->record, 0); + + avahi_elapse_time(&tv, AVAHI_PROBE_INTERVAL_MSEC, 0); + set_timeout(a, &tv); + + a->n_iteration++; + } + + } else if (a->state == AVAHI_ANNOUNCING) { + + if (a->entry->flags & AVAHI_PUBLISH_UNIQUE) + /* Send the whole rrset at once */ + avahi_server_prepare_matching_responses(a->server, a->interface, a->entry->record->key, 0); + else + avahi_server_prepare_response(a->server, a->interface, a->entry, 0, 0); + + avahi_server_generate_response(a->server, a->interface, NULL, NULL, 0, 0, 0); + + if (++a->n_iteration >= 4) { + /* Announcing done */ + + a->state = AVAHI_ESTABLISHED; + + set_timeout(a, NULL); + } else { + struct timeval tv; + avahi_elapse_time(&tv, a->sec_delay*1000, AVAHI_ANNOUNCEMENT_JITTER_MSEC); + + if (a->n_iteration < 10) + a->sec_delay *= 2; + + set_timeout(a, &tv); + } + } +} + +static void elapse_announce(AvahiTimeEvent *e, void *userdata) { + assert(e); + + next_state(userdata); +} + +static AvahiAnnouncer *get_announcer(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) { + AvahiAnnouncer *a; + + assert(s); + assert(e); + assert(i); + + for (a = e->announcers; a; a = a->by_entry_next) + if (a->interface == i) + return a; + + return NULL; +} + +static void go_to_initial_state(AvahiAnnouncer *a) { + AvahiEntry *e; + struct timeval tv; + + assert(a); + e = a->entry; + + if ((e->flags & AVAHI_PUBLISH_UNIQUE) && !(e->flags & AVAHI_PUBLISH_NO_PROBE)) + a->state = AVAHI_PROBING; + else if (!(e->flags & AVAHI_PUBLISH_NO_ANNOUNCE)) { + + if (!e->group || e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED) + a->state = AVAHI_ANNOUNCING; + else + a->state = AVAHI_WAITING; + + } else + a->state = AVAHI_ESTABLISHED; + + a->n_iteration = 1; + a->sec_delay = 1; + + if (a->state == AVAHI_PROBING && e->group) + e->group->n_probing++; + + if (a->state == AVAHI_PROBING) + set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_PROBE_JITTER_MSEC)); + else if (a->state == AVAHI_ANNOUNCING) + set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC)); + else + set_timeout(a, NULL); +} + +static void new_announcer(AvahiServer *s, AvahiInterface *i, AvahiEntry *e) { + AvahiAnnouncer *a; + + assert(s); + assert(i); + assert(e); + assert(!e->dead); + + if (!avahi_interface_match(i, e->interface, e->protocol) || !i->announcing || !avahi_entry_is_commited(e)) + return; + + /* We don't want duplicate announcers */ + if (get_announcer(s, e, i)) + return; + + if ((!(a = avahi_new(AvahiAnnouncer, 1)))) { + avahi_log_error(__FILE__": Out of memory."); + return; + } + + a->server = s; + a->interface = i; + a->entry = e; + a->time_event = NULL; + + AVAHI_LLIST_PREPEND(AvahiAnnouncer, by_interface, i->announcers, a); + AVAHI_LLIST_PREPEND(AvahiAnnouncer, by_entry, e->announcers, a); + + go_to_initial_state(a); +} + +void avahi_announce_interface(AvahiServer *s, AvahiInterface *i) { + AvahiEntry *e; + + assert(s); + assert(i); + + if (!i->announcing) + return; + + for (e = s->entries; e; e = e->entries_next) + if (!e->dead) + new_announcer(s, i, e); +} + +static void announce_walk_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + AvahiEntry *e = userdata; + + assert(m); + assert(i); + assert(e); + assert(!e->dead); + + new_announcer(m->server, i, e); +} + +void avahi_announce_entry(AvahiServer *s, AvahiEntry *e) { + assert(s); + assert(e); + assert(!e->dead); + + avahi_interface_monitor_walk(s->monitor, e->interface, e->protocol, announce_walk_callback, e); +} + +void avahi_announce_group(AvahiServer *s, AvahiSEntryGroup *g) { + AvahiEntry *e; + + assert(s); + assert(g); + + for (e = g->entries; e; e = e->by_group_next) + if (!e->dead) + avahi_announce_entry(s, e); +} + +int avahi_entry_is_registered(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) { + AvahiAnnouncer *a; + + assert(s); + assert(e); + assert(i); + assert(!e->dead); + + if (!(a = get_announcer(s, e, i))) + return 0; + + return + a->state == AVAHI_ANNOUNCING || + a->state == AVAHI_ESTABLISHED || + (a->state == AVAHI_WAITING && !(e->flags & AVAHI_PUBLISH_UNIQUE)); +} + +int avahi_entry_is_probing(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) { + AvahiAnnouncer *a; + + assert(s); + assert(e); + assert(i); + assert(!e->dead); + + if (!(a = get_announcer(s, e, i))) + return 0; + + return + a->state == AVAHI_PROBING || + (a->state == AVAHI_WAITING && (e->flags & AVAHI_PUBLISH_UNIQUE)); +} + +void avahi_entry_return_to_initial_state(AvahiServer *s, AvahiEntry *e, AvahiInterface *i) { + AvahiAnnouncer *a; + + assert(s); + assert(e); + assert(i); + + if (!(a = get_announcer(s, e, i))) + return; + + if (a->state == AVAHI_PROBING && a->entry->group) + a->entry->group->n_probing--; + + go_to_initial_state(a); +} + +static AvahiRecord *make_goodbye_record(AvahiRecord *r) { + AvahiRecord *g; + + assert(r); + + if (!(g = avahi_record_copy(r))) + return NULL; /* OOM */ + + assert(g->ref == 1); + g->ttl = 0; + + return g; +} + +static int is_duplicate_entry(AvahiServer *s, AvahiEntry *e) { + AvahiEntry *i; + + assert(s); + assert(e); + + for (i = avahi_hashmap_lookup(s->entries_by_key, e->record->key); i; i = i->by_key_next) { + + if (i == e) + continue; + + if (!avahi_record_equal_no_ttl(i->record, e->record)) + continue; + + return 1; + } + + return 0; +} + +static void send_goodbye_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + AvahiEntry *e = userdata; + AvahiRecord *g; + + assert(m); + assert(i); + assert(e); + assert(!e->dead); + + if (!avahi_interface_match(i, e->interface, e->protocol)) + return; + + if (e->flags & AVAHI_PUBLISH_NO_ANNOUNCE) + return; + + if (!avahi_entry_is_registered(m->server, e, i)) + return; + + if (is_duplicate_entry(m->server, e)) + return; + + if (!(g = make_goodbye_record(e->record))) + return; /* OOM */ + + avahi_interface_post_response(i, g, e->flags & AVAHI_PUBLISH_UNIQUE, NULL, 1); + avahi_record_unref(g); +} + +static void reannounce(AvahiAnnouncer *a) { + AvahiEntry *e; + struct timeval tv; + + assert(a); + e = a->entry; + + /* If the group this entry belongs to is not even commited, there's nothing to reannounce */ + if (e->group && (e->group->state == AVAHI_ENTRY_GROUP_UNCOMMITED || e->group->state == AVAHI_ENTRY_GROUP_COLLISION)) + return; + + /* Because we might change state we decrease the probing counter first */ + if (a->state == AVAHI_PROBING && a->entry->group) + a->entry->group->n_probing--; + + if (a->state == AVAHI_PROBING || + (a->state == AVAHI_WAITING && (e->flags & AVAHI_PUBLISH_UNIQUE) && !(e->flags & AVAHI_PUBLISH_NO_PROBE))) + + /* We were probing or waiting after probe, so we restart probing from the beginning here */ + + a->state = AVAHI_PROBING; + else if (a->state == AVAHI_WAITING) + + /* We were waiting, but were not probing before, so we continue waiting */ + a->state = AVAHI_WAITING; + + else if (e->flags & AVAHI_PUBLISH_NO_ANNOUNCE) + + /* No announcer needed */ + a->state = AVAHI_ESTABLISHED; + + else { + + /* Ok, let's restart announcing */ + a->state = AVAHI_ANNOUNCING; + } + + /* Now let's increase the probing counter again */ + if (a->state == AVAHI_PROBING && e->group) + e->group->n_probing++; + + a->n_iteration = 1; + a->sec_delay = 1; + + if (a->state == AVAHI_PROBING) + set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_PROBE_JITTER_MSEC)); + else if (a->state == AVAHI_ANNOUNCING) + set_timeout(a, avahi_elapse_time(&tv, 0, AVAHI_ANNOUNCEMENT_JITTER_MSEC)); + else + set_timeout(a, NULL); +} + + +static void reannounce_walk_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + AvahiEntry *e = userdata; + AvahiAnnouncer *a; + + assert(m); + assert(i); + assert(e); + assert(!e->dead); + + if (!(a = get_announcer(m->server, e, i))) + return; + + reannounce(a); +} + +void avahi_reannounce_entry(AvahiServer *s, AvahiEntry *e) { + + assert(s); + assert(e); + assert(!e->dead); + + avahi_interface_monitor_walk(s->monitor, e->interface, e->protocol, reannounce_walk_callback, e); +} + +void avahi_goodbye_interface(AvahiServer *s, AvahiInterface *i, int send_goodbye, int remove) { + assert(s); + assert(i); + + if (send_goodbye) + if (avahi_interface_is_relevant(i)) { + AvahiEntry *e; + + for (e = s->entries; e; e = e->entries_next) + if (!e->dead) + send_goodbye_callback(s->monitor, i, e); + } + + if (remove) + while (i->announcers) + remove_announcer(s, i->announcers); +} + +void avahi_goodbye_entry(AvahiServer *s, AvahiEntry *e, int send_goodbye, int remove) { + assert(s); + assert(e); + + if (send_goodbye) + if (!e->dead) + avahi_interface_monitor_walk(s->monitor, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, send_goodbye_callback, e); + + if (remove) + while (e->announcers) + remove_announcer(s, e->announcers); +} + diff --git a/trunk/avahi-core/announce.h b/trunk/avahi-core/announce.h new file mode 100644 index 0000000..3a8bcf3 --- /dev/null +++ b/trunk/avahi-core/announce.h @@ -0,0 +1,71 @@ +#ifndef fooannouncehfoo +#define fooannouncehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiAnnouncer AvahiAnnouncer; + +#include <avahi-common/llist.h> +#include "iface.h" +#include "internal.h" +#include "timeeventq.h" +#include "publish.h" + +typedef enum { + AVAHI_PROBING, /* probing phase */ + AVAHI_WAITING, /* wait for other records in group */ + AVAHI_ANNOUNCING, /* announcing phase */ + AVAHI_ESTABLISHED /* we'e established */ +} AvahiAnnouncerState; + +struct AvahiAnnouncer { + AvahiServer *server; + AvahiInterface *interface; + AvahiEntry *entry; + + AvahiTimeEvent *time_event; + + AvahiAnnouncerState state; + unsigned n_iteration; + unsigned sec_delay; + + AVAHI_LLIST_FIELDS(AvahiAnnouncer, by_interface); + AVAHI_LLIST_FIELDS(AvahiAnnouncer, by_entry); +}; + +void avahi_announce_interface(AvahiServer *s, AvahiInterface *i); +void avahi_announce_entry(AvahiServer *s, AvahiEntry *e); +void avahi_announce_group(AvahiServer *s, AvahiSEntryGroup *g); + +void avahi_entry_return_to_initial_state(AvahiServer *s, AvahiEntry *e, AvahiInterface *i); + +void avahi_s_entry_group_check_probed(AvahiSEntryGroup *g, int immediately); + +int avahi_entry_is_registered(AvahiServer *s, AvahiEntry *e, AvahiInterface *i); +int avahi_entry_is_probing(AvahiServer *s, AvahiEntry *e, AvahiInterface *i); + +void avahi_goodbye_interface(AvahiServer *s, AvahiInterface *i, int send_goodbye, int rem); +void avahi_goodbye_entry(AvahiServer *s, AvahiEntry *e, int send_goodbye, int rem); + +void avahi_reannounce_entry(AvahiServer *s, AvahiEntry *e); + +#endif diff --git a/trunk/avahi-core/avahi-reflector.c b/trunk/avahi-core/avahi-reflector.c new file mode 100644 index 0000000..df5539f --- /dev/null +++ b/trunk/avahi-core/avahi-reflector.c @@ -0,0 +1,63 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <avahi-common/simple-watch.h> +#include <avahi-core/core.h> + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char*argv[]) { + AvahiServer *server; + AvahiServerConfig config; + int error; + AvahiSimplePoll *simple_poll; + + simple_poll = avahi_simple_poll_new(); + + avahi_server_config_init(&config); + config.publish_hinfo = 0; + config.publish_addresses = 0; + config.publish_workstation = 0; + config.publish_domain = 0; + config.use_ipv6 = 0; + config.enable_reflector = 1; + + server = avahi_server_new(avahi_simple_poll_get(simple_poll), &config, NULL, NULL, &error); + avahi_server_config_free(&config); + + for (;;) + if (avahi_simple_poll_iterate(simple_poll, -1) != 0) + break; + + avahi_server_free(server); + avahi_simple_poll_free(simple_poll); + + return 0; +} diff --git a/trunk/avahi-core/avahi-test.c b/trunk/avahi-core/avahi-test.c new file mode 100644 index 0000000..cb7b97e --- /dev/null +++ b/trunk/avahi-core/avahi-test.c @@ -0,0 +1,403 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <avahi-common/malloc.h> +#include <avahi-common/simple-watch.h> +#include <avahi-common/alternative.h> +#include <avahi-common/timeval.h> + +#include <avahi-core/core.h> +#include <avahi-core/log.h> +#include <avahi-core/publish.h> +#include <avahi-core/lookup.h> +#include <avahi-core/dns-srv-rr.h> + +static AvahiSEntryGroup *group = NULL; +static AvahiServer *server = NULL; +static char *service_name = NULL; + +static const AvahiPoll *poll_api; + +static void quit_timeout_callback(AVAHI_GCC_UNUSED AvahiTimeout *timeout, void* userdata) { + AvahiSimplePoll *simple_poll = userdata; + + avahi_simple_poll_quit(simple_poll); +} + +static void dump_line(const char *text, AVAHI_GCC_UNUSED void* userdata) { + printf("%s\n", text); +} + +static void dump_timeout_callback(AvahiTimeout *timeout, void* userdata) { + struct timeval tv; + + AvahiServer *avahi = userdata; + avahi_server_dump(avahi, dump_line, NULL); + + avahi_elapse_time(&tv, 5000, 0); + poll_api->timeout_update(timeout, &tv); +} + +static const char *browser_event_to_string(AvahiBrowserEvent event) { + switch (event) { + case AVAHI_BROWSER_NEW : return "NEW"; + case AVAHI_BROWSER_REMOVE : return "REMOVE"; + case AVAHI_BROWSER_CACHE_EXHAUSTED : return "CACHE_EXHAUSTED"; + case AVAHI_BROWSER_ALL_FOR_NOW : return "ALL_FOR_NOW"; + case AVAHI_BROWSER_FAILURE : return "FAILURE"; + } + + abort(); +} + +static const char *resolver_event_to_string(AvahiResolverEvent event) { + switch (event) { + case AVAHI_RESOLVER_FOUND: return "FOUND"; + case AVAHI_RESOLVER_FAILURE: return "FAILURE"; + } + abort(); +} + +static void record_browser_callback( + AvahiSRecordBrowser *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + char *t; + + assert(r); + + if (record) { + avahi_log_debug("RB: record [%s] on %i.%i is %s", t = avahi_record_to_string(record), interface, protocol, browser_event_to_string(event)); + avahi_free(t); + } else + avahi_log_debug("RB: [%s]", browser_event_to_string(event)); + +} + +static void remove_entries(void); +static void create_entries(int new_name); + +static void entry_group_callback(AVAHI_GCC_UNUSED AvahiServer *s, AVAHI_GCC_UNUSED AvahiSEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata) { + avahi_log_debug("entry group state: %i", state); + + if (state == AVAHI_ENTRY_GROUP_COLLISION) { + remove_entries(); + create_entries(1); + avahi_log_debug("Service name conflict, retrying with <%s>", service_name); + } else if (state == AVAHI_ENTRY_GROUP_ESTABLISHED) { + avahi_log_debug("Service established under name <%s>", service_name); + } +} + +static void server_callback(AvahiServer *s, AvahiServerState state, AVAHI_GCC_UNUSED void* userdata) { + + server = s; + avahi_log_debug("server state: %i", state); + + if (state == AVAHI_SERVER_RUNNING) { + avahi_log_debug("Server startup complete. Host name is <%s>. Service cookie is %u", avahi_server_get_host_name_fqdn(s), avahi_server_get_local_service_cookie(s)); + create_entries(0); + } else if (state == AVAHI_SERVER_COLLISION) { + char *n; + remove_entries(); + + n = avahi_alternative_host_name(avahi_server_get_host_name(s)); + + avahi_log_debug("Host name conflict, retrying with <%s>", n); + avahi_server_set_host_name(s, n); + avahi_free(n); + } +} + +static void remove_entries(void) { + if (group) + avahi_s_entry_group_reset(group); +} + +static void create_entries(int new_name) { + AvahiAddress a; + AvahiRecord *r; + + remove_entries(); + + if (!group) + group = avahi_s_entry_group_new(server, entry_group_callback, NULL); + + assert(avahi_s_entry_group_is_empty(group)); + + if (!service_name) + service_name = avahi_strdup("Test Service"); + else if (new_name) { + char *n = avahi_alternative_service_name(service_name); + avahi_free(service_name); + service_name = n; + } + + if (avahi_server_add_service(server, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, service_name, "_http._tcp", NULL, NULL, 80, "foo", NULL) < 0) { + avahi_log_error("Failed to add HTTP service"); + goto fail; + } + + if (avahi_server_add_service(server, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, service_name, "_ftp._tcp", NULL, NULL, 21, "foo", NULL) < 0) { + avahi_log_error("Failed to add FTP service"); + goto fail; + } + + if (avahi_server_add_service(server, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0,service_name, "_webdav._tcp", NULL, NULL, 80, "foo", NULL) < 0) { + avahi_log_error("Failed to add WEBDAV service"); + goto fail; + } + + if (avahi_server_add_dns_server_address(server, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, NULL, AVAHI_DNS_SERVER_RESOLVE, avahi_address_parse("192.168.50.1", AVAHI_PROTO_UNSPEC, &a), 53) < 0) { + avahi_log_error("Failed to add new DNS Server address"); + goto fail; + } + + r = avahi_record_new_full("cname.local", AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_CNAME, AVAHI_DEFAULT_TTL); + r->data.cname.name = avahi_strdup("cocaine.local"); + + if (avahi_server_add(server, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, r) < 0) { + avahi_record_unref(r); + avahi_log_error("Failed to add CNAME record"); + goto fail; + } + avahi_record_unref(r); + + avahi_s_entry_group_commit(group); + return; + +fail: + if (group) + avahi_s_entry_group_free(group); + + group = NULL; +} + +static void hnr_callback( + AVAHI_GCC_UNUSED AvahiSHostNameResolver *r, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *hostname, + const AvahiAddress *a, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + char t[AVAHI_ADDRESS_STR_MAX]; + + if (a) + avahi_address_snprint(t, sizeof(t), a); + + avahi_log_debug("HNR: (%i.%i) <%s> -> %s [%s]", iface, protocol, hostname, a ? t : "n/a", resolver_event_to_string(event)); +} + +static void ar_callback( + AVAHI_GCC_UNUSED AvahiSAddressResolver *r, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const AvahiAddress *a, + const char *hostname, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + char t[AVAHI_ADDRESS_STR_MAX]; + + avahi_address_snprint(t, sizeof(t), a); + + avahi_log_debug("AR: (%i.%i) %s -> <%s> [%s]", iface, protocol, t, hostname ? hostname : "n/a", resolver_event_to_string(event)); +} + +static void db_callback( + AVAHI_GCC_UNUSED AvahiSDomainBrowser *b, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + + avahi_log_debug("DB: (%i.%i) <%s> [%s]", iface, protocol, domain ? domain : "NULL", browser_event_to_string(event)); +} + +static void stb_callback( + AVAHI_GCC_UNUSED AvahiSServiceTypeBrowser *b, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *service_type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + + avahi_log_debug("STB: (%i.%i) %s in <%s> [%s]", iface, protocol, service_type ? service_type : "NULL", domain ? domain : "NULL", browser_event_to_string(event)); +} + +static void sb_callback( + AVAHI_GCC_UNUSED AvahiSServiceBrowser *b, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *service_type, + const char *domain, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + avahi_log_debug("SB: (%i.%i) <%s> as %s in <%s> [%s]", iface, protocol, name ? name : "NULL", service_type ? service_type : "NULL", domain ? domain : "NULL", browser_event_to_string(event)); +} + +static void sr_callback( + AVAHI_GCC_UNUSED AvahiSServiceResolver *r, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char*service_type, + const char*domain_name, + const char*hostname, + const AvahiAddress *a, + uint16_t port, + AvahiStringList *txt, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + + if (event != AVAHI_RESOLVER_FOUND) + avahi_log_debug("SR: (%i.%i) <%s> as %s in <%s> [%s]", iface, protocol, name, service_type, domain_name, resolver_event_to_string(event)); + else { + char t[AVAHI_ADDRESS_STR_MAX], *s; + + avahi_address_snprint(t, sizeof(t), a); + + s = avahi_string_list_to_string(txt); + avahi_log_debug("SR: (%i.%i) <%s> as %s in <%s>: %s/%s:%i (%s) [%s]", iface, protocol, name, service_type, domain_name, hostname, t, port, s, resolver_event_to_string(event)); + avahi_free(s); + } +} + +static void dsb_callback( + AVAHI_GCC_UNUSED AvahiSDNSServerBrowser *b, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char*hostname, + const AvahiAddress *a, + uint16_t port, + AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + + char t[AVAHI_ADDRESS_STR_MAX] = "n/a"; + + if (a) + avahi_address_snprint(t, sizeof(t), a); + + avahi_log_debug("DSB: (%i.%i): %s/%s:%i [%s]", iface, protocol, hostname ? hostname : "NULL", t, port, browser_event_to_string(event)); +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + AvahiSRecordBrowser *r; + AvahiSHostNameResolver *hnr; + AvahiSAddressResolver *ar; + AvahiKey *k; + AvahiServerConfig config; + AvahiAddress a; + AvahiSDomainBrowser *db; + AvahiSServiceTypeBrowser *stb; + AvahiSServiceBrowser *sb; + AvahiSServiceResolver *sr; + AvahiSDNSServerBrowser *dsb; + AvahiSimplePoll *simple_poll; + int error; + struct timeval tv; + + simple_poll = avahi_simple_poll_new(); + poll_api = avahi_simple_poll_get(simple_poll); + + avahi_server_config_init(&config); + + avahi_address_parse("192.168.50.1", AVAHI_PROTO_UNSPEC, &config.wide_area_servers[0]); + config.n_wide_area_servers = 1; + config.enable_wide_area = 1; + + server = avahi_server_new(poll_api, &config, server_callback, NULL, &error); + avahi_server_config_free(&config); + + k = avahi_key_new("_http._tcp.0pointer.de", AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR); + r = avahi_s_record_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, k, 0, record_browser_callback, NULL); + avahi_key_unref(k); + + hnr = avahi_s_host_name_resolver_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "cname.local", AVAHI_PROTO_UNSPEC, 0, hnr_callback, NULL); + + ar = avahi_s_address_resolver_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, avahi_address_parse("192.168.50.1", AVAHI_PROTO_INET, &a), 0, ar_callback, NULL); + + db = avahi_s_domain_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, AVAHI_DOMAIN_BROWSER_BROWSE, 0, db_callback, NULL); + + stb = avahi_s_service_type_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, 0, stb_callback, NULL); + + sb = avahi_s_service_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_http._tcp", NULL, 0, sb_callback, NULL); + + sr = avahi_s_service_resolver_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "Ecstasy HTTP", "_http._tcp", "local", AVAHI_PROTO_UNSPEC, 0, sr_callback, NULL); + + dsb = avahi_s_dns_server_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "local", AVAHI_DNS_SERVER_RESOLVE, AVAHI_PROTO_UNSPEC, 0, dsb_callback, NULL); + + avahi_elapse_time(&tv, 1000*5, 0); + poll_api->timeout_new(poll_api, &tv, dump_timeout_callback, server); + + avahi_elapse_time(&tv, 1000*60, 0); + poll_api->timeout_new(poll_api, &tv, quit_timeout_callback, simple_poll); + + avahi_simple_poll_loop(simple_poll); + + avahi_s_record_browser_free(r); + avahi_s_host_name_resolver_free(hnr); + avahi_s_address_resolver_free(ar); + avahi_s_service_type_browser_free(stb); + avahi_s_service_browser_free(sb); + avahi_s_service_resolver_free(sr); + avahi_s_dns_server_browser_free(dsb); + + if (group) + avahi_s_entry_group_free(group); + + if (server) + avahi_server_free(server); + + if (simple_poll) + avahi_simple_poll_free(simple_poll); + + avahi_free(service_name); + + return 0; +} diff --git a/trunk/avahi-core/browse-dns-server.c b/trunk/avahi-core/browse-dns-server.c new file mode 100644 index 0000000..48d23ad --- /dev/null +++ b/trunk/avahi-core/browse-dns-server.c @@ -0,0 +1,324 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" +#include "rr.h" + +typedef struct AvahiDNSServerInfo AvahiDNSServerInfo; + +struct AvahiDNSServerInfo { + AvahiSDNSServerBrowser *browser; + + AvahiIfIndex interface; + AvahiProtocol protocol; + AvahiRecord *srv_record; + AvahiSHostNameResolver *host_name_resolver; + AvahiAddress address; + AvahiLookupResultFlags flags; + + AVAHI_LLIST_FIELDS(AvahiDNSServerInfo, info); +}; + +struct AvahiSDNSServerBrowser { + AvahiServer *server; + + AvahiSRecordBrowser *record_browser; + AvahiSDNSServerBrowserCallback callback; + void* userdata; + AvahiProtocol aprotocol; + AvahiLookupFlags user_flags; + + unsigned n_info; + + AVAHI_LLIST_FIELDS(AvahiSDNSServerBrowser, browser); + AVAHI_LLIST_HEAD(AvahiDNSServerInfo, info); +}; + +static AvahiDNSServerInfo* get_server_info(AvahiSDNSServerBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiRecord *r) { + AvahiDNSServerInfo *i; + + assert(b); + assert(r); + + for (i = b->info; i; i = i->info_next) + if (i->interface == interface && + i->protocol == protocol && + avahi_record_equal_no_ttl(r, i->srv_record)) + return i; + + return NULL; +} + +static void server_info_free(AvahiSDNSServerBrowser *b, AvahiDNSServerInfo *i) { + assert(b); + assert(i); + + avahi_record_unref(i->srv_record); + if (i->host_name_resolver) + avahi_s_host_name_resolver_free(i->host_name_resolver); + + AVAHI_LLIST_REMOVE(AvahiDNSServerInfo, info, b->info, i); + + assert(b->n_info >= 1); + b->n_info--; + + avahi_free(i); +} + +static void host_name_resolver_callback( + AvahiSHostNameResolver *r, + AVAHI_GCC_UNUSED AvahiIfIndex interface, + AVAHI_GCC_UNUSED AvahiProtocol protocol, + AvahiResolverEvent event, + const char *host_name, + const AvahiAddress *a, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiDNSServerInfo *i = userdata; + + assert(r); + assert(host_name); + assert(i); + + switch (event) { + case AVAHI_RESOLVER_FOUND: { + i->address = *a; + + i->browser->callback( + i->browser, + i->interface, + i->protocol, + AVAHI_BROWSER_NEW, + i->srv_record->data.srv.name, + &i->address, + i->srv_record->data.srv.port, + i->flags | flags, + i->browser->userdata); + + break; + } + + case AVAHI_RESOLVER_FAILURE: + /* Ignore */ + break; + } + + avahi_s_host_name_resolver_free(i->host_name_resolver); + i->host_name_resolver = NULL; +} + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSDNSServerBrowser *b = userdata; + + assert(rr); + assert(b); + + /* Filter flags */ + flags &= AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST | AVAHI_LOOKUP_RESULT_WIDE_AREA; + + switch (event) { + case AVAHI_BROWSER_NEW: { + AvahiDNSServerInfo *i; + + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_SRV); + + if (get_server_info(b, interface, protocol, record)) + return; + + if (b->n_info >= 10) + return; + + if (!(i = avahi_new(AvahiDNSServerInfo, 1))) + return; /* OOM */ + + i->browser = b; + i->interface = interface; + i->protocol = protocol; + i->srv_record = avahi_record_ref(record); + i->host_name_resolver = avahi_s_host_name_resolver_new( + b->server, + interface, protocol, + record->data.srv.name, + b->aprotocol, + b->user_flags, + host_name_resolver_callback, i); + i->flags = flags; + + AVAHI_LLIST_PREPEND(AvahiDNSServerInfo, info, b->info, i); + + b->n_info++; + break; + } + + case AVAHI_BROWSER_REMOVE: { + AvahiDNSServerInfo *i; + + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_SRV); + + if (!(i = get_server_info(b, interface, protocol, record))) + return; + + if (!i->host_name_resolver) + b->callback( + b, + interface, + protocol, + event, + i->srv_record->data.srv.name, + &i->address, + i->srv_record->data.srv.port, + i->flags | flags, + b->userdata); + + server_info_free(b, i); + break; + } + + case AVAHI_BROWSER_FAILURE: + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + + b->callback( + b, + interface, + protocol, + event, + NULL, + NULL, + 0, + flags, + b->userdata); + + break; + } +} + +AvahiSDNSServerBrowser *avahi_s_dns_server_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiDNSServerType type, + AvahiProtocol aprotocol, + AvahiLookupFlags flags, + AvahiSDNSServerBrowserCallback callback, + void* userdata) { + + static const char * const type_table[AVAHI_DNS_SERVER_MAX] = { + "_domain._udp", + "_dns-update._udp" + }; + + AvahiSDNSServerBrowser *b; + AvahiKey *k = NULL; + char n[AVAHI_DOMAIN_NAME_MAX]; + int r; + + assert(server); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, type < AVAHI_DNS_SERVER_MAX, AVAHI_ERR_INVALID_FLAGS); + + if (!domain) + domain = server->domain_name; + + if ((r = avahi_service_name_join(n, sizeof(n), NULL, type_table[type], domain)) < 0) { + avahi_server_set_errno(server, r); + return NULL; + } + + if (!(b = avahi_new(AvahiSDNSServerBrowser, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + b->server = server; + b->callback = callback; + b->userdata = userdata; + b->aprotocol = aprotocol; + b->n_info = 0; + b->user_flags = flags; + + AVAHI_LLIST_HEAD_INIT(AvahiDNSServerInfo, b->info); + AVAHI_LLIST_PREPEND(AvahiSDNSServerBrowser, browser, server->dns_server_browsers, b); + + if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(b->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, b))) + goto fail; + + avahi_key_unref(k); + + return b; + +fail: + + if (k) + avahi_key_unref(k); + + avahi_s_dns_server_browser_free(b); + return NULL; +} + +void avahi_s_dns_server_browser_free(AvahiSDNSServerBrowser *b) { + assert(b); + + while (b->info) + server_info_free(b, b->info); + + AVAHI_LLIST_REMOVE(AvahiSDNSServerBrowser, browser, b->server->dns_server_browsers, b); + + if (b->record_browser) + avahi_s_record_browser_free(b->record_browser); + + avahi_free(b); +} + diff --git a/trunk/avahi-core/browse-domain.c b/trunk/avahi-core/browse-domain.c new file mode 100644 index 0000000..0043806 --- /dev/null +++ b/trunk/avahi-core/browse-domain.c @@ -0,0 +1,237 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" + +struct AvahiSDomainBrowser { + int ref; + + AvahiServer *server; + + AvahiSRecordBrowser *record_browser; + + AvahiDomainBrowserType type; + AvahiSDomainBrowserCallback callback; + void* userdata; + + AvahiTimeEvent *defer_event; + + int all_for_now_scheduled; + + AVAHI_LLIST_FIELDS(AvahiSDomainBrowser, browser); +}; + +static void inc_ref(AvahiSDomainBrowser *b) { + assert(b); + assert(b->ref >= 1); + + b->ref++; +} + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSDomainBrowser *b = userdata; + char *n = NULL; + + assert(rr); + assert(b); + + if (event == AVAHI_BROWSER_ALL_FOR_NOW && + b->defer_event) { + + b->all_for_now_scheduled = 1; + return; + } + + /* Filter flags */ + flags &= AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST | AVAHI_LOOKUP_RESULT_WIDE_AREA; + + if (record) { + assert(record->key->type == AVAHI_DNS_TYPE_PTR); + n = record->data.ptr.name; + + if (b->type == AVAHI_DOMAIN_BROWSER_BROWSE) { + AvahiStringList *l; + + /* Filter out entries defined statically */ + + for (l = b->server->config.browse_domains; l; l = l->next) + if (avahi_domain_equal((char*) l->text, n)) + return; + } + + } + + b->callback(b, interface, protocol, event, n, flags, b->userdata); +} + +static void defer_callback(AvahiTimeEvent *e, void *userdata) { + AvahiSDomainBrowser *b = userdata; + AvahiStringList *l; + + assert(e); + assert(b); + + assert(b->type == AVAHI_DOMAIN_BROWSER_BROWSE); + + avahi_time_event_free(b->defer_event); + b->defer_event = NULL; + + /* Increase ref counter */ + inc_ref(b); + + for (l = b->server->config.browse_domains; l; l = l->next) { + + /* Check whether this object still exists outside our own + * stack frame */ + if (b->ref <= 1) + break; + + b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_BROWSER_NEW, (char*) l->text, AVAHI_LOOKUP_RESULT_STATIC, b->userdata); + } + + if (b->ref > 1) { + /* If the ALL_FOR_NOW event has already been scheduled, execute it now */ + + if (b->all_for_now_scheduled) + b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_BROWSER_ALL_FOR_NOW, NULL, 0, b->userdata); + } + + /* Decrease ref counter */ + avahi_s_domain_browser_free(b); +} + +AvahiSDomainBrowser *avahi_s_domain_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiDomainBrowserType type, + AvahiLookupFlags flags, + AvahiSDomainBrowserCallback callback, + void* userdata) { + + static const char * const type_table[AVAHI_DOMAIN_BROWSER_MAX] = { + "b", + "db", + "r", + "dr", + "lb" + }; + + AvahiSDomainBrowser *b; + AvahiKey *k = NULL; + char n[AVAHI_DOMAIN_NAME_MAX]; + int r; + + assert(server); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, type < AVAHI_DOMAIN_BROWSER_MAX, AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + + if (!domain) + domain = server->domain_name; + + if ((r = avahi_service_name_join(n, sizeof(n), type_table[type], "_dns-sd._udp", domain)) < 0) { + avahi_server_set_errno(server, r); + return NULL; + } + + if (!(b = avahi_new(AvahiSDomainBrowser, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + b->ref = 1; + b->server = server; + b->callback = callback; + b->userdata = userdata; + b->record_browser = NULL; + b->type = type; + b->all_for_now_scheduled = 0; + b->defer_event = NULL; + + AVAHI_LLIST_PREPEND(AvahiSDomainBrowser, browser, server->domain_browsers, b); + + if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(b->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, b))) + goto fail; + + avahi_key_unref(k); + + if (type == AVAHI_DOMAIN_BROWSER_BROWSE && b->server->config.browse_domains) + b->defer_event = avahi_time_event_new(server->time_event_queue, NULL, defer_callback, b); + + return b; + +fail: + + if (k) + avahi_key_unref(k); + + avahi_s_domain_browser_free(b); + + return NULL; +} + +void avahi_s_domain_browser_free(AvahiSDomainBrowser *b) { + assert(b); + + assert(b->ref >= 1); + if (--b->ref > 0) + return; + + AVAHI_LLIST_REMOVE(AvahiSDomainBrowser, browser, b->server->domain_browsers, b); + + if (b->record_browser) + avahi_s_record_browser_free(b->record_browser); + + if (b->defer_event) + avahi_time_event_free(b->defer_event); + + avahi_free(b); +} diff --git a/trunk/avahi-core/browse-service-type.c b/trunk/avahi-core/browse-service-type.c new file mode 100644 index 0000000..252e3cb --- /dev/null +++ b/trunk/avahi-core/browse-service-type.c @@ -0,0 +1,159 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" + +struct AvahiSServiceTypeBrowser { + AvahiServer *server; + char *domain_name; + + AvahiSRecordBrowser *record_browser; + + AvahiSServiceTypeBrowserCallback callback; + void* userdata; + + AVAHI_LLIST_FIELDS(AvahiSServiceTypeBrowser, browser); +}; + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSServiceTypeBrowser *b = userdata; + + assert(rr); + assert(b); + + /* Filter flags */ + flags &= AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST | AVAHI_LOOKUP_RESULT_WIDE_AREA; + + if (record) { + char type[AVAHI_DOMAIN_NAME_MAX], domain[AVAHI_DOMAIN_NAME_MAX]; + + assert(record->key->type == AVAHI_DNS_TYPE_PTR); + + if (avahi_service_name_split(record->data.ptr.name, NULL, 0, type, sizeof(type), domain, sizeof(domain)) < 0) { + avahi_log_warn("Invalid service type '%s'", record->key->name); + return; + } + + b->callback(b, interface, protocol, event, type, domain, flags, b->userdata); + } else + b->callback(b, interface, protocol, event, NULL, b->domain_name, flags, b->userdata); +} + +AvahiSServiceTypeBrowser *avahi_s_service_type_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiLookupFlags flags, + AvahiSServiceTypeBrowserCallback callback, + void* userdata) { + + AvahiSServiceTypeBrowser *b; + AvahiKey *k = NULL; + char n[AVAHI_DOMAIN_NAME_MAX]; + int r; + + assert(server); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + + if (!domain) + domain = server->domain_name; + + if ((r = avahi_service_name_join(n, sizeof(n), NULL, "_services._dns-sd._udp", domain)) < 0) { + avahi_server_set_errno(server, r); + return NULL; + } + + if (!(b = avahi_new(AvahiSServiceTypeBrowser, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + b->server = server; + b->callback = callback; + b->userdata = userdata; + b->record_browser = NULL; + + AVAHI_LLIST_PREPEND(AvahiSServiceTypeBrowser, browser, server->service_type_browsers, b); + + if (!(b->domain_name = avahi_normalize_name_strdup(domain))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(b->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, b))) + goto fail; + + avahi_key_unref(k); + + return b; + +fail: + if (k) + avahi_key_unref(k); + + avahi_s_service_type_browser_free(b); + + return NULL; +} + +void avahi_s_service_type_browser_free(AvahiSServiceTypeBrowser *b) { + assert(b); + + AVAHI_LLIST_REMOVE(AvahiSServiceTypeBrowser, browser, b->server->service_type_browsers, b); + + if (b->record_browser) + avahi_s_record_browser_free(b->record_browser); + + avahi_free(b->domain_name); + avahi_free(b); +} + + diff --git a/trunk/avahi-core/browse-service.c b/trunk/avahi-core/browse-service.c new file mode 100644 index 0000000..43778dd --- /dev/null +++ b/trunk/avahi-core/browse-service.c @@ -0,0 +1,169 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" + +struct AvahiSServiceBrowser { + AvahiServer *server; + char *domain_name; + char *service_type; + + AvahiSRecordBrowser *record_browser; + + AvahiSServiceBrowserCallback callback; + void* userdata; + + AVAHI_LLIST_FIELDS(AvahiSServiceBrowser, browser); +}; + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSServiceBrowser *b = userdata; + + assert(rr); + assert(b); + + /* Filter flags */ + flags &= AVAHI_LOOKUP_RESULT_CACHED | AVAHI_LOOKUP_RESULT_MULTICAST | AVAHI_LOOKUP_RESULT_WIDE_AREA; + + if (record) { + char service[AVAHI_LABEL_MAX], type[AVAHI_DOMAIN_NAME_MAX], domain[AVAHI_DOMAIN_NAME_MAX]; + + assert(record->key->type == AVAHI_DNS_TYPE_PTR); + + if (event == AVAHI_BROWSER_NEW && avahi_server_is_service_local(b->server, interface, protocol, record->data.ptr.name)) + flags |= AVAHI_LOOKUP_RESULT_LOCAL; + + if (avahi_service_name_split(record->data.ptr.name, service, sizeof(service), type, sizeof(type), domain, sizeof(domain)) < 0) { + avahi_log_warn("Failed to split '%s'", record->key->name); + return; + } + + b->callback(b, interface, protocol, event, service, type, domain, flags, b->userdata); + + } else + b->callback(b, interface, protocol, event, NULL, b->service_type, b->domain_name, flags, b->userdata); + +} + +AvahiSServiceBrowser *avahi_s_service_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *service_type, + const char *domain, + AvahiLookupFlags flags, + AvahiSServiceBrowserCallback callback, + void* userdata) { + + AvahiSServiceBrowser *b; + AvahiKey *k = NULL; + char n[AVAHI_DOMAIN_NAME_MAX]; + int r; + + assert(server); + assert(callback); + assert(service_type); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_is_valid_service_type_generic(service_type), AVAHI_ERR_INVALID_SERVICE_TYPE); + + if (!domain) + domain = server->domain_name; + + if ((r = avahi_service_name_join(n, sizeof(n), NULL, service_type, domain)) < 0) { + avahi_server_set_errno(server, r); + return NULL; + } + + if (!(b = avahi_new(AvahiSServiceBrowser, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + b->server = server; + b->domain_name = b->service_type = NULL; + b->callback = callback; + b->userdata = userdata; + b->record_browser = NULL; + + AVAHI_LLIST_PREPEND(AvahiSServiceBrowser, browser, server->service_browsers, b); + + if (!(b->domain_name = avahi_normalize_name_strdup(domain)) || + !(b->service_type = avahi_normalize_name_strdup(service_type))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if (!(b->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, b))) + goto fail; + + avahi_key_unref(k); + + return b; + +fail: + + if (k) + avahi_key_unref(k); + + avahi_s_service_browser_free(b); + return NULL; +} + +void avahi_s_service_browser_free(AvahiSServiceBrowser *b) { + assert(b); + + AVAHI_LLIST_REMOVE(AvahiSServiceBrowser, browser, b->server->service_browsers, b); + + if (b->record_browser) + avahi_s_record_browser_free(b->record_browser); + + avahi_free(b->domain_name); + avahi_free(b->service_type); + avahi_free(b); +} diff --git a/trunk/avahi-core/browse.c b/trunk/avahi-core/browse.c new file mode 100644 index 0000000..16d8954 --- /dev/null +++ b/trunk/avahi-core/browse.c @@ -0,0 +1,615 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> +#include <avahi-common/rlist.h> +#include <avahi-common/address.h> + +#include "browse.h" +#include "log.h" +#include "querier.h" +#include "domain-util.h" +#include "rr-util.h" + +#define AVAHI_LOOKUPS_PER_BROWSER_MAX 15 + +struct AvahiSRBLookup { + AvahiSRecordBrowser *record_browser; + + unsigned ref; + + AvahiIfIndex interface; + AvahiProtocol protocol; + AvahiLookupFlags flags; + + AvahiKey *key; + + AvahiWideAreaLookup *wide_area; + AvahiMulticastLookup *multicast; + + AvahiRList *cname_lookups; + + AVAHI_LLIST_FIELDS(AvahiSRBLookup, lookups); +}; + +static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r); +static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r); + +static void transport_flags_from_domain(AvahiServer *s, AvahiLookupFlags *flags, const char *domain) { + assert(flags); + assert(domain); + + assert(!((*flags & AVAHI_LOOKUP_USE_MULTICAST) && (*flags & AVAHI_LOOKUP_USE_WIDE_AREA))); + + if (*flags & (AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_USE_WIDE_AREA)) + return; + + if (!s->wide_area_lookup_engine || + !avahi_wide_area_has_servers(s->wide_area_lookup_engine) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_LOCAL) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV4) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV6)) + *flags |= AVAHI_LOOKUP_USE_MULTICAST; + else + *flags |= AVAHI_LOOKUP_USE_WIDE_AREA; +} + +static AvahiSRBLookup* lookup_new( + AvahiSRecordBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiLookupFlags flags, + AvahiKey *key) { + + AvahiSRBLookup *l; + + assert(b); + assert(AVAHI_IF_VALID(interface)); + assert(AVAHI_PROTO_VALID(protocol)); + + if (b->n_lookups >= AVAHI_LOOKUPS_PER_BROWSER_MAX) + /* We don't like cyclic CNAMEs */ + return NULL; + + if (!(l = avahi_new(AvahiSRBLookup, 1))) + return NULL; + + l->ref = 1; + l->record_browser = b; + l->interface = interface; + l->protocol = protocol; + l->key = avahi_key_ref(key); + l->wide_area = NULL; + l->multicast = NULL; + l->cname_lookups = NULL; + l->flags = flags; + + transport_flags_from_domain(b->server, &l->flags, key->name); + + AVAHI_LLIST_PREPEND(AvahiSRBLookup, lookups, b->lookups, l); + + b->n_lookups ++; + + return l; +} + +static void lookup_unref(AvahiSRBLookup *l) { + assert(l); + assert(l->ref >= 1); + + if (--l->ref >= 1) + return; + + AVAHI_LLIST_REMOVE(AvahiSRBLookup, lookups, l->record_browser->lookups, l); + l->record_browser->n_lookups --; + + if (l->wide_area) { + avahi_wide_area_lookup_free(l->wide_area); + l->wide_area = NULL; + } + + if (l->multicast) { + avahi_multicast_lookup_free(l->multicast); + l->multicast = NULL; + } + + while (l->cname_lookups) { + lookup_unref(l->cname_lookups->data); + l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, l->cname_lookups); + } + + avahi_key_unref(l->key); + avahi_free(l); +} + +static AvahiSRBLookup* lookup_ref(AvahiSRBLookup *l) { + assert(l); + assert(l->ref >= 1); + + l->ref++; + return l; +} + +static AvahiSRBLookup *lookup_find( + AvahiSRecordBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiLookupFlags flags, + AvahiKey *key) { + + AvahiSRBLookup *l; + + assert(b); + + for (l = b->lookups; l; l = l->lookups_next) { + + if ((l->interface == AVAHI_IF_UNSPEC || l->interface == interface) && + (l->interface == AVAHI_PROTO_UNSPEC || l->protocol == protocol) && + l->flags == flags && + avahi_key_equal(l->key, key)) + + return l; + } + + return NULL; +} + +static void browser_cancel(AvahiSRecordBrowser *b) { + assert(b); + + if (b->root_lookup) { + lookup_unref(b->root_lookup); + b->root_lookup = NULL; + } + + if (b->defer_time_event) { + avahi_time_event_free(b->defer_time_event); + b->defer_time_event = NULL; + } +} + +static void lookup_wide_area_callback( + AvahiWideAreaLookupEngine *e, + AvahiBrowserEvent event, + AvahiLookupResultFlags flags, + AvahiRecord *r, + void *userdata) { + + AvahiSRBLookup *l = userdata; + AvahiSRecordBrowser *b; + + assert(e); + assert(l); + assert(l->ref >= 1); + + b = l->record_browser; + + if (b->dead) + return; + + lookup_ref(l); + + switch (event) { + case AVAHI_BROWSER_NEW: + assert(r); + + if (r->key->clazz == AVAHI_DNS_CLASS_IN && + r->key->type == AVAHI_DNS_TYPE_CNAME) + /* It's a CNAME record, so let's follow it. We only follow it on wide area DNS! */ + lookup_handle_cname(l, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_USE_WIDE_AREA, r); + else { + /* It's a normal record, so let's call the user callback */ + assert(avahi_key_equal(r->key, l->key)); + + b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, r, flags, b->userdata); + } + break; + + case AVAHI_BROWSER_REMOVE: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + /* Not defined for wide area DNS */ + abort(); + + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_FAILURE: + + b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata); + break; + } + + lookup_unref(l); + +} + +static void lookup_multicast_callback( + AvahiMulticastLookupEngine *e, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiLookupResultFlags flags, + AvahiRecord *r, + void *userdata) { + + AvahiSRBLookup *l = userdata; + AvahiSRecordBrowser *b; + + assert(e); + assert(l); + + b = l->record_browser; + + if (b->dead) + return; + + lookup_ref(l); + + switch (event) { + case AVAHI_BROWSER_NEW: + assert(r); + + if (r->key->clazz == AVAHI_DNS_CLASS_IN && + r->key->type == AVAHI_DNS_TYPE_CNAME) + /* It's a CNAME record, so let's follow it. We allow browsing on both multicast and wide area. */ + lookup_handle_cname(l, interface, protocol, b->flags, r); + else { + /* It's a normal record, so let's call the user callback */ + + if (avahi_server_is_record_local(b->server, interface, protocol, r)) + flags |= AVAHI_LOOKUP_RESULT_LOCAL; + + b->callback(b, interface, protocol, event, r, flags, b->userdata); + } + break; + + case AVAHI_BROWSER_REMOVE: + assert(r); + + if (r->key->clazz == AVAHI_DNS_CLASS_IN && + r->key->type == AVAHI_DNS_TYPE_CNAME) + /* It's a CNAME record, so let's drop that query! */ + lookup_drop_cname(l, interface, protocol, 0, r); + else { + /* It's a normal record, so let's call the user callback */ + assert(avahi_key_equal(b->key, l->key)); + + b->callback(b, interface, protocol, event, r, flags, b->userdata); + } + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + + b->callback(b, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, event, NULL, flags, b->userdata); + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + case AVAHI_BROWSER_FAILURE: + /* Not defined for multicast DNS */ + abort(); + + } + + lookup_unref(l); +} + +static int lookup_start(AvahiSRBLookup *l) { + assert(l); + + assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST)); + assert(!l->wide_area && !l->multicast); + + if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) { + + if (!(l->wide_area = avahi_wide_area_lookup_new(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l))) + return -1; + + } else { + assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST); + + if (!(l->multicast = avahi_multicast_lookup_new(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l))) + return -1; + } + + return 0; +} + +static int lookup_scan_cache(AvahiSRBLookup *l) { + int n = 0; + + assert(l); + + assert(!(l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) != !(l->flags & AVAHI_LOOKUP_USE_MULTICAST)); + + + if (l->flags & AVAHI_LOOKUP_USE_WIDE_AREA) { + n = (int) avahi_wide_area_scan_cache(l->record_browser->server->wide_area_lookup_engine, l->key, lookup_wide_area_callback, l); + + } else { + assert(l->flags & AVAHI_LOOKUP_USE_MULTICAST); + n = (int) avahi_multicast_lookup_engine_scan_cache(l->record_browser->server->multicast_lookup_engine, l->interface, l->protocol, l->key, lookup_multicast_callback, l); + } + + return n; +} + +static AvahiSRBLookup* lookup_add(AvahiSRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiKey *key) { + AvahiSRBLookup *l; + + assert(b); + assert(!b->dead); + + if ((l = lookup_find(b, interface, protocol, flags, key))) + return lookup_ref(l); + + if (!(l = lookup_new(b, interface, protocol, flags, key))) + return NULL; + + return l; +} + +static int lookup_go(AvahiSRBLookup *l) { + int n = 0; + assert(l); + + if (l->record_browser->dead) + return 0; + + lookup_ref(l); + + /* Browse the cache for the root request */ + n = lookup_scan_cache(l); + + /* Start the lookup */ + if (!l->record_browser->dead && l->ref > 1) { + + if ((l->flags & AVAHI_LOOKUP_USE_MULTICAST) || n == 0) + /* We do no start a query if the cache contained entries and we're on wide area */ + + if (lookup_start(l) < 0) + n = -1; + } + + lookup_unref(l); + + return n; +} + +static void lookup_handle_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) { + AvahiKey *k; + AvahiSRBLookup *n; + + assert(l); + assert(r); + + assert(r->key->clazz == AVAHI_DNS_CLASS_IN); + assert(r->key->type == AVAHI_DNS_TYPE_CNAME); + + k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type); + n = lookup_add(l->record_browser, interface, protocol, flags, k); + avahi_key_unref(k); + + if (!n) { + avahi_log_debug(__FILE__": Failed to create SRBLookup."); + return; + } + + l->cname_lookups = avahi_rlist_prepend(l->cname_lookups, lookup_ref(n)); + + lookup_go(n); + lookup_unref(n); +} + +static void lookup_drop_cname(AvahiSRBLookup *l, AvahiIfIndex interface, AvahiProtocol protocol, AvahiLookupFlags flags, AvahiRecord *r) { + AvahiKey *k; + AvahiSRBLookup *n = NULL; + AvahiRList *rl; + + assert(r->key->clazz == AVAHI_DNS_CLASS_IN); + assert(r->key->type == AVAHI_DNS_TYPE_CNAME); + + k = avahi_key_new(r->data.ptr.name, l->record_browser->key->clazz, l->record_browser->key->type); + + for (rl = l->cname_lookups; rl; rl = rl->rlist_next) { + n = rl->data; + + assert(n); + + if ((n->interface == AVAHI_IF_UNSPEC || n->interface == interface) && + (n->interface == AVAHI_PROTO_UNSPEC || n->protocol == protocol) && + n->flags == flags && + avahi_key_equal(n->key, k)) + break; + } + + avahi_key_unref(k); + + if (rl) { + l->cname_lookups = avahi_rlist_remove_by_link(l->cname_lookups, rl); + lookup_unref(n); + } +} + +static void defer_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata) { + AvahiSRecordBrowser *b = userdata; + int n; + + assert(b); + assert(!b->dead); + + /* Remove the defer timeout */ + if (b->defer_time_event) { + avahi_time_event_free(b->defer_time_event); + b->defer_time_event = NULL; + } + + /* Create initial query */ + assert(!b->root_lookup); + b->root_lookup = lookup_add(b, b->interface, b->protocol, b->flags, b->key); + assert(b->root_lookup); + + n = lookup_go(b->root_lookup); + + if (b->dead) + return; + + if (n < 0) { + /* sending of the initial query failed */ + + avahi_server_set_errno(b->server, AVAHI_ERR_FAILURE); + + b->callback( + b, b->interface, b->protocol, AVAHI_BROWSER_FAILURE, NULL, + b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST, + b->userdata); + + browser_cancel(b); + return; + } + + /* Tell the client that we're done with the cache */ + b->callback( + b, b->interface, b->protocol, AVAHI_BROWSER_CACHE_EXHAUSTED, NULL, + b->flags & AVAHI_LOOKUP_USE_WIDE_AREA ? AVAHI_LOOKUP_RESULT_WIDE_AREA : AVAHI_LOOKUP_RESULT_MULTICAST, + b->userdata); + + if (!b->dead && b->root_lookup && b->root_lookup->flags & AVAHI_LOOKUP_USE_WIDE_AREA && n > 0) { + + /* If we do wide area lookups and the the cache contained + * entries, we assume that it is complete, and tell the user + * so by firing ALL_FOR_NOW. */ + + b->callback(b, b->interface, b->protocol, AVAHI_BROWSER_ALL_FOR_NOW, NULL, AVAHI_LOOKUP_RESULT_WIDE_AREA, b->userdata); + } +} + +void avahi_s_record_browser_restart(AvahiSRecordBrowser *b) { + assert(b); + assert(!b->dead); + + browser_cancel(b); + + /* Request a new iteration of the cache scanning */ + if (!b->defer_time_event) { + b->defer_time_event = avahi_time_event_new(b->server->time_event_queue, NULL, defer_callback, b); + assert(b->defer_time_event); + } +} + +AvahiSRecordBrowser *avahi_s_record_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiKey *key, + AvahiLookupFlags flags, + AvahiSRecordBrowserCallback callback, + void* userdata) { + + AvahiSRecordBrowser *b; + + assert(server); + assert(key); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !avahi_key_is_pattern(key), AVAHI_ERR_IS_PATTERN); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_key_is_valid(key), AVAHI_ERR_INVALID_KEY); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !(flags & AVAHI_LOOKUP_USE_WIDE_AREA) || !(flags & AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + + if (!(b = avahi_new(AvahiSRecordBrowser, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + b->dead = 0; + b->server = server; + b->interface = interface; + b->protocol = protocol; + b->key = avahi_key_ref(key); + b->flags = flags; + b->callback = callback; + b->userdata = userdata; + b->n_lookups = 0; + AVAHI_LLIST_HEAD_INIT(AvahiSRBLookup, b->lookups); + b->root_lookup = NULL; + + AVAHI_LLIST_PREPEND(AvahiSRecordBrowser, browser, server->record_browsers, b); + + /* The currently cached entries are scanned a bit later, and than we will start querying, too */ + b->defer_time_event = avahi_time_event_new(server->time_event_queue, NULL, defer_callback, b); + assert(b->defer_time_event); + + return b; +} + +void avahi_s_record_browser_free(AvahiSRecordBrowser *b) { + assert(b); + assert(!b->dead); + + b->dead = 1; + b->server->need_browser_cleanup = 1; + + browser_cancel(b); +} + +void avahi_s_record_browser_destroy(AvahiSRecordBrowser *b) { + assert(b); + + browser_cancel(b); + + AVAHI_LLIST_REMOVE(AvahiSRecordBrowser, browser, b->server->record_browsers, b); + + avahi_key_unref(b->key); + + avahi_free(b); +} + +void avahi_browser_cleanup(AvahiServer *server) { + AvahiSRecordBrowser *b; + AvahiSRecordBrowser *n; + + assert(server); + + while (server->need_browser_cleanup) { + server->need_browser_cleanup = 0; + + for (b = server->record_browsers; b; b = n) { + n = b->browser_next; + + if (b->dead) + avahi_s_record_browser_destroy(b); + } + } + + if (server->wide_area_lookup_engine) + avahi_wide_area_cleanup(server->wide_area_lookup_engine); + avahi_multicast_lookup_engine_cleanup(server->multicast_lookup_engine); +} + diff --git a/trunk/avahi-core/browse.h b/trunk/avahi-core/browse.h new file mode 100644 index 0000000..36d4e2e --- /dev/null +++ b/trunk/avahi-core/browse.h @@ -0,0 +1,62 @@ +#ifndef foobrowsehfoo +#define foobrowsehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <avahi-common/llist.h> + +#include "core.h" +#include "timeeventq.h" +#include "internal.h" +#include "dns.h" +#include "lookup.h" + +typedef struct AvahiSRBLookup AvahiSRBLookup; + +struct AvahiSRecordBrowser { + AVAHI_LLIST_FIELDS(AvahiSRecordBrowser, browser); + int dead; + AvahiServer *server; + + AvahiKey *key; + AvahiIfIndex interface; + AvahiProtocol protocol; + AvahiLookupFlags flags; + + AvahiTimeEvent *defer_time_event; + + AvahiSRecordBrowserCallback callback; + void* userdata; + + /* Lookup data */ + AVAHI_LLIST_HEAD(AvahiSRBLookup, lookups); + unsigned n_lookups; + + AvahiSRBLookup *root_lookup; +}; + +void avahi_browser_cleanup(AvahiServer *server); + +void avahi_s_record_browser_destroy(AvahiSRecordBrowser *b); +void avahi_s_record_browser_restart(AvahiSRecordBrowser *b); + +#endif diff --git a/trunk/avahi-core/cache.c b/trunk/avahi-core/cache.c new file mode 100644 index 0000000..4ba88b5 --- /dev/null +++ b/trunk/avahi-core/cache.c @@ -0,0 +1,508 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "cache.h" +#include "log.h" +#include "rr-util.h" + +#define AVAHI_CACHE_ENTRIES_MAX 500 + +static void remove_entry(AvahiCache *c, AvahiCacheEntry *e) { + AvahiCacheEntry *t; + + assert(c); + assert(e); + +/* avahi_log_debug("removing from cache: %p %p", c, e); */ + + /* Remove from hash table */ + t = avahi_hashmap_lookup(c->hashmap, e->record->key); + AVAHI_LLIST_REMOVE(AvahiCacheEntry, by_key, t, e); + if (t) + avahi_hashmap_replace(c->hashmap, t->record->key, t); + else + avahi_hashmap_remove(c->hashmap, e->record->key); + + /* Remove from linked list */ + AVAHI_LLIST_REMOVE(AvahiCacheEntry, entry, c->entries, e); + + if (e->time_event) + avahi_time_event_free(e->time_event); + + avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_REMOVE); + + avahi_record_unref(e->record); + + avahi_free(e); + + assert(c->n_entries-- >= 1); +} + +AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *iface) { + AvahiCache *c; + assert(server); + + if (!(c = avahi_new(AvahiCache, 1))) { + avahi_log_error(__FILE__": Out of memory."); + return NULL; /* OOM */ + } + + c->server = server; + c->interface = iface; + + if (!(c->hashmap = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL))) { + avahi_log_error(__FILE__": Out of memory."); + avahi_free(c); + return NULL; /* OOM */ + } + + AVAHI_LLIST_HEAD_INIT(AvahiCacheEntry, c->entries); + c->n_entries = 0; + + c->last_rand_timestamp = 0; + + return c; +} + +void avahi_cache_free(AvahiCache *c) { + assert(c); + + while (c->entries) + remove_entry(c, c->entries); + assert(c->n_entries == 0); + + avahi_hashmap_free(c->hashmap); + + avahi_free(c); +} + +static AvahiCacheEntry *lookup_key(AvahiCache *c, AvahiKey *k) { + assert(c); + assert(k); + + assert(!avahi_key_is_pattern(k)); + + return avahi_hashmap_lookup(c->hashmap, k); +} + +void* avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, void* userdata) { + void* ret; + + assert(c); + assert(pattern); + assert(cb); + + if (avahi_key_is_pattern(pattern)) { + AvahiCacheEntry *e, *n; + + for (e = c->entries; e; e = n) { + n = e->entry_next; + + if (avahi_key_pattern_match(pattern, e->record->key)) + if ((ret = cb(c, pattern, e, userdata))) + return ret; + } + + } else { + AvahiCacheEntry *e, *n; + + for (e = lookup_key(c, pattern); e; e = n) { + n = e->by_key_next; + + if ((ret = cb(c, pattern, e, userdata))) + return ret; + } + } + + return NULL; +} + +static void* lookup_record_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) { + assert(c); + assert(pattern); + assert(e); + + if (avahi_record_equal_no_ttl(e->record, userdata)) + return e; + + return NULL; +} + +static AvahiCacheEntry *lookup_record(AvahiCache *c, AvahiRecord *r) { + assert(c); + assert(r); + + return avahi_cache_walk(c, r->key, lookup_record_callback, r); +} + +static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent); + +static void elapse_func(AvahiTimeEvent *t, void *userdata) { + AvahiCacheEntry *e = userdata; +/* char *txt; */ + unsigned percent = 0; + + assert(t); + assert(e); + +/* txt = avahi_record_to_string(e->record); */ + + switch (e->state) { + + case AVAHI_CACHE_EXPIRY_FINAL: + case AVAHI_CACHE_POOF_FINAL: + case AVAHI_CACHE_GOODBYE_FINAL: + case AVAHI_CACHE_REPLACE_FINAL: + + remove_entry(e->cache, e); + + e = NULL; +/* avahi_log_debug("Removing entry from cache due to expiration (%s)", txt); */ + break; + + case AVAHI_CACHE_VALID: + case AVAHI_CACHE_POOF: + e->state = AVAHI_CACHE_EXPIRY1; + percent = 85; + break; + + case AVAHI_CACHE_EXPIRY1: + e->state = AVAHI_CACHE_EXPIRY2; + percent = 90; + break; + case AVAHI_CACHE_EXPIRY2: + e->state = AVAHI_CACHE_EXPIRY3; + percent = 95; + break; + + case AVAHI_CACHE_EXPIRY3: + e->state = AVAHI_CACHE_EXPIRY_FINAL; + percent = 100; + break; + } + + if (e) { + + assert(percent > 0); + + /* Request a cache update if we are subscribed to this entry */ + if (avahi_querier_shall_refresh_cache(e->cache->interface, e->record->key)) + avahi_interface_post_query(e->cache->interface, e->record->key, 0, NULL); + + /* Check again later */ + next_expiry(e->cache, e, percent); + + } + +/* avahi_free(txt); */ +} + +static void update_time_event(AvahiCache *c, AvahiCacheEntry *e) { + assert(c); + assert(e); + + if (e->time_event) + avahi_time_event_update(e->time_event, &e->expiry); + else + e->time_event = avahi_time_event_new(c->server->time_event_queue, &e->expiry, elapse_func, e); +} + +static void next_expiry(AvahiCache *c, AvahiCacheEntry *e, unsigned percent) { + AvahiUsec usec, left, right; + time_t now; + + assert(c); + assert(e); + assert(percent > 0 && percent <= 100); + + usec = (AvahiUsec) e->record->ttl * 10000; + + left = usec * percent; + right = usec * (percent+2); /* 2% jitter */ + + now = time(NULL); + + if (now >= c->last_rand_timestamp + 10) { + c->last_rand = rand(); + c->last_rand_timestamp = now; + } + + usec = left + (AvahiUsec) ((double) (right-left) * c->last_rand / (RAND_MAX+1.0)); + + e->expiry = e->timestamp; + avahi_timeval_add(&e->expiry, usec); + +/* g_message("wake up in +%lu seconds", e->expiry.tv_sec - e->timestamp.tv_sec); */ + + update_time_event(c, e); +} + +static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e, AvahiCacheEntryState state) { + assert(c); + assert(e); + + e->state = state; + gettimeofday(&e->expiry, NULL); + avahi_timeval_add(&e->expiry, 1000000); /* 1s */ + update_time_event(c, e); +} + +void avahi_cache_update(AvahiCache *c, AvahiRecord *r, int cache_flush, const AvahiAddress *a) { +/* char *txt; */ + + assert(c); + assert(r && r->ref >= 1); + +/* txt = avahi_record_to_string(r); */ + + if (r->ttl == 0) { + /* This is a goodbye request */ + + AvahiCacheEntry *e; + + if ((e = lookup_record(c, r))) + expire_in_one_second(c, e, AVAHI_CACHE_GOODBYE_FINAL); + + } else { + AvahiCacheEntry *e = NULL, *first; + struct timeval now; + + gettimeofday(&now, NULL); + + /* This is an update request */ + + if ((first = lookup_key(c, r->key))) { + + if (cache_flush) { + + /* For unique entries drop all entries older than one second */ + for (e = first; e; e = e->by_key_next) { + AvahiUsec t; + + t = avahi_timeval_diff(&now, &e->timestamp); + + if (t > 1000000) + expire_in_one_second(c, e, AVAHI_CACHE_REPLACE_FINAL); + } + } + + /* Look for exactly the same entry */ + for (e = first; e; e = e->by_key_next) + if (avahi_record_equal_no_ttl(e->record, r)) + break; + } + + if (e) { + +/* avahi_log_debug("found matching cache entry"); */ + + /* We need to update the hash table key if we replace the + * record */ + if (e->by_key_prev == NULL) + avahi_hashmap_replace(c->hashmap, r->key, e); + + /* Update the record */ + avahi_record_unref(e->record); + e->record = avahi_record_ref(r); + +/* avahi_log_debug("cache: updating %s", txt); */ + + } else { + /* No entry found, therefore we create a new one */ + +/* avahi_log_debug("cache: couldn't find matching cache entry for %s", txt); */ + + if (c->n_entries >= AVAHI_CACHE_ENTRIES_MAX) + return; + + if (!(e = avahi_new(AvahiCacheEntry, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return; + } + + e->cache = c; + e->time_event = NULL; + e->record = avahi_record_ref(r); + + /* Append to hash table */ + AVAHI_LLIST_PREPEND(AvahiCacheEntry, by_key, first, e); + avahi_hashmap_replace(c->hashmap, e->record->key, first); + + /* Append to linked list */ + AVAHI_LLIST_PREPEND(AvahiCacheEntry, entry, c->entries, e); + + c->n_entries++; + + /* Notify subscribers */ + avahi_multicast_lookup_engine_notify(c->server->multicast_lookup_engine, c->interface, e->record, AVAHI_BROWSER_NEW); + } + + e->origin = *a; + e->timestamp = now; + next_expiry(c, e, 80); + e->state = AVAHI_CACHE_VALID; + e->cache_flush = cache_flush; + } + +/* avahi_free(txt); */ +} + +struct dump_data { + AvahiDumpCallback callback; + void* userdata; +}; + +static void dump_callback(void* key, void* data, void* userdata) { + AvahiCacheEntry *e = data; + AvahiKey *k = key; + struct dump_data *dump_data = userdata; + + assert(k); + assert(e); + assert(data); + + for (; e; e = e->by_key_next) { + char *t; + + if (!(t = avahi_record_to_string(e->record))) + continue; /* OOM */ + + dump_data->callback(t, dump_data->userdata); + avahi_free(t); + } +} + +int avahi_cache_dump(AvahiCache *c, AvahiDumpCallback callback, void* userdata) { + struct dump_data data; + + assert(c); + assert(callback); + + callback(";;; CACHE DUMP FOLLOWS ;;;", userdata); + + data.callback = callback; + data.userdata = userdata; + + avahi_hashmap_foreach(c->hashmap, dump_callback, &data); + + return 0; +} + +int avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e) { + struct timeval now; + unsigned age; + + assert(c); + assert(e); + + gettimeofday(&now, NULL); + + age = (unsigned) (avahi_timeval_diff(&now, &e->timestamp)/1000000); + +/* avahi_log_debug("age: %lli, ttl/2: %u", age, e->record->ttl); */ + + return age >= e->record->ttl/2; +} + +void avahi_cache_flush(AvahiCache *c) { + assert(c); + + while (c->entries) + remove_entry(c, c->entries); +} + +/*** Passive observation of failure ***/ + +static void* start_poof_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void *userdata) { + AvahiAddress *a = userdata; + + assert(c); + assert(pattern); + assert(e); + assert(a); + + switch (e->state) { + case AVAHI_CACHE_VALID: + + /* The entry was perfectly valid till, now, so let's enter + * POOF mode */ + + e->state = AVAHI_CACHE_POOF; + e->poof_address = *a; + + break; + + case AVAHI_CACHE_POOF: + + /* This is the second time we got no response, so let's + * fucking remove this entry. */ + + expire_in_one_second(c, e, AVAHI_CACHE_POOF_FINAL); + break; + + default: + ; + } + + return NULL; +} + +void avahi_cache_start_poof(AvahiCache *c, AvahiKey *key, const AvahiAddress *a) { + assert(c); + assert(key); + + avahi_cache_walk(c, key, start_poof_callback, (void*) a); +} + +void avahi_cache_stop_poof(AvahiCache *c, AvahiRecord *record, const AvahiAddress *a) { + AvahiCacheEntry *e; + + assert(c); + assert(record); + assert(a); + + if (!(e = lookup_record(c, record))) + return; + + /* This function is called for each response suppression + record. If the matching cache entry is in POOF state and the + query address is the same, we put it back into valid mode */ + + if (e->state == AVAHI_CACHE_POOF || e->state == AVAHI_CACHE_POOF_FINAL) + if (avahi_address_cmp(a, &e->poof_address) == 0) { + e->state = AVAHI_CACHE_VALID; + next_expiry(c, e, 80); + } +} + + + diff --git a/trunk/avahi-core/cache.h b/trunk/avahi-core/cache.h new file mode 100644 index 0000000..edf9fa5 --- /dev/null +++ b/trunk/avahi-core/cache.h @@ -0,0 +1,101 @@ +#ifndef foocachehfoo +#define foocachehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiCache AvahiCache; + +#include <avahi-common/llist.h> +#include "prioq.h" +#include "internal.h" +#include "timeeventq.h" +#include "hashmap.h" + +typedef enum { + AVAHI_CACHE_VALID, + AVAHI_CACHE_EXPIRY1, + AVAHI_CACHE_EXPIRY2, + AVAHI_CACHE_EXPIRY3, + AVAHI_CACHE_EXPIRY_FINAL, + AVAHI_CACHE_POOF, /* Passive observation of failure */ + AVAHI_CACHE_POOF_FINAL, + AVAHI_CACHE_GOODBYE_FINAL, + AVAHI_CACHE_REPLACE_FINAL +} AvahiCacheEntryState; + +typedef struct AvahiCacheEntry AvahiCacheEntry; + +struct AvahiCacheEntry { + AvahiCache *cache; + AvahiRecord *record; + struct timeval timestamp; + struct timeval expiry; + int cache_flush; + + AvahiAddress origin; + + AvahiCacheEntryState state; + AvahiTimeEvent *time_event; + + AvahiAddress poof_address; + + AVAHI_LLIST_FIELDS(AvahiCacheEntry, by_key); + AVAHI_LLIST_FIELDS(AvahiCacheEntry, entry); +}; + +struct AvahiCache { + AvahiServer *server; + + AvahiInterface *interface; + + AvahiHashmap *hashmap; + + AVAHI_LLIST_HEAD(AvahiCacheEntry, entries); + + unsigned n_entries; + + int last_rand; + time_t last_rand_timestamp; +}; + +AvahiCache *avahi_cache_new(AvahiServer *server, AvahiInterface *interface); +void avahi_cache_free(AvahiCache *c); + +void avahi_cache_update(AvahiCache *c, AvahiRecord *r, int cache_flush, const AvahiAddress *a); + +int avahi_cache_dump(AvahiCache *c, AvahiDumpCallback callback, void* userdata); + +typedef void* AvahiCacheWalkCallback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void* userdata); +void* avahi_cache_walk(AvahiCache *c, AvahiKey *pattern, AvahiCacheWalkCallback cb, void* userdata); + +int avahi_cache_entry_half_ttl(AvahiCache *c, AvahiCacheEntry *e); + +/** Start the "Passive observation of Failure" algorithm for all + * records of the specified key. The specified address is */ +void avahi_cache_start_poof(AvahiCache *c, AvahiKey *key, const AvahiAddress *a); + +/* Stop a previously started POOF algorithm for a record. (Used for response suppresions records */ +void avahi_cache_stop_poof(AvahiCache *c, AvahiRecord *record, const AvahiAddress *a); + +void avahi_cache_flush(AvahiCache *c); + +#endif diff --git a/trunk/avahi-core/conformance-test.c b/trunk/avahi-core/conformance-test.c new file mode 100644 index 0000000..dc3c864 --- /dev/null +++ b/trunk/avahi-core/conformance-test.c @@ -0,0 +1,160 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/malloc.h> +#include <avahi-common/simple-watch.h> +#include <avahi-common/timeval.h> + +#include <avahi-core/core.h> +#include <avahi-core/log.h> +#include <avahi-core/lookup.h> +#include <avahi-core/publish.h> + +static char *name = NULL; +static AvahiSEntryGroup *group = NULL; +static int try = 0; +static AvahiServer *avahi = NULL; +static const AvahiPoll *poll_api; + +static void dump_line(const char *text, AVAHI_GCC_UNUSED void* userdata) { + printf("%s\n", text); +} + +static void dump_timeout_callback(AvahiTimeout *timeout, AVAHI_GCC_UNUSED void* userdata) { + struct timeval tv; + + avahi_server_dump(avahi, dump_line, NULL); + + avahi_elapse_time(&tv, 5000, 0); + poll_api->timeout_update(timeout, &tv); +} + +static void entry_group_callback(AvahiServer *s, AvahiSEntryGroup *g, AvahiEntryGroupState state, void* userdata); + +static void create_service(const char *t) { + char *n; + + assert(t || name); + + n = t ? avahi_strdup(t) : avahi_alternative_service_name(name); + avahi_free(name); + name = n; + + if (group) + avahi_s_entry_group_reset(group); + else + group = avahi_s_entry_group_new(avahi, entry_group_callback, NULL); + + avahi_server_add_service(avahi, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, name, "_http._tcp", NULL, NULL, 80, "foo", NULL); + avahi_s_entry_group_commit(group); + + try++; +} + +static void rename_timeout_callback(AvahiTimeout *timeout, AVAHI_GCC_UNUSED void *userdata) { + struct timeval tv; + + if (access("flag", F_OK) == 0) { + create_service("New - Bonjour Service Name"); + return; + } + + avahi_elapse_time(&tv, 5000, 0); + poll_api->timeout_update(timeout, &tv); +} + +static void entry_group_callback(AVAHI_GCC_UNUSED AvahiServer *s, AVAHI_GCC_UNUSED AvahiSEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void* userdata) { + if (state == AVAHI_ENTRY_GROUP_COLLISION) + create_service(NULL); + else if (state == AVAHI_ENTRY_GROUP_ESTABLISHED) { + avahi_log_debug("ESTABLISHED !!!!"); + try = 0; + } +} + +static void server_callback(AvahiServer *s, AvahiServerState state, AVAHI_GCC_UNUSED void* userdata) { + avahi_log_debug("server state: %i", state); + + if (state == AVAHI_SERVER_RUNNING) { + avahi_server_dump(avahi, dump_line, NULL); + } else if (state == AVAHI_SERVER_COLLISION) { + char *n; + + n = avahi_alternative_host_name(avahi_server_get_host_name(s)); + avahi_log_warn("Host name conflict, retrying with <%s>", n); + avahi_server_set_host_name(s, n); + avahi_free(n); + + } +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + int error; + AvahiSimplePoll *simple_poll; + struct timeval tv; + struct AvahiServerConfig config; + + simple_poll = avahi_simple_poll_new(); + poll_api = avahi_simple_poll_get(simple_poll); + + avahi_server_config_init(&config); + config.publish_workstation = 0; + config.use_ipv6 = 0; + config.publish_domain = 0; + config.publish_hinfo = 0; + avahi = avahi_server_new(poll_api, &config, server_callback, NULL, &error); + avahi_server_config_free(&config); + + avahi_elapse_time(&tv, 5000, 0); + poll_api->timeout_new(poll_api, &tv, dump_timeout_callback, avahi); + + avahi_elapse_time(&tv, 5000, 0); + poll_api->timeout_new(poll_api, &tv, rename_timeout_callback, avahi); + + /* Evil, but the conformace test requires that*/ + create_service("gurke"); + + + avahi_simple_poll_loop(simple_poll); + + if (group) + avahi_s_entry_group_free(group); + avahi_server_free(avahi); + + avahi_simple_poll_free(simple_poll); + + return 0; +} diff --git a/trunk/avahi-core/core.h b/trunk/avahi-core/core.h new file mode 100644 index 0000000..e73abd2 --- /dev/null +++ b/trunk/avahi-core/core.h @@ -0,0 +1,153 @@ +#ifndef foocorehfoo +#define foocorehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** \file core.h The Avahi Multicast DNS and DNS Service Discovery implementation. */ + +/** An mDNS responder object */ +typedef struct AvahiServer AvahiServer; + +#include <avahi-common/cdecl.h> +#include <avahi-common/address.h> +#include <avahi-common/defs.h> +#include <avahi-common/watch.h> +#include <avahi-core/rr.h> + +AVAHI_C_DECL_BEGIN + +/** Maximum number of defined DNS servers for wide area DNS */ +#define AVAHI_WIDE_AREA_SERVERS_MAX 4 + +/** Prototype for callback functions which are called whenever the state of an AvahiServer object changes */ +typedef void (*AvahiServerCallback) (AvahiServer *s, AvahiServerState state, void* userdata); + +/** Stores configuration options for a server instance */ +typedef struct AvahiServerConfig { + char *host_name; /**< Default host name. If left empty defaults to the result of gethostname(2) of the libc */ + char *domain_name; /**< Default domain name. If left empty defaults to .local */ + int use_ipv4; /**< Enable IPv4 support */ + int use_ipv6; /**< Enable IPv6 support */ + int publish_hinfo; /**< Register a HINFO record for the host containing the local OS and CPU type */ + int publish_addresses; /**< Register A, AAAA and PTR records for all local IP addresses */ + int publish_workstation; /**< Register a _workstation._tcp service */ + int publish_domain; /**< Announce the local domain for browsing */ + int check_response_ttl; /**< If enabled the server ignores all incoming responses with IP TTL != 255. Newer versions of the RFC do no longer contain this check, so it is disabled by default. */ + int use_iff_running; /**< Require IFF_RUNNING on local network interfaces. This is the official way to check for link beat. Unfortunately this doesn't work with all drivers. So bettere leave this off. */ + int enable_reflector; /**< Reflect incoming mDNS traffic to all local networks. This allows mDNS based network browsing beyond ethernet borders */ + int reflect_ipv; /**< if enable_reflector is 1, enable/disable reflecting between IPv4 and IPv6 */ + int add_service_cookie; /**< Add magic service cookie to all locally generated records implicitly */ + int enable_wide_area; /**< Enable wide area support */ + AvahiAddress wide_area_servers[AVAHI_WIDE_AREA_SERVERS_MAX]; /** Unicast DNS server to use for wide area lookup */ + unsigned n_wide_area_servers; /**< Number of servers in wide_area_servers[] */ + int disallow_other_stacks; /**< Make sure that only one mDNS responder is run at the same time on the local machine. If this is enable Avahi will not set SO_REUSADDR on its sockets, effectively preventing other stacks from running on the local machine */ + AvahiStringList *browse_domains; /**< Additional browsing domains */ + int disable_publishing; /**< Disable publishing of any record */ + int allow_point_to_point; /**< Enable publishing on POINTOPOINT interfaces */ +} AvahiServerConfig; + +/** Allocate a new mDNS responder object. */ +AvahiServer *avahi_server_new( + const AvahiPoll *api, /**< The main loop adapter */ + const AvahiServerConfig *sc, /**< If non-NULL a pointer to a configuration structure for the server. The server makes an internal deep copy of this structure, so you may free it using avahi_server_config_done() immediately after calling this function. */ + AvahiServerCallback callback, /**< A callback which is called whenever the state of the server changes */ + void* userdata, /**< An opaque pointer which is passed to the callback function */ + int *error); + +/** Free an mDNS responder object */ +void avahi_server_free(AvahiServer* s); + +/** Fill in default values for a server configuration structure. If you + * make use of an AvahiServerConfig structure be sure to initialize + * it with this function for the sake of upwards library + * compatibility. This call may allocate strings on the heap. To + * release this memory make sure to call + * avahi_server_config_done(). If you want to replace any strings in + * the structure be sure to free the strings filled in by this + * function with avahi_free() first and allocate the replacements with + * g_malloc() (or g_strdup()).*/ +AvahiServerConfig* avahi_server_config_init( + AvahiServerConfig *c /**< A structure which shall be filled in */ ); + +/** Make a deep copy of the configuration structure *c to *ret. */ +AvahiServerConfig* avahi_server_config_copy( + AvahiServerConfig *ret /**< destination */, + const AvahiServerConfig *c /**< source */); + +/** Free the data in a server configuration structure. */ +void avahi_server_config_free(AvahiServerConfig *c); + +/** Return the currently chosen domain name of the server object. The + * return value points to an internally allocated string. Be sure to + * make a copy of the string before calling any other library + * functions. */ +const char* avahi_server_get_domain_name(AvahiServer *s); + +/** Return the currently chosen host name. The return value points to a internally allocated string. */ +const char* avahi_server_get_host_name(AvahiServer *s); + +/** Return the currently chosen host name as a FQDN ("fully qualified + * domain name", i.e. the concatenation of the host and domain + * name). The return value points to a internally allocated string. */ +const char* avahi_server_get_host_name_fqdn(AvahiServer *s); + +/** Change the host name of a running mDNS responder. This will drop +all automicatilly generated RRs and readd them with the new +name. Since the responder has to probe for the new RRs this function +takes some time to take effect altough it returns immediately. This +function is intended to be called when a host name conflict is +reported using AvahiServerCallback. The caller should readd all user +defined RRs too since they otherwise continue to point to the outdated +host name..*/ +int avahi_server_set_host_name(AvahiServer *s, const char *host_name); + +/** Change the domain name of a running mDNS responder. The same rules + * as with avahi_server_set_host_name() apply. */ +int avahi_server_set_domain_name(AvahiServer *s, const char *domain_name); + +/** Return the opaque user data pointer attached to a server object */ +void* avahi_server_get_data(AvahiServer *s); + +/** Change the opaque user data pointer attached to a server object */ +void avahi_server_set_data(AvahiServer *s, void* userdata); + +/** Return the current state of the server object */ +AvahiServerState avahi_server_get_state(AvahiServer *s); + +/** Callback prototype for avahi_server_dump() */ +typedef void (*AvahiDumpCallback)(const char *text, void* userdata); + +/** Dump the current server status by calling "callback" for each line. */ +int avahi_server_dump(AvahiServer *s, AvahiDumpCallback callback, void* userdata); + +/** Return the last error code */ +int avahi_server_errno(AvahiServer *s); + +/** Return the local service cookie */ +uint32_t avahi_server_get_local_service_cookie(AvahiServer *s); + +/** Set the wide area DNS servers */ +int avahi_server_set_wide_area_servers(AvahiServer *s, const AvahiAddress *a, unsigned n); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/dns-srv-rr.h b/trunk/avahi-core/dns-srv-rr.h new file mode 100644 index 0000000..1a7f95f --- /dev/null +++ b/trunk/avahi-core/dns-srv-rr.h @@ -0,0 +1,89 @@ +#ifndef foodnssrvhfoo +#define foodnssrvhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** \file avahi-core/dns-srv-rr.h Functions for announcing and browsing for unicast DNS servers via mDNS */ + +/** A domain service browser object. Use this to browse for + * conventional unicast DNS servers which may be used to resolve + * conventional domain names */ +typedef struct AvahiSDNSServerBrowser AvahiSDNSServerBrowser; + +#include <avahi-common/cdecl.h> +#include <avahi-common/defs.h> +#include <avahi-core/core.h> +#include <avahi-core/publish.h> + +AVAHI_C_DECL_BEGIN + +/** The type of DNS server */ +typedef enum { + AVAHI_DNS_SERVER_RESOLVE, /**< Unicast DNS servers for normal resolves (_domain._udp)*/ + AVAHI_DNS_SERVER_UPDATE, /**< Unicast DNS servers for updates (_dns-update._udp)*/ + AVAHI_DNS_SERVER_MAX +} AvahiDNSServerType; + +/** Publish the specified unicast DNS server address via mDNS. You may + * browse for records create this way wit + * avahi_s_dns_server_browser_new(). */ +int avahi_server_add_dns_server_address( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *domain, + AvahiDNSServerType type, + const AvahiAddress *address, + uint16_t port /** should be 53 */); + +/** Callback prototype for AvahiSDNSServerBrowser events */ +typedef void (*AvahiSDNSServerBrowserCallback)( + AvahiSDNSServerBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *host_name, /**< Host name of the DNS server, probably useless */ + const AvahiAddress *a, /**< Address of the DNS server */ + uint16_t port, /**< Port number of the DNS servers, probably 53 */ + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create a new AvahiSDNSServerBrowser object */ +AvahiSDNSServerBrowser *avahi_s_dns_server_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiDNSServerType type, + AvahiProtocol aprotocol, /**< Address protocol for the DNS server */ + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSDNSServerBrowserCallback callback, + void* userdata); + +/** Free an AvahiSDNSServerBrowser object */ +void avahi_s_dns_server_browser_free(AvahiSDNSServerBrowser *b); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/dns-test.c b/trunk/avahi-core/dns-test.c new file mode 100644 index 0000000..ae2343d --- /dev/null +++ b/trunk/avahi-core/dns-test.c @@ -0,0 +1,115 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <avahi-common/domain.h> +#include <avahi-common/defs.h> +#include <avahi-common/malloc.h> + +#include "dns.h" +#include "log.h" +#include "util.h" + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + char t[AVAHI_DOMAIN_NAME_MAX], *m; + const char *a, *b, *c, *d; + AvahiDnsPacket *p; + AvahiRecord *r, *r2; + uint8_t rdata[AVAHI_DNS_RDATA_MAX]; + size_t l; + + p = avahi_dns_packet_new(0); + + assert(avahi_dns_packet_append_name(p, a = "Ahello.hello.hello.de.")); + assert(avahi_dns_packet_append_name(p, b = "Bthis is a test.hello.de.")); + assert(avahi_dns_packet_append_name(p, c = "Cthis\\.is\\.a\\.test\\.with\\.dots.hello.de.")); + assert(avahi_dns_packet_append_name(p, d = "Dthis\\\\is another test.hello.de.")); + + avahi_hexdump(AVAHI_DNS_PACKET_DATA(p), p->size); + + assert(avahi_dns_packet_consume_name(p, t, sizeof(t)) == 0); + avahi_log_debug(">%s<", t); + assert(avahi_domain_equal(a, t)); + + assert(avahi_dns_packet_consume_name(p, t, sizeof(t)) == 0); + avahi_log_debug(">%s<", t); + assert(avahi_domain_equal(b, t)); + + assert(avahi_dns_packet_consume_name(p, t, sizeof(t)) == 0); + avahi_log_debug(">%s<", t); + assert(avahi_domain_equal(c, t)); + + assert(avahi_dns_packet_consume_name(p, t, sizeof(t)) == 0); + avahi_log_debug(">%s<", t); + assert(avahi_domain_equal(d, t)); + + avahi_dns_packet_free(p); + + /* RDATA PARSING AND SERIALIZATION */ + + /* Create an AvahiRecord with some usful data */ + r = avahi_record_new_full("foobar.local", AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_HINFO, AVAHI_DEFAULT_TTL); + assert(r); + r->data.hinfo.cpu = avahi_strdup("FOO"); + r->data.hinfo.os = avahi_strdup("BAR"); + + /* Serialize it into a blob */ + assert((l = avahi_rdata_serialize(r, rdata, sizeof(rdata))) != (size_t) -1); + + /* Print it */ + avahi_hexdump(rdata, l); + + /* Create a new record and fill in the data from the blob */ + r2 = avahi_record_new(r->key, AVAHI_DEFAULT_TTL); + assert(r2); + assert(avahi_rdata_parse(r2, rdata, l) >= 0); + + /* Compare both versions */ + assert(avahi_record_equal_no_ttl(r, r2)); + + /* Free the records */ + avahi_record_unref(r); + avahi_record_unref(r2); + + r = avahi_record_new_full("foobar", 77, 77, AVAHI_DEFAULT_TTL); + assert(r); + + assert(r->data.generic.data = avahi_memdup("HALLO", r->data.generic.size = 5)); + + m = avahi_record_to_string(r); + assert(m); + + avahi_log_debug(">%s<", m); + + avahi_free(m); + avahi_record_unref(r); + + return 0; +} diff --git a/trunk/avahi-core/dns.c b/trunk/avahi-core/dns.c new file mode 100644 index 0000000..b31aa20 --- /dev/null +++ b/trunk/avahi-core/dns.c @@ -0,0 +1,856 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#include <netinet/in.h> + +#include <avahi-common/defs.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include "dns.h" +#include "log.h" + +AvahiDnsPacket* avahi_dns_packet_new(unsigned mtu) { + AvahiDnsPacket *p; + size_t max_size; + + if (mtu <= 0) + max_size = AVAHI_DNS_PACKET_SIZE_MAX; + else if (mtu >= AVAHI_DNS_PACKET_EXTRA_SIZE) + max_size = mtu - AVAHI_DNS_PACKET_EXTRA_SIZE; + else + max_size = 0; + + if (max_size < AVAHI_DNS_PACKET_HEADER_SIZE) + max_size = AVAHI_DNS_PACKET_HEADER_SIZE; + + if (!(p = avahi_malloc(sizeof(AvahiDnsPacket) + max_size))) + return p; + + p->size = p->rindex = AVAHI_DNS_PACKET_HEADER_SIZE; + p->max_size = max_size; + p->name_table = NULL; + p->data = NULL; + + memset(AVAHI_DNS_PACKET_DATA(p), 0, p->size); + return p; +} + +AvahiDnsPacket* avahi_dns_packet_new_query(unsigned mtu) { + AvahiDnsPacket *p; + + if (!(p = avahi_dns_packet_new(mtu))) + return NULL; + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_FLAGS, AVAHI_DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + return p; +} + +AvahiDnsPacket* avahi_dns_packet_new_response(unsigned mtu, int aa) { + AvahiDnsPacket *p; + + if (!(p = avahi_dns_packet_new(mtu))) + return NULL; + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_FLAGS, AVAHI_DNS_FLAGS(1, 0, aa, 0, 0, 0, 0, 0, 0, 0)); + return p; +} + +AvahiDnsPacket* avahi_dns_packet_new_reply(AvahiDnsPacket* p, unsigned mtu, int copy_queries, int aa) { + AvahiDnsPacket *r; + assert(p); + + if (!(r = avahi_dns_packet_new_response(mtu, aa))) + return NULL; + + if (copy_queries) { + unsigned saved_rindex; + uint32_t n; + + saved_rindex = p->rindex; + p->rindex = AVAHI_DNS_PACKET_HEADER_SIZE; + + for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); n > 0; n--) { + AvahiKey *k; + int unicast_response; + + if ((k = avahi_dns_packet_consume_key(p, &unicast_response))) { + avahi_dns_packet_append_key(r, k, unicast_response); + avahi_key_unref(k); + } + } + + p->rindex = saved_rindex; + + avahi_dns_packet_set_field(r, AVAHI_DNS_FIELD_QDCOUNT, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT)); + } + + avahi_dns_packet_set_field(r, AVAHI_DNS_FIELD_ID, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID)); + + avahi_dns_packet_set_field(r, AVAHI_DNS_FIELD_FLAGS, + (avahi_dns_packet_get_field(r, AVAHI_DNS_FIELD_FLAGS) & ~AVAHI_DNS_FLAG_OPCODE) | + (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_OPCODE)); + + return r; +} + + +void avahi_dns_packet_free(AvahiDnsPacket *p) { + assert(p); + + if (p->name_table) + avahi_hashmap_free(p->name_table); + + avahi_free(p); +} + +void avahi_dns_packet_set_field(AvahiDnsPacket *p, unsigned idx, uint16_t v) { + assert(p); + assert(idx < AVAHI_DNS_PACKET_HEADER_SIZE); + + ((uint16_t*) AVAHI_DNS_PACKET_DATA(p))[idx] = htons(v); +} + +uint16_t avahi_dns_packet_get_field(AvahiDnsPacket *p, unsigned idx) { + assert(p); + assert(idx < AVAHI_DNS_PACKET_HEADER_SIZE); + + return ntohs(((uint16_t*) AVAHI_DNS_PACKET_DATA(p))[idx]); +} + +void avahi_dns_packet_inc_field(AvahiDnsPacket *p, unsigned idx) { + assert(p); + assert(idx < AVAHI_DNS_PACKET_HEADER_SIZE); + + avahi_dns_packet_set_field(p, idx, avahi_dns_packet_get_field(p, idx) + 1); +} + +uint8_t* avahi_dns_packet_append_name(AvahiDnsPacket *p, const char *name) { + uint8_t *d, *saved_ptr = NULL; + size_t saved_size; + + assert(p); + assert(name); + + saved_size = p->size; + saved_ptr = avahi_dns_packet_extend(p, 0); + + while (*name) { + uint8_t* prev; + const char *pname; + char label[64], *u; + + /* Check whether we can compress this name. */ + + if (p->name_table && (prev = avahi_hashmap_lookup(p->name_table, name))) { + unsigned idx; + + assert(prev >= AVAHI_DNS_PACKET_DATA(p)); + idx = (unsigned) (prev - AVAHI_DNS_PACKET_DATA(p)); + + assert(idx < p->size); + + if (idx < 0x4000) { + uint8_t *t; + if (!(t = (uint8_t*) avahi_dns_packet_extend(p, sizeof(uint16_t)))) + return NULL; + + t[0] = (uint8_t) ((0xC000 | idx) >> 8); + t[1] = (uint8_t) idx; + return saved_ptr; + } + } + + pname = name; + + if (!(avahi_unescape_label(&name, label, sizeof(label)))) + goto fail; + + if (!(d = avahi_dns_packet_append_string(p, label))) + goto fail; + + if (!p->name_table) + /* This works only for normalized domain names */ + p->name_table = avahi_hashmap_new(avahi_string_hash, avahi_string_equal, avahi_free, NULL); + + if (!(u = avahi_strdup(pname))) + avahi_log_error("avahi_strdup() failed."); + else + avahi_hashmap_insert(p->name_table, u, d); + } + + if (!(d = avahi_dns_packet_extend(p, 1))) + goto fail; + + *d = 0; + + return saved_ptr; + +fail: + p->size = saved_size; + return NULL; +} + +uint8_t* avahi_dns_packet_append_uint16(AvahiDnsPacket *p, uint16_t v) { + uint8_t *d; + assert(p); + + if (!(d = avahi_dns_packet_extend(p, sizeof(uint16_t)))) + return NULL; + + d[0] = (uint8_t) (v >> 8); + d[1] = (uint8_t) v; + return d; +} + +uint8_t *avahi_dns_packet_append_uint32(AvahiDnsPacket *p, uint32_t v) { + uint8_t *d; + assert(p); + + if (!(d = avahi_dns_packet_extend(p, sizeof(uint32_t)))) + return NULL; + + d[0] = (uint8_t) (v >> 24); + d[1] = (uint8_t) (v >> 16); + d[2] = (uint8_t) (v >> 8); + d[3] = (uint8_t) v; + + return d; +} + +uint8_t *avahi_dns_packet_append_bytes(AvahiDnsPacket *p, const void *b, size_t l) { + uint8_t* d; + + assert(p); + assert(b); + assert(l); + + if (!(d = avahi_dns_packet_extend(p, l))) + return NULL; + + memcpy(d, b, l); + return d; +} + +uint8_t* avahi_dns_packet_append_string(AvahiDnsPacket *p, const char *s) { + uint8_t* d; + size_t k; + + assert(p); + assert(s); + + if ((k = strlen(s)) >= 255) + k = 255; + + if (!(d = avahi_dns_packet_extend(p, k+1))) + return NULL; + + *d = (uint8_t) k; + memcpy(d+1, s, k); + + return d; +} + +uint8_t *avahi_dns_packet_extend(AvahiDnsPacket *p, size_t l) { + uint8_t *d; + + assert(p); + + if (p->size+l > p->max_size) + return NULL; + + d = AVAHI_DNS_PACKET_DATA(p) + p->size; + p->size += l; + + return d; +} + +int avahi_dns_packet_check_valid(AvahiDnsPacket *p) { + uint16_t flags; + assert(p); + + if (p->size < AVAHI_DNS_PACKET_HEADER_SIZE) + return -1; + + flags = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS); + + if (flags & AVAHI_DNS_FLAG_OPCODE) + return -1; + + return 0; +} + +int avahi_dns_packet_check_valid_multicast(AvahiDnsPacket *p) { + uint16_t flags; + assert(p); + + if (avahi_dns_packet_check_valid(p) < 0) + return -1; + + flags = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS); + + if (flags & AVAHI_DNS_FLAG_RCODE) + return -1; + + return 0; +} + +int avahi_dns_packet_is_query(AvahiDnsPacket *p) { + assert(p); + + return !(avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_QR); +} + +static int consume_labels(AvahiDnsPacket *p, unsigned idx, char *ret_name, size_t l) { + int ret = 0; + int compressed = 0; + int first_label = 1; + unsigned label_ptr; + int i; + assert(p && ret_name && l); + + for (i = 0; i < AVAHI_DNS_LABELS_MAX; i++) { + uint8_t n; + + if (idx+1 > p->size) + return -1; + + n = AVAHI_DNS_PACKET_DATA(p)[idx]; + + if (!n) { + idx++; + if (!compressed) + ret++; + + if (l < 1) + return -1; + *ret_name = 0; + + return ret; + + } else if (n <= 63) { + /* Uncompressed label */ + idx++; + if (!compressed) + ret++; + + if (idx + n > p->size) + return -1; + + if ((size_t) n + 1 > l) + return -1; + + if (!first_label) { + *(ret_name++) = '.'; + l--; + } else + first_label = 0; + + if (!(avahi_escape_label((char*) AVAHI_DNS_PACKET_DATA(p) + idx, n, &ret_name, &l))) + return -1; + + idx += n; + + if (!compressed) + ret += n; + } else if ((n & 0xC0) == 0xC0) { + /* Compressed label */ + + if (idx+2 > p->size) + return -1; + + label_ptr = ((unsigned) (AVAHI_DNS_PACKET_DATA(p)[idx] & ~0xC0)) << 8 | AVAHI_DNS_PACKET_DATA(p)[idx+1]; + + if ((label_ptr < AVAHI_DNS_PACKET_HEADER_SIZE) || (label_ptr >= idx)) + return -1; + + idx = label_ptr; + + if (!compressed) + ret += 2; + + compressed = 1; + } else + return -1; + } +} + +int avahi_dns_packet_consume_name(AvahiDnsPacket *p, char *ret_name, size_t l) { + int r; + + if ((r = consume_labels(p, p->rindex, ret_name, l)) < 0) + return -1; + + p->rindex += r; + return 0; +} + +int avahi_dns_packet_consume_uint16(AvahiDnsPacket *p, uint16_t *ret_v) { + uint8_t *d; + + assert(p); + assert(ret_v); + + if (p->rindex + sizeof(uint16_t) > p->size) + return -1; + + d = (uint8_t*) (AVAHI_DNS_PACKET_DATA(p) + p->rindex); + *ret_v = (d[0] << 8) | d[1]; + p->rindex += sizeof(uint16_t); + + return 0; +} + +int avahi_dns_packet_consume_uint32(AvahiDnsPacket *p, uint32_t *ret_v) { + uint8_t* d; + + assert(p); + assert(ret_v); + + if (p->rindex + sizeof(uint32_t) > p->size) + return -1; + + d = (uint8_t*) (AVAHI_DNS_PACKET_DATA(p) + p->rindex); + *ret_v = (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | d[3]; + p->rindex += sizeof(uint32_t); + + return 0; +} + +int avahi_dns_packet_consume_bytes(AvahiDnsPacket *p, void * ret_data, size_t l) { + assert(p); + assert(ret_data); + assert(l > 0); + + if (p->rindex + l > p->size) + return -1; + + memcpy(ret_data, AVAHI_DNS_PACKET_DATA(p) + p->rindex, l); + p->rindex += l; + + return 0; +} + +int avahi_dns_packet_consume_string(AvahiDnsPacket *p, char *ret_string, size_t l) { + size_t k; + + assert(p); + assert(ret_string); + assert(l > 0); + + if (p->rindex >= p->size) + return -1; + + k = AVAHI_DNS_PACKET_DATA(p)[p->rindex]; + + if (p->rindex+1+k > p->size) + return -1; + + if (l > k+1) + l = k+1; + + memcpy(ret_string, AVAHI_DNS_PACKET_DATA(p)+p->rindex+1, l-1); + ret_string[l-1] = 0; + + p->rindex += 1+k; + + return 0; +} + +const void* avahi_dns_packet_get_rptr(AvahiDnsPacket *p) { + assert(p); + + if (p->rindex > p->size) + return NULL; + + return AVAHI_DNS_PACKET_DATA(p) + p->rindex; +} + +int avahi_dns_packet_skip(AvahiDnsPacket *p, size_t length) { + assert(p); + + if (p->rindex + length > p->size) + return -1; + + p->rindex += length; + return 0; +} + +static int parse_rdata(AvahiDnsPacket *p, AvahiRecord *r, uint16_t rdlength) { + char buf[AVAHI_DOMAIN_NAME_MAX]; + const void* start; + + assert(p); + assert(r); + + start = avahi_dns_packet_get_rptr(p); + + switch (r->key->type) { + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + + if (avahi_dns_packet_consume_name(p, buf, sizeof(buf)) < 0) + return -1; + + r->data.ptr.name = avahi_strdup(buf); + break; + + + case AVAHI_DNS_TYPE_SRV: + + if (avahi_dns_packet_consume_uint16(p, &r->data.srv.priority) < 0 || + avahi_dns_packet_consume_uint16(p, &r->data.srv.weight) < 0 || + avahi_dns_packet_consume_uint16(p, &r->data.srv.port) < 0 || + avahi_dns_packet_consume_name(p, buf, sizeof(buf)) < 0) + return -1; + + r->data.srv.name = avahi_strdup(buf); + break; + + case AVAHI_DNS_TYPE_HINFO: + + if (avahi_dns_packet_consume_string(p, buf, sizeof(buf)) < 0) + return -1; + + r->data.hinfo.cpu = avahi_strdup(buf); + + if (avahi_dns_packet_consume_string(p, buf, sizeof(buf)) < 0) + return -1; + + r->data.hinfo.os = avahi_strdup(buf); + break; + + case AVAHI_DNS_TYPE_TXT: + + if (rdlength > 0) { + if (avahi_string_list_parse(avahi_dns_packet_get_rptr(p), rdlength, &r->data.txt.string_list) < 0) + return -1; + + if (avahi_dns_packet_skip(p, rdlength) < 0) + return -1; + } else + r->data.txt.string_list = NULL; + + break; + + case AVAHI_DNS_TYPE_A: + +/* avahi_log_debug("A"); */ + + if (avahi_dns_packet_consume_bytes(p, &r->data.a.address, sizeof(AvahiIPv4Address)) < 0) + return -1; + + break; + + case AVAHI_DNS_TYPE_AAAA: + +/* avahi_log_debug("aaaa"); */ + + if (avahi_dns_packet_consume_bytes(p, &r->data.aaaa.address, sizeof(AvahiIPv6Address)) < 0) + return -1; + + break; + + default: + +/* avahi_log_debug("generic"); */ + + if (rdlength > 0) { + + r->data.generic.data = avahi_memdup(avahi_dns_packet_get_rptr(p), rdlength); + + if (avahi_dns_packet_skip(p, rdlength) < 0) + return -1; + } + + break; + } + + /* Check if we read enough data */ + if ((const uint8_t*) avahi_dns_packet_get_rptr(p) - (const uint8_t*) start != rdlength) + return -1; + + return 0; +} + +AvahiRecord* avahi_dns_packet_consume_record(AvahiDnsPacket *p, int *ret_cache_flush) { + char name[AVAHI_DOMAIN_NAME_MAX]; + uint16_t type, class; + uint32_t ttl; + uint16_t rdlength; + AvahiRecord *r = NULL; + + assert(p); + + if (avahi_dns_packet_consume_name(p, name, sizeof(name)) < 0 || + avahi_dns_packet_consume_uint16(p, &type) < 0 || + avahi_dns_packet_consume_uint16(p, &class) < 0 || + avahi_dns_packet_consume_uint32(p, &ttl) < 0 || + avahi_dns_packet_consume_uint16(p, &rdlength) < 0 || + p->rindex + rdlength > p->size) + goto fail; + + if (ret_cache_flush) + *ret_cache_flush = !!(class & AVAHI_DNS_CACHE_FLUSH); + class &= ~AVAHI_DNS_CACHE_FLUSH; + + if (!(r = avahi_record_new_full(name, class, type, ttl))) + goto fail; + + if (parse_rdata(p, r, rdlength) < 0) + goto fail; + + if (!avahi_record_is_valid(r)) + goto fail; + + return r; + +fail: + if (r) + avahi_record_unref(r); + + return NULL; +} + +AvahiKey* avahi_dns_packet_consume_key(AvahiDnsPacket *p, int *ret_unicast_response) { + char name[256]; + uint16_t type, class; + AvahiKey *k; + + assert(p); + + if (avahi_dns_packet_consume_name(p, name, sizeof(name)) < 0 || + avahi_dns_packet_consume_uint16(p, &type) < 0 || + avahi_dns_packet_consume_uint16(p, &class) < 0) + return NULL; + + if (ret_unicast_response) + *ret_unicast_response = !!(class & AVAHI_DNS_UNICAST_RESPONSE); + + class &= ~AVAHI_DNS_UNICAST_RESPONSE; + + if (!(k = avahi_key_new(name, class, type))) + return NULL; + + if (!avahi_key_is_valid(k)) { + avahi_key_unref(k); + return NULL; + } + + return k; +} + +uint8_t* avahi_dns_packet_append_key(AvahiDnsPacket *p, AvahiKey *k, int unicast_response) { + uint8_t *t; + size_t size; + + assert(p); + assert(k); + + size = p->size; + + if (!(t = avahi_dns_packet_append_name(p, k->name)) || + !avahi_dns_packet_append_uint16(p, k->type) || + !avahi_dns_packet_append_uint16(p, k->clazz | (unicast_response ? AVAHI_DNS_UNICAST_RESPONSE : 0))) { + p->size = size; + return NULL; + } + + return t; +} + +static int append_rdata(AvahiDnsPacket *p, AvahiRecord *r) { + assert(p); + assert(r); + + switch (r->key->type) { + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + + if (!(avahi_dns_packet_append_name(p, r->data.ptr.name))) + return -1; + + break; + + case AVAHI_DNS_TYPE_SRV: + + if (!avahi_dns_packet_append_uint16(p, r->data.srv.priority) || + !avahi_dns_packet_append_uint16(p, r->data.srv.weight) || + !avahi_dns_packet_append_uint16(p, r->data.srv.port) || + !avahi_dns_packet_append_name(p, r->data.srv.name)) + return -1; + + break; + + case AVAHI_DNS_TYPE_HINFO: + if (!avahi_dns_packet_append_string(p, r->data.hinfo.cpu) || + !avahi_dns_packet_append_string(p, r->data.hinfo.os)) + return -1; + + break; + + case AVAHI_DNS_TYPE_TXT: { + + uint8_t *data; + size_t n; + + n = avahi_string_list_serialize(r->data.txt.string_list, NULL, 0); + + if (!(data = avahi_dns_packet_extend(p, n))) + return -1; + + avahi_string_list_serialize(r->data.txt.string_list, data, n); + break; + } + + + case AVAHI_DNS_TYPE_A: + + if (!avahi_dns_packet_append_bytes(p, &r->data.a.address, sizeof(r->data.a.address))) + return -1; + + break; + + case AVAHI_DNS_TYPE_AAAA: + + if (!avahi_dns_packet_append_bytes(p, &r->data.aaaa.address, sizeof(r->data.aaaa.address))) + return -1; + + break; + + default: + + if (r->data.generic.size) + if (avahi_dns_packet_append_bytes(p, r->data.generic.data, r->data.generic.size)) + return -1; + + break; + } + + return 0; +} + + +uint8_t* avahi_dns_packet_append_record(AvahiDnsPacket *p, AvahiRecord *r, int cache_flush, unsigned max_ttl) { + uint8_t *t, *l, *start; + size_t size; + + assert(p); + assert(r); + + size = p->size; + + if (!(t = avahi_dns_packet_append_name(p, r->key->name)) || + !avahi_dns_packet_append_uint16(p, r->key->type) || + !avahi_dns_packet_append_uint16(p, cache_flush ? (r->key->clazz | AVAHI_DNS_CACHE_FLUSH) : (r->key->clazz &~ AVAHI_DNS_CACHE_FLUSH)) || + !avahi_dns_packet_append_uint32(p, (max_ttl && r->ttl > max_ttl) ? max_ttl : r->ttl) || + !(l = avahi_dns_packet_append_uint16(p, 0))) + goto fail; + + start = avahi_dns_packet_extend(p, 0); + + if (append_rdata(p, r) < 0) + goto fail; + + size = avahi_dns_packet_extend(p, 0) - start; + assert(size <= 0xFFFF); + +/* avahi_log_debug("appended %u", size); */ + + l[0] = (uint8_t) ((uint16_t) size >> 8); + l[1] = (uint8_t) ((uint16_t) size); + + return t; + + +fail: + p->size = size; + return NULL; +} + +int avahi_dns_packet_is_empty(AvahiDnsPacket *p) { + assert(p); + + return p->size <= AVAHI_DNS_PACKET_HEADER_SIZE; +} + +size_t avahi_dns_packet_space(AvahiDnsPacket *p) { + assert(p); + + assert(p->size <= p->max_size); + + return p->max_size - p->size; +} + +int avahi_rdata_parse(AvahiRecord *record, const void* rdata, size_t size) { + int ret; + AvahiDnsPacket p; + + assert(record); + assert(rdata); + + p.data = (void*) rdata; + p.max_size = p.size = size; + p.rindex = 0; + p.name_table = NULL; + + ret = parse_rdata(&p, record, size); + + assert(!p.name_table); + + return ret; +} + +size_t avahi_rdata_serialize(AvahiRecord *record, void *rdata, size_t max_size) { + int ret; + AvahiDnsPacket p; + + assert(record); + assert(rdata); + assert(max_size > 0); + + p.data = (void*) rdata; + p.max_size = max_size; + p.size = p.rindex = 0; + p.name_table = NULL; + + ret = append_rdata(&p, record); + + if (p.name_table) + avahi_hashmap_free(p.name_table); + + if (ret < 0) + return (size_t) -1; + + return p.size; +} diff --git a/trunk/avahi-core/dns.h b/trunk/avahi-core/dns.h new file mode 100644 index 0000000..d1c06a5 --- /dev/null +++ b/trunk/avahi-core/dns.h @@ -0,0 +1,112 @@ +#ifndef foodnshfoo +#define foodnshfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "rr.h" +#include "hashmap.h" + +#define AVAHI_DNS_PACKET_SIZE_MAX 9000 +#define AVAHI_DNS_PACKET_HEADER_SIZE 12 +#define AVAHI_DNS_PACKET_EXTRA_SIZE 48 +#define AVAHI_DNS_LABELS_MAX 127 + +typedef struct AvahiDnsPacket { + size_t size, rindex, max_size; + AvahiHashmap *name_table; /* for name compression */ + uint8_t *data; +} AvahiDnsPacket; + +#define AVAHI_DNS_PACKET_DATA(p) ((p)->data ? (p)->data : ((uint8_t*) p) + sizeof(AvahiDnsPacket)) + +AvahiDnsPacket* avahi_dns_packet_new(unsigned mtu); +AvahiDnsPacket* avahi_dns_packet_new_query(unsigned mtu); +AvahiDnsPacket* avahi_dns_packet_new_response(unsigned mtu, int aa); + +AvahiDnsPacket* avahi_dns_packet_new_reply(AvahiDnsPacket* p, unsigned mtu, int copy_queries, int aa); + +void avahi_dns_packet_free(AvahiDnsPacket *p); +void avahi_dns_packet_set_field(AvahiDnsPacket *p, unsigned idx, uint16_t v); +uint16_t avahi_dns_packet_get_field(AvahiDnsPacket *p, unsigned idx); +void avahi_dns_packet_inc_field(AvahiDnsPacket *p, unsigned idx); + +uint8_t *avahi_dns_packet_extend(AvahiDnsPacket *p, size_t l); + +uint8_t *avahi_dns_packet_append_uint16(AvahiDnsPacket *p, uint16_t v); +uint8_t *avahi_dns_packet_append_uint32(AvahiDnsPacket *p, uint32_t v); +uint8_t *avahi_dns_packet_append_name(AvahiDnsPacket *p, const char *name); +uint8_t *avahi_dns_packet_append_bytes(AvahiDnsPacket *p, const void *d, size_t l); +uint8_t* avahi_dns_packet_append_key(AvahiDnsPacket *p, AvahiKey *k, int unicast_response); +uint8_t* avahi_dns_packet_append_record(AvahiDnsPacket *p, AvahiRecord *r, int cache_flush, unsigned max_ttl); +uint8_t* avahi_dns_packet_append_string(AvahiDnsPacket *p, const char *s); + +int avahi_dns_packet_is_query(AvahiDnsPacket *p); +int avahi_dns_packet_check_valid(AvahiDnsPacket *p); +int avahi_dns_packet_check_valid_multicast(AvahiDnsPacket *p); + +int avahi_dns_packet_consume_uint16(AvahiDnsPacket *p, uint16_t *ret_v); +int avahi_dns_packet_consume_uint32(AvahiDnsPacket *p, uint32_t *ret_v); +int avahi_dns_packet_consume_name(AvahiDnsPacket *p, char *ret_name, size_t l); +int avahi_dns_packet_consume_bytes(AvahiDnsPacket *p, void* ret_data, size_t l); +AvahiKey* avahi_dns_packet_consume_key(AvahiDnsPacket *p, int *ret_unicast_response); +AvahiRecord* avahi_dns_packet_consume_record(AvahiDnsPacket *p, int *ret_cache_flush); +int avahi_dns_packet_consume_string(AvahiDnsPacket *p, char *ret_string, size_t l); + +const void* avahi_dns_packet_get_rptr(AvahiDnsPacket *p); + +int avahi_dns_packet_skip(AvahiDnsPacket *p, size_t length); + +int avahi_dns_packet_is_empty(AvahiDnsPacket *p); +size_t avahi_dns_packet_space(AvahiDnsPacket *p); + +#define AVAHI_DNS_FIELD_ID 0 +#define AVAHI_DNS_FIELD_FLAGS 1 +#define AVAHI_DNS_FIELD_QDCOUNT 2 +#define AVAHI_DNS_FIELD_ANCOUNT 3 +#define AVAHI_DNS_FIELD_NSCOUNT 4 +#define AVAHI_DNS_FIELD_ARCOUNT 5 + +#define AVAHI_DNS_FLAG_QR (1 << 15) +#define AVAHI_DNS_FLAG_OPCODE (15 << 11) +#define AVAHI_DNS_FLAG_RCODE (15) +#define AVAHI_DNS_FLAG_TC (1 << 9) +#define AVAHI_DNS_FLAG_AA (1 << 10) + +#define AVAHI_DNS_FLAGS(qr, opcode, aa, tc, rd, ra, z, ad, cd, rcode) \ + (((uint16_t) !!qr << 15) | \ + ((uint16_t) (opcode & 15) << 11) | \ + ((uint16_t) !!aa << 10) | \ + ((uint16_t) !!tc << 9) | \ + ((uint16_t) !!rd << 8) | \ + ((uint16_t) !!ra << 7) | \ + ((uint16_t) !!ad << 5) | \ + ((uint16_t) !!cd << 4) | \ + ((uint16_t) (rcode & 15))) + +#define AVAHI_MDNS_SUFFIX_LOCAL "local" +#define AVAHI_MDNS_SUFFIX_ADDR_IPV4 "254.169.in-addr.arpa" +#define AVAHI_MDNS_SUFFIX_ADDR_IPV6 "0.8.e.f.ip6.arpa" + +#define AVAHI_DNS_RDATA_MAX 65535 + +#endif + diff --git a/trunk/avahi-core/domain-util.c b/trunk/avahi-core/domain-util.c new file mode 100644 index 0000000..3aeba12 --- /dev/null +++ b/trunk/avahi-core/domain-util.c @@ -0,0 +1,135 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <assert.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include <sys/utsname.h> +#include <stdio.h> + +#include <avahi-common/malloc.h> + +#include "domain-util.h" +#include "util.h" + +static void strip_bad_chars(char *s) { + char *p, *d; + + s[strcspn(s, ".")] = 0; + + for (p = s, d = s; *p; p++) + if ((*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') || + (*p >= '0' && *p <= '9') || + *p == '-') + *(d++) = *p; + + *d = 0; +} + +char *avahi_get_host_name(char *ret_s, size_t size) { + assert(ret_s); + assert(size > 0); + + if (gethostname(ret_s, size) >= 0) { + ret_s[size-1] = 0; + strip_bad_chars(ret_s); + } else + *ret_s = 0; + + if (*ret_s == 0) { + struct utsname utsname; + + /* No hostname was set, so let's take the OS name */ + + if (uname(&utsname) >= 0) { + snprintf(ret_s, size, "%s", utsname.sysname); + strip_bad_chars(ret_s); + avahi_strdown(ret_s); + } + + if (*ret_s == 0) + snprintf(ret_s, size, "unnamed"); + } + + if (size >= AVAHI_LABEL_MAX) + ret_s[AVAHI_LABEL_MAX-1] = 0; + + return ret_s; +} + +char *avahi_get_host_name_strdup(void) { + char t[AVAHI_DOMAIN_NAME_MAX]; + + if (!(avahi_get_host_name(t, sizeof(t)))) + return NULL; + + return avahi_strdup(t); +} + +int avahi_binary_domain_cmp(const char *a, const char *b) { + assert(a); + assert(b); + + if (a == b) + return 0; + + for (;;) { + char ca[AVAHI_LABEL_MAX], cb[AVAHI_LABEL_MAX], *p; + int r; + + p = avahi_unescape_label(&a, ca, sizeof(ca)); + assert(p); + p = avahi_unescape_label(&b, cb, sizeof(cb)); + assert(p); + + if ((r = strcmp(ca, cb))) + return r; + + if (!*a && !*b) + return 0; + } +} + +int avahi_domain_ends_with(const char *domain, const char *suffix) { + assert(domain); + assert(suffix); + + for (;;) { + char dummy[AVAHI_LABEL_MAX], *r; + + if (*domain == 0) + return 0; + + if (avahi_domain_equal(domain, suffix)) + return 1; + + r = avahi_unescape_label(&domain, dummy, sizeof(dummy)); + assert(r); + } +} + diff --git a/trunk/avahi-core/domain-util.h b/trunk/avahi-core/domain-util.h new file mode 100644 index 0000000..01233d8 --- /dev/null +++ b/trunk/avahi-core/domain-util.h @@ -0,0 +1,47 @@ +#ifndef foodomainutilhfoo +#define foodomainutilhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <inttypes.h> +#include <sys/types.h> + +#include <avahi-common/cdecl.h> +#include <avahi-common/domain.h> + +AVAHI_C_DECL_BEGIN + +/** Return the local host name. */ +char *avahi_get_host_name(char *ret_s, size_t size); + +/** Return the local host name. avahi_free() the result! */ +char *avahi_get_host_name_strdup(void); + +/** Do a binary comparison of to specified domain names, return -1, 0, or 1, depending on the order. */ +int avahi_binary_domain_cmp(const char *a, const char *b); + +/** Returns 1 if the the end labels of domain are eqal to suffix */ +int avahi_domain_ends_with(const char *domain, const char *suffix); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/entry.c b/trunk/avahi-core/entry.c new file mode 100644 index 0000000..9e9c0dd --- /dev/null +++ b/trunk/avahi-core/entry.c @@ -0,0 +1,1206 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> + +#include <arpa/inet.h> + +#include <sys/utsname.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <avahi-common/domain.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> + +#include "internal.h" +#include "iface.h" +#include "socket.h" +#include "browse.h" +#include "log.h" +#include "util.h" +#include "dns-srv-rr.h" +#include "rr-util.h" +#include "domain-util.h" + +static void transport_flags_from_domain(AvahiServer *s, AvahiPublishFlags *flags, const char *domain) { + assert(flags); + assert(domain); + + assert(!((*flags & AVAHI_PUBLISH_USE_MULTICAST) && (*flags & AVAHI_PUBLISH_USE_WIDE_AREA))); + + if (*flags & (AVAHI_PUBLISH_USE_MULTICAST|AVAHI_PUBLISH_USE_WIDE_AREA)) + return; + + if (!s->wide_area_lookup_engine || + !avahi_wide_area_has_servers(s->wide_area_lookup_engine) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_LOCAL) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV4) || + avahi_domain_ends_with(domain, AVAHI_MDNS_SUFFIX_ADDR_IPV6)) + *flags |= AVAHI_PUBLISH_USE_MULTICAST; + else + *flags |= AVAHI_PUBLISH_USE_WIDE_AREA; +} + +void avahi_entry_free(AvahiServer*s, AvahiEntry *e) { + AvahiEntry *t; + + assert(s); + assert(e); + + avahi_goodbye_entry(s, e, 1, 1); + + /* Remove from linked list */ + AVAHI_LLIST_REMOVE(AvahiEntry, entries, s->entries, e); + + /* Remove from hash table indexed by name */ + t = avahi_hashmap_lookup(s->entries_by_key, e->record->key); + AVAHI_LLIST_REMOVE(AvahiEntry, by_key, t, e); + if (t) + avahi_hashmap_replace(s->entries_by_key, t->record->key, t); + else + avahi_hashmap_remove(s->entries_by_key, e->record->key); + + /* Remove from associated group */ + if (e->group) + AVAHI_LLIST_REMOVE(AvahiEntry, by_group, e->group->entries, e); + + avahi_record_unref(e->record); + avahi_free(e); +} + +void avahi_entry_group_free(AvahiServer *s, AvahiSEntryGroup *g) { + assert(s); + assert(g); + + while (g->entries) + avahi_entry_free(s, g->entries); + + if (g->register_time_event) + avahi_time_event_free(g->register_time_event); + + AVAHI_LLIST_REMOVE(AvahiSEntryGroup, groups, s->groups, g); + avahi_free(g); +} + +void avahi_cleanup_dead_entries(AvahiServer *s) { + assert(s); + + if (s->need_group_cleanup) { + AvahiSEntryGroup *g, *next; + + for (g = s->groups; g; g = next) { + next = g->groups_next; + + if (g->dead) + avahi_entry_group_free(s, g); + } + + s->need_group_cleanup = 0; + } + + if (s->need_entry_cleanup) { + AvahiEntry *e, *next; + + for (e = s->entries; e; e = next) { + next = e->entries_next; + + if (e->dead) + avahi_entry_free(s, e); + } + + s->need_entry_cleanup = 0; + } + + if (s->need_browser_cleanup) + avahi_browser_cleanup(s); +} + +static int check_record_conflict(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, AvahiRecord *r, AvahiPublishFlags flags) { + AvahiEntry *e; + + assert(s); + assert(r); + + for (e = avahi_hashmap_lookup(s->entries_by_key, r->key); e; e = e->by_key_next) { + if (e->dead) + continue; + + if (!(flags & AVAHI_PUBLISH_UNIQUE) && !(e->flags & AVAHI_PUBLISH_UNIQUE)) + continue; + + if ((flags & AVAHI_PUBLISH_ALLOW_MULTIPLE) && (e->flags & AVAHI_PUBLISH_ALLOW_MULTIPLE) ) + continue; + + if ((interface <= 0 || + e->interface <= 0 || + e->interface == interface) && + (protocol == AVAHI_PROTO_UNSPEC || + e->protocol == AVAHI_PROTO_UNSPEC || + e->protocol == protocol)) + + return -1; + } + + return 0; +} + +static AvahiEntry * server_add_internal( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + AvahiRecord *r) { + + AvahiEntry *e; + + assert(s); + assert(r); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, s->state != AVAHI_SERVER_FAILURE && s->state != AVAHI_SERVER_INVALID, AVAHI_ERR_BAD_STATE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, AVAHI_FLAGS_VALID( + flags, + AVAHI_PUBLISH_NO_ANNOUNCE| + AVAHI_PUBLISH_NO_PROBE| + AVAHI_PUBLISH_UNIQUE| + AVAHI_PUBLISH_ALLOW_MULTIPLE| + AVAHI_PUBLISH_UPDATE| + AVAHI_PUBLISH_USE_WIDE_AREA| + AVAHI_PUBLISH_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, avahi_is_valid_domain_name(r->key->name), AVAHI_ERR_INVALID_HOST_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, r->ttl != 0, AVAHI_ERR_INVALID_TTL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, !avahi_key_is_pattern(r->key), AVAHI_ERR_IS_PATTERN); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, avahi_record_is_valid(r), AVAHI_ERR_INVALID_RECORD); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, r->key->clazz == AVAHI_DNS_CLASS_IN, AVAHI_ERR_INVALID_DNS_CLASS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, + (r->key->type != 0) && + (r->key->type != AVAHI_DNS_TYPE_ANY) && + (r->key->type != AVAHI_DNS_TYPE_OPT) && + (r->key->type != AVAHI_DNS_TYPE_TKEY) && + (r->key->type != AVAHI_DNS_TYPE_TSIG) && + (r->key->type != AVAHI_DNS_TYPE_IXFR) && + (r->key->type != AVAHI_DNS_TYPE_AXFR), AVAHI_ERR_INVALID_DNS_TYPE); + + transport_flags_from_domain(s, &flags, r->key->name); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, !s->config.disable_publishing, AVAHI_ERR_NOT_PERMITTED); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, + !g || + (g->state != AVAHI_ENTRY_GROUP_ESTABLISHED && g->state != AVAHI_ENTRY_GROUP_REGISTERING) || + (flags & AVAHI_PUBLISH_UPDATE), AVAHI_ERR_BAD_STATE); + + if (flags & AVAHI_PUBLISH_UPDATE) { + AvahiRecord *old_record; + int is_first = 1; + + /* Update and existing record */ + + /* Find the first matching entry */ + for (e = avahi_hashmap_lookup(s->entries_by_key, r->key); e; e = e->by_key_next) { + if (!e->dead && e->group == g && e->interface == interface && e->protocol == protocol) + break; + + is_first = 0; + } + + /* Hmm, nothing found? */ + if (!e) { + avahi_server_set_errno(s, AVAHI_ERR_NOT_FOUND); + return NULL; + } + + /* Update the entry */ + old_record = e->record; + e->record = avahi_record_ref(r); + e->flags = flags; + + /* Announce our changes when needed */ + if (!avahi_record_equal_no_ttl(old_record, r) && (!g || g->state != AVAHI_ENTRY_GROUP_UNCOMMITED)) { + + /* Remove the old entry from all caches, if needed */ + if (!(e->flags & AVAHI_PUBLISH_UNIQUE)) + avahi_goodbye_entry(s, e, 1, 0); + + /* Reannounce our updated entry */ + avahi_reannounce_entry(s, e); + } + + /* If we were the first entry in the list, we need to update the key */ + if (is_first) + avahi_hashmap_replace(s->entries_by_key, e->record->key, e); + + avahi_record_unref(old_record); + + } else { + AvahiEntry *t; + + /* Add a new record */ + + if (check_record_conflict(s, interface, protocol, r, flags) < 0) { + avahi_server_set_errno(s, AVAHI_ERR_COLLISION); + return NULL; + } + + if (!(e = avahi_new(AvahiEntry, 1))) { + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + e->server = s; + e->record = avahi_record_ref(r); + e->group = g; + e->interface = interface; + e->protocol = protocol; + e->flags = flags; + e->dead = 0; + + AVAHI_LLIST_HEAD_INIT(AvahiAnnouncer, e->announcers); + + AVAHI_LLIST_PREPEND(AvahiEntry, entries, s->entries, e); + + /* Insert into hash table indexed by name */ + t = avahi_hashmap_lookup(s->entries_by_key, e->record->key); + AVAHI_LLIST_PREPEND(AvahiEntry, by_key, t, e); + avahi_hashmap_replace(s->entries_by_key, e->record->key, t); + + /* Insert into group list */ + if (g) + AVAHI_LLIST_PREPEND(AvahiEntry, by_group, g->entries, e); + + avahi_announce_entry(s, e); + } + + return e; +} + +int avahi_server_add( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + AvahiRecord *r) { + + if (!server_add_internal(s, g, interface, protocol, flags, r)) + return avahi_server_errno(s); + + return AVAHI_OK; +} + +const AvahiRecord *avahi_server_iterate(AvahiServer *s, AvahiSEntryGroup *g, void **state) { + AvahiEntry **e = (AvahiEntry**) state; + assert(s); + assert(e); + + if (!*e) + *e = g ? g->entries : s->entries; + + while (*e && (*e)->dead) + *e = g ? (*e)->by_group_next : (*e)->entries_next; + + if (!*e) + return NULL; + + return avahi_record_ref((*e)->record); +} + +int avahi_server_dump(AvahiServer *s, AvahiDumpCallback callback, void* userdata) { + AvahiEntry *e; + + assert(s); + assert(callback); + + callback(";;; ZONE DUMP FOLLOWS ;;;", userdata); + + for (e = s->entries; e; e = e->entries_next) { + char *t; + char ln[256]; + + if (e->dead) + continue; + + if (!(t = avahi_record_to_string(e->record))) + return avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + + snprintf(ln, sizeof(ln), "%s ; iface=%i proto=%i", t, e->interface, e->protocol); + avahi_free(t); + + callback(ln, userdata); + } + + avahi_dump_caches(s->monitor, callback, userdata); + + if (s->wide_area_lookup_engine) + avahi_wide_area_cache_dump(s->wide_area_lookup_engine, callback, userdata); + return AVAHI_OK; +} + +static AvahiEntry *server_add_ptr_internal( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + uint32_t ttl, + const char *name, + const char *dest) { + + AvahiRecord *r; + AvahiEntry *e; + + assert(s); + assert(dest); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, !name || avahi_is_valid_domain_name(name), AVAHI_ERR_INVALID_HOST_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, avahi_is_valid_domain_name(dest), AVAHI_ERR_INVALID_HOST_NAME); + + if (!name) + name = s->host_name_fqdn; + + if (!(r = avahi_record_new_full(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR, ttl))) { + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + r->data.ptr.name = avahi_normalize_name_strdup(dest); + e = server_add_internal(s, g, interface, protocol, flags, r); + avahi_record_unref(r); + return e; +} + +int avahi_server_add_ptr( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + uint32_t ttl, + const char *name, + const char *dest) { + + AvahiEntry *e; + + assert(s); + + if (!(e = server_add_ptr_internal(s, g, interface, protocol, flags, ttl, name, dest))) + return avahi_server_errno(s); + + return AVAHI_OK; +} + +int avahi_server_add_address( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + AvahiAddress *a) { + + char n[AVAHI_DOMAIN_NAME_MAX]; + int ret = AVAHI_OK; + AvahiEntry *entry = NULL, *reverse = NULL; + AvahiRecord *r; + + assert(s); + assert(a); + + AVAHI_CHECK_VALIDITY(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY(s, AVAHI_PROTO_VALID(protocol) && AVAHI_PROTO_VALID(a->proto), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY(s, AVAHI_FLAGS_VALID(flags, + AVAHI_PUBLISH_NO_REVERSE| + AVAHI_PUBLISH_NO_ANNOUNCE| + AVAHI_PUBLISH_NO_PROBE| + AVAHI_PUBLISH_UPDATE| + AVAHI_PUBLISH_USE_WIDE_AREA| + AVAHI_PUBLISH_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY(s, !name || avahi_is_valid_fqdn(name), AVAHI_ERR_INVALID_HOST_NAME); + + /* Prepare the host naem */ + + if (!name) + name = s->host_name_fqdn; + else { + AVAHI_ASSERT_TRUE(avahi_normalize_name(name, n, sizeof(n))); + name = n; + } + + transport_flags_from_domain(s, &flags, name); + AVAHI_CHECK_VALIDITY(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + /* Create the A/AAAA record */ + + if (a->proto == AVAHI_PROTO_INET) { + + if (!(r = avahi_record_new_full(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A, AVAHI_DEFAULT_TTL_HOST_NAME))) { + ret = avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + goto finish; + } + + r->data.a.address = a->data.ipv4; + + } else { + assert(a->proto == AVAHI_PROTO_INET6); + + if (!(r = avahi_record_new_full(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA, AVAHI_DEFAULT_TTL_HOST_NAME))) { + ret = avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + goto finish; + } + + r->data.aaaa.address = a->data.ipv6; + } + + entry = server_add_internal(s, g, interface, protocol, (flags & ~ AVAHI_PUBLISH_NO_REVERSE) | AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_ALLOW_MULTIPLE, r); + avahi_record_unref(r); + + if (!entry) { + ret = avahi_server_errno(s); + goto finish; + } + + /* Create the reverse lookup entry */ + + if (!(flags & AVAHI_PUBLISH_NO_REVERSE)) { + char reverse_n[AVAHI_DOMAIN_NAME_MAX]; + avahi_reverse_lookup_name(a, reverse_n, sizeof(reverse_n)); + + if (!(reverse = server_add_ptr_internal(s, g, interface, protocol, flags | AVAHI_PUBLISH_UNIQUE, AVAHI_DEFAULT_TTL_HOST_NAME, reverse_n, name))) { + ret = avahi_server_errno(s); + goto finish; + } + } + +finish: + + if (ret != AVAHI_OK && !(flags & AVAHI_PUBLISH_UPDATE)) { + if (entry) + avahi_entry_free(s, entry); + if (reverse) + avahi_entry_free(s, reverse); + } + + return ret; +} + +static AvahiEntry *server_add_txt_strlst_nocopy( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + uint32_t ttl, + const char *name, + AvahiStringList *strlst) { + + AvahiRecord *r; + AvahiEntry *e; + + assert(s); + + if (!(r = avahi_record_new_full(name ? name : s->host_name_fqdn, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT, ttl))) { + avahi_string_list_free(strlst); + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + r->data.txt.string_list = strlst; + e = server_add_internal(s, g, interface, protocol, flags, r); + avahi_record_unref(r); + + return e; +} + +static AvahiStringList *add_magic_cookie( + AvahiServer *s, + AvahiStringList *strlst) { + + assert(s); + + if (!s->config.add_service_cookie) + return strlst; + + if (avahi_string_list_find(strlst, AVAHI_SERVICE_COOKIE)) + /* This string list already contains a magic cookie */ + return strlst; + + return avahi_string_list_add_printf(strlst, AVAHI_SERVICE_COOKIE"=%u", s->local_service_cookie); +} + +static int server_add_service_strlst_nocopy( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *host, + uint16_t port, + AvahiStringList *strlst) { + + char ptr_name[AVAHI_DOMAIN_NAME_MAX], svc_name[AVAHI_DOMAIN_NAME_MAX], enum_ptr[AVAHI_DOMAIN_NAME_MAX], *h = NULL; + AvahiRecord *r = NULL; + int ret = AVAHI_OK; + AvahiEntry *srv_entry = NULL, *txt_entry = NULL, *ptr_entry = NULL, *enum_entry = NULL; + + assert(s); + assert(type); + assert(name); + + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_FLAGS_VALID(flags, + AVAHI_PUBLISH_NO_COOKIE| + AVAHI_PUBLISH_UPDATE| + AVAHI_PUBLISH_USE_WIDE_AREA| + AVAHI_PUBLISH_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, !host || avahi_is_valid_fqdn(host), AVAHI_ERR_INVALID_HOST_NAME); + + if (!domain) + domain = s->domain_name; + + if (!host) + host = s->host_name_fqdn; + + transport_flags_from_domain(s, &flags, domain); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + if (!(h = avahi_normalize_name_strdup(host))) { + ret = avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + if ((ret = avahi_service_name_join(svc_name, sizeof(svc_name), name, type, domain)) < 0 || + (ret = avahi_service_name_join(ptr_name, sizeof(ptr_name), NULL, type, domain)) < 0 || + (ret = avahi_service_name_join(enum_ptr, sizeof(enum_ptr), NULL, "_services._dns-sd._udp", domain)) < 0) { + avahi_server_set_errno(s, ret); + goto fail; + } + + /* Add service enumeration PTR record */ + + if (!(ptr_entry = server_add_ptr_internal(s, g, interface, protocol, 0, AVAHI_DEFAULT_TTL, ptr_name, svc_name))) { + ret = avahi_server_errno(s); + goto fail; + } + + /* Add SRV record */ + + if (!(r = avahi_record_new_full(svc_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV, AVAHI_DEFAULT_TTL_HOST_NAME))) { + ret = avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + goto fail; + } + + r->data.srv.priority = 0; + r->data.srv.weight = 0; + r->data.srv.port = port; + r->data.srv.name = h; + h = NULL; + srv_entry = server_add_internal(s, g, interface, protocol, AVAHI_PUBLISH_UNIQUE, r); + avahi_record_unref(r); + + if (!srv_entry) { + ret = avahi_server_errno(s); + goto fail; + } + + /* Add TXT record */ + + if (!(flags & AVAHI_PUBLISH_NO_COOKIE)) + strlst = add_magic_cookie(s, strlst); + + txt_entry = server_add_txt_strlst_nocopy(s, g, interface, protocol, AVAHI_PUBLISH_UNIQUE, AVAHI_DEFAULT_TTL, svc_name, strlst); + strlst = NULL; + + if (!txt_entry) { + ret = avahi_server_errno(s); + goto fail; + } + + /* Add service type enumeration record */ + + if (!(enum_entry = server_add_ptr_internal(s, g, interface, protocol, 0, AVAHI_DEFAULT_TTL, enum_ptr, ptr_name))) { + ret = avahi_server_errno(s); + goto fail; + } + +fail: + if (ret != AVAHI_OK && !(flags & AVAHI_PUBLISH_UPDATE)) { + if (srv_entry) + avahi_entry_free(s, srv_entry); + if (txt_entry) + avahi_entry_free(s, txt_entry); + if (ptr_entry) + avahi_entry_free(s, ptr_entry); + if (enum_entry) + avahi_entry_free(s, enum_entry); + } + + avahi_string_list_free(strlst); + avahi_free(h); + + return ret; +} + +int avahi_server_add_service_strlst( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *host, + uint16_t port, + AvahiStringList *strlst) { + + assert(s); + assert(type); + assert(name); + + return server_add_service_strlst_nocopy(s, g, interface, protocol, flags, name, type, domain, host, port, avahi_string_list_copy(strlst)); +} + +int avahi_server_add_service( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *host, + uint16_t port, + ... ){ + + va_list va; + int ret; + + va_start(va, port); + ret = server_add_service_strlst_nocopy(s, g, interface, protocol, flags, name, type, domain, host, port, avahi_string_list_new_va(va)); + va_end(va); + + return ret; +} + +static int server_update_service_txt_strlst_nocopy( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + AvahiStringList *strlst) { + + char svc_name[AVAHI_DOMAIN_NAME_MAX]; + int ret = AVAHI_OK; + AvahiEntry *e; + + assert(s); + assert(type); + assert(name); + + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_FLAGS_VALID(flags, + AVAHI_PUBLISH_NO_COOKIE| + AVAHI_PUBLISH_USE_WIDE_AREA| + AVAHI_PUBLISH_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + + if (!domain) + domain = s->domain_name; + + transport_flags_from_domain(s, &flags, domain); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + if ((ret = avahi_service_name_join(svc_name, sizeof(svc_name), name, type, domain)) < 0) { + avahi_server_set_errno(s, ret); + goto fail; + } + + /* Add TXT record */ + if (!(flags & AVAHI_PUBLISH_NO_COOKIE)) + strlst = add_magic_cookie(s, strlst); + + e = server_add_txt_strlst_nocopy(s, g, interface, protocol, AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_UPDATE, AVAHI_DEFAULT_TTL, svc_name, strlst); + strlst = NULL; + + if (!e) + ret = avahi_server_errno(s); + +fail: + + avahi_string_list_free(strlst); + + return ret; +} + +int avahi_server_update_service_txt_strlst( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + AvahiStringList *strlst) { + + return server_update_service_txt_strlst_nocopy(s, g, interface, protocol, flags, name, type, domain, avahi_string_list_copy(strlst)); +} + +/** Update the TXT record for a service with the NULL termonate list of strings */ +int avahi_server_update_service_txt( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + ...) { + + va_list va; + int ret; + + va_start(va, domain); + ret = server_update_service_txt_strlst_nocopy(s, g, interface, protocol, flags, name, type, domain, avahi_string_list_new_va(va)); + va_end(va); + + return ret; +} + +int avahi_server_add_service_subtype( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *subtype) { + + int ret = AVAHI_OK; + char svc_name[AVAHI_DOMAIN_NAME_MAX], ptr_name[AVAHI_DOMAIN_NAME_MAX]; + + assert(name); + assert(type); + assert(subtype); + + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, AVAHI_FLAGS_VALID(flags, AVAHI_PUBLISH_USE_MULTICAST|AVAHI_PUBLISH_USE_WIDE_AREA), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, avahi_is_valid_service_subtype(subtype), AVAHI_ERR_INVALID_SERVICE_SUBTYPE); + + if (!domain) + domain = s->domain_name; + + transport_flags_from_domain(s, &flags, domain); + AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + if ((ret = avahi_service_name_join(svc_name, sizeof(svc_name), name, type, domain)) < 0 || + (ret = avahi_service_name_join(ptr_name, sizeof(ptr_name), NULL, subtype, domain)) < 0) { + avahi_server_set_errno(s, ret); + goto fail; + } + + if ((ret = avahi_server_add_ptr(s, g, interface, protocol, 0, AVAHI_DEFAULT_TTL, ptr_name, svc_name)) < 0) + goto fail; + +fail: + + return ret; +} + +static void hexstring(char *s, size_t sl, const void *p, size_t pl) { + static const char hex[] = "0123456789abcdef"; + int b = 0; + const uint8_t *k = p; + + while (sl > 1 && pl > 0) { + *(s++) = hex[(b ? *k : *k >> 4) & 0xF]; + + if (b) { + k++; + pl--; + } + + b = !b; + + sl--; + } + + if (sl > 0) + *s = 0; +} + +static AvahiEntry *server_add_dns_server_name( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *domain, + AvahiDNSServerType type, + const char *name, + uint16_t port /** should be 53 */) { + + AvahiEntry *e; + char t[AVAHI_DOMAIN_NAME_MAX], normalized_d[AVAHI_DOMAIN_NAME_MAX], *n; + + AvahiRecord *r; + + assert(s); + assert(name); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, AVAHI_FLAGS_VALID(flags, AVAHI_PUBLISH_USE_WIDE_AREA|AVAHI_PUBLISH_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, type == AVAHI_DNS_SERVER_UPDATE || type == AVAHI_DNS_SERVER_RESOLVE, AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, port != 0, AVAHI_ERR_INVALID_PORT); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, avahi_is_valid_fqdn(name), AVAHI_ERR_INVALID_HOST_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + + if (!domain) + domain = s->domain_name; + + transport_flags_from_domain(s, &flags, domain); + AVAHI_CHECK_VALIDITY_RETURN_NULL(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + if (!(n = avahi_normalize_name_strdup(name))) { + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + AVAHI_ASSERT_TRUE(avahi_normalize_name(domain, normalized_d, sizeof(normalized_d))); + + snprintf(t, sizeof(t), "%s.%s", type == AVAHI_DNS_SERVER_RESOLVE ? "_domain._udp" : "_dns-update._udp", normalized_d); + + if (!(r = avahi_record_new_full(t, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV, AVAHI_DEFAULT_TTL_HOST_NAME))) { + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + avahi_free(n); + return NULL; + } + + r->data.srv.priority = 0; + r->data.srv.weight = 0; + r->data.srv.port = port; + r->data.srv.name = n; + e = server_add_internal(s, g, interface, protocol, 0, r); + avahi_record_unref(r); + + return e; +} + +int avahi_server_add_dns_server_address( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *domain, + AvahiDNSServerType type, + const AvahiAddress *address, + uint16_t port /** should be 53 */) { + + AvahiRecord *r; + char n[64], h[64]; + AvahiEntry *a_entry, *s_entry; + + assert(s); + assert(address); + + AVAHI_CHECK_VALIDITY(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY(s, AVAHI_PROTO_VALID(protocol) && AVAHI_PROTO_VALID(address->proto), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY(s, AVAHI_FLAGS_VALID(flags, AVAHI_PUBLISH_USE_MULTICAST|AVAHI_PUBLISH_USE_WIDE_AREA), AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY(s, type == AVAHI_DNS_SERVER_UPDATE || type == AVAHI_DNS_SERVER_RESOLVE, AVAHI_ERR_INVALID_FLAGS); + AVAHI_CHECK_VALIDITY(s, port != 0, AVAHI_ERR_INVALID_PORT); + AVAHI_CHECK_VALIDITY(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + + if (!domain) + domain = s->domain_name; + + transport_flags_from_domain(s, &flags, domain); + AVAHI_CHECK_VALIDITY(s, flags & AVAHI_PUBLISH_USE_MULTICAST, AVAHI_ERR_NOT_SUPPORTED); + + if (address->proto == AVAHI_PROTO_INET) { + hexstring(h, sizeof(h), &address->data, sizeof(AvahiIPv4Address)); + snprintf(n, sizeof(n), "ip-%s.%s", h, domain); + r = avahi_record_new_full(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A, AVAHI_DEFAULT_TTL_HOST_NAME); + r->data.a.address = address->data.ipv4; + } else { + hexstring(h, sizeof(h), &address->data, sizeof(AvahiIPv6Address)); + snprintf(n, sizeof(n), "ip6-%s.%s", h, domain); + r = avahi_record_new_full(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA, AVAHI_DEFAULT_TTL_HOST_NAME); + r->data.aaaa.address = address->data.ipv6; + } + + if (!r) + return avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + + a_entry = server_add_internal(s, g, interface, protocol, AVAHI_PUBLISH_UNIQUE | AVAHI_PUBLISH_ALLOW_MULTIPLE, r); + avahi_record_unref(r); + + if (!a_entry) + return avahi_server_errno(s); + + if (!(s_entry = server_add_dns_server_name(s, g, interface, protocol, flags, domain, type, n, port))) { + if (!(flags & AVAHI_PUBLISH_UPDATE)) + avahi_entry_free(s, a_entry); + return avahi_server_errno(s); + } + + return AVAHI_OK; +} + +void avahi_s_entry_group_change_state(AvahiSEntryGroup *g, AvahiEntryGroupState state) { + assert(g); + + if (g->state == state) + return; + + assert(state <= AVAHI_ENTRY_GROUP_COLLISION); + + if (g->state == AVAHI_ENTRY_GROUP_ESTABLISHED) { + + /* If the entry group was established for a time longer then + * 5s, reset the establishment trial counter */ + + if (avahi_age(&g->established_at) > 5000000) + g->n_register_try = 0; + } + + if (state == AVAHI_ENTRY_GROUP_ESTABLISHED) + + /* If the entry group is now established, remember the time + * this happened */ + + gettimeofday(&g->established_at, NULL); + + g->state = state; + + if (g->callback) + g->callback(g->server, g, state, g->userdata); +} + +AvahiSEntryGroup *avahi_s_entry_group_new(AvahiServer *s, AvahiSEntryGroupCallback callback, void* userdata) { + AvahiSEntryGroup *g; + + assert(s); + + if (!(g = avahi_new(AvahiSEntryGroup, 1))) { + avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + g->server = s; + g->callback = callback; + g->userdata = userdata; + g->dead = 0; + g->state = AVAHI_ENTRY_GROUP_UNCOMMITED; + g->n_probing = 0; + g->n_register_try = 0; + g->register_time_event = NULL; + g->register_time.tv_sec = 0; + g->register_time.tv_usec = 0; + AVAHI_LLIST_HEAD_INIT(AvahiEntry, g->entries); + + AVAHI_LLIST_PREPEND(AvahiSEntryGroup, groups, s->groups, g); + return g; +} + +void avahi_s_entry_group_free(AvahiSEntryGroup *g) { + AvahiEntry *e; + + assert(g); + assert(g->server); + + for (e = g->entries; e; e = e->by_group_next) { + if (!e->dead) { + avahi_goodbye_entry(g->server, e, 1, 1); + e->dead = 1; + } + } + + if (g->register_time_event) { + avahi_time_event_free(g->register_time_event); + g->register_time_event = NULL; + } + + g->dead = 1; + + g->server->need_group_cleanup = 1; + g->server->need_entry_cleanup = 1; +} + +static void entry_group_commit_real(AvahiSEntryGroup *g) { + assert(g); + + gettimeofday(&g->register_time, NULL); + + avahi_s_entry_group_change_state(g, AVAHI_ENTRY_GROUP_REGISTERING); + + if (g->dead) + return; + + avahi_announce_group(g->server, g); + avahi_s_entry_group_check_probed(g, 0); +} + +static void entry_group_register_time_event_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* userdata) { + AvahiSEntryGroup *g = userdata; + assert(g); + + avahi_time_event_free(g->register_time_event); + g->register_time_event = NULL; + + /* Holdoff time passed, so let's start probing */ + entry_group_commit_real(g); +} + +int avahi_s_entry_group_commit(AvahiSEntryGroup *g) { + struct timeval now; + + assert(g); + assert(!g->dead); + + if (g->state != AVAHI_ENTRY_GROUP_UNCOMMITED && g->state != AVAHI_ENTRY_GROUP_COLLISION) + return avahi_server_set_errno(g->server, AVAHI_ERR_BAD_STATE); + + if (avahi_s_entry_group_is_empty(g)) + return avahi_server_set_errno(g->server, AVAHI_ERR_IS_EMPTY); + + g->n_register_try++; + + avahi_timeval_add(&g->register_time, + 1000*(g->n_register_try >= AVAHI_RR_RATE_LIMIT_COUNT ? + AVAHI_RR_HOLDOFF_MSEC_RATE_LIMIT : + AVAHI_RR_HOLDOFF_MSEC)); + + gettimeofday(&now, NULL); + + if (avahi_timeval_compare(&g->register_time, &now) <= 0) { + + /* Holdoff time passed, so let's start probing */ + entry_group_commit_real(g); + } else { + + /* Holdoff time has not yet passed, so let's wait */ + assert(!g->register_time_event); + g->register_time_event = avahi_time_event_new(g->server->time_event_queue, &g->register_time, entry_group_register_time_event_callback, g); + + avahi_s_entry_group_change_state(g, AVAHI_ENTRY_GROUP_REGISTERING); + } + + return AVAHI_OK; +} + +void avahi_s_entry_group_reset(AvahiSEntryGroup *g) { + AvahiEntry *e; + assert(g); + + for (e = g->entries; e; e = e->by_group_next) { + if (!e->dead) { + avahi_goodbye_entry(g->server, e, 1, 1); + e->dead = 1; + } + } + g->server->need_entry_cleanup = 1; + + if (g->register_time_event) { + avahi_time_event_free(g->register_time_event); + g->register_time_event = NULL; + } + + g->n_probing = 0; + + gettimeofday(&g->register_time, NULL); + + avahi_s_entry_group_change_state(g, AVAHI_ENTRY_GROUP_UNCOMMITED); +} + +int avahi_entry_is_commited(AvahiEntry *e) { + assert(e); + assert(!e->dead); + + return !e->group || + e->group->state == AVAHI_ENTRY_GROUP_REGISTERING || + e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED; +} + +AvahiEntryGroupState avahi_s_entry_group_get_state(AvahiSEntryGroup *g) { + assert(g); + assert(!g->dead); + + return g->state; +} + +void avahi_s_entry_group_set_data(AvahiSEntryGroup *g, void* userdata) { + assert(g); + + g->userdata = userdata; +} + +void* avahi_s_entry_group_get_data(AvahiSEntryGroup *g) { + assert(g); + + return g->userdata; +} + +int avahi_s_entry_group_is_empty(AvahiSEntryGroup *g) { + AvahiEntry *e; + assert(g); + + /* Look for an entry that is not dead */ + for (e = g->entries; e; e = e->by_group_next) + if (!e->dead) + return 0; + + return 1; +} diff --git a/trunk/avahi-core/fdutil.c b/trunk/avahi-core/fdutil.c new file mode 100644 index 0000000..de7b0cf --- /dev/null +++ b/trunk/avahi-core/fdutil.c @@ -0,0 +1,73 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <fcntl.h> +#include <assert.h> + +#include "fdutil.h" + +int avahi_set_cloexec(int fd) { + int n; + + assert(fd >= 0); + + if ((n = fcntl(fd, F_GETFD)) < 0) + return -1; + + if (n & FD_CLOEXEC) + return 0; + + return fcntl(fd, F_SETFD, n|FD_CLOEXEC); +} + +int avahi_set_nonblock(int fd) { + int n; + + assert(fd >= 0); + + if ((n = fcntl(fd, F_GETFL)) < 0) + return -1; + + if (n & O_NONBLOCK) + return 0; + + return fcntl(fd, F_SETFL, n|O_NONBLOCK); +} + +int avahi_wait_for_write(int fd) { + fd_set fds; + int r; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if ((r = select(fd+1, NULL, &fds, NULL, NULL)) < 0) + return -1; + + assert(r > 0); + + return 0; +} diff --git a/trunk/avahi-core/fdutil.h b/trunk/avahi-core/fdutil.h new file mode 100644 index 0000000..047b9bb --- /dev/null +++ b/trunk/avahi-core/fdutil.h @@ -0,0 +1,35 @@ +#ifndef foofdutilhfoo +#define foofdutilhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <avahi-common/cdecl.h> + +AVAHI_C_DECL_BEGIN + +int avahi_set_cloexec(int fd); +int avahi_set_nonblock(int fd); +int avahi_wait_for_write(int fd); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/findstatic.pl b/trunk/avahi-core/findstatic.pl new file mode 100755 index 0000000..43a4916 --- /dev/null +++ b/trunk/avahi-core/findstatic.pl @@ -0,0 +1,70 @@ +#!/usr/bin/perl -w +# find a list of fns and variables in the code that could be static +# usually called with something like this: +# findstatic.pl `find . -name "*.o"` +# Andrew Tridgell <tridge@samba.org> + +use strict; + +# use nm to find the symbols +my($saved_delim) = $/; +undef $/; +my($syms) = `nm -o @ARGV`; +$/ = $saved_delim; + +my(@lines) = split(/\n/s, $syms); + +my(%def); +my(%undef); +my(%stype); + +my(%typemap) = ( + "T" => "function", + "C" => "uninitialised variable", + "D" => "initialised variable" + ); + + +# parse the symbols into defined and undefined +for (my($i)=0; $i <= $#{@lines}; $i++) { + my($line) = $lines[$i]; + if ($line =~ /(.*):[a-f0-9]* ([TCD]) (.*)/) { + my($fname) = $1; + my($symbol) = $3; + push(@{$def{$fname}}, $symbol); + $stype{$symbol} = $2; + } + if ($line =~ /(.*):\s* U (.*)/) { + my($fname) = $1; + my($symbol) = $2; + push(@{$undef{$fname}}, $symbol); + } +} + +# look for defined symbols that are never referenced outside the place they +# are defined +foreach my $f (keys %def) { + print "Checking $f\n"; + my($found_one) = 0; + foreach my $s (@{$def{$f}}) { + my($found) = 0; + foreach my $f2 (keys %undef) { + if ($f2 ne $f) { + foreach my $s2 (@{$undef{$f2}}) { + if ($s2 eq $s) { + $found = 1; + $found_one = 1; + } + } + } + } + if ($found == 0) { + my($t) = $typemap{$stype{$s}}; + print " '$s' is unique to $f ($t)\n"; + } + } + if ($found_one == 0) { + print " all symbols in '$f' are unused (main program?)\n"; + } +} + diff --git a/trunk/avahi-core/hashmap-test.c b/trunk/avahi-core/hashmap-test.c new file mode 100644 index 0000000..9826d53 --- /dev/null +++ b/trunk/avahi-core/hashmap-test.c @@ -0,0 +1,64 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include "hashmap.h" +#include "util.h" + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + unsigned n; + AvahiHashmap *m; + const char *t; + + m = avahi_hashmap_new(avahi_string_hash, avahi_string_equal, avahi_free, avahi_free); + + avahi_hashmap_insert(m, avahi_strdup("bla"), avahi_strdup("#1")); + avahi_hashmap_insert(m, avahi_strdup("bla2"), avahi_strdup("asdf")); + avahi_hashmap_insert(m, avahi_strdup("gurke"), avahi_strdup("ffsdf")); + avahi_hashmap_insert(m, avahi_strdup("blubb"), avahi_strdup("sadfsd")); + avahi_hashmap_insert(m, avahi_strdup("bla"), avahi_strdup("#2")); + + for (n = 0; n < 1000; n ++) + avahi_hashmap_insert(m, avahi_strdup_printf("key %u", n), avahi_strdup_printf("value %u", n)); + + printf("%s\n", (const char*) avahi_hashmap_lookup(m, "bla")); + + avahi_hashmap_replace(m, avahi_strdup("bla"), avahi_strdup("#3")); + + printf("%s\n", (const char*) avahi_hashmap_lookup(m, "bla")); + + avahi_hashmap_remove(m, "bla"); + + t = (const char*) avahi_hashmap_lookup(m, "bla"); + printf("%s\n", t ? t : "(null)"); + + avahi_hashmap_free(m); + + return 0; +} diff --git a/trunk/avahi-core/hashmap.c b/trunk/avahi-core/hashmap.c new file mode 100644 index 0000000..07bd707 --- /dev/null +++ b/trunk/avahi-core/hashmap.c @@ -0,0 +1,250 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include <avahi-common/llist.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include "hashmap.h" +#include "util.h" + +#define HASH_MAP_SIZE 123 + +typedef struct Entry Entry; +struct Entry { + AvahiHashmap *hashmap; + void *key; + void *value; + + AVAHI_LLIST_FIELDS(Entry, bucket); + AVAHI_LLIST_FIELDS(Entry, entries); +}; + +struct AvahiHashmap { + AvahiHashFunc hash_func; + AvahiEqualFunc equal_func; + AvahiFreeFunc key_free_func, value_free_func; + + Entry *entries[HASH_MAP_SIZE]; + AVAHI_LLIST_HEAD(Entry, entries_list); +}; + +static Entry* entry_get(AvahiHashmap *m, const void *key) { + unsigned idx; + Entry *e; + + idx = m->hash_func(key) % HASH_MAP_SIZE; + + for (e = m->entries[idx]; e; e = e->bucket_next) + if (m->equal_func(key, e->key)) + return e; + + return NULL; +} + +static void entry_free(AvahiHashmap *m, Entry *e, int stolen) { + unsigned idx; + assert(m); + assert(e); + + idx = m->hash_func(e->key) % HASH_MAP_SIZE; + + AVAHI_LLIST_REMOVE(Entry, bucket, m->entries[idx], e); + AVAHI_LLIST_REMOVE(Entry, entries, m->entries_list, e); + + if (m->key_free_func) + m->key_free_func(e->key); + if (m->value_free_func && !stolen) + m->value_free_func(e->value); + + avahi_free(e); +} + +AvahiHashmap* avahi_hashmap_new(AvahiHashFunc hash_func, AvahiEqualFunc equal_func, AvahiFreeFunc key_free_func, AvahiFreeFunc value_free_func) { + AvahiHashmap *m; + + assert(hash_func); + assert(equal_func); + + if (!(m = avahi_new0(AvahiHashmap, 1))) + return NULL; + + m->hash_func = hash_func; + m->equal_func = equal_func; + m->key_free_func = key_free_func; + m->value_free_func = value_free_func; + + AVAHI_LLIST_HEAD_INIT(Entry, m->entries_list); + + return m; +} + +void avahi_hashmap_free(AvahiHashmap *m) { + assert(m); + + while (m->entries_list) + entry_free(m, m->entries_list, 0); + + avahi_free(m); +} + +void* avahi_hashmap_lookup(AvahiHashmap *m, const void *key) { + Entry *e; + + assert(m); + + if (!(e = entry_get(m, key))) + return NULL; + + return e->value; +} + +int avahi_hashmap_insert(AvahiHashmap *m, void *key, void *value) { + unsigned idx; + Entry *e; + + assert(m); + + if ((e = entry_get(m, key))) { + if (m->key_free_func) + m->key_free_func(key); + if (m->value_free_func) + m->value_free_func(value); + + return 1; + } + + if (!(e = avahi_new(Entry, 1))) + return -1; + + e->hashmap = m; + e->key = key; + e->value = value; + + AVAHI_LLIST_PREPEND(Entry, entries, m->entries_list, e); + + idx = m->hash_func(key) % HASH_MAP_SIZE; + AVAHI_LLIST_PREPEND(Entry, bucket, m->entries[idx], e); + + return 0; +} + + +int avahi_hashmap_replace(AvahiHashmap *m, void *key, void *value) { + unsigned idx; + Entry *e; + + assert(m); + + if ((e = entry_get(m, key))) { + if (m->key_free_func) + m->key_free_func(e->key); + if (m->value_free_func) + m->value_free_func(e->value); + + e->key = key; + e->value = value; + + return 1; + } + + if (!(e = avahi_new(Entry, 1))) + return -1; + + e->hashmap = m; + e->key = key; + e->value = value; + + AVAHI_LLIST_PREPEND(Entry, entries, m->entries_list, e); + + idx = m->hash_func(key) % HASH_MAP_SIZE; + AVAHI_LLIST_PREPEND(Entry, bucket, m->entries[idx], e); + + return 0; +} + +void avahi_hashmap_remove(AvahiHashmap *m, const void *key) { + Entry *e; + + assert(m); + + if (!(e = entry_get(m, key))) + return; + + entry_free(m, e, 0); +} + +void avahi_hashmap_foreach(AvahiHashmap *m, AvahiHashmapForeachCallback callback, void *userdata) { + Entry *e, *next; + assert(m); + assert(callback); + + for (e = m->entries_list; e; e = next) { + next = e->entries_next; + + callback(e->key, e->value, userdata); + } +} + +unsigned avahi_string_hash(const void *data) { + const char *p = data; + unsigned hash = 0; + + assert(p); + + for (; *p; p++) + hash = 31 * hash + *p; + + return hash; +} + +int avahi_string_equal(const void *a, const void *b) { + const char *p = a, *q = b; + + assert(p); + assert(q); + + return strcmp(p, q) == 0; +} + +unsigned avahi_int_hash(const void *data) { + const int *i = data; + + assert(i); + + return (unsigned) *i; +} + +int avahi_int_equal(const void *a, const void *b) { + const int *_a = a, *_b = b; + + assert(_a); + assert(_b); + + return *_a == *_b; +} diff --git a/trunk/avahi-core/hashmap.h b/trunk/avahi-core/hashmap.h new file mode 100644 index 0000000..120cf30 --- /dev/null +++ b/trunk/avahi-core/hashmap.h @@ -0,0 +1,55 @@ +#ifndef foohashmaphfoo +#define foohashmaphfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <avahi-common/cdecl.h> + +AVAHI_C_DECL_BEGIN + +typedef struct AvahiHashmap AvahiHashmap; + +typedef unsigned (*AvahiHashFunc)(const void *data); +typedef int (*AvahiEqualFunc)(const void *a, const void *b); +typedef void (*AvahiFreeFunc)(void *p); + +AvahiHashmap* avahi_hashmap_new(AvahiHashFunc hash_func, AvahiEqualFunc equal_func, AvahiFreeFunc key_free_func, AvahiFreeFunc value_free_func); + +void avahi_hashmap_free(AvahiHashmap *m); +void* avahi_hashmap_lookup(AvahiHashmap *m, const void *key); +int avahi_hashmap_insert(AvahiHashmap *m, void *key, void *value); +int avahi_hashmap_replace(AvahiHashmap *m, void *key, void *value); +void avahi_hashmap_remove(AvahiHashmap *m, const void *key); + +typedef void (*AvahiHashmapForeachCallback)(void *key, void *value, void *userdata); + +void avahi_hashmap_foreach(AvahiHashmap *m, AvahiHashmapForeachCallback callback, void *userdata); + +unsigned avahi_string_hash(const void *data); +int avahi_string_equal(const void *a, const void *b); + +unsigned avahi_int_hash(const void *data); +int avahi_int_equal(const void *a, const void *b); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/iface-linux.c b/trunk/avahi-core/iface-linux.c new file mode 100644 index 0000000..c8ed9e0 --- /dev/null +++ b/trunk/avahi-core/iface-linux.c @@ -0,0 +1,371 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <net/if.h> +#include <errno.h> +#include <string.h> + +#include <avahi-common/malloc.h> + +#include "log.h" +#include "iface.h" +#include "iface-linux.h" + +#ifndef IFLA_RTA +#include <linux/if_addr.h> +#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) +#endif + +#ifndef IFA_RTA +#include <linux/if_addr.h> +#define IFA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) +#endif + +static int netlink_list_items(AvahiNetlink *nl, uint16_t type, unsigned *ret_seq) { + struct nlmsghdr *n; + struct rtgenmsg *gen; + uint8_t req[1024]; + + /* Issue a wild dump NETLINK request */ + + memset(&req, 0, sizeof(req)); + n = (struct nlmsghdr*) req; + n->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)); + n->nlmsg_type = type; + n->nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST; + n->nlmsg_pid = 0; + + gen = NLMSG_DATA(n); + memset(gen, 0, sizeof(struct rtgenmsg)); + gen->rtgen_family = AF_UNSPEC; + + return avahi_netlink_send(nl, n, ret_seq); +} + +static void netlink_callback(AvahiNetlink *nl, struct nlmsghdr *n, void* userdata) { + AvahiInterfaceMonitor *m = userdata; + + /* This routine is called for every RTNETLINK response packet */ + + assert(m); + assert(n); + assert(m->osdep.netlink == nl); + + if (n->nlmsg_type == RTM_NEWLINK) { + + /* A new interface appeared or an existing one has been modified */ + + struct ifinfomsg *ifinfomsg = NLMSG_DATA(n); + AvahiHwInterface *hw; + struct rtattr *a = NULL; + size_t l; + + /* A (superfluous?) sanity check */ + if (ifinfomsg->ifi_family != AF_UNSPEC) + return; + + /* Check whether there already is an AvahiHwInterface object + * for this link, so that we can update its data. Note that + * Netlink sends us an RTM_NEWLINK not only when a new + * interface appears, but when it changes, too */ + + if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifinfomsg->ifi_index))) + + /* No object found, so let's create a new + * one. avahi_hw_interface_new() will call + * avahi_interface_new() internally twice for IPv4 and + * IPv6, so there is no need for us to do that + * ourselves */ + if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) ifinfomsg->ifi_index))) + return; /* OOM */ + + /* Check whether the flags of this interface are OK for us */ + hw->flags_ok = + (ifinfomsg->ifi_flags & IFF_UP) && + (!m->server->config.use_iff_running || (ifinfomsg->ifi_flags & IFF_RUNNING)) && + !(ifinfomsg->ifi_flags & IFF_LOOPBACK) && + (ifinfomsg->ifi_flags & IFF_MULTICAST) && + (m->server->config.allow_point_to_point || !(ifinfomsg->ifi_flags & IFF_POINTOPOINT)); + + /* Handle interface attributes */ + l = NLMSG_PAYLOAD(n, sizeof(struct ifinfomsg)); + a = IFLA_RTA(ifinfomsg); + + while (RTA_OK(a, l)) { + switch(a->rta_type) { + case IFLA_IFNAME: + + /* Fill in interface name */ + avahi_free(hw->name); + hw->name = avahi_strndup(RTA_DATA(a), RTA_PAYLOAD(a)); + break; + + case IFLA_MTU: + + /* Fill in MTU */ + assert(RTA_PAYLOAD(a) == sizeof(unsigned int)); + hw->mtu = *((unsigned int*) RTA_DATA(a)); + break; + + case IFLA_ADDRESS: + + /* Fill in hardware (MAC) address */ + hw->mac_address_size = RTA_PAYLOAD(a); + if (hw->mac_address_size > AVAHI_MAC_ADDRESS_MAX) + hw->mac_address_size = AVAHI_MAC_ADDRESS_MAX; + + memcpy(hw->mac_address, RTA_DATA(a), hw->mac_address_size); + break; + + default: + ; + } + + a = RTA_NEXT(a, l); + } + + /* Check whether this interface is now "relevant" for us. If + * it is Avahi will start to announce its records on this + * interface and send out queries for subscribed records on + * it */ + avahi_hw_interface_check_relevant(hw); + + /* Update any associated RRs of this interface. (i.e. the + * _workstation._tcp record containing the MAC address) */ + avahi_hw_interface_update_rrs(hw, 0); + + } else if (n->nlmsg_type == RTM_DELLINK) { + + /* An interface has been removed */ + + struct ifinfomsg *ifinfomsg = NLMSG_DATA(n); + AvahiHwInterface *hw; + + /* A (superfluous?) sanity check */ + if (ifinfomsg->ifi_family != AF_UNSPEC) + return; + + /* Get a reference to our AvahiHwInterface object of this interface */ + if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) ifinfomsg->ifi_index))) + return; + + /* Free our object */ + avahi_hw_interface_free(hw, 0); + + } else if (n->nlmsg_type == RTM_NEWADDR || n->nlmsg_type == RTM_DELADDR) { + + /* An address has been added, modified or removed */ + + struct ifaddrmsg *ifaddrmsg = NLMSG_DATA(n); + AvahiInterface *i; + struct rtattr *a = NULL; + size_t l; + AvahiAddress raddr; + int raddr_valid = 0; + + /* We are only interested in IPv4 and IPv6 */ + if (ifaddrmsg->ifa_family != AF_INET && ifaddrmsg->ifa_family != AF_INET6) + return; + + /* Try to get a reference to our AvahiInterface object for the + * interface this address is assigned to. If ther is no object + * for this interface, we ignore this address. */ + if (!(i = avahi_interface_monitor_get_interface(m, (AvahiIfIndex) ifaddrmsg->ifa_index, avahi_af_to_proto(ifaddrmsg->ifa_family)))) + return; + + /* Fill in address family for our new address */ + raddr.proto = avahi_af_to_proto(ifaddrmsg->ifa_family); + + l = NLMSG_PAYLOAD(n, sizeof(struct ifaddrmsg)); + a = IFA_RTA(ifaddrmsg); + + while (RTA_OK(a, l)) { + + switch(a->rta_type) { + case IFA_ADDRESS: + /* Fill in address data */ + + if ((raddr.proto == AVAHI_PROTO_INET6 && RTA_PAYLOAD(a) != 16) || + (raddr.proto == AVAHI_PROTO_INET && RTA_PAYLOAD(a) != 4)) + return; + + memcpy(raddr.data.data, RTA_DATA(a), RTA_PAYLOAD(a)); + raddr_valid = 1; + + break; + + default: + ; + } + + a = RTA_NEXT(a, l); + } + + /* If there was no adress attached to this message, let's quit. */ + if (!raddr_valid) + return; + + if (n->nlmsg_type == RTM_NEWADDR) { + AvahiInterfaceAddress *addr; + + /* This address is new or has been modified, so let's get an object for it */ + if (!(addr = avahi_interface_monitor_get_address(m, i, &raddr))) + + /* Mmm, no object existing yet, so let's create a new one */ + if (!(addr = avahi_interface_address_new(m, i, &raddr, ifaddrmsg->ifa_prefixlen))) + return; /* OOM */ + + /* Update the scope field for the address */ + addr->global_scope = ifaddrmsg->ifa_scope == RT_SCOPE_UNIVERSE || ifaddrmsg->ifa_scope == RT_SCOPE_SITE; + } else { + AvahiInterfaceAddress *addr; + assert(n->nlmsg_type == RTM_DELADDR); + + /* Try to get a reference to our AvahiInterfaceAddress object for this address */ + if (!(addr = avahi_interface_monitor_get_address(m, i, &raddr))) + return; + + /* And free it */ + avahi_interface_address_free(addr); + } + + /* Avahi only considers interfaces with at least one address + * attached relevant. Since we migh have added or removed an + * address, let's have it check again whether the interface is + * now relevant */ + avahi_interface_check_relevant(i); + + /* Update any associated RRs, like A or AAAA for our new/removed address */ + avahi_interface_update_rrs(i, 0); + + } else if (n->nlmsg_type == NLMSG_DONE) { + + /* This wild dump request ended, so let's see what we do next */ + + if (m->osdep.list == LIST_IFACE) { + + /* Mmmm, interfaces have been wild dumped already, so + * let's go on with wild dumping the addresses */ + + if (netlink_list_items(m->osdep.netlink, RTM_GETADDR, &m->osdep.query_addr_seq) < 0) { + avahi_log_warn("NETLINK: Failed to list addrs: %s", strerror(errno)); + m->osdep.list = LIST_DONE; + } else + + /* Update state information */ + m->osdep.list = LIST_ADDR; + + } else + /* We're done. Tell avahi_interface_monitor_sync() to finish. */ + m->osdep.list = LIST_DONE; + + if (m->osdep.list == LIST_DONE) { + + /* Only after this boolean variable has been set, Avahi + * will start to announce or browse on all interfaces. It + * is originaly set to 0, which means that relevancy + * checks and RR updates are disabled during the wild + * dumps. */ + m->list_complete = 1; + + /* So let's check if any interfaces are relevant now */ + avahi_interface_monitor_check_relevant(m); + + /* And update all RRs attached to any interface */ + avahi_interface_monitor_update_rrs(m, 0); + + /* Tell the user that the wild dump is complete */ + avahi_log_info("Network interface enumeration completed."); + } + + } else if (n->nlmsg_type == NLMSG_ERROR && + (n->nlmsg_seq == m->osdep.query_link_seq || n->nlmsg_seq == m->osdep.query_addr_seq)) { + struct nlmsgerr *e = NLMSG_DATA (n); + + /* Some kind of error happened. Let's just tell the user and + * ignore it otherwise */ + + if (e->error) + avahi_log_warn("NETLINK: Failed to browse: %s", strerror(-e->error)); + } +} + +int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m) { + assert(m); + + /* Initialize our own data */ + + m->osdep.netlink = NULL; + m->osdep.query_addr_seq = m->osdep.query_link_seq = 0; + + /* Create a netlink object for us. It abstracts some things and + * makes netlink easier to use. It will attach to the main loop + * for us and call netlink_callback() whenever an event + * happens. */ + if (!(m->osdep.netlink = avahi_netlink_new(m->server->poll_api, RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR, netlink_callback, m))) + goto fail; + + /* Set the initial state. */ + m->osdep.list = LIST_IFACE; + + /* Start the wild dump for the interfaces */ + if (netlink_list_items(m->osdep.netlink, RTM_GETLINK, &m->osdep.query_link_seq) < 0) + goto fail; + + return 0; + +fail: + + if (m->osdep.netlink) { + avahi_netlink_free(m->osdep.netlink); + m->osdep.netlink = NULL; + } + + return -1; +} + +void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m) { + assert(m); + + if (m->osdep.netlink) { + avahi_netlink_free(m->osdep.netlink); + m->osdep.netlink = NULL; + } +} + +void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) { + assert(m); + + /* Let's handle netlink events until we are done with wild + * dumping */ + + while (!m->list_complete) + if (!avahi_netlink_work(m->osdep.netlink, 1) == 0) + break; + + /* At this point Avahi knows about all local interfaces and + * addresses in existance. */ +} diff --git a/trunk/avahi-core/iface-linux.h b/trunk/avahi-core/iface-linux.h new file mode 100644 index 0000000..eed648c --- /dev/null +++ b/trunk/avahi-core/iface-linux.h @@ -0,0 +1,42 @@ +#ifndef fooifacelinuxhfoo +#define fooifacelinuxhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiInterfaceMonitorOSDep AvahiInterfaceMonitorOSDep; + +#include "netlink.h" + +struct AvahiInterfaceMonitorOSDep { + AvahiNetlink *netlink; + + unsigned query_addr_seq, query_link_seq; + + enum { + LIST_IFACE, + LIST_ADDR, + LIST_DONE + } list; +}; + + +#endif diff --git a/trunk/avahi-core/iface-pfroute.c b/trunk/avahi-core/iface-pfroute.c new file mode 100644 index 0000000..035e267 --- /dev/null +++ b/trunk/avahi-core/iface-pfroute.c @@ -0,0 +1,521 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <avahi-common/malloc.h> + +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/param.h> +#ifdef HAVE_SYS_SYSCTL_H +#include <sys/sysctl.h> +#else +#include <sys/sockio.h> +#endif + +#include <net/route.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <netinet/in.h> + +#include "log.h" +#include "iface.h" +#include "iface-pfroute.h" +#include "util.h" + +static int bitcount (unsigned int n) +{ + int count=0 ; + while (n) + { + count++ ; + n &= (n - 1) ; + } + return count ; +} + +static void rtm_info(struct rt_msghdr *rtm, AvahiInterfaceMonitor *m) +{ + AvahiHwInterface *hw; + struct if_msghdr *ifm = (struct if_msghdr *)rtm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(ifm + 1); + + if (sdl->sdl_family != AF_LINK) + return; + + if (ifm->ifm_addrs == 0 && ifm->ifm_index > 0) { + if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) ifm->ifm_index))) + return; + avahi_hw_interface_free(hw, 0); + return; + } + + if (!(hw = avahi_interface_monitor_get_hw_interface(m, ifm->ifm_index))) + if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) ifm->ifm_index))) + return; /* OOM */ + + hw->flags_ok = + (ifm->ifm_flags & IFF_UP) && + (!m->server->config.use_iff_running || (ifm->ifm_flags & IFF_RUNNING)) && + !(ifm->ifm_flags & IFF_LOOPBACK) && + (ifm->ifm_flags & IFF_MULTICAST) && + (m->server->config.allow_point_to_point || !(ifm->ifm_flags & IFF_POINTOPOINT)); + + avahi_free(hw->name); + hw->name = avahi_strndup(sdl->sdl_data, sdl->sdl_nlen); + + hw->mtu = ifm->ifm_data.ifi_mtu; + + hw->mac_address_size = sdl->sdl_alen; + if (hw->mac_address_size > AVAHI_MAC_ADDRESS_MAX) + hw->mac_address_size = AVAHI_MAC_ADDRESS_MAX; + + memcpy(hw->mac_address, sdl->sdl_data + sdl->sdl_nlen, hw->mac_address_size); + +/* { */ +/* char mac[256]; */ +/* avahi_log_debug("======\n name: %s\n index:%d\n mtu:%d\n mac:%s\n flags_ok:%d\n======", */ +/* hw->name, hw->index, */ +/* hw->mtu, */ +/* avahi_format_mac_address(mac, sizeof(mac), hw->mac_address, hw->mac_address_size), */ +/* hw->flags_ok); */ +/* } */ + + avahi_hw_interface_check_relevant(hw); + avahi_hw_interface_update_rrs(hw, 0); +} + +#define ROUNDUP(a) \ + ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) +#ifdef HAVE_SYS_SYSCTL_H +#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len)) +#else +#define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr))) +#endif + +static void rtm_addr(struct rt_msghdr *rtm, AvahiInterfaceMonitor *m) +{ + AvahiInterface *iface; + AvahiAddress raddr; + int raddr_valid = 0; + struct ifa_msghdr *ifam = (struct ifa_msghdr *) rtm; + char *cp = (char *)(ifam + 1); + int addrs = ifam->ifam_addrs; + int i; + int prefixlen = 0; + struct sockaddr *sa =NULL; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + if(((struct sockaddr *)cp)->sa_family == AF_UNSPEC) + ((struct sockaddr *)cp)->sa_family = AF_INET; +#endif + + if(((struct sockaddr *)cp)->sa_family != AF_INET && ((struct sockaddr *)cp)->sa_family != AF_INET6) + return; + + if (!(iface = avahi_interface_monitor_get_interface(m, (AvahiIfIndex) ifam->ifam_index, avahi_af_to_proto(((struct sockaddr *)cp)->sa_family)))) + return; + + raddr.proto = avahi_af_to_proto(((struct sockaddr *)cp)->sa_family); + + for(i = 0; addrs != 0 && i < RTAX_MAX; addrs &= ~(1<<i), i++) + { + if (!(addrs & 1<<i)) + continue; + sa = (struct sockaddr *)cp; +#ifdef HAVE_SYS_SYSCTL_H + if (sa->sa_len == 0) + continue; +#endif + switch(sa->sa_family) { + case AF_INET: + switch (1<<i) { + case RTA_NETMASK: + prefixlen = bitcount((unsigned int)((struct sockaddr_in *)sa)->sin_addr.s_addr); + break; + case RTA_IFA: + memcpy(raddr.data.data, &((struct sockaddr_in *)sa)->sin_addr, sizeof(struct in_addr)); + raddr_valid = 1; + default: + break; + } + break; + case AF_INET6: + switch (1<<i) { + case RTA_NETMASK: + prefixlen = bitcount((unsigned int)((struct sockaddr_in6 *)sa)->sin6_addr.s6_addr); + break; + case RTA_IFA: + memcpy(raddr.data.data, &((struct sockaddr_in6 *)sa)->sin6_addr, sizeof(struct in6_addr)); + raddr_valid = 1; + default: + break; + } + break; + default: + break; + } +#ifdef SA_SIZE + cp += SA_SIZE(sa); +#else + ADVANCE(cp, sa); +#endif + } + + if (!raddr_valid) + return; + + if(rtm->rtm_type == RTM_NEWADDR) + { + AvahiInterfaceAddress *addriface; + if (!(addriface = avahi_interface_monitor_get_address(m, iface, &raddr))) + if (!(addriface = avahi_interface_address_new(m, iface, &raddr, prefixlen))) + return; /* OOM */ + /* FIXME */ + /* addriface->global_scope = ifaddrmsg->ifa_scope == RT_SCOPE_UNIVERSE || ifaddrmsg->ifa_scope == RT_SCOPE_SITE; */ + addriface->global_scope = 1; + } + else + { + AvahiInterfaceAddress *addriface; + assert(rtm->rtm_type == RTM_DELADDR); + if (!(addriface = avahi_interface_monitor_get_address(m, iface, &raddr))) + return; + avahi_interface_address_free(addriface); + } + + avahi_interface_check_relevant(iface); + avahi_interface_update_rrs(iface, 0); +} + +static void parse_rtmsg(struct rt_msghdr *rtm, AvahiInterfaceMonitor *m) +{ + assert(m); + assert(rtm); + + if (rtm->rtm_version != RTM_VERSION) { + avahi_log_warn("routing message version %d not understood", + rtm->rtm_version); + return; + } + + switch (rtm->rtm_type) { + case RTM_IFINFO: + rtm_info(rtm,m); + break; + case RTM_NEWADDR: + case RTM_DELADDR: + rtm_addr(rtm,m); + break; + default: + break; + } +} + +static void socket_event(AvahiWatch *w, int fd, AVAHI_GCC_UNUSED AvahiWatchEvent event,void *userdata) { + AvahiInterfaceMonitor *m = (AvahiInterfaceMonitor *)userdata; + AvahiPfRoute *nl = m->osdep.pfroute; + ssize_t bytes; + char msg[2048]; + + assert(m); + assert(w); + assert(nl); + assert(fd == nl->fd); + + do { + if((bytes = recv(nl->fd, msg, 2048, MSG_DONTWAIT)) < 0) { + if (errno == EAGAIN || errno == EINTR) + return; + avahi_log_error(__FILE__": recv() failed: %s", strerror(errno)); + return; + } + parse_rtmsg((struct rt_msghdr *)msg, m); + } + while (bytes > 0); +} + +int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m) { + int fd = -1; + m->osdep.pfroute = NULL; + + assert(m); + + if ((fd = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC)) < 0) { + avahi_log_error(__FILE__": socket(PF_ROUTE): %s", strerror(errno)); + goto fail; + } + + if (!(m->osdep.pfroute = avahi_new(AvahiPfRoute , 1))) { + avahi_log_error(__FILE__": avahi_new() failed."); + goto fail; + } + m->osdep.pfroute->fd = fd; + + if (!(m->osdep.pfroute->watch = m->server->poll_api->watch_new(m->server->poll_api, + m->osdep.pfroute->fd, + AVAHI_WATCH_IN, + socket_event, + m))) { + avahi_log_error(__FILE__": Failed to create watch."); + goto fail; + } + + return 0; + +fail: + + if (m->osdep.pfroute) { + if (m->osdep.pfroute->watch) + m->server->poll_api->watch_free(m->osdep.pfroute->watch); + + if (fd >= 0) + close(fd); + + m->osdep.pfroute = NULL; + } + + return -1; +} + +void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m) { + assert(m); + + if (m->osdep.pfroute) { + if (m->osdep.pfroute->watch) + m->server->poll_api->watch_free(m->osdep.pfroute->watch); + + if (m->osdep.pfroute->fd >= 0) + close(m->osdep.pfroute->fd); + + avahi_free(m->osdep.pfroute); + m->osdep.pfroute = NULL; + } +} + +#if defined (SIOCGLIFNUM) && defined(HAVE_STRUCT_LIFCONF) /* Solaris 8 and later; Sol 7? */ +/* + * I got this function from GNU zsbra + */ +static int ip6_masklen (struct in6_addr netmask) { + int len = 0; + unsigned char val; + unsigned char *pnt; + + pnt = (unsigned char *) & netmask; + + while ((*pnt == 0xff) && len < 128) { + len += 8; + pnt++; + } + + if (len < 128) { + val = *pnt; + while (val) { + len++; + val <<= 1; + } + } + return len; +} + +static void if_add_interface(struct lifreq *lifreq, AvahiInterfaceMonitor *m, int fd, int count) +{ + AvahiHwInterface *hw; + AvahiAddress addr; + struct lifreq lifrcopy; + unsigned int index; + int flags; + int mtu; + int prefixlen; + AvahiInterfaceAddress *addriface; + AvahiInterface *iface; + struct sockaddr_in mask; + struct sockaddr_in6 mask6; + char caddr[AVAHI_ADDRESS_STR_MAX]; + + lifrcopy = *lifreq; + + if (ioctl(fd, SIOCGLIFFLAGS, &lifrcopy) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFFLAGS) %s", strerror(errno)); + return; + } + flags = lifrcopy.lifr_flags; + + if (ioctl(fd, SIOCGLIFMTU, &lifrcopy) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFMTU) %s", strerror(errno)); + return; + } + mtu = lifrcopy.lifr_metric; + + if (ioctl(fd, SIOCGLIFADDR, &lifrcopy) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFADDR) %s", strerror(errno)); + return; + } + addr.proto = avahi_af_to_proto(lifreq->lifr_addr.ss_family); + if (ioctl(fd, SIOCGLIFNETMASK, &lifrcopy) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFNETMASK) %s", strerror(errno)); + return; + } + switch (lifreq->lifr_addr.ss_family) { + case AF_INET: + memcpy(addr.data.data, &((struct sockaddr_in *)&lifreq->lifr_addr)->sin_addr, sizeof(struct in_addr)); + memcpy(&mask, &((struct sockaddr_in *)&lifrcopy.lifr_addr)->sin_addr, sizeof(struct in_addr)); + prefixlen = bitcount((unsigned int) mask.sin_addr.s_addr); + break; + case AF_INET6: + memcpy(addr.data.data, &((struct sockaddr_in6 *)&lifreq->lifr_addr)->sin6_addr, sizeof(struct in6_addr)); + memcpy(&mask6, &((struct sockaddr_in6 *)&lifrcopy.lifr_addr)->sin6_addr, sizeof(struct in6_addr)); + prefixlen = lifrcopy.lifr_addrlen; + break; + default: + break; + } + index = if_nametoindex(lifreq->lifr_name); + + if (!(hw = avahi_interface_monitor_get_hw_interface(m, (AvahiIfIndex) index))) { + if (!(hw = avahi_hw_interface_new(m, (AvahiIfIndex) index))) + return; /* OOM */ + + hw->flags_ok = + (flags & IFF_UP) && + (!m->server->config.use_iff_running || (flags & IFF_RUNNING)) && + !(flags & IFF_LOOPBACK) && + (flags & IFF_MULTICAST) && + (m->server->config.allow_point_to_point || !(flags & IFF_POINTOPOINT)); + hw->name = avahi_strdup(lifreq->lifr_name); + hw->mtu = mtu; + /* TODO get mac address */ + } + + if (!(iface = avahi_interface_monitor_get_interface(m, (AvahiIfIndex)index, addr.proto))) + return; + + if (!(addriface = avahi_interface_monitor_get_address(m, iface, &addr))) + if (!(addriface = avahi_interface_address_new(m, iface, &addr, prefixlen))) + return; /* OOM */ + + addriface->global_scope = 1; + + avahi_hw_interface_check_relevant(hw); + avahi_hw_interface_update_rrs(hw, 0); +} +#endif + +void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m) { +#ifndef HAVE_STRUCT_LIFCONF + size_t needed; + int mib[6]; + char *buf, *lim, *next, count = 0; + struct rt_msghdr *rtm; + + assert(m); + + retry2: + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; /* protocol */ + mib[3] = 0; /* wildcard address family */ + mib[4] = NET_RT_IFLIST; + mib[5] = 0; /* no flags */ + if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) + { + avahi_log_error("sysctl failed: %s", strerror(errno)); + avahi_log_error("route-sysctl-estimate"); + return; + } + if ((buf = avahi_malloc(needed)) == NULL) + { + avahi_log_error("malloc failed in avahi_interface_monitor_sync"); + return; + } + if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) { + avahi_log_warn("sysctl failed: %s", strerror(errno)); + if (errno == ENOMEM && count++ < 10) { + avahi_log_warn("Routing table grew, retrying"); + sleep(1); + avahi_free(buf); + goto retry2; + } + } + lim = buf + needed; + for (next = buf; next < lim; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr *)next; + parse_rtmsg(rtm, m); + } + + m->list_complete = 1; + avahi_interface_monitor_check_relevant(m); + avahi_interface_monitor_update_rrs(m, 0); + avahi_log_info("Network interface enumeration completed."); +#elif defined (SIOCGLIFNUM) && defined(HAVE_STRUCT_LIFCONF) /* Solaris 8 and later; Sol 7? */ + int sockfd; + int ret; + int n; + struct lifnum lifn; + struct lifconf lifc; + struct lifreq *lifreq; + + if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + avahi_log_error(__FILE__": socket(PFROUTE): %s", strerror(errno)); + return; + } + lifc.lifc_buf = NULL; + lifn.lifn_family = AF_UNSPEC; + lifn.lifn_flags = 0; + if (ioctl(sockfd, SIOCGLIFNUM, &lifn) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFNUM): %s", strerror(errno)); + goto end; + } + lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq); + if ((lifc.lifc_buf = avahi_malloc(lifc.lifc_len)) == NULL) { + avahi_log_error("malloc failed in avahi_interface_monitor_sync"); + goto end; + } + lifc.lifc_family = NULL; + lifc.lifc_flags = 0; + if(ioctl(sockfd, SIOCGLIFCONF, &lifc) < 0) { + avahi_log_error(__FILE__": ioctl(SIOCGLIFCONF): %s", strerror(errno)); + goto end; + } + lifreq = lifc.lifc_req; + + for (n = 0; n < lifc.lifc_len; n += sizeof(struct lifreq)) { + if_add_interface(lifreq, m, sockfd, lifn.lifn_count); + lifreq++; + } + m->list_complete = 1; + avahi_interface_monitor_check_relevant(m); + avahi_interface_monitor_update_rrs(m, 0); +end: + close(sockfd); + avahi_free(lifc.lifc_buf); + + avahi_log_info("Network interface enumeration completed."); +#endif +} diff --git a/trunk/avahi-core/iface-pfroute.h b/trunk/avahi-core/iface-pfroute.h new file mode 100644 index 0000000..34098da --- /dev/null +++ b/trunk/avahi-core/iface-pfroute.h @@ -0,0 +1,39 @@ +#ifndef fooifacepfroutehfoo +#define fooifacepfroutehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ +#include <avahi-common/watch.h> + +typedef struct AvahiPfRoute AvahiPfRoute; +struct AvahiPfRoute { + int fd; + AvahiWatch *watch; + AvahiInterfaceMonitor *m; +}; + +typedef struct AvahiInterfaceMonitorOSDep AvahiInterfaceMonitorOSDep; + +struct AvahiInterfaceMonitorOSDep { + AvahiPfRoute *pfroute; +}; + +#endif diff --git a/trunk/avahi-core/iface.c b/trunk/avahi-core/iface.c new file mode 100644 index 0000000..5685618 --- /dev/null +++ b/trunk/avahi-core/iface.c @@ -0,0 +1,812 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <avahi-common/error.h> +#include <avahi-common/malloc.h> +#include <avahi-common/domain.h> + +#include "iface.h" +#include "dns.h" +#include "socket.h" +#include "announce.h" +#include "util.h" +#include "log.h" +#include "multicast-lookup.h" +#include "querier.h" + +void avahi_interface_address_update_rrs(AvahiInterfaceAddress *a, int remove_rrs) { + AvahiInterfaceMonitor *m; + + assert(a); + m = a->monitor; + + if (a->interface->announcing && + m->list_complete && + avahi_interface_address_is_relevant(a) && + !remove_rrs && + m->server->config.publish_addresses && + (m->server->state == AVAHI_SERVER_RUNNING || + m->server->state == AVAHI_SERVER_REGISTERING)) { + + /* Fill the entry group */ + if (!a->entry_group) + a->entry_group = avahi_s_entry_group_new(m->server, avahi_host_rr_entry_group_callback, NULL); + + if (!a->entry_group) /* OOM */ + return; + + if (avahi_s_entry_group_is_empty(a->entry_group)) { + char t[AVAHI_ADDRESS_STR_MAX]; + avahi_address_snprint(t, sizeof(t), &a->address); + + avahi_log_info("Registering new address record for %s on %s.", t, a->interface->hardware->name); + + if (avahi_server_add_address(m->server, a->entry_group, a->interface->hardware->index, a->interface->protocol, 0, NULL, &a->address) < 0) { + avahi_log_warn(__FILE__": avahi_server_add_address() failed: %s", avahi_strerror(m->server->error)); + avahi_s_entry_group_free(a->entry_group); + a->entry_group = NULL; + return; + } + + avahi_s_entry_group_commit(a->entry_group); + } + } else { + + /* Clear the entry group */ + + if (a->entry_group && !avahi_s_entry_group_is_empty(a->entry_group)) { + char t[AVAHI_ADDRESS_STR_MAX]; + avahi_address_snprint(t, sizeof(t), &a->address); + + if (avahi_s_entry_group_get_state(a->entry_group) == AVAHI_ENTRY_GROUP_REGISTERING && + m->server->state == AVAHI_SERVER_REGISTERING) + avahi_server_decrease_host_rr_pending(m->server); + + avahi_log_info("Withdrawing address record for %s on %s.", t, a->interface->hardware->name); + + avahi_s_entry_group_reset(a->entry_group); + } + } +} + +void avahi_interface_update_rrs(AvahiInterface *i, int remove_rrs) { + AvahiInterfaceAddress *a; + + assert(i); + + for (a = i->addresses; a; a = a->address_next) + avahi_interface_address_update_rrs(a, remove_rrs); +} + +void avahi_hw_interface_update_rrs(AvahiHwInterface *hw, int remove_rrs) { + AvahiInterface *i; + AvahiInterfaceMonitor *m; + + assert(hw); + m = hw->monitor; + + for (i = hw->interfaces; i; i = i->by_hardware_next) + avahi_interface_update_rrs(i, remove_rrs); + + if (m->list_complete && + !remove_rrs && + m->server->config.publish_workstation && + (m->server->state == AVAHI_SERVER_RUNNING)) { + + if (!hw->entry_group) + hw->entry_group = avahi_s_entry_group_new(m->server, avahi_host_rr_entry_group_callback, NULL); + + if (!hw->entry_group) + return; /* OOM */ + + if (avahi_s_entry_group_is_empty(hw->entry_group)) { + char name[AVAHI_LABEL_MAX], mac[256]; + + avahi_format_mac_address(mac, sizeof(mac), hw->mac_address, hw->mac_address_size); + snprintf(name, sizeof(name), "%s [%s]", m->server->host_name, mac); + + if (avahi_server_add_service(m->server, hw->entry_group, hw->index, AVAHI_PROTO_UNSPEC, 0, name, "_workstation._tcp", NULL, NULL, 9, NULL) < 0) { + avahi_log_warn(__FILE__": avahi_server_add_service() failed: %s", avahi_strerror(m->server->error)); + avahi_s_entry_group_free(hw->entry_group); + hw->entry_group = NULL; + } else + avahi_s_entry_group_commit(hw->entry_group); + } + + } else { + + if (hw->entry_group && !avahi_s_entry_group_is_empty(hw->entry_group)) { + + if (avahi_s_entry_group_get_state(hw->entry_group) == AVAHI_ENTRY_GROUP_REGISTERING) + avahi_server_decrease_host_rr_pending(m->server); + + avahi_s_entry_group_reset(hw->entry_group); + } + } +} + +void avahi_interface_monitor_update_rrs(AvahiInterfaceMonitor *m, int remove_rrs) { + AvahiHwInterface *hw; + + assert(m); + + for (hw = m->hw_interfaces; hw; hw = hw->hardware_next) + avahi_hw_interface_update_rrs(hw, remove_rrs); +} + +static int interface_mdns_mcast_join(AvahiInterface *i, int join) { + char at[AVAHI_ADDRESS_STR_MAX]; + int r; + assert(i); + + if (!!join == !!i->mcast_joined) + return 0; + + if (join) { + AvahiInterfaceAddress *a; + + /* Look if there's an address with global scope */ + for (a = i->addresses; a; a = a->address_next) + if (a->global_scope) + break; + + /* No address with a global scope has been found, so let's use + * any. */ + if (!a) + a = i->addresses; + + /* Hmm, there is no address available. */ + if (!a) { + avahi_log_warn(__FILE__": interface_mdns_mcast_join() called but no local address available."); + return -1; + } + + i->local_mcast_address = a->address; + } + + avahi_log_info("%s mDNS multicast group on interface %s.%s with address %s.", + join ? "Joining" : "Leaving", + i->hardware->name, + avahi_proto_to_string(i->protocol), + avahi_address_snprint(at, sizeof(at), &i->local_mcast_address)); + + if (i->protocol == AVAHI_PROTO_INET6) + r = avahi_mdns_mcast_join_ipv6(i->monitor->server->fd_ipv6, &i->local_mcast_address.data.ipv6, i->hardware->index, join); + else { + assert(i->protocol == AVAHI_PROTO_INET); + + r = avahi_mdns_mcast_join_ipv4(i->monitor->server->fd_ipv4, &i->local_mcast_address.data.ipv4, i->hardware->index, join); + } + + if (r < 0) + i->mcast_joined = 0; + else + i->mcast_joined = join; + + return 0; +} + +static int interface_mdns_mcast_rejoin(AvahiInterface *i) { + AvahiInterfaceAddress *a, *usable = NULL, *found = NULL; + assert(i); + + if (!i->mcast_joined) + return 0; + + /* Check whether old address we joined with is still available. If + * not, rejoin using an other address. */ + + for (a = i->addresses; a; a = a->address_next) { + if (a->global_scope && !usable) + usable = a; + + if (avahi_address_cmp(&a->address, &i->local_mcast_address) == 0) { + + if (a->global_scope) + /* No action necessary: the address still exists and + * has global scope. */ + return 0; + + found = a; + } + } + + if (found && !usable) + /* No action necessary: the address still exists and no better one has been found */ + return 0; + + interface_mdns_mcast_join(i, 0); + return interface_mdns_mcast_join(i, 1); +} + +void avahi_interface_address_free(AvahiInterfaceAddress *a) { + assert(a); + assert(a->interface); + + avahi_interface_address_update_rrs(a, 1); + AVAHI_LLIST_REMOVE(AvahiInterfaceAddress, address, a->interface->addresses, a); + + if (a->entry_group) + avahi_s_entry_group_free(a->entry_group); + + interface_mdns_mcast_rejoin(a->interface); + + avahi_free(a); +} + +void avahi_interface_free(AvahiInterface *i, int send_goodbye) { + assert(i); + + /* Handle goodbyes and remove announcers */ + avahi_goodbye_interface(i->monitor->server, i, send_goodbye, 1); + avahi_response_scheduler_force(i->response_scheduler); + assert(!i->announcers); + + if (i->mcast_joined) + interface_mdns_mcast_join(i, 0); + + /* Remove queriers */ + avahi_querier_free_all(i); + avahi_hashmap_free(i->queriers_by_key); + + /* Remove local RRs */ + avahi_interface_update_rrs(i, 1); + + while (i->addresses) + avahi_interface_address_free(i->addresses); + + avahi_response_scheduler_free(i->response_scheduler); + avahi_query_scheduler_free(i->query_scheduler); + avahi_probe_scheduler_free(i->probe_scheduler); + avahi_cache_free(i->cache); + + AVAHI_LLIST_REMOVE(AvahiInterface, interface, i->monitor->interfaces, i); + AVAHI_LLIST_REMOVE(AvahiInterface, by_hardware, i->hardware->interfaces, i); + + avahi_free(i); +} + +void avahi_hw_interface_free(AvahiHwInterface *hw, int send_goodbye) { + assert(hw); + + avahi_hw_interface_update_rrs(hw, 1); + + while (hw->interfaces) + avahi_interface_free(hw->interfaces, send_goodbye); + + if (hw->entry_group) + avahi_s_entry_group_free(hw->entry_group); + + AVAHI_LLIST_REMOVE(AvahiHwInterface, hardware, hw->monitor->hw_interfaces, hw); + avahi_hashmap_remove(hw->monitor->hashmap, &hw->index); + + avahi_free(hw->name); + avahi_free(hw); +} + +AvahiInterface* avahi_interface_new(AvahiInterfaceMonitor *m, AvahiHwInterface *hw, AvahiProtocol protocol) { + AvahiInterface *i; + + assert(m); + assert(hw); + assert(AVAHI_PROTO_VALID(protocol)); + + if (!(i = avahi_new(AvahiInterface, 1))) + goto fail; /* OOM */ + + i->monitor = m; + i->hardware = hw; + i->protocol = protocol; + i->announcing = 0; + i->mcast_joined = 0; + + AVAHI_LLIST_HEAD_INIT(AvahiInterfaceAddress, i->addresses); + AVAHI_LLIST_HEAD_INIT(AvahiAnnouncer, i->announcers); + + AVAHI_LLIST_HEAD_INIT(AvahiQuerier, i->queriers); + i->queriers_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL); + + i->cache = avahi_cache_new(m->server, i); + i->response_scheduler = avahi_response_scheduler_new(i); + i->query_scheduler = avahi_query_scheduler_new(i); + i->probe_scheduler = avahi_probe_scheduler_new(i); + + if (!i->cache || !i->response_scheduler || !i->query_scheduler || !i->probe_scheduler) + goto fail; /* OOM */ + + AVAHI_LLIST_PREPEND(AvahiInterface, by_hardware, hw->interfaces, i); + AVAHI_LLIST_PREPEND(AvahiInterface, interface, m->interfaces, i); + + return i; + +fail: + + if (i) { + if (i->cache) + avahi_cache_free(i->cache); + if (i->response_scheduler) + avahi_response_scheduler_free(i->response_scheduler); + if (i->query_scheduler) + avahi_query_scheduler_free(i->query_scheduler); + if (i->probe_scheduler) + avahi_probe_scheduler_free(i->probe_scheduler); + } + + return NULL; +} + +AvahiHwInterface *avahi_hw_interface_new(AvahiInterfaceMonitor *m, AvahiIfIndex idx) { + AvahiHwInterface *hw; + + assert(m); + assert(AVAHI_IF_VALID(idx)); + + if (!(hw = avahi_new(AvahiHwInterface, 1))) + return NULL; + + hw->monitor = m; + hw->name = NULL; + hw->flags_ok = 0; + hw->mtu = 1500; + hw->index = idx; + hw->mac_address_size = 0; + hw->entry_group = NULL; + + AVAHI_LLIST_HEAD_INIT(AvahiInterface, hw->interfaces); + AVAHI_LLIST_PREPEND(AvahiHwInterface, hardware, m->hw_interfaces, hw); + + avahi_hashmap_insert(m->hashmap, &hw->index, hw); + + if (m->server->fd_ipv4 >= 0) + avahi_interface_new(m, hw, AVAHI_PROTO_INET); + if (m->server->fd_ipv6 >= 0) + avahi_interface_new(m, hw, AVAHI_PROTO_INET6); + + return hw; +} + +AvahiInterfaceAddress *avahi_interface_address_new(AvahiInterfaceMonitor *m, AvahiInterface *i, const AvahiAddress *addr, unsigned prefix_len) { + AvahiInterfaceAddress *a; + + assert(m); + assert(i); + + if (!(a = avahi_new(AvahiInterfaceAddress, 1))) + return NULL; + + a->interface = i; + a->monitor = m; + a->address = *addr; + a->prefix_len = prefix_len; + a->global_scope = 0; + a->entry_group = NULL; + + AVAHI_LLIST_PREPEND(AvahiInterfaceAddress, address, i->addresses, a); + + return a; +} + +void avahi_interface_check_relevant(AvahiInterface *i) { + int b; + AvahiInterfaceMonitor *m; + + assert(i); + m = i->monitor; + + b = avahi_interface_is_relevant(i); + + if (m->list_complete && b && !i->announcing) { + avahi_log_info("New relevant interface %s.%s for mDNS.", i->hardware->name, avahi_proto_to_string(i->protocol)); + + interface_mdns_mcast_join(i, 1); + + i->announcing = 1; + avahi_announce_interface(m->server, i); + avahi_multicast_lookup_engine_new_interface(m->server->multicast_lookup_engine, i); + } else if (!b && i->announcing) { + avahi_log_info("Interface %s.%s no longer relevant for mDNS.", i->hardware->name, avahi_proto_to_string(i->protocol)); + + interface_mdns_mcast_join(i, 0); + + avahi_goodbye_interface(m->server, i, 0, 1); + avahi_querier_free_all(i); + + avahi_response_scheduler_clear(i->response_scheduler); + avahi_query_scheduler_clear(i->query_scheduler); + avahi_probe_scheduler_clear(i->probe_scheduler); + avahi_cache_flush(i->cache); + + i->announcing = 0; + + } else + interface_mdns_mcast_rejoin(i); +} + +void avahi_hw_interface_check_relevant(AvahiHwInterface *hw) { + AvahiInterface *i; + + assert(hw); + + for (i = hw->interfaces; i; i = i->by_hardware_next) + avahi_interface_check_relevant(i); +} + +void avahi_interface_monitor_check_relevant(AvahiInterfaceMonitor *m) { + AvahiInterface *i; + + assert(m); + + for (i = m->interfaces; i; i = i->interface_next) + avahi_interface_check_relevant(i); +} + +AvahiInterfaceMonitor *avahi_interface_monitor_new(AvahiServer *s) { + AvahiInterfaceMonitor *m = NULL; + + if (!(m = avahi_new0(AvahiInterfaceMonitor, 1))) + return NULL; /* OOM */ + + m->server = s; + m->list_complete = 0; + m->hashmap = avahi_hashmap_new(avahi_int_hash, avahi_int_equal, NULL, NULL); + + AVAHI_LLIST_HEAD_INIT(AvahiInterface, m->interfaces); + AVAHI_LLIST_HEAD_INIT(AvahiHwInterface, m->hw_interfaces); + + if (avahi_interface_monitor_init_osdep(m) < 0) + goto fail; + + return m; + +fail: + avahi_interface_monitor_free(m); + return NULL; +} + +void avahi_interface_monitor_free(AvahiInterfaceMonitor *m) { + assert(m); + + while (m->hw_interfaces) + avahi_hw_interface_free(m->hw_interfaces, 1); + + assert(!m->interfaces); + + avahi_interface_monitor_free_osdep(m); + + if (m->hashmap) + avahi_hashmap_free(m->hashmap); + + avahi_free(m); +} + + +AvahiInterface* avahi_interface_monitor_get_interface(AvahiInterfaceMonitor *m, AvahiIfIndex idx, AvahiProtocol protocol) { + AvahiHwInterface *hw; + AvahiInterface *i; + + assert(m); + assert(idx >= 0); + assert(protocol != AVAHI_PROTO_UNSPEC); + + if (!(hw = avahi_interface_monitor_get_hw_interface(m, idx))) + return NULL; + + for (i = hw->interfaces; i; i = i->by_hardware_next) + if (i->protocol == protocol) + return i; + + return NULL; +} + +AvahiHwInterface* avahi_interface_monitor_get_hw_interface(AvahiInterfaceMonitor *m, AvahiIfIndex idx) { + assert(m); + assert(idx > 0); + + return avahi_hashmap_lookup(m->hashmap, &idx); +} + +AvahiInterfaceAddress* avahi_interface_monitor_get_address(AvahiInterfaceMonitor *m, AvahiInterface *i, const AvahiAddress *raddr) { + AvahiInterfaceAddress *ia; + + assert(m); + assert(i); + assert(raddr); + + for (ia = i->addresses; ia; ia = ia->address_next) + if (avahi_address_cmp(&ia->address, raddr) == 0) + return ia; + + return NULL; +} + +void avahi_interface_send_packet_unicast(AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, uint16_t port) { + assert(i); + assert(p); + + if (!avahi_interface_is_relevant(i)) + return; + + assert(!a || a->proto == i->protocol); + + if (i->protocol == AVAHI_PROTO_INET && i->monitor->server->fd_ipv4 >= 0) + avahi_send_dns_packet_ipv4(i->monitor->server->fd_ipv4, i->hardware->index, p, i->mcast_joined ? &i->local_mcast_address.data.ipv4 : NULL, a ? &a->data.ipv4 : NULL, port); + else if (i->protocol == AVAHI_PROTO_INET6 && i->monitor->server->fd_ipv6 >= 0) + avahi_send_dns_packet_ipv6(i->monitor->server->fd_ipv6, i->hardware->index, p, i->mcast_joined ? &i->local_mcast_address.data.ipv6 : NULL, a ? &a->data.ipv6 : NULL, port); +} + +void avahi_interface_send_packet(AvahiInterface *i, AvahiDnsPacket *p) { + assert(i); + assert(p); + + avahi_interface_send_packet_unicast(i, p, NULL, 0); +} + +int avahi_interface_post_query(AvahiInterface *i, AvahiKey *key, int immediately, unsigned *ret_id) { + assert(i); + assert(key); + + if (avahi_interface_is_relevant(i)) + return avahi_query_scheduler_post(i->query_scheduler, key, immediately, ret_id); + + return 0; +} + +int avahi_interface_withraw_query(AvahiInterface *i, unsigned id) { + + return avahi_query_scheduler_withdraw_by_id(i->query_scheduler, id); +} + +int avahi_interface_post_response(AvahiInterface *i, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately) { + assert(i); + assert(record); + + if (avahi_interface_is_relevant(i)) + return avahi_response_scheduler_post(i->response_scheduler, record, flush_cache, querier, immediately); + + return 0; +} + +int avahi_interface_post_probe(AvahiInterface *i, AvahiRecord *record, int immediately) { + assert(i); + assert(record); + + if (avahi_interface_is_relevant(i)) + return avahi_probe_scheduler_post(i->probe_scheduler, record, immediately); + + return 0; +} + +int avahi_dump_caches(AvahiInterfaceMonitor *m, AvahiDumpCallback callback, void* userdata) { + AvahiInterface *i; + assert(m); + + for (i = m->interfaces; i; i = i->interface_next) { + if (avahi_interface_is_relevant(i)) { + char ln[256]; + snprintf(ln, sizeof(ln), ";;; INTERFACE %s.%s ;;;", i->hardware->name, avahi_proto_to_string(i->protocol)); + callback(ln, userdata); + if (avahi_cache_dump(i->cache, callback, userdata) < 0) + return -1; + } + } + + return 0; +} + +int avahi_interface_is_relevant(AvahiInterface *i) { + AvahiInterfaceAddress *a; + int relevant_address; + + assert(i); + + relevant_address = 0; + + for (a = i->addresses; a; a = a->address_next) + if (avahi_interface_address_is_relevant(a)) { + relevant_address = 1; + break; + } + + return i->hardware->flags_ok && relevant_address; +} + +int avahi_interface_address_is_relevant(AvahiInterfaceAddress *a) { + AvahiInterfaceAddress *b; + assert(a); + + /* Publish public IP addresses */ + if (a->global_scope) + return 1; + else { + + /* Publish link local IP addresses if they are the only ones on the link */ + for (b = a->interface->addresses; b; b = b->address_next) { + if (b == a) + continue; + + if (b->global_scope) + return 0; + } + + return 1; + } + + return 0; +} + +int avahi_interface_match(AvahiInterface *i, AvahiIfIndex idx, AvahiProtocol protocol) { + assert(i); + + if (idx != AVAHI_IF_UNSPEC && idx != i->hardware->index) + return 0; + + if (protocol != AVAHI_PROTO_UNSPEC && protocol != i->protocol) + return 0; + + return 1; +} + +void avahi_interface_monitor_walk(AvahiInterfaceMonitor *m, AvahiIfIndex interface, AvahiProtocol protocol, AvahiInterfaceMonitorWalkCallback callback, void* userdata) { + assert(m); + assert(callback); + + if (interface != AVAHI_IF_UNSPEC) { + if (protocol != AVAHI_PROTO_UNSPEC) { + AvahiInterface *i; + + if ((i = avahi_interface_monitor_get_interface(m, interface, protocol))) + callback(m, i, userdata); + + } else { + AvahiHwInterface *hw; + AvahiInterface *i; + + if ((hw = avahi_interface_monitor_get_hw_interface(m, interface))) + for (i = hw->interfaces; i; i = i->by_hardware_next) + if (avahi_interface_match(i, interface, protocol)) + callback(m, i, userdata); + } + + } else { + AvahiInterface *i; + + for (i = m->interfaces; i; i = i->interface_next) + if (avahi_interface_match(i, interface, protocol)) + callback(m, i, userdata); + } +} + + +int avahi_address_is_local(AvahiInterfaceMonitor *m, const AvahiAddress *a) { + AvahiInterface *i; + AvahiInterfaceAddress *ia; + assert(m); + assert(a); + + for (i = m->interfaces; i; i = i->interface_next) + for (ia = i->addresses; ia; ia = ia->address_next) + if (avahi_address_cmp(a, &ia->address) == 0) + return 1; + + return 0; +} + +int avahi_interface_address_on_link(AvahiInterface *i, const AvahiAddress *a) { + AvahiInterfaceAddress *ia; + + assert(i); + assert(a); + + if (a->proto != i->protocol) + return 0; + + for (ia = i->addresses; ia; ia = ia->address_next) { + + if (a->proto == AVAHI_PROTO_INET) { + uint32_t m; + + m = ~(((uint32_t) -1) >> ia->prefix_len); + + if ((ntohl(a->data.ipv4.address) & m) == (ntohl(ia->address.data.ipv4.address) & m)) + return 1; + } else { + unsigned j; + unsigned char pl; + assert(a->proto == AVAHI_PROTO_INET6); + + pl = ia->prefix_len; + + for (j = 0; j < 16; j++) { + uint8_t m; + + if (pl == 0) + return 1; + + if (pl >= 8) { + m = 0xFF; + pl -= 8; + } else { + m = ~(0xFF >> pl); + pl = 0; + } + + if ((a->data.ipv6.address[j] & m) != (ia->address.data.ipv6.address[j] & m)) + break; + } + } + } + + return 0; +} + +int avahi_interface_has_address(AvahiInterfaceMonitor *m, AvahiIfIndex iface, const AvahiAddress *a) { + AvahiInterface *i; + AvahiInterfaceAddress *j; + + assert(m); + assert(iface != AVAHI_IF_UNSPEC); + assert(a); + + if (!(i = avahi_interface_monitor_get_interface(m, iface, a->proto))) + return 0; + + for (j = i->addresses; j; j = j->address_next) + if (avahi_address_cmp(a, &j->address) == 0) + return 1; + + return 0; +} + +AvahiIfIndex avahi_find_interface_for_address(AvahiInterfaceMonitor *m, const AvahiAddress *a) { + AvahiInterface *i; + assert(m); + + /* Some stupid OS don't support passing the interface index when a + * packet is recieved. We have to work around that limitation by + * looking for an interface that has the incoming address + * attached. This is sometimes ambiguous, but we have to live with + * it. */ + + for (i = m->interfaces; i; i = i->interface_next) { + AvahiInterfaceAddress *ai; + + if (i->protocol != a->proto) + continue; + + for (ai = i->addresses; ai; ai = ai->address_next) + if (avahi_address_cmp(a, &ai->address) == 0) + return i->hardware->index; + } + + return AVAHI_IF_UNSPEC; +} diff --git a/trunk/avahi-core/iface.h b/trunk/avahi-core/iface.h new file mode 100644 index 0000000..4106ea7 --- /dev/null +++ b/trunk/avahi-core/iface.h @@ -0,0 +1,192 @@ +#ifndef fooifacehfoo +#define fooifacehfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiInterfaceMonitor AvahiInterfaceMonitor; +typedef struct AvahiInterfaceAddress AvahiInterfaceAddress; +typedef struct AvahiInterface AvahiInterface; +typedef struct AvahiHwInterface AvahiHwInterface; + +#include <avahi-common/llist.h> +#include <avahi-common/address.h> + +#include "internal.h" +#include "cache.h" +#include "response-sched.h" +#include "query-sched.h" +#include "probe-sched.h" +#include "dns.h" +#include "announce.h" +#include "browse.h" +#include "querier.h" + +#ifdef HAVE_NETLINK +#include "iface-linux.h" +#elif defined(HAVE_PF_ROUTE) +#include "iface-pfroute.h" +#else +typedef struct AvahiInterfaceMonitorOSDep AvahiInterfaceMonitorOSDep; +struct AvahiInterfaceMonitorOSDep { + + unsigned query_addr_seq, query_link_seq; + + enum { + LIST_IFACE, + LIST_ADDR, + LIST_DONE + } list; +}; +#endif + +#define AVAHI_MAC_ADDRESS_MAX 32 + +struct AvahiInterfaceMonitor { + AvahiServer *server; + AvahiHashmap *hashmap; + + AVAHI_LLIST_HEAD(AvahiInterface, interfaces); + AVAHI_LLIST_HEAD(AvahiHwInterface, hw_interfaces); + + int list_complete; + AvahiInterfaceMonitorOSDep osdep; +}; + +struct AvahiHwInterface { + AvahiInterfaceMonitor *monitor; + + AVAHI_LLIST_FIELDS(AvahiHwInterface, hardware); + + char *name; + AvahiIfIndex index; + int flags_ok; + + unsigned mtu; + + uint8_t mac_address[AVAHI_MAC_ADDRESS_MAX]; + size_t mac_address_size; + + AvahiSEntryGroup *entry_group; + + AVAHI_LLIST_HEAD(AvahiInterface, interfaces); +}; + +struct AvahiInterface { + AvahiInterfaceMonitor *monitor; + AvahiHwInterface *hardware; + + AVAHI_LLIST_FIELDS(AvahiInterface, interface); + AVAHI_LLIST_FIELDS(AvahiInterface, by_hardware); + + AvahiProtocol protocol; + int announcing; + AvahiAddress local_mcast_address; + int mcast_joined; + + AvahiCache *cache; + + AvahiQueryScheduler *query_scheduler; + AvahiResponseScheduler * response_scheduler; + AvahiProbeScheduler *probe_scheduler; + + AVAHI_LLIST_HEAD(AvahiInterfaceAddress, addresses); + AVAHI_LLIST_HEAD(AvahiAnnouncer, announcers); + + AvahiHashmap *queriers_by_key; + AVAHI_LLIST_HEAD(AvahiQuerier, queriers); +}; + +struct AvahiInterfaceAddress { + AvahiInterfaceMonitor *monitor; + AvahiInterface *interface; + + AVAHI_LLIST_FIELDS(AvahiInterfaceAddress, address); + + AvahiAddress address; + unsigned prefix_len; + + int global_scope; + + AvahiSEntryGroup *entry_group; +}; + +AvahiInterfaceMonitor *avahi_interface_monitor_new(AvahiServer *server); +void avahi_interface_monitor_free(AvahiInterfaceMonitor *m); + +int avahi_interface_monitor_init_osdep(AvahiInterfaceMonitor *m); +void avahi_interface_monitor_free_osdep(AvahiInterfaceMonitor *m); +void avahi_interface_monitor_sync(AvahiInterfaceMonitor *m); + +typedef void (*AvahiInterfaceMonitorWalkCallback)(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata); +void avahi_interface_monitor_walk(AvahiInterfaceMonitor *m, AvahiIfIndex idx, AvahiProtocol protocol, AvahiInterfaceMonitorWalkCallback callback, void* userdata); +int avahi_dump_caches(AvahiInterfaceMonitor *m, AvahiDumpCallback callback, void* userdata); + +void avahi_interface_monitor_update_rrs(AvahiInterfaceMonitor *m, int remove_rrs); +int avahi_address_is_local(AvahiInterfaceMonitor *m, const AvahiAddress *a); +void avahi_interface_monitor_check_relevant(AvahiInterfaceMonitor *m); + +/* AvahiHwInterface */ + +AvahiHwInterface *avahi_hw_interface_new(AvahiInterfaceMonitor *m, AvahiIfIndex idx); +void avahi_hw_interface_free(AvahiHwInterface *hw, int send_goodbye); + +void avahi_hw_interface_update_rrs(AvahiHwInterface *hw, int remove_rrs); +void avahi_hw_interface_check_relevant(AvahiHwInterface *hw); + +AvahiHwInterface* avahi_interface_monitor_get_hw_interface(AvahiInterfaceMonitor *m, int idx); + +/* AvahiInterface */ + +AvahiInterface* avahi_interface_new(AvahiInterfaceMonitor *m, AvahiHwInterface *hw, AvahiProtocol protocol); +void avahi_interface_free(AvahiInterface *i, int send_goodbye); + +void avahi_interface_update_rrs(AvahiInterface *i, int remove_rrs); +void avahi_interface_check_relevant(AvahiInterface *i); +int avahi_interface_is_relevant(AvahiInterface *i); + +void avahi_interface_send_packet(AvahiInterface *i, AvahiDnsPacket *p); +void avahi_interface_send_packet_unicast(AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, uint16_t port); + +int avahi_interface_post_query(AvahiInterface *i, AvahiKey *k, int immediately, unsigned *ret_id); +int avahi_interface_withraw_query(AvahiInterface *i, unsigned id); +int avahi_interface_post_response(AvahiInterface *i, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately); +int avahi_interface_post_probe(AvahiInterface *i, AvahiRecord *p, int immediately); + +int avahi_interface_match(AvahiInterface *i, AvahiIfIndex idx, AvahiProtocol protocol); +int avahi_interface_address_on_link(AvahiInterface *i, const AvahiAddress *a); +int avahi_interface_has_address(AvahiInterfaceMonitor *m, AvahiIfIndex iface, const AvahiAddress *a); + +AvahiInterface* avahi_interface_monitor_get_interface(AvahiInterfaceMonitor *m, AvahiIfIndex idx, AvahiProtocol protocol); + +/* AvahiInterfaceAddress */ + +AvahiInterfaceAddress *avahi_interface_address_new(AvahiInterfaceMonitor *m, AvahiInterface *i, const AvahiAddress *addr, unsigned prefix_len); +void avahi_interface_address_free(AvahiInterfaceAddress *a); + +void avahi_interface_address_update_rrs(AvahiInterfaceAddress *a, int remove_rrs); +int avahi_interface_address_is_relevant(AvahiInterfaceAddress *a); + +AvahiInterfaceAddress* avahi_interface_monitor_get_address(AvahiInterfaceMonitor *m, AvahiInterface *i, const AvahiAddress *raddr); + +AvahiIfIndex avahi_find_interface_for_address(AvahiInterfaceMonitor *m, const AvahiAddress *a); + +#endif diff --git a/trunk/avahi-core/internal.h b/trunk/avahi-core/internal.h new file mode 100644 index 0000000..0d88d5a --- /dev/null +++ b/trunk/avahi-core/internal.h @@ -0,0 +1,227 @@ +#ifndef foointernalhfoo +#define foointernalhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** A locally registered DNS resource record */ +typedef struct AvahiEntry AvahiEntry; + +#include <avahi-common/llist.h> +#include <avahi-common/watch.h> + +#include "core.h" +#include "iface.h" +#include "prioq.h" +#include "timeeventq.h" +#include "announce.h" +#include "browse.h" +#include "dns.h" +#include "rrlist.h" +#include "hashmap.h" +#include "wide-area.h" +#include "multicast-lookup.h" +#include "dns-srv-rr.h" + +#define AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX 100 + +#define AVAHI_FLAGS_VALID(flags, max) (!((flags) & ~(max))) + +#define AVAHI_RR_HOLDOFF_MSEC 1000 +#define AVAHI_RR_HOLDOFF_MSEC_RATE_LIMIT 20000 +#define AVAHI_RR_RATE_LIMIT_COUNT 15 + +typedef struct AvahiLegacyUnicastReflectSlot AvahiLegacyUnicastReflectSlot; + +struct AvahiLegacyUnicastReflectSlot { + AvahiServer *server; + + uint16_t id, original_id; + AvahiAddress address; + uint16_t port; + int interface; + struct timeval elapse_time; + AvahiTimeEvent *time_event; +}; + +struct AvahiEntry { + AvahiServer *server; + AvahiSEntryGroup *group; + + int dead; + + AvahiPublishFlags flags; + AvahiRecord *record; + AvahiIfIndex interface; + AvahiProtocol protocol; + + AVAHI_LLIST_FIELDS(AvahiEntry, entries); + AVAHI_LLIST_FIELDS(AvahiEntry, by_key); + AVAHI_LLIST_FIELDS(AvahiEntry, by_group); + + AVAHI_LLIST_HEAD(AvahiAnnouncer, announcers); +}; + +struct AvahiSEntryGroup { + AvahiServer *server; + int dead; + + AvahiEntryGroupState state; + void* userdata; + AvahiSEntryGroupCallback callback; + + unsigned n_probing; + + unsigned n_register_try; + struct timeval register_time; + AvahiTimeEvent *register_time_event; + + struct timeval established_at; + + AVAHI_LLIST_FIELDS(AvahiSEntryGroup, groups); + AVAHI_LLIST_HEAD(AvahiEntry, entries); +}; + +struct AvahiServer { + const AvahiPoll *poll_api; + + AvahiInterfaceMonitor *monitor; + AvahiServerConfig config; + + AVAHI_LLIST_HEAD(AvahiEntry, entries); + AvahiHashmap *entries_by_key; + + AVAHI_LLIST_HEAD(AvahiSEntryGroup, groups); + + AVAHI_LLIST_HEAD(AvahiSRecordBrowser, record_browsers); + AvahiHashmap *record_browser_hashmap; + AVAHI_LLIST_HEAD(AvahiSHostNameResolver, host_name_resolvers); + AVAHI_LLIST_HEAD(AvahiSAddressResolver, address_resolvers); + AVAHI_LLIST_HEAD(AvahiSDomainBrowser, domain_browsers); + AVAHI_LLIST_HEAD(AvahiSServiceTypeBrowser, service_type_browsers); + AVAHI_LLIST_HEAD(AvahiSServiceBrowser, service_browsers); + AVAHI_LLIST_HEAD(AvahiSServiceResolver, service_resolvers); + AVAHI_LLIST_HEAD(AvahiSDNSServerBrowser, dns_server_browsers); + + int need_entry_cleanup, need_group_cleanup, need_browser_cleanup; + + AvahiTimeEventQueue *time_event_queue; + + char *host_name, *host_name_fqdn, *domain_name; + + int fd_ipv4, fd_ipv6, + /* The following two sockets two are used for reflection only */ + fd_legacy_unicast_ipv4, fd_legacy_unicast_ipv6; + + AvahiWatch *watch_ipv4, *watch_ipv6, + *watch_legacy_unicast_ipv4, *watch_legacy_unicast_ipv6; + + AvahiServerState state; + AvahiServerCallback callback; + void* userdata; + + AvahiSEntryGroup *hinfo_entry_group; + AvahiSEntryGroup *browse_domain_entry_group; + unsigned n_host_rr_pending; + + /* Used for assembling responses */ + AvahiRecordList *record_list; + + /* Used for reflection of legacy unicast packets */ + AvahiLegacyUnicastReflectSlot **legacy_unicast_reflect_slots; + uint16_t legacy_unicast_reflect_id; + + /* The last error code */ + int error; + + /* The local service cookie */ + uint32_t local_service_cookie; + + AvahiMulticastLookupEngine *multicast_lookup_engine; + AvahiWideAreaLookupEngine *wide_area_lookup_engine; + + AvahiStringList *static_browse_domains; +}; + +void avahi_entry_free(AvahiServer*s, AvahiEntry *e); +void avahi_entry_group_free(AvahiServer *s, AvahiSEntryGroup *g); + +void avahi_cleanup_dead_entries(AvahiServer *s); + +void avahi_server_prepare_response(AvahiServer *s, AvahiInterface *i, AvahiEntry *e, int unicast_response, int auxiliary); +void avahi_server_prepare_matching_responses(AvahiServer *s, AvahiInterface *i, AvahiKey *k, int unicast_response); +void avahi_server_generate_response(AvahiServer *s, AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, uint16_t port, int legacy_unicast, int is_probe); + +void avahi_s_entry_group_change_state(AvahiSEntryGroup *g, AvahiEntryGroupState state); + +int avahi_entry_is_commited(AvahiEntry *e); + +void avahi_server_enumerate_aux_records(AvahiServer *s, AvahiInterface *i, AvahiRecord *r, void (*callback)(AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata), void* userdata); + +void avahi_host_rr_entry_group_callback(AvahiServer *s, AvahiSEntryGroup *g, AvahiEntryGroupState state, void *userdata); + +void avahi_server_decrease_host_rr_pending(AvahiServer *s); + +int avahi_server_set_errno(AvahiServer *s, int error); + +int avahi_server_is_service_local(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, const char *name); +int avahi_server_is_record_local(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, AvahiRecord *record); + +int avahi_server_add_ptr( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + uint32_t ttl, + const char *name, + const char *dest); + +#define AVAHI_CHECK_VALIDITY(server, expression, error) { \ + if (!(expression)) \ + return avahi_server_set_errno((server), (error)); \ +} + +#define AVAHI_CHECK_VALIDITY_RETURN_NULL(server, expression, error) { \ + if (!(expression)) { \ + avahi_server_set_errno((server), (error)); \ + return NULL; \ + } \ +} + +#define AVAHI_CHECK_VALIDITY_SET_RET_GOTO_FAIL(server, expression, error) {\ + if (!(expression)) { \ + ret = avahi_server_set_errno((server), (error)); \ + goto fail; \ + } \ +} + +#define AVAHI_ASSERT_TRUE(expression) { \ + int __tmp = !!(expression); \ + assert(__tmp); \ +} + +#define AVAHI_ASSERT_SUCCESS(expression) { \ + int __tmp = (expression); \ + assert(__tmp == 0); \ +} + +#endif diff --git a/trunk/avahi-core/log.c b/trunk/avahi-core/log.c new file mode 100644 index 0000000..bfd4021 --- /dev/null +++ b/trunk/avahi-core/log.c @@ -0,0 +1,88 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdarg.h> + +#include "log.h" + +static AvahiLogFunction log_function = NULL; + +void avahi_set_log_function(AvahiLogFunction function) { + log_function = function; +} + +void avahi_log_ap(AvahiLogLevel level, const char*format, va_list ap) { + char txt[256]; + + vsnprintf(txt, sizeof(txt), format, ap); + + if (log_function) + log_function(level, txt); + else + fprintf(stderr, "%s\n", txt); +} + +void avahi_log(AvahiLogLevel level, const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(level, format, ap); + va_end(ap); +} + +void avahi_log_error(const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(AVAHI_LOG_ERROR, format, ap); + va_end(ap); +} + +void avahi_log_warn(const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(AVAHI_LOG_WARN, format, ap); + va_end(ap); +} + +void avahi_log_notice(const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(AVAHI_LOG_NOTICE, format, ap); + va_end(ap); +} + +void avahi_log_info(const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(AVAHI_LOG_INFO, format, ap); + va_end(ap); +} + +void avahi_log_debug(const char*format, ...) { + va_list ap; + va_start(ap, format); + avahi_log_ap(AVAHI_LOG_DEBUG, format, ap); + va_end(ap); +} diff --git a/trunk/avahi-core/log.h b/trunk/avahi-core/log.h new file mode 100644 index 0000000..25e3940 --- /dev/null +++ b/trunk/avahi-core/log.h @@ -0,0 +1,75 @@ +#ifndef foologhfoo +#define foologhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <stdarg.h> + +#include <avahi-common/cdecl.h> +#include <avahi-common/gccmacro.h> + +/** \file log.h Extensible logging subsystem */ + +AVAHI_C_DECL_BEGIN + +/** Log level for avahi_log_xxx() */ +typedef enum { + AVAHI_LOG_ERROR = 0, /**< Error messages */ + AVAHI_LOG_WARN = 1, /**< Warning messages */ + AVAHI_LOG_NOTICE = 2, /**< Notice messages */ + AVAHI_LOG_INFO = 3, /**< Info messages */ + AVAHI_LOG_DEBUG = 4, /**< Debug messages */ + AVAHI_LOG_LEVEL_MAX +} AvahiLogLevel; + +/** Prototype for a user supplied log function */ +typedef void (*AvahiLogFunction)(AvahiLogLevel level, const char *txt); + +/** Set a user supplied log function, replacing the default which + * prints to log messages unconditionally to STDERR. Pass NULL for + * resetting to the default log function */ +void avahi_set_log_function(AvahiLogFunction function); + +/** Issue a log message using a va_list object */ +void avahi_log_ap(AvahiLogLevel level, const char *format, va_list ap); + +/** Issue a log message by passing a log level and a format string */ +void avahi_log(AvahiLogLevel level, const char*format, ...) AVAHI_GCC_PRINTF_ATTR23; + +/** Shortcut for avahi_log(AVAHI_LOG_ERROR, ...) */ +void avahi_log_error(const char*format, ...) AVAHI_GCC_PRINTF_ATTR12; + +/** Shortcut for avahi_log(AVAHI_LOG_WARN, ...) */ +void avahi_log_warn(const char*format, ...) AVAHI_GCC_PRINTF_ATTR12; + +/** Shortcut for avahi_log(AVAHI_LOG_NOTICE, ...) */ +void avahi_log_notice(const char*format, ...) AVAHI_GCC_PRINTF_ATTR12; + +/** Shortcut for avahi_log(AVAHI_LOG_INFO, ...) */ +void avahi_log_info(const char*format, ...) AVAHI_GCC_PRINTF_ATTR12; + +/** Shortcut for avahi_log(AVAHI_LOG_DEBUG, ...) */ +void avahi_log_debug(const char*format, ...) AVAHI_GCC_PRINTF_ATTR12; + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/lookup.h b/trunk/avahi-core/lookup.h new file mode 100644 index 0000000..0ce6fe8 --- /dev/null +++ b/trunk/avahi-core/lookup.h @@ -0,0 +1,237 @@ +#ifndef foolookuphfoo +#define foolookuphfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** \file avahi-core/lookup.h Functions for browsing/resolving services and other RRs */ + +/** \example core-browse-services.c Example how to browse for DNS-SD + * services using an embedded mDNS stack. */ + +/** A browsing object for arbitrary RRs */ +typedef struct AvahiSRecordBrowser AvahiSRecordBrowser; + +/** A host name to IP adddress resolver object */ +typedef struct AvahiSHostNameResolver AvahiSHostNameResolver; + +/** An IP address to host name resolver object ("reverse lookup") */ +typedef struct AvahiSAddressResolver AvahiSAddressResolver; + +/** A local domain browsing object. May be used to enumerate domains used on the local LAN */ +typedef struct AvahiSDomainBrowser AvahiSDomainBrowser; + +/** A DNS-SD service type browsing object. May be used to enumerate the service types of all available services on the local LAN */ +typedef struct AvahiSServiceTypeBrowser AvahiSServiceTypeBrowser; + +/** A DNS-SD service browser. Use this to enumerate available services of a certain kind on the local LAN. Use AvahiSServiceResolver to get specific service data like address and port for a service. */ +typedef struct AvahiSServiceBrowser AvahiSServiceBrowser; + +/** A DNS-SD service resolver. Use this to retrieve addres, port and TXT data for a DNS-SD service */ +typedef struct AvahiSServiceResolver AvahiSServiceResolver; + +#include <avahi-common/cdecl.h> +#include <avahi-common/defs.h> +#include <avahi-core/core.h> + +AVAHI_C_DECL_BEGIN + +/** Callback prototype for AvahiSRecordBrowser events */ +typedef void (*AvahiSRecordBrowserCallback)( + AvahiSRecordBrowser *b, /**< The AvahiSRecordBrowser object that is emitting this callback */ + AvahiIfIndex interface, /**< Logical OS network interface number the record was found on */ + AvahiProtocol protocol, /**< Protocol number the record was found. */ + AvahiBrowserEvent event, /**< Browsing event, either AVAHI_BROWSER_NEW or AVAHI_BROWSER_REMOVE */ + AvahiRecord *record, /**< The record that was found */ + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata /**< Arbitrary user data passed to avahi_s_record_browser_new() */ ); + +/** Create a new browsing object for arbitrary RRs */ +AvahiSRecordBrowser *avahi_s_record_browser_new( + AvahiServer *server, /**< The server object to which attach this query */ + AvahiIfIndex interface, /**< Logical OS interface number where to look for the records, or AVAHI_IF_UNSPEC to look on interfaces */ + AvahiProtocol protocol, /**< Protocol number to use when looking for the record, or AVAHI_PROTO_UNSPEC to look on all protocols */ + AvahiKey *key, /**< The search key */ + AvahiLookupFlags flags, /**< Lookup flags. Must have set either AVAHI_LOOKUP_FORCE_WIDE_AREA or AVAHI_LOOKUP_FORCE_MULTICAST, since domain based detection is not available here. */ + AvahiSRecordBrowserCallback callback, /**< The callback to call on browsing events */ + void* userdata /**< Arbitrary use suppliable data which is passed to the callback */); + +/** Free an AvahiSRecordBrowser object */ +void avahi_s_record_browser_free(AvahiSRecordBrowser *b); + +/** Callback prototype for AvahiSHostNameResolver events */ +typedef void (*AvahiSHostNameResolverCallback)( + AvahiSHostNameResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, /**< Resolving event */ + const char *host_name, /**< Host name which should be resolved. May differ in case from the query */ + const AvahiAddress *a, /**< The address, or NULL if the host name couldn't be resolved. */ + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create an AvahiSHostNameResolver object for resolving a host name to an adddress. See AvahiSRecordBrowser for more info on the paramters. */ +AvahiSHostNameResolver *avahi_s_host_name_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *host_name, /**< The host name to look for */ + AvahiProtocol aprotocol, /**< The address family of the desired address or AVAHI_PROTO_UNSPEC if doesn't matter. */ + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSHostNameResolverCallback calback, + void* userdata); + +/** Free a AvahiSHostNameResolver object */ +void avahi_s_host_name_resolver_free(AvahiSHostNameResolver *r); + +/** Callback prototype for AvahiSAddressResolver events */ +typedef void (*AvahiSAddressResolverCallback)( + AvahiSAddressResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const AvahiAddress *a, + const char *host_name, /**< A host name for the specified address, if one was found, i.e. event == AVAHI_RESOLVER_FOUND */ + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create an AvahiSAddressResolver object. See AvahiSRecordBrowser for more info on the paramters. */ +AvahiSAddressResolver *avahi_s_address_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const AvahiAddress *address, + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSAddressResolverCallback calback, + void* userdata); + +/** Free an AvahiSAddressResolver object */ +void avahi_s_address_resolver_free(AvahiSAddressResolver *r); + +/** Callback prototype for AvahiSDomainBrowser events */ +typedef void (*AvahiSDomainBrowserCallback)( + AvahiSDomainBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *domain, + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create a new AvahiSDomainBrowser object */ +AvahiSDomainBrowser *avahi_s_domain_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiDomainBrowserType type, + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSDomainBrowserCallback callback, + void* userdata); + +/** Free an AvahiSDomainBrowser object */ +void avahi_s_domain_browser_free(AvahiSDomainBrowser *b); + +/** Callback prototype for AvahiSServiceTypeBrowser events */ +typedef void (*AvahiSServiceTypeBrowserCallback)( + AvahiSServiceTypeBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create a new AvahiSServiceTypeBrowser object. */ +AvahiSServiceTypeBrowser *avahi_s_service_type_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *domain, + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSServiceTypeBrowserCallback callback, + void* userdata); + +/** Free an AvahiSServiceTypeBrowser object */ +void avahi_s_service_type_browser_free(AvahiSServiceTypeBrowser *b); + +/** Callback prototype for AvahiSServiceBrowser events */ +typedef void (*AvahiSServiceBrowserCallback)( + AvahiSServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name /**< Service name, e.g. "Lennart's Files" */, + const char *type /**< DNS-SD type, e.g. "_http._tcp" */, + const char *domain /**< Domain of this service, e.g. "local" */, + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create a new AvahiSServiceBrowser object. */ +AvahiSServiceBrowser *avahi_s_service_browser_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *service_type /** DNS-SD service type, e.g. "_http._tcp" */, + const char *domain, + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSServiceBrowserCallback callback, + void* userdata); + +/** Free an AvahiSServiceBrowser object */ +void avahi_s_service_browser_free(AvahiSServiceBrowser *b); + +/** Callback prototype for AvahiSServiceResolver events */ +typedef void (*AvahiSServiceResolverCallback)( + AvahiSServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, /**< Is AVAHI_RESOLVER_FOUND when the service was resolved successfully, and everytime it changes. Is AVAHI_RESOLVER_TIMOUT when the service failed to resolve or disappeared. */ + const char *name, /**< Service name */ + const char *type, /**< Service Type */ + const char *domain, + const char *host_name, /**< Host name of the service */ + const AvahiAddress *a, /**< The resolved host name */ + uint16_t port, /**< Service name */ + AvahiStringList *txt, /**< TXT record data */ + AvahiLookupResultFlags flags, /**< Lookup flags */ + void* userdata); + +/** Create a new AvahiSServiceResolver object. The specified callback function will be called with the resolved service data. */ +AvahiSServiceResolver *avahi_s_service_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *name, + const char *type, + const char *domain, + AvahiProtocol aprotocol, /**< Address family of the desired service address. Use AVAHI_PROTO_UNSPEC if you don't care */ + AvahiLookupFlags flags, /**< Lookup flags. */ + AvahiSServiceResolverCallback calback, + void* userdata); + +/** Free an AvahiSServiceResolver object */ +void avahi_s_service_resolver_free(AvahiSServiceResolver *r); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/multicast-lookup.c b/trunk/avahi-core/multicast-lookup.c new file mode 100644 index 0000000..c3afcb0 --- /dev/null +++ b/trunk/avahi-core/multicast-lookup.c @@ -0,0 +1,352 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/malloc.h> +#include <avahi-common/timeval.h> + +#include "internal.h" +#include "browse.h" +#include "socket.h" +#include "log.h" +#include "hashmap.h" +#include "multicast-lookup.h" +#include "rr-util.h" + +struct AvahiMulticastLookup { + AvahiMulticastLookupEngine *engine; + int dead; + + AvahiKey *key, *cname_key; + + AvahiMulticastLookupCallback callback; + void *userdata; + + AvahiIfIndex interface; + AvahiProtocol protocol; + + int queriers_added; + + AvahiTimeEvent *all_for_now_event; + + AVAHI_LLIST_FIELDS(AvahiMulticastLookup, lookups); + AVAHI_LLIST_FIELDS(AvahiMulticastLookup, by_key); +}; + +struct AvahiMulticastLookupEngine { + AvahiServer *server; + + /* Lookups */ + AVAHI_LLIST_HEAD(AvahiMulticastLookup, lookups); + AvahiHashmap *lookups_by_key; + + int cleanup_dead; +}; + +static void all_for_now_callback(AvahiTimeEvent *e, void* userdata) { + AvahiMulticastLookup *l = userdata; + + assert(e); + assert(l); + + avahi_time_event_free(l->all_for_now_event); + l->all_for_now_event = NULL; + + l->callback(l->engine, l->interface, l->protocol, AVAHI_BROWSER_ALL_FOR_NOW, AVAHI_LOOKUP_RESULT_MULTICAST, NULL, l->userdata); +} + +AvahiMulticastLookup *avahi_multicast_lookup_new( + AvahiMulticastLookupEngine *e, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiKey *key, + AvahiMulticastLookupCallback callback, + void *userdata) { + + AvahiMulticastLookup *l, *t; + struct timeval tv; + + assert(e); + assert(AVAHI_IF_VALID(interface)); + assert(AVAHI_PROTO_VALID(protocol)); + assert(key); + assert(callback); + + l = avahi_new(AvahiMulticastLookup, 1); + l->engine = e; + l->dead = 0; + l->key = avahi_key_ref(key); + l->cname_key = avahi_key_new_cname(l->key); + l->callback = callback; + l->userdata = userdata; + l->interface = interface; + l->protocol = protocol; + l->all_for_now_event = NULL; + l->queriers_added = 0; + + t = avahi_hashmap_lookup(e->lookups_by_key, l->key); + AVAHI_LLIST_PREPEND(AvahiMulticastLookup, by_key, t, l); + avahi_hashmap_replace(e->lookups_by_key, avahi_key_ref(l->key), t); + + AVAHI_LLIST_PREPEND(AvahiMulticastLookup, lookups, e->lookups, l); + + avahi_querier_add_for_all(e->server, interface, protocol, l->key, &tv); + l->queriers_added = 1; + + /* Add a second */ + avahi_timeval_add(&tv, 1000000); + + /* Issue the ALL_FOR_NOW event one second after the querier was initially created */ + l->all_for_now_event = avahi_time_event_new(e->server->time_event_queue, &tv, all_for_now_callback, l); + + return l; +} + +static void lookup_stop(AvahiMulticastLookup *l) { + assert(l); + + l->callback = NULL; + + if (l->queriers_added) { + avahi_querier_remove_for_all(l->engine->server, l->interface, l->protocol, l->key); + l->queriers_added = 0; + } + + if (l->all_for_now_event) { + avahi_time_event_free(l->all_for_now_event); + l->all_for_now_event = NULL; + } +} + +static void lookup_destroy(AvahiMulticastLookup *l) { + AvahiMulticastLookup *t; + assert(l); + + lookup_stop(l); + + t = avahi_hashmap_lookup(l->engine->lookups_by_key, l->key); + AVAHI_LLIST_REMOVE(AvahiMulticastLookup, by_key, t, l); + if (t) + avahi_hashmap_replace(l->engine->lookups_by_key, avahi_key_ref(l->key), t); + else + avahi_hashmap_remove(l->engine->lookups_by_key, l->key); + + AVAHI_LLIST_REMOVE(AvahiMulticastLookup, lookups, l->engine->lookups, l); + + if (l->key) + avahi_key_unref(l->key); + + if (l->cname_key) + avahi_key_unref(l->cname_key); + + avahi_free(l); +} + +void avahi_multicast_lookup_free(AvahiMulticastLookup *l) { + assert(l); + + if (l->dead) + return; + + l->dead = 1; + l->engine->cleanup_dead = 1; + lookup_stop(l); +} + +void avahi_multicast_lookup_engine_cleanup(AvahiMulticastLookupEngine *e) { + AvahiMulticastLookup *l, *n; + assert(e); + + while (e->cleanup_dead) { + e->cleanup_dead = 0; + + for (l = e->lookups; l; l = n) { + n = l->lookups_next; + + if (l->dead) + lookup_destroy(l); + } + } +} + +struct cbdata { + AvahiMulticastLookupEngine *engine; + AvahiMulticastLookupCallback callback; + void *userdata; + AvahiKey *key, *cname_key; + AvahiInterface *interface; + unsigned n_found; +}; + +static void* scan_cache_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void* userdata) { + struct cbdata *cbdata = userdata; + + assert(c); + assert(pattern); + assert(e); + assert(cbdata); + + cbdata->callback( + cbdata->engine, + cbdata->interface->hardware->index, + cbdata->interface->protocol, + AVAHI_BROWSER_NEW, + AVAHI_LOOKUP_RESULT_CACHED|AVAHI_LOOKUP_RESULT_MULTICAST, + e->record, + cbdata->userdata); + + cbdata->n_found ++; + + return NULL; +} + +static void scan_interface_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + struct cbdata *cbdata = userdata; + + assert(m); + assert(i); + assert(cbdata); + + cbdata->interface = i; + + avahi_cache_walk(i->cache, cbdata->key, scan_cache_callback, cbdata); + + if (cbdata->cname_key) + avahi_cache_walk(i->cache, cbdata->cname_key, scan_cache_callback, cbdata); + + cbdata->interface = NULL; +} + +unsigned avahi_multicast_lookup_engine_scan_cache( + AvahiMulticastLookupEngine *e, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiKey *key, + AvahiMulticastLookupCallback callback, + void *userdata) { + + struct cbdata cbdata; + + assert(e); + assert(key); + assert(callback); + + assert(AVAHI_IF_VALID(interface)); + assert(AVAHI_PROTO_VALID(protocol)); + + cbdata.engine = e; + cbdata.key = key; + cbdata.cname_key = avahi_key_new_cname(key); + cbdata.callback = callback; + cbdata.userdata = userdata; + cbdata.interface = NULL; + cbdata.n_found = 0; + + avahi_interface_monitor_walk(e->server->monitor, interface, protocol, scan_interface_callback, &cbdata); + + if (cbdata.cname_key) + avahi_key_unref(cbdata.cname_key); + + return cbdata.n_found; +} + +void avahi_multicast_lookup_engine_new_interface(AvahiMulticastLookupEngine *e, AvahiInterface *i) { + AvahiMulticastLookup *l; + + assert(e); + assert(i); + + for (l = e->lookups; l; l = l->lookups_next) { + + if (l->dead || !l->callback) + continue; + + if (l->queriers_added && avahi_interface_match(i, l->interface, l->protocol)) + avahi_querier_add(i, l->key, NULL); + } +} + +void avahi_multicast_lookup_engine_notify(AvahiMulticastLookupEngine *e, AvahiInterface *i, AvahiRecord *record, AvahiBrowserEvent event) { + AvahiMulticastLookup *l; + + assert(e); + assert(record); + assert(i); + + for (l = avahi_hashmap_lookup(e->lookups_by_key, record->key); l; l = l->by_key_next) { + if (l->dead || !l->callback) + continue; + + if (avahi_interface_match(i, l->interface, l->protocol)) + l->callback(e, i->hardware->index, i->protocol, event, AVAHI_LOOKUP_RESULT_MULTICAST, record, l->userdata); + } + + + if (record->key->clazz == AVAHI_DNS_CLASS_IN && record->key->type == AVAHI_DNS_TYPE_CNAME) { + /* It's a CNAME record, so we have to scan the all lookups to see if one matches */ + + for (l = e->lookups; l; l = l->lookups_next) { + AvahiKey *key; + + if (l->dead || !l->callback) + continue; + + if ((key = avahi_key_new_cname(l->key))) { + if (avahi_key_equal(record->key, key)) + l->callback(e, i->hardware->index, i->protocol, event, AVAHI_LOOKUP_RESULT_MULTICAST, record, l->userdata); + + avahi_key_unref(key); + } + } + } +} + +AvahiMulticastLookupEngine *avahi_multicast_lookup_engine_new(AvahiServer *s) { + AvahiMulticastLookupEngine *e; + + assert(s); + + e = avahi_new(AvahiMulticastLookupEngine, 1); + e->server = s; + e->cleanup_dead = 0; + + /* Initialize lookup list */ + e->lookups_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL); + AVAHI_LLIST_HEAD_INIT(AvahiWideAreaLookup, e->lookups); + + return e; +} + +void avahi_multicast_lookup_engine_free(AvahiMulticastLookupEngine *e) { + assert(e); + + while (e->lookups) + lookup_destroy(e->lookups); + + avahi_hashmap_free(e->lookups_by_key); + avahi_free(e); +} + diff --git a/trunk/avahi-core/multicast-lookup.h b/trunk/avahi-core/multicast-lookup.h new file mode 100644 index 0000000..43e240d --- /dev/null +++ b/trunk/avahi-core/multicast-lookup.h @@ -0,0 +1,53 @@ +#ifndef foomulticastlookuphfoo +#define foomulticastlookuphfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "lookup.h" +#include "browse.h" + +typedef struct AvahiMulticastLookupEngine AvahiMulticastLookupEngine; +typedef struct AvahiMulticastLookup AvahiMulticastLookup; + +typedef void (*AvahiMulticastLookupCallback)( + AvahiMulticastLookupEngine *e, + AvahiIfIndex idx, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiLookupResultFlags flags, + AvahiRecord *r, + void *userdata); + +AvahiMulticastLookupEngine *avahi_multicast_lookup_engine_new(AvahiServer *s); +void avahi_multicast_lookup_engine_free(AvahiMulticastLookupEngine *e); + +unsigned avahi_multicast_lookup_engine_scan_cache(AvahiMulticastLookupEngine *e, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key, AvahiMulticastLookupCallback callback, void *userdata); +void avahi_multicast_lookup_engine_new_interface(AvahiMulticastLookupEngine *e, AvahiInterface *i); +void avahi_multicast_lookup_engine_cleanup(AvahiMulticastLookupEngine *e); +void avahi_multicast_lookup_engine_notify(AvahiMulticastLookupEngine *e, AvahiInterface *i, AvahiRecord *record, AvahiBrowserEvent event); + +AvahiMulticastLookup *avahi_multicast_lookup_new(AvahiMulticastLookupEngine *e, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key, AvahiMulticastLookupCallback callback, void *userdata); +void avahi_multicast_lookup_free(AvahiMulticastLookup *q); + + +#endif + diff --git a/trunk/avahi-core/netlink.c b/trunk/avahi-core/netlink.c new file mode 100644 index 0000000..7411c90 --- /dev/null +++ b/trunk/avahi-core/netlink.c @@ -0,0 +1,211 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/ioctl.h> +#include <assert.h> + +#include <avahi-common/malloc.h> +#include "netlink.h" +#include "log.h" + +struct AvahiNetlink { + int fd; + unsigned seq; + AvahiNetlinkCallback callback; + void* userdata; + uint8_t* buffer; + size_t buffer_length; + + const AvahiPoll *poll_api; + AvahiWatch *watch; +}; + +int avahi_netlink_work(AvahiNetlink *nl, int block) { + ssize_t bytes; + struct msghdr smsg; + struct cmsghdr *cmsg; + struct ucred *cred; + struct iovec iov; + struct nlmsghdr *p; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + + assert(nl); + + iov.iov_base = nl->buffer; + iov.iov_len = nl->buffer_length; + + smsg.msg_name = (void*) NULL; + smsg.msg_namelen = 0; + smsg.msg_iov = &iov; + smsg.msg_iovlen = 1; + smsg.msg_control = cred_msg; + smsg.msg_controllen = sizeof(cred_msg); + smsg.msg_flags = (block ? 0 : MSG_DONTWAIT); + + if ((bytes = recvmsg(nl->fd, &smsg, 0)) < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + avahi_log_error(__FILE__": recvmsg() failed: %s", strerror(errno)); + return -1; + } + + cmsg = CMSG_FIRSTHDR(&smsg); + cred = (struct ucred *) CMSG_DATA (cmsg); + + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + avahi_log_error("No sender credentials received, ignoring data."); + return -1; + } + + if (cred->uid != 0) { + avahi_log_warn("Netlink message received from cred->uid != 0 (%d)", cred->uid); + return -1; + } + + p = (struct nlmsghdr *) nl->buffer; + + assert(nl->callback); + + for (; bytes > 0; p = NLMSG_NEXT(p, bytes)) { + if (!NLMSG_OK(p, (size_t) bytes)) { + avahi_log_warn(__FILE__": packet truncated"); + return -1; + } + + nl->callback(nl, p, nl->userdata); + } + + return 0; +} + +static void socket_event(AvahiWatch *w, int fd, AVAHI_GCC_UNUSED AvahiWatchEvent event, void *userdata) { + AvahiNetlink *nl = userdata; + + assert(w); + assert(nl); + assert(fd == nl->fd); + + avahi_netlink_work(nl, 0); +} + +AvahiNetlink *avahi_netlink_new(const AvahiPoll *poll_api, uint32_t groups, void (*cb) (AvahiNetlink *nl, struct nlmsghdr *n, void* userdata), void* userdata) { + int fd = -1; + const int on = 1; + struct sockaddr_nl addr; + AvahiNetlink *nl = NULL; + + assert(poll_api); + assert(cb); + + if ((fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) { + avahi_log_error(__FILE__": socket(PF_NETLINK): %s", strerror(errno)); + return NULL; + } + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = groups; + addr.nl_pid = getpid(); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + avahi_log_error(__FILE__": bind(): %s", strerror(errno)); + goto fail; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) { + avahi_log_error(__FILE__": bind(): %s", strerror(errno)); + goto fail; + } + + if (!(nl = avahi_new(AvahiNetlink, 1))) { + avahi_log_error(__FILE__": avahi_new() failed."); + goto fail; + } + + nl->poll_api = poll_api; + nl->fd = fd; + nl->seq = 0; + nl->callback = cb; + nl->userdata = userdata; + + if (!(nl->buffer = avahi_new(uint8_t, nl->buffer_length = 64*1024))) { + avahi_log_error(__FILE__": avahi_new() failed."); + goto fail; + } + + if (!(nl->watch = poll_api->watch_new(poll_api, fd, AVAHI_WATCH_IN, socket_event, nl))) { + avahi_log_error(__FILE__": Failed to create watch."); + goto fail; + } + + return nl; + +fail: + + if (fd >= 0) + close(fd); + + if (nl) { + avahi_free(nl->buffer); + avahi_free(nl); + } + + return NULL; +} + +void avahi_netlink_free(AvahiNetlink *nl) { + assert(nl); + + if (nl->watch) + nl->poll_api->watch_free(nl->watch); + + if (nl->fd >= 0) + close(nl->fd); + + avahi_free(nl->buffer); + avahi_free(nl); +} + +int avahi_netlink_send(AvahiNetlink *nl, struct nlmsghdr *m, unsigned *ret_seq) { + assert(nl); + assert(m); + + m->nlmsg_seq = nl->seq++; + m->nlmsg_flags |= NLM_F_ACK; + + if (send(nl->fd, m, m->nlmsg_len, 0) < 0) { + avahi_log_error(__FILE__": send(): %s", strerror(errno)); + return -1; + } + + if (ret_seq) + *ret_seq = m->nlmsg_seq; + + return 0; +} diff --git a/trunk/avahi-core/netlink.h b/trunk/avahi-core/netlink.h new file mode 100644 index 0000000..8f2f8cb --- /dev/null +++ b/trunk/avahi-core/netlink.h @@ -0,0 +1,43 @@ +#ifndef foonetlinkhfoo +#define foonetlinkhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sys/socket.h> +#include <asm/types.h> +#include <inttypes.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <avahi-common/watch.h> + +typedef struct AvahiNetlink AvahiNetlink; + +typedef void (*AvahiNetlinkCallback)(AvahiNetlink *n, struct nlmsghdr *m, void* userdata); + +AvahiNetlink *avahi_netlink_new(const AvahiPoll *poll_api, uint32_t groups, AvahiNetlinkCallback callback, void* userdata); +void avahi_netlink_free(AvahiNetlink *n); +int avahi_netlink_send(AvahiNetlink *n, struct nlmsghdr *m, unsigned *ret_seq); +int avahi_netlink_work(AvahiNetlink *n, int block); + +#endif diff --git a/trunk/avahi-core/prioq-test.c b/trunk/avahi-core/prioq-test.c new file mode 100644 index 0000000..d85a222 --- /dev/null +++ b/trunk/avahi-core/prioq-test.c @@ -0,0 +1,122 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <time.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> + +#include <avahi-common/gccmacro.h> + +#include "prioq.h" + +#define POINTER_TO_INT(p) ((int) (p)) +#define INT_TO_POINTER(i) ((void*) (i)) + +static int compare_int(const void* a, const void* b) { + int i = POINTER_TO_INT(a), j = POINTER_TO_INT(b); + + return i < j ? -1 : (i > j ? 1 : 0); +} + +static int compare_ptr(const void* a, const void* b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +static void rec(AvahiPrioQueueNode *n) { + if (!n) + return; + + if (n->left) + assert(n->left->parent == n); + + if (n->right) + assert(n->right->parent == n); + + if (n->parent) { + assert(n->parent->left == n || n->parent->right == n); + + if (n->parent->left == n) + assert(n->next == n->parent->right); + } + + if (!n->next) { + assert(n->queue->last == n); + + if (n->parent && n->parent->left == n) + assert(n->parent->right == NULL); + } + + + if (n->parent) { + int a = POINTER_TO_INT(n->parent->data), b = POINTER_TO_INT(n->data); + if (a > b) { + printf("%i <= %i: NO\n", a, b); + abort(); + } + } + + rec(n->left); + rec(n->right); +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + AvahiPrioQueue *q, *q2; + int i; + + q = avahi_prio_queue_new(compare_int); + q2 = avahi_prio_queue_new(compare_ptr); + + srand(time(NULL)); + + for (i = 0; i < 10000; i++) + avahi_prio_queue_put(q2, avahi_prio_queue_put(q, INT_TO_POINTER(random() & 0xFFFF))); + + while (q2->root) { + rec(q->root); + rec(q2->root); + + assert(q->n_nodes == q2->n_nodes); + + printf("%i\n", POINTER_TO_INT(((AvahiPrioQueueNode*)q2->root->data)->data)); + + avahi_prio_queue_remove(q, q2->root->data); + avahi_prio_queue_remove(q2, q2->root); + } + + +/* prev = 0; */ +/* while (q->root) { */ +/* int v = GPOINTER_TO_INT(q->root->data); */ +/* rec(q->root); */ +/* printf("%i\n", v); */ +/* avahi_prio_queue_remove(q, q->root); */ +/* assert(v >= prev); */ +/* prev = v; */ +/* } */ + + avahi_prio_queue_free(q); + return 0; +} diff --git a/trunk/avahi-core/prioq.c b/trunk/avahi-core/prioq.c new file mode 100644 index 0000000..8d91d87 --- /dev/null +++ b/trunk/avahi-core/prioq.c @@ -0,0 +1,390 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> + +#include <avahi-common/malloc.h> + +#include "prioq.h" + +AvahiPrioQueue* avahi_prio_queue_new(AvahiPQCompareFunc compare) { + AvahiPrioQueue *q; + assert(compare); + + if (!(q = avahi_new(AvahiPrioQueue, 1))) + return NULL; /* OOM */ + + q->root = q->last = NULL; + q->n_nodes = 0; + q->compare = compare; + + return q; +} + +void avahi_prio_queue_free(AvahiPrioQueue *q) { + assert(q); + + while (q->last) + avahi_prio_queue_remove(q, q->last); + + assert(!q->n_nodes); + avahi_free(q); +} + +static AvahiPrioQueueNode* get_node_at_xy(AvahiPrioQueue *q, unsigned x, unsigned y) { + unsigned r; + AvahiPrioQueueNode *n; + assert(q); + + n = q->root; + assert(n); + + for (r = 0; r < y; r++) { + assert(n); + + if ((x >> (y-r-1)) & 1) + n = n->right; + else + n = n->left; + } + + assert(n->x == x); + assert(n->y == y); + + return n; +} + +static void exchange_nodes(AvahiPrioQueue *q, AvahiPrioQueueNode *a, AvahiPrioQueueNode *b) { + AvahiPrioQueueNode *l, *r, *p, *ap, *an, *bp, *bn; + unsigned t; + assert(q); + assert(a); + assert(b); + assert(a != b); + + /* Swap positions */ + t = a->x; a->x = b->x; b->x = t; + t = a->y; a->y = b->y; b->y = t; + + if (a->parent == b) { + /* B is parent of A */ + + p = b->parent; + b->parent = a; + + if ((a->parent = p)) { + if (a->parent->left == b) + a->parent->left = a; + else + a->parent->right = a; + } else + q->root = a; + + if (b->left == a) { + if ((b->left = a->left)) + b->left->parent = b; + a->left = b; + + r = a->right; + if ((a->right = b->right)) + a->right->parent = a; + if ((b->right = r)) + b->right->parent = b; + + } else { + if ((b->right = a->right)) + b->right->parent = b; + a->right = b; + + l = a->left; + if ((a->left = b->left)) + a->left->parent = a; + if ((b->left = l)) + b->left->parent = b; + } + } else if (b->parent == a) { + /* A ist parent of B */ + + p = a->parent; + a->parent = b; + + if ((b->parent = p)) { + if (b->parent->left == a) + b->parent->left = b; + else + b->parent->right = b; + } else + q->root = b; + + if (a->left == b) { + if ((a->left = b->left)) + a->left->parent = a; + b->left = a; + + r = a->right; + if ((a->right = b->right)) + a->right->parent = a; + if ((b->right = r)) + b->right->parent = b; + } else { + if ((a->right = b->right)) + a->right->parent = a; + b->right = a; + + l = a->left; + if ((a->left = b->left)) + a->left->parent = a; + if ((b->left = l)) + b->left->parent = b; + } + } else { + AvahiPrioQueueNode *apl = NULL, *bpl = NULL; + + /* Swap parents */ + ap = a->parent; + bp = b->parent; + + if (ap) + apl = ap->left; + if (bp) + bpl = bp->left; + + if ((a->parent = bp)) { + if (bpl == b) + bp->left = a; + else + bp->right = a; + } else + q->root = a; + + if ((b->parent = ap)) { + if (apl == a) + ap->left = b; + else + ap->right = b; + } else + q->root = b; + + /* Swap children */ + l = a->left; + r = a->right; + + if ((a->left = b->left)) + a->left->parent = a; + + if ((b->left = l)) + b->left->parent = b; + + if ((a->right = b->right)) + a->right->parent = a; + + if ((b->right = r)) + b->right->parent = b; + } + + /* Swap siblings */ + ap = a->prev; an = a->next; + bp = b->prev; bn = b->next; + + if (a->next == b) { + /* A is predecessor of B */ + a->prev = b; + b->next = a; + + if ((a->next = bn)) + a->next->prev = a; + else + q->last = a; + + if ((b->prev = ap)) + b->prev->next = b; + + } else if (b->next == a) { + /* B is predecessor of A */ + a->next = b; + b->prev = a; + + if ((a->prev = bp)) + a->prev->next = a; + + if ((b->next = an)) + b->next->prev = b; + else + q->last = b; + + } else { + /* A is no neighbour of B */ + + if ((a->prev = bp)) + a->prev->next = a; + + if ((a->next = bn)) + a->next->prev = a; + else + q->last = a; + + if ((b->prev = ap)) + b->prev->next = b; + + if ((b->next = an)) + b->next->prev = b; + else + q->last = b; + } +} + +/* Move a node to the correct position */ +void avahi_prio_queue_shuffle(AvahiPrioQueue *q, AvahiPrioQueueNode *n) { + assert(q); + assert(n); + assert(n->queue == q); + + /* Move up until the position is OK */ + while (n->parent && q->compare(n->parent->data, n->data) > 0) + exchange_nodes(q, n, n->parent); + + /* Move down until the position is OK */ + for (;;) { + AvahiPrioQueueNode *min; + + if (!(min = n->left)) { + /* No children */ + assert(!n->right); + break; + } + + if (n->right && q->compare(n->right->data, min->data) < 0) + min = n->right; + + /* min now contains the smaller one of our two children */ + + if (q->compare(n->data, min->data) <= 0) + /* Order OK */ + break; + + exchange_nodes(q, n, min); + } +} + +AvahiPrioQueueNode* avahi_prio_queue_put(AvahiPrioQueue *q, void* data) { + AvahiPrioQueueNode *n; + assert(q); + + if (!(n = avahi_new(AvahiPrioQueueNode, 1))) + return NULL; /* OOM */ + + n->queue = q; + n->data = data; + + if (q->last) { + assert(q->root); + assert(q->n_nodes); + + n->y = q->last->y; + n->x = q->last->x+1; + + if (n->x >= ((unsigned) 1 << n->y)) { + n->x = 0; + n->y++; + } + + q->last->next = n; + n->prev = q->last; + + assert(n->y > 0); + n->parent = get_node_at_xy(q, n->x/2, n->y-1); + + if (n->x & 1) + n->parent->right = n; + else + n->parent->left = n; + } else { + assert(!q->root); + assert(!q->n_nodes); + + n->y = n->x = 0; + q->root = n; + n->prev = n->parent = NULL; + } + + n->next = n->left = n->right = NULL; + q->last = n; + q->n_nodes++; + + avahi_prio_queue_shuffle(q, n); + + return n; +} + +void avahi_prio_queue_remove(AvahiPrioQueue *q, AvahiPrioQueueNode *n) { + assert(q); + assert(n); + assert(q == n->queue); + + if (n != q->last) { + AvahiPrioQueueNode *replacement = q->last; + exchange_nodes(q, replacement, n); + avahi_prio_queue_remove(q, n); + avahi_prio_queue_shuffle(q, replacement); + return; + } + + assert(n == q->last); + assert(!n->next); + assert(!n->left); + assert(!n->right); + + q->last = n->prev; + + if (n->prev) { + n->prev->next = NULL; + assert(n->parent); + } else + assert(!n->parent); + + if (n->parent) { + assert(n->prev); + if (n->parent->left == n) { + assert(n->parent->right == NULL); + n->parent->left = NULL; + } else { + assert(n->parent->right == n); + assert(n->parent->left != NULL); + n->parent->right = NULL; + } + } else { + assert(q->root == n); + assert(!n->prev); + assert(q->n_nodes == 1); + q->root = NULL; + } + + avahi_free(n); + + assert(q->n_nodes > 0); + q->n_nodes--; +} + diff --git a/trunk/avahi-core/prioq.h b/trunk/avahi-core/prioq.h new file mode 100644 index 0000000..ace92f6 --- /dev/null +++ b/trunk/avahi-core/prioq.h @@ -0,0 +1,51 @@ +#ifndef fooprioqhfoo +#define fooprioqhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiPrioQueue AvahiPrioQueue; +typedef struct AvahiPrioQueueNode AvahiPrioQueueNode; + +typedef int (*AvahiPQCompareFunc)(const void* a, const void* b); + +struct AvahiPrioQueue { + AvahiPrioQueueNode *root, *last; + unsigned n_nodes; + AvahiPQCompareFunc compare; +}; + +struct AvahiPrioQueueNode { + AvahiPrioQueue *queue; + void* data; + unsigned x, y; + AvahiPrioQueueNode *left, *right, *parent, *next, *prev; +}; + +AvahiPrioQueue* avahi_prio_queue_new(AvahiPQCompareFunc compare); +void avahi_prio_queue_free(AvahiPrioQueue *q); + +AvahiPrioQueueNode* avahi_prio_queue_put(AvahiPrioQueue *q, void* data); +void avahi_prio_queue_remove(AvahiPrioQueue *q, AvahiPrioQueueNode *n); + +void avahi_prio_queue_shuffle(AvahiPrioQueue *q, AvahiPrioQueueNode *n); + +#endif diff --git a/trunk/avahi-core/probe-sched.c b/trunk/avahi-core/probe-sched.c new file mode 100644 index 0000000..f430bce --- /dev/null +++ b/trunk/avahi-core/probe-sched.c @@ -0,0 +1,402 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/domain.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "probe-sched.h" +#include "log.h" +#include "rr-util.h" + +#define AVAHI_PROBE_HISTORY_MSEC 150 +#define AVAHI_PROBE_DEFER_MSEC 50 + +typedef struct AvahiProbeJob AvahiProbeJob; + +struct AvahiProbeJob { + AvahiProbeScheduler *scheduler; + AvahiTimeEvent *time_event; + + int chosen; /* Use for packet assembling */ + int done; + struct timeval delivery; + + AvahiRecord *record; + + AVAHI_LLIST_FIELDS(AvahiProbeJob, jobs); +}; + +struct AvahiProbeScheduler { + AvahiInterface *interface; + AvahiTimeEventQueue *time_event_queue; + + AVAHI_LLIST_HEAD(AvahiProbeJob, jobs); + AVAHI_LLIST_HEAD(AvahiProbeJob, history); +}; + +static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record, int done) { + AvahiProbeJob *pj; + + assert(s); + assert(record); + + if (!(pj = avahi_new(AvahiProbeJob, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; /* OOM */ + } + + pj->scheduler = s; + pj->record = avahi_record_ref(record); + pj->time_event = NULL; + pj->chosen = 0; + + if ((pj->done = done)) + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj); + else + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->jobs, pj); + + return pj; +} + +static void job_free(AvahiProbeScheduler *s, AvahiProbeJob *pj) { + assert(pj); + + if (pj->time_event) + avahi_time_event_free(pj->time_event); + + if (pj->done) + AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->history, pj); + else + AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj); + + avahi_record_unref(pj->record); + avahi_free(pj); +} + +static void elapse_callback(AvahiTimeEvent *e, void* data); + +static void job_set_elapse_time(AvahiProbeScheduler *s, AvahiProbeJob *pj, unsigned msec, unsigned jitter) { + struct timeval tv; + + assert(s); + assert(pj); + + avahi_elapse_time(&tv, msec, jitter); + + if (pj->time_event) + avahi_time_event_update(pj->time_event, &tv); + else + pj->time_event = avahi_time_event_new(s->time_event_queue, &tv, elapse_callback, pj); +} + +static void job_mark_done(AvahiProbeScheduler *s, AvahiProbeJob *pj) { + assert(s); + assert(pj); + + assert(!pj->done); + + AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj); + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj); + + pj->done = 1; + + job_set_elapse_time(s, pj, AVAHI_PROBE_HISTORY_MSEC, 0); + gettimeofday(&pj->delivery, NULL); +} + +AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *i) { + AvahiProbeScheduler *s; + + assert(i); + + if (!(s = avahi_new(AvahiProbeScheduler, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; + } + + s->interface = i; + s->time_event_queue = i->monitor->server->time_event_queue; + + AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->jobs); + AVAHI_LLIST_HEAD_INIT(AvahiProbeJob, s->history); + + return s; +} + +void avahi_probe_scheduler_free(AvahiProbeScheduler *s) { + assert(s); + + avahi_probe_scheduler_clear(s); + avahi_free(s); +} + +void avahi_probe_scheduler_clear(AvahiProbeScheduler *s) { + assert(s); + + while (s->jobs) + job_free(s, s->jobs); + while (s->history) + job_free(s, s->history); +} + +static int packet_add_probe_query(AvahiProbeScheduler *s, AvahiDnsPacket *p, AvahiProbeJob *pj) { + size_t size; + AvahiKey *k; + int b; + + assert(s); + assert(p); + assert(pj); + + assert(!pj->chosen); + + /* Estimate the size for this record */ + size = + avahi_key_get_estimate_size(pj->record->key) + + avahi_record_get_estimate_size(pj->record); + + /* Too large */ + if (size > avahi_dns_packet_space(p)) + return 0; + + /* Create the probe query */ + if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY))) + return 0; /* OOM */ + + b = !!avahi_dns_packet_append_key(p, k, 0); + assert(b); + + /* Mark this job for addition to the packet */ + pj->chosen = 1; + + /* Scan for more jobs whith matching key pattern */ + for (pj = s->jobs; pj; pj = pj->jobs_next) { + if (pj->chosen) + continue; + + /* Does the record match the probe? */ + if (k->clazz != pj->record->key->clazz || !avahi_domain_equal(k->name, pj->record->key->name)) + continue; + + /* This job wouldn't fit in */ + if (avahi_record_get_estimate_size(pj->record) > avahi_dns_packet_space(p)) + break; + + /* Mark this job for addition to the packet */ + pj->chosen = 1; + } + + avahi_key_unref(k); + + return 1; +} + +static void elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* data) { + AvahiProbeJob *pj = data, *next; + AvahiProbeScheduler *s; + AvahiDnsPacket *p; + unsigned n; + + assert(pj); + s = pj->scheduler; + + if (pj->done) { + /* Lets remove it from the history */ + job_free(s, pj); + return; + } + + if (!(p = avahi_dns_packet_new_query(s->interface->hardware->mtu))) + return; /* OOM */ + n = 1; + + /* Add the import probe */ + if (!packet_add_probe_query(s, p, pj)) { + size_t size; + AvahiKey *k; + int b; + + avahi_dns_packet_free(p); + + /* The probe didn't fit in the package, so let's allocate a larger one */ + + size = + avahi_key_get_estimate_size(pj->record->key) + + avahi_record_get_estimate_size(pj->record) + + AVAHI_DNS_PACKET_HEADER_SIZE; + + if (size > AVAHI_DNS_PACKET_SIZE_MAX) + size = AVAHI_DNS_PACKET_SIZE_MAX; + + if (!(p = avahi_dns_packet_new_query(size))) + return; /* OOM */ + + if (!(k = avahi_key_new(pj->record->key->name, pj->record->key->clazz, AVAHI_DNS_TYPE_ANY))) { + avahi_dns_packet_free(p); + return; /* OOM */ + } + + b = avahi_dns_packet_append_key(p, k, 0) && avahi_dns_packet_append_record(p, pj->record, 0, 0); + avahi_key_unref(k); + + if (b) { + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, 1); + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, 1); + avahi_interface_send_packet(s->interface, p); + } else + avahi_log_warn("Probe record too large, cannot send"); + + avahi_dns_packet_free(p); + job_mark_done(s, pj); + + return; + } + + /* Try to fill up packet with more probes, if available */ + for (pj = s->jobs; pj; pj = pj->jobs_next) { + + if (pj->chosen) + continue; + + if (!packet_add_probe_query(s, p, pj)) + break; + + n++; + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n); + + n = 0; + + /* Now add the chosen records to the authorative section */ + for (pj = s->jobs; pj; pj = next) { + + next = pj->jobs_next; + + if (!pj->chosen) + continue; + + if (!avahi_dns_packet_append_record(p, pj->record, 0, 0)) { +/* avahi_log_warn("Bad probe size estimate!"); */ + + /* Unmark all following jobs */ + for (; pj; pj = pj->jobs_next) + pj->chosen = 0; + + break; + } + + job_mark_done(s, pj); + + n ++; + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_NSCOUNT, n); + + /* Send it now */ + avahi_interface_send_packet(s->interface, p); + avahi_dns_packet_free(p); +} + +static AvahiProbeJob* find_scheduled_job(AvahiProbeScheduler *s, AvahiRecord *record) { + AvahiProbeJob *pj; + + assert(s); + assert(record); + + for (pj = s->jobs; pj; pj = pj->jobs_next) { + assert(!pj->done); + + if (avahi_record_equal_no_ttl(pj->record, record)) + return pj; + } + + return NULL; +} + +static AvahiProbeJob* find_history_job(AvahiProbeScheduler *s, AvahiRecord *record) { + AvahiProbeJob *pj; + + assert(s); + assert(record); + + for (pj = s->history; pj; pj = pj->jobs_next) { + assert(pj->done); + + if (avahi_record_equal_no_ttl(pj->record, record)) { + /* Check whether this entry is outdated */ + + if (avahi_age(&pj->delivery) > AVAHI_PROBE_HISTORY_MSEC*1000) { + /* it is outdated, so let's remove it */ + job_free(s, pj); + return NULL; + } + + return pj; + } + } + + return NULL; +} + +int avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, int immediately) { + AvahiProbeJob *pj; + struct timeval tv; + + assert(s); + assert(record); + assert(!avahi_key_is_pattern(record->key)); + + if ((pj = find_history_job(s, record))) + return 0; + + avahi_elapse_time(&tv, immediately ? 0 : AVAHI_PROBE_DEFER_MSEC, 0); + + if ((pj = find_scheduled_job(s, record))) { + + if (avahi_timeval_compare(&tv, &pj->delivery) < 0) { + /* If the new entry should be scheduled earlier, update the old entry */ + pj->delivery = tv; + avahi_time_event_update(pj->time_event, &pj->delivery); + } + + return 1; + } else { + /* Create a new job and schedule it */ + if (!(pj = job_new(s, record, 0))) + return 0; /* OOM */ + + pj->delivery = tv; + pj->time_event = avahi_time_event_new(s->time_event_queue, &pj->delivery, elapse_callback, pj); + + +/* avahi_log_debug("Accepted new probe job."); */ + + return 1; + } +} diff --git a/trunk/avahi-core/probe-sched.h b/trunk/avahi-core/probe-sched.h new file mode 100644 index 0000000..3af1319 --- /dev/null +++ b/trunk/avahi-core/probe-sched.h @@ -0,0 +1,36 @@ +#ifndef fooprobeschedhfoo +#define fooprobeschedhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiProbeScheduler AvahiProbeScheduler; + +#include <avahi-common/address.h> +#include "iface.h" + +AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *i); +void avahi_probe_scheduler_free(AvahiProbeScheduler *s); +void avahi_probe_scheduler_clear(AvahiProbeScheduler *s); + +int avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, int immediately); + +#endif diff --git a/trunk/avahi-core/publish.h b/trunk/avahi-core/publish.h new file mode 100644 index 0000000..77c49af --- /dev/null +++ b/trunk/avahi-core/publish.h @@ -0,0 +1,177 @@ +#ifndef foopublishhfoo +#define foopublishhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** \file core/publish.h Functions for publising local services and RRs */ + +/** \example core-publish-service.c Example how to register a DNS-SD + * service using an embedded mDNS stack. It behaves like a network + * printer registering both an IPP and a BSD LPR service. */ + +/** A group of locally registered DNS RRs */ +typedef struct AvahiSEntryGroup AvahiSEntryGroup; + +#include <avahi-common/cdecl.h> +#include <avahi-core/core.h> + +AVAHI_C_DECL_BEGIN + +/** Prototype for callback functions which are called whenever the state of an AvahiSEntryGroup object changes */ +typedef void (*AvahiSEntryGroupCallback) (AvahiServer *s, AvahiSEntryGroup *g, AvahiEntryGroupState state, void* userdata); + +/** Iterate through all local entries of the server. (when g is NULL) + * or of a specified entry group. At the first call state should point + * to a NULL initialized void pointer, That pointer is used to track + * the current iteration. It is not safe to call any other + * avahi_server_xxx() function during the iteration. If the last entry + * has been read, NULL is returned. */ +const AvahiRecord *avahi_server_iterate(AvahiServer *s, AvahiSEntryGroup *g, void **state); + +/** Create a new entry group. The specified callback function is + * called whenever the state of the group changes. Use entry group + * objects to keep track of you RRs. Add new RRs to a group using + * avahi_server_add_xxx(). Make sure to call avahi_s_entry_group_commit() + * to start the registration process for your RRs */ +AvahiSEntryGroup *avahi_s_entry_group_new(AvahiServer *s, AvahiSEntryGroupCallback callback, void* userdata); + +/** Free an entry group. All RRs assigned to the group are removed from the server */ +void avahi_s_entry_group_free(AvahiSEntryGroup *g); + +/** Commit an entry group. This starts the probing and registration process for all RRs in the group */ +int avahi_s_entry_group_commit(AvahiSEntryGroup *g); + +/** Remove all entries from the entry group and reset the state to AVAHI_ENTRY_GROUP_UNCOMMITED. */ +void avahi_s_entry_group_reset(AvahiSEntryGroup *g); + +/** Return 1 if the entry group is empty, i.e. has no records attached. */ +int avahi_s_entry_group_is_empty(AvahiSEntryGroup *g); + +/** Return the current state of the specified entry group */ +AvahiEntryGroupState avahi_s_entry_group_get_state(AvahiSEntryGroup *g); + +/** Change the opaque user data pointer attached to an entry group object */ +void avahi_s_entry_group_set_data(AvahiSEntryGroup *g, void* userdata); + +/** Return the opaque user data pointer currently set for the entry group object */ +void* avahi_s_entry_group_get_data(AvahiSEntryGroup *g); + +/** Add a new resource record to the server. Returns 0 on success, negative otherwise. */ +int avahi_server_add( + AvahiServer *s, /**< The server object to add this record to */ + AvahiSEntryGroup *g, /**< An entry group object if this new record shall be attached to one, or NULL. If you plan to remove the record sometime later you a required to pass an entry group object here. */ + AvahiIfIndex interface, /**< A numeric index of a network interface to attach this record to, or AVAHI_IF_UNSPEC to attach this record to all interfaces */ + AvahiProtocol protocol, /**< A protocol family to attach this record to. One of the AVAHI_PROTO_xxx constants. Use AVAHI_PROTO_UNSPEC to make this record available on all protocols (wich means on both IPv4 and IPv6). */ + AvahiPublishFlags flags, /**< Special flags for this record */ + AvahiRecord *r /**< The record to add. This function increases the reference counter of this object. */); + +/** Add an IP address mapping to the server. This will add both the + * host-name-to-address and the reverse mapping to the server. See + * avahi_server_add() for more information. If adding one of the RRs + * fails, the function returns with an error, but it is not defined if + * the other RR is deleted from the server or not. Therefore, you have + * to free the AvahiSEntryGroup and create a new one before + * proceeding. */ +int avahi_server_add_address( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + AvahiAddress *a); + +/** Add an DNS-SD service to the Server. This will add all required + * RRs to the server. See avahi_server_add() for more information. If + * adding one of the RRs fails, the function returns with an error, + * but it is not defined if the other RR is deleted from the server or + * not. Therefore, you have to free the AvahiSEntryGroup and create a + * new one before proceeding. */ +int avahi_server_add_service( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, /**< Service name, e.g. "Lennart's Files" */ + const char *type, /**< DNS-SD type, e.g. "_http._tcp" */ + const char *domain, + const char *host, /**< Host name where this servcie resides, or NULL if on the local host */ + uint16_t port, /**< Port number of the service */ + ... /**< Text records, terminated by NULL */) AVAHI_GCC_SENTINEL; + +/** Mostly identical to avahi_server_add_service(), but takes an AvahiStringList object for the TXT records. The AvahiStringList object is copied. */ +int avahi_server_add_service_strlst( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + const char *host, + uint16_t port, + AvahiStringList *strlst); + +/** Add a subtype for an already existing service */ +int avahi_server_add_service_subtype( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, /**< Specify the name of main service you already added here */ + const char *type, /**< Specify the main type of the service you already added here */ + const char *domain, /**< Specify the main type of the service you already added here */ + const char *subtype /**< The new subtype for the specified service */ ); + +/** Update the TXT record for a service with the data from the specified string list */ +int avahi_server_update_service_txt_strlst( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + AvahiStringList *strlst); + +/** Update the TXT record for a service with the NULL termonate list of strings */ +int avahi_server_update_service_txt( + AvahiServer *s, + AvahiSEntryGroup *g, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiPublishFlags flags, + const char *name, + const char *type, + const char *domain, + ...) AVAHI_GCC_SENTINEL; + +/** Check if there is a service locally defined and return the entry group it is attached to. Returns NULL if the service isn't local*/ +int avahi_server_get_group_of_service(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain, AvahiSEntryGroup** ret_group); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/querier-test.c b/trunk/avahi-core/querier-test.c new file mode 100644 index 0000000..1c28ea0 --- /dev/null +++ b/trunk/avahi-core/querier-test.c @@ -0,0 +1,124 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <assert.h> + +#include <avahi-common/malloc.h> +#include <avahi-common/simple-watch.h> +#include <avahi-common/alternative.h> +#include <avahi-common/timeval.h> + +#include <avahi-core/core.h> +#include <avahi-core/log.h> +#include <avahi-core/publish.h> +#include <avahi-core/lookup.h> + +#define DOMAIN NULL +#define SERVICE_TYPE "_http._tcp" + +static AvahiSServiceBrowser *service_browser1 = NULL, *service_browser2 = NULL; +static const AvahiPoll * poll_api = NULL; +static AvahiServer *server = NULL; +static AvahiSimplePoll *simple_poll; + +static const char *browser_event_to_string(AvahiBrowserEvent event) { + switch (event) { + case AVAHI_BROWSER_NEW : return "NEW"; + case AVAHI_BROWSER_REMOVE : return "REMOVE"; + case AVAHI_BROWSER_CACHE_EXHAUSTED : return "CACHE_EXHAUSTED"; + case AVAHI_BROWSER_ALL_FOR_NOW : return "ALL_FOR_NOW"; + case AVAHI_BROWSER_FAILURE : return "FAILURE"; + } + + abort(); +} + +static void sb_callback( + AvahiSServiceBrowser *b, + AvahiIfIndex iface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *service_type, + const char *domain, + AvahiLookupResultFlags flags, + AVAHI_GCC_UNUSED void* userdata) { + avahi_log_debug("SB%i: (%i.%s) <%s> as <%s> in <%s> [%s] cached=%i", b == service_browser1 ? 1 : 2, iface, avahi_proto_to_string(protocol), name, service_type, domain, browser_event_to_string(event), !!(flags & AVAHI_LOOKUP_RESULT_CACHED)); +} + +static void create_second_service_browser(AvahiTimeout *timeout, AVAHI_GCC_UNUSED void* userdata) { + + service_browser2 = avahi_s_service_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, SERVICE_TYPE, DOMAIN, 0, sb_callback, NULL); + assert(service_browser2); + + poll_api->timeout_free(timeout); +} + +static void quit(AVAHI_GCC_UNUSED AvahiTimeout *timeout, AVAHI_GCC_UNUSED void *userdata) { + avahi_simple_poll_quit(simple_poll); +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + struct timeval tv; + AvahiServerConfig config; + + simple_poll = avahi_simple_poll_new(); + assert(simple_poll); + + poll_api = avahi_simple_poll_get(simple_poll); + assert(poll_api); + + avahi_server_config_init(&config); + config.publish_hinfo = 0; + config.publish_addresses = 0; + config.publish_workstation = 0; + config.publish_domain = 0; + + avahi_address_parse("192.168.50.1", AVAHI_PROTO_UNSPEC, &config.wide_area_servers[0]); + config.n_wide_area_servers = 1; + config.enable_wide_area = 1; + + server = avahi_server_new(poll_api, &config, NULL, NULL, NULL); + assert(server); + avahi_server_config_free(&config); + + service_browser1 = avahi_s_service_browser_new(server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, SERVICE_TYPE, DOMAIN, 0, sb_callback, NULL); + assert(service_browser1); + + poll_api->timeout_new(poll_api, avahi_elapse_time(&tv, 10000, 0), create_second_service_browser, NULL); + + poll_api->timeout_new(poll_api, avahi_elapse_time(&tv, 60000, 0), quit, NULL); + + + for (;;) + if (avahi_simple_poll_iterate(simple_poll, -1) != 0) + break; + + avahi_server_free(server); + avahi_simple_poll_free(simple_poll); + + return 0; +} diff --git a/trunk/avahi-core/querier.c b/trunk/avahi-core/querier.c new file mode 100644 index 0000000..8a230cd --- /dev/null +++ b/trunk/avahi-core/querier.c @@ -0,0 +1,271 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> + +#include "querier.h" +#include "log.h" + +struct AvahiQuerier { + AvahiInterface *interface; + + AvahiKey *key; + int n_used; + + unsigned sec_delay; + + AvahiTimeEvent *time_event; + + struct timeval creation_time; + + unsigned post_id; + int post_id_valid; + + AVAHI_LLIST_FIELDS(AvahiQuerier, queriers); +}; + +void avahi_querier_free(AvahiQuerier *q) { + assert(q); + + AVAHI_LLIST_REMOVE(AvahiQuerier, queriers, q->interface->queriers, q); + avahi_hashmap_remove(q->interface->queriers_by_key, q->key); + + avahi_key_unref(q->key); + avahi_time_event_free(q->time_event); + + avahi_free(q); +} + +static void querier_elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void *userdata) { + AvahiQuerier *q = userdata; + struct timeval tv; + + assert(q); + + if (q->n_used <= 0) { + + /* We are not referenced by anyone anymore, so let's free + * ourselves. We should not send out any further queries from + * this querier object anymore. */ + + avahi_querier_free(q); + return; + } + + if (avahi_interface_post_query(q->interface, q->key, 0, &q->post_id)) { + + /* The queue accepted our query. We store the query id here, + * that allows us to drop the query at a later point if the + * query is very short-lived. */ + + q->post_id_valid = 1; + } + + q->sec_delay *= 2; + + if (q->sec_delay >= 60*60) /* 1h */ + q->sec_delay = 60*60; + + avahi_elapse_time(&tv, q->sec_delay*1000, 0); + avahi_time_event_update(q->time_event, &tv); +} + +void avahi_querier_add(AvahiInterface *i, AvahiKey *key, struct timeval *ret_ctime) { + AvahiQuerier *q; + struct timeval tv; + + assert(i); + assert(key); + + if ((q = avahi_hashmap_lookup(i->queriers_by_key, key))) { + + /* Someone is already browsing for records of this RR key */ + q->n_used++; + + /* Return the creation time. This is used for generating the + * ALL_FOR_NOW event one second after the querier was + * initially created. */ + if (ret_ctime) + *ret_ctime = q->creation_time; + return; + } + + /* No one is browsing for this RR key, so we add a new querier */ + if (!(q = avahi_new(AvahiQuerier, 1))) + return; /* OOM */ + + q->key = avahi_key_ref(key); + q->interface = i; + q->n_used = 1; + q->sec_delay = 1; + q->post_id_valid = 0; + gettimeofday(&q->creation_time, NULL); + + /* Do the initial query */ + if (avahi_interface_post_query(i, key, 0, &q->post_id)) + q->post_id_valid = 1; + + /* Schedule next queries */ + q->time_event = avahi_time_event_new(i->monitor->server->time_event_queue, avahi_elapse_time(&tv, q->sec_delay*1000, 0), querier_elapse_callback, q); + + AVAHI_LLIST_PREPEND(AvahiQuerier, queriers, i->queriers, q); + avahi_hashmap_insert(i->queriers_by_key, q->key, q); + + /* Return the creation time. This is used for generating the + * ALL_FOR_NOW event one second after the querier was initially + * created. */ + if (ret_ctime) + *ret_ctime = q->creation_time; +} + +void avahi_querier_remove(AvahiInterface *i, AvahiKey *key) { + AvahiQuerier *q; + + if (!(q = avahi_hashmap_lookup(i->queriers_by_key, key)) || q->n_used <= 0) { + /* There was no querier for this RR key, or it wasn't referenced by anyone */ + avahi_log_warn(__FILE__": querier_remove() called but no querier to remove."); + return; + } + + if ((--q->n_used) <= 0) { + + /* Nobody references us anymore. */ + + if (q->post_id_valid && avahi_interface_withraw_query(i, q->post_id)) { + + /* We succeeded in withdrawing our query from the queue, + * so let's drop dead. */ + + avahi_querier_free(q); + } + + /* If we failed to withdraw our query from the queue, we stay + * alive, in case someone else might recycle our querier at a + * later point. We are freed at our next expiry, in case + * nobody recycled us. */ + } +} + +static void remove_querier_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + assert(m); + assert(i); + assert(userdata); + + if (i->announcing) + avahi_querier_remove(i, (AvahiKey*) userdata); +} + +void avahi_querier_remove_for_all(AvahiServer *s, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key) { + assert(s); + assert(key); + + avahi_interface_monitor_walk(s->monitor, idx, protocol, remove_querier_callback, key); +} + +struct cbdata { + AvahiKey *key; + struct timeval *ret_ctime; +}; + +static void add_querier_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, void* userdata) { + struct cbdata *cbdata = userdata; + + assert(m); + assert(i); + assert(cbdata); + + if (i->announcing) { + struct timeval tv; + avahi_querier_add(i, cbdata->key, &tv); + + if (cbdata->ret_ctime && avahi_timeval_compare(&tv, cbdata->ret_ctime) > 0) + *cbdata->ret_ctime = tv; + } +} + +void avahi_querier_add_for_all(AvahiServer *s, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key, struct timeval *ret_ctime) { + struct cbdata cbdata; + + assert(s); + assert(key); + + cbdata.key = key; + cbdata.ret_ctime = ret_ctime; + + if (ret_ctime) + ret_ctime->tv_sec = ret_ctime->tv_usec = 0; + + avahi_interface_monitor_walk(s->monitor, idx, protocol, add_querier_callback, &cbdata); +} + +int avahi_querier_shall_refresh_cache(AvahiInterface *i, AvahiKey *key) { + AvahiQuerier *q; + + assert(i); + assert(key); + + /* Called by the cache maintainer */ + + if (!(q = avahi_hashmap_lookup(i->queriers_by_key, key))) + /* This key is currently not subscribed at all, so no cache + * refresh is needed */ + return 0; + + if (q->n_used <= 0) { + + /* If this is an entry nobody references right now, don't + * consider it "existing". */ + + /* Remove this querier since it is referenced by nobody + * and the cached data will soon be out of date */ + avahi_querier_free(q); + + /* Tell the cache that no refresh is needed */ + return 0; + + } else { + struct timeval tv; + + /* We can defer our query a little, since the cache will now + * issue a refresh query anyway. */ + avahi_elapse_time(&tv, q->sec_delay*1000, 0); + avahi_time_event_update(q->time_event, &tv); + + /* Tell the cache that a refresh should be issued */ + return 1; + } +} + +void avahi_querier_free_all(AvahiInterface *i) { + assert(i); + + while (i->queriers) + avahi_querier_free(i->queriers); +} diff --git a/trunk/avahi-core/querier.h b/trunk/avahi-core/querier.h new file mode 100644 index 0000000..3f4eead --- /dev/null +++ b/trunk/avahi-core/querier.h @@ -0,0 +1,50 @@ +#ifndef fooquerierhfoo +#define fooquerierhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiQuerier AvahiQuerier; + +#include "iface.h" + +/** Add querier for the specified key to the specified interface */ +void avahi_querier_add(AvahiInterface *i, AvahiKey *key, struct timeval *ret_ctime); + +/** Remove a querier for the specified key from the specified interface */ +void avahi_querier_remove(AvahiInterface *i, AvahiKey *key); + +/** Add a querier for the specified key on all interfaces that mach */ +void avahi_querier_add_for_all(AvahiServer *s, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key, struct timeval *ret_ctime); + +/** Remove a querier for the specified key on all interfaces that mach */ +void avahi_querier_remove_for_all(AvahiServer *s, AvahiIfIndex idx, AvahiProtocol protocol, AvahiKey *key); + +/** Free all queriers */ +void avahi_querier_free(AvahiQuerier *q); + +/** Free all queriers on the specified interface */ +void avahi_querier_free_all(AvahiInterface *i); + +/** Return 1 if there is a querier for the specified key on the specified interface */ +int avahi_querier_shall_refresh_cache(AvahiInterface *i, AvahiKey *key); + +#endif diff --git a/trunk/avahi-core/query-sched.c b/trunk/avahi-core/query-sched.c new file mode 100644 index 0000000..0319b02 --- /dev/null +++ b/trunk/avahi-core/query-sched.c @@ -0,0 +1,452 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "query-sched.h" +#include "log.h" + +#define AVAHI_QUERY_HISTORY_MSEC 100 +#define AVAHI_QUERY_DEFER_MSEC 100 + +typedef struct AvahiQueryJob AvahiQueryJob; +typedef struct AvahiKnownAnswer AvahiKnownAnswer; + +struct AvahiQueryJob { + unsigned id; + int n_posted; + + AvahiQueryScheduler *scheduler; + AvahiTimeEvent *time_event; + + int done; + struct timeval delivery; + + AvahiKey *key; + + /* Jobs are stored in a simple linked list. It might turn out in + * the future that this list grows too long and we must switch to + * some other kind of data structure. This needs further + * investigation. I expect the list to be very short (< 20 + * entries) most of the time, but this might be a wrong + * assumption, especially on setups where traffic reflection is + * involved. */ + + AVAHI_LLIST_FIELDS(AvahiQueryJob, jobs); +}; + +struct AvahiKnownAnswer { + AvahiQueryScheduler *scheduler; + AvahiRecord *record; + + AVAHI_LLIST_FIELDS(AvahiKnownAnswer, known_answer); +}; + +struct AvahiQueryScheduler { + AvahiInterface *interface; + AvahiTimeEventQueue *time_event_queue; + + unsigned next_id; + + AVAHI_LLIST_HEAD(AvahiQueryJob, jobs); + AVAHI_LLIST_HEAD(AvahiQueryJob, history); + AVAHI_LLIST_HEAD(AvahiKnownAnswer, known_answers); +}; + +static AvahiQueryJob* job_new(AvahiQueryScheduler *s, AvahiKey *key, int done) { + AvahiQueryJob *qj; + + assert(s); + assert(key); + + if (!(qj = avahi_new(AvahiQueryJob, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; + } + + qj->scheduler = s; + qj->key = avahi_key_ref(key); + qj->time_event = NULL; + qj->n_posted = 1; + qj->id = s->next_id++; + + if ((qj->done = done)) + AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj); + else + AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->jobs, qj); + + return qj; +} + +static void job_free(AvahiQueryScheduler *s, AvahiQueryJob *qj) { + assert(s); + assert(qj); + + if (qj->time_event) + avahi_time_event_free(qj->time_event); + + if (qj->done) + AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->history, qj); + else + AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj); + + avahi_key_unref(qj->key); + avahi_free(qj); +} + +static void elapse_callback(AvahiTimeEvent *e, void* data); + +static void job_set_elapse_time(AvahiQueryScheduler *s, AvahiQueryJob *qj, unsigned msec, unsigned jitter) { + struct timeval tv; + + assert(s); + assert(qj); + + avahi_elapse_time(&tv, msec, jitter); + + if (qj->time_event) + avahi_time_event_update(qj->time_event, &tv); + else + qj->time_event = avahi_time_event_new(s->time_event_queue, &tv, elapse_callback, qj); +} + +static void job_mark_done(AvahiQueryScheduler *s, AvahiQueryJob *qj) { + assert(s); + assert(qj); + + assert(!qj->done); + + AVAHI_LLIST_REMOVE(AvahiQueryJob, jobs, s->jobs, qj); + AVAHI_LLIST_PREPEND(AvahiQueryJob, jobs, s->history, qj); + + qj->done = 1; + + job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0); + gettimeofday(&qj->delivery, NULL); +} + +AvahiQueryScheduler *avahi_query_scheduler_new(AvahiInterface *i) { + AvahiQueryScheduler *s; + assert(i); + + if (!(s = avahi_new(AvahiQueryScheduler, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; /* OOM */ + } + + s->interface = i; + s->time_event_queue = i->monitor->server->time_event_queue; + s->next_id = 0; + + AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->jobs); + AVAHI_LLIST_HEAD_INIT(AvahiQueryJob, s->history); + AVAHI_LLIST_HEAD_INIT(AvahiKnownAnswer, s->known_answers); + + return s; +} + +void avahi_query_scheduler_free(AvahiQueryScheduler *s) { + assert(s); + + assert(!s->known_answers); + avahi_query_scheduler_clear(s); + avahi_free(s); +} + +void avahi_query_scheduler_clear(AvahiQueryScheduler *s) { + assert(s); + + while (s->jobs) + job_free(s, s->jobs); + while (s->history) + job_free(s, s->history); +} + +static void* known_answer_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void* userdata) { + AvahiQueryScheduler *s = userdata; + AvahiKnownAnswer *ka; + + assert(c); + assert(pattern); + assert(e); + assert(s); + + if (avahi_cache_entry_half_ttl(c, e)) + return NULL; + + if (!(ka = avahi_new0(AvahiKnownAnswer, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; + } + + ka->scheduler = s; + ka->record = avahi_record_ref(e->record); + + AVAHI_LLIST_PREPEND(AvahiKnownAnswer, known_answer, s->known_answers, ka); + return NULL; +} + +static int packet_add_query_job(AvahiQueryScheduler *s, AvahiDnsPacket *p, AvahiQueryJob *qj) { + assert(s); + assert(p); + assert(qj); + + if (!avahi_dns_packet_append_key(p, qj->key, 0)) + return 0; + + /* Add all matching known answers to the list */ + avahi_cache_walk(s->interface->cache, qj->key, known_answer_walk_callback, s); + + job_mark_done(s, qj); + + return 1; +} + +static void append_known_answers_and_send(AvahiQueryScheduler *s, AvahiDnsPacket *p) { + AvahiKnownAnswer *ka; + unsigned n; + assert(s); + assert(p); + + n = 0; + + while ((ka = s->known_answers)) { + int too_large = 0; + + while (!avahi_dns_packet_append_record(p, ka->record, 0, 0)) { + + if (avahi_dns_packet_is_empty(p)) { + /* The record is too large to fit into one packet, so + there's no point in sending it. Better is letting + the owner of the record send it as a response. This + has the advantage of a cache refresh. */ + + too_large = 1; + break; + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_FLAGS, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) | AVAHI_DNS_FLAG_TC); + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n); + avahi_interface_send_packet(s->interface, p); + avahi_dns_packet_free(p); + + p = avahi_dns_packet_new_query(s->interface->hardware->mtu); + n = 0; + } + + AVAHI_LLIST_REMOVE(AvahiKnownAnswer, known_answer, s->known_answers, ka); + avahi_record_unref(ka->record); + avahi_free(ka); + + if (!too_large) + n++; + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n); + avahi_interface_send_packet(s->interface, p); + avahi_dns_packet_free(p); +} + +static void elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* data) { + AvahiQueryJob *qj = data; + AvahiQueryScheduler *s; + AvahiDnsPacket *p; + unsigned n; + int b; + + assert(qj); + s = qj->scheduler; + + if (qj->done) { + /* Lets remove it from the history */ + job_free(s, qj); + return; + } + + assert(!s->known_answers); + + if (!(p = avahi_dns_packet_new_query(s->interface->hardware->mtu))) + return; /* OOM */ + + b = packet_add_query_job(s, p, qj); + assert(b); /* An query must always fit in */ + n = 1; + + /* Try to fill up packet with more queries, if available */ + while (s->jobs) { + + if (!packet_add_query_job(s, p, s->jobs)) + break; + + n++; + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_QDCOUNT, n); + + /* Now add known answers */ + append_known_answers_and_send(s, p); +} + +static AvahiQueryJob* find_scheduled_job(AvahiQueryScheduler *s, AvahiKey *key) { + AvahiQueryJob *qj; + + assert(s); + assert(key); + + for (qj = s->jobs; qj; qj = qj->jobs_next) { + assert(!qj->done); + + if (avahi_key_equal(qj->key, key)) + return qj; + } + + return NULL; +} + +static AvahiQueryJob* find_history_job(AvahiQueryScheduler *s, AvahiKey *key) { + AvahiQueryJob *qj; + + assert(s); + assert(key); + + for (qj = s->history; qj; qj = qj->jobs_next) { + assert(qj->done); + + if (avahi_key_equal(qj->key, key)) { + /* Check whether this entry is outdated */ + + if (avahi_age(&qj->delivery) > AVAHI_QUERY_HISTORY_MSEC*1000) { + /* it is outdated, so let's remove it */ + job_free(s, qj); + return NULL; + } + + return qj; + } + } + + return NULL; +} + +int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately, unsigned *ret_id) { + struct timeval tv; + AvahiQueryJob *qj; + + assert(s); + assert(key); + + if ((qj = find_history_job(s, key))) + return 0; + + avahi_elapse_time(&tv, immediately ? 0 : AVAHI_QUERY_DEFER_MSEC, 0); + + if ((qj = find_scheduled_job(s, key))) { + /* Duplicate questions suppression */ + + if (avahi_timeval_compare(&tv, &qj->delivery) < 0) { + /* If the new entry should be scheduled earlier, + * update the old entry */ + qj->delivery = tv; + avahi_time_event_update(qj->time_event, &qj->delivery); + } + + qj->n_posted++; + + } else { + + if (!(qj = job_new(s, key, 0))) + return 0; /* OOM */ + + qj->delivery = tv; + qj->time_event = avahi_time_event_new(s->time_event_queue, &qj->delivery, elapse_callback, qj); + } + + if (ret_id) + *ret_id = qj->id; + + return 1; +} + +void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key) { + AvahiQueryJob *qj; + + assert(s); + assert(key); + + /* This function is called whenever an incoming query was + * received. We drop scheduled queries that match. The keyword is + * "DUPLICATE QUESTION SUPPRESION". */ + + if ((qj = find_scheduled_job(s, key))) { + job_mark_done(s, qj); + return; + } + + /* Look if there's a history job for this key. If there is, just + * update the elapse time */ + if (!(qj = find_history_job(s, key))) + if (!(qj = job_new(s, key, 1))) + return; /* OOM */ + + gettimeofday(&qj->delivery, NULL); + job_set_elapse_time(s, qj, AVAHI_QUERY_HISTORY_MSEC, 0); +} + +int avahi_query_scheduler_withdraw_by_id(AvahiQueryScheduler *s, unsigned id) { + AvahiQueryJob *qj; + + assert(s); + + /* Very short lived queries can withdraw an already scheduled item + * from the queue using this function, simply by passing the id + * returned by avahi_query_scheduler_post(). */ + + for (qj = s->jobs; qj; qj = qj->jobs_next) { + assert(!qj->done); + + if (qj->id == id) { + /* Entry found */ + + assert(qj->n_posted >= 1); + + if (--qj->n_posted <= 0) { + + /* We withdraw this job only if the calling object was + * the only remaining poster. (Usually this is the + * case since there should exist only one querier per + * key, but there are exceptions, notably reflected + * traffic.) */ + + job_free(s, qj); + return 1; + } + } + } + + return 0; +} diff --git a/trunk/avahi-core/query-sched.h b/trunk/avahi-core/query-sched.h new file mode 100644 index 0000000..5238558 --- /dev/null +++ b/trunk/avahi-core/query-sched.h @@ -0,0 +1,38 @@ +#ifndef fooqueryschedhfoo +#define fooqueryschedhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiQueryScheduler AvahiQueryScheduler; + +#include <avahi-common/address.h> +#include "iface.h" + +AvahiQueryScheduler *avahi_query_scheduler_new(AvahiInterface *i); +void avahi_query_scheduler_free(AvahiQueryScheduler *s); +void avahi_query_scheduler_clear(AvahiQueryScheduler *s); + +int avahi_query_scheduler_post(AvahiQueryScheduler *s, AvahiKey *key, int immediately, unsigned *ret_id); +int avahi_query_scheduler_withdraw_by_id(AvahiQueryScheduler *s, unsigned id); +void avahi_query_scheduler_incoming(AvahiQueryScheduler *s, AvahiKey *key); + +#endif diff --git a/trunk/avahi-core/resolve-address.c b/trunk/avahi-core/resolve-address.c new file mode 100644 index 0000000..25d21ac --- /dev/null +++ b/trunk/avahi-core/resolve-address.c @@ -0,0 +1,270 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> + +#include "browse.h" + +#define TIMEOUT_MSEC 5000 + +struct AvahiSAddressResolver { + AvahiServer *server; + AvahiAddress address; + + AvahiSRecordBrowser *record_browser; + + AvahiSAddressResolverCallback callback; + void* userdata; + + AvahiRecord *ptr_record; + AvahiIfIndex interface; + AvahiProtocol protocol; + AvahiLookupResultFlags flags; + + int retry_with_multicast; + AvahiKey *key; + + AvahiTimeEvent *time_event; + + AVAHI_LLIST_FIELDS(AvahiSAddressResolver, resolver); +}; + +static void finish(AvahiSAddressResolver *r, AvahiResolverEvent event) { + assert(r); + + if (r->time_event) { + avahi_time_event_free(r->time_event); + r->time_event = NULL; + } + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + r->callback(r, r->interface, r->protocol, event, &r->address, NULL, r->flags, r->userdata); + break; + + case AVAHI_RESOLVER_FOUND: + assert(r->ptr_record); + r->callback(r, r->interface, r->protocol, event, &r->address, r->ptr_record->data.ptr.name, r->flags, r->userdata); + break; + } +} + +static void time_event_callback(AvahiTimeEvent *e, void *userdata) { + AvahiSAddressResolver *r = userdata; + + assert(e); + assert(r); + + avahi_server_set_errno(r->server, AVAHI_ERR_TIMEOUT); + finish(r, AVAHI_RESOLVER_FAILURE); +} + +static void start_timeout(AvahiSAddressResolver *r) { + struct timeval tv; + assert(r); + + if (r->time_event) + return; + + avahi_elapse_time(&tv, TIMEOUT_MSEC, 0); + r->time_event = avahi_time_event_new(r->server->time_event_queue, &tv, time_event_callback, r); +} + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSAddressResolver *r = userdata; + + assert(rr); + assert(r); + + switch (event) { + case AVAHI_BROWSER_NEW: + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_PTR); + + if (r->interface > 0 && interface != r->interface) + return; + + if (r->protocol != AVAHI_PROTO_UNSPEC && protocol != r->protocol) + return; + + if (r->interface <= 0) + r->interface = interface; + + if (r->protocol == AVAHI_PROTO_UNSPEC) + r->protocol = protocol; + + if (!r->ptr_record) { + r->ptr_record = avahi_record_ref(record); + r->flags = flags; + + finish(r, AVAHI_RESOLVER_FOUND); + } + break; + + case AVAHI_BROWSER_REMOVE: + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_PTR); + + if (r->ptr_record && avahi_record_equal_no_ttl(record, r->ptr_record)) { + avahi_record_unref(r->ptr_record); + r->ptr_record = NULL; + r->flags = flags; + + /** Look for a replacement */ + avahi_s_record_browser_restart(r->record_browser); + start_timeout(r); + } + + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + + case AVAHI_BROWSER_FAILURE: + + if (r->retry_with_multicast) { + r->retry_with_multicast = 0; + + avahi_s_record_browser_free(r->record_browser); + r->record_browser = avahi_s_record_browser_new(r->server, r->interface, r->protocol, r->key, AVAHI_LOOKUP_USE_MULTICAST, record_browser_callback, r); + + if (r->record_browser) { + start_timeout(r); + break; + } + } + + r->flags = flags; + finish(r, AVAHI_RESOLVER_FAILURE); + break; + } +} + +AvahiSAddressResolver *avahi_s_address_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const AvahiAddress *address, + AvahiLookupFlags flags, + AvahiSAddressResolverCallback callback, + void* userdata) { + + AvahiSAddressResolver *r; + AvahiKey *k; + char n[AVAHI_DOMAIN_NAME_MAX]; + + assert(server); + assert(address); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, address->proto == AVAHI_PROTO_INET || address->proto == AVAHI_PROTO_INET6, AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + + avahi_reverse_lookup_name(address, n, sizeof(n)); + + if (!(k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + if (!(r = avahi_new(AvahiSAddressResolver, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + avahi_key_unref(k); + return NULL; + } + + r->server = server; + r->address = *address; + r->callback = callback; + r->userdata = userdata; + r->ptr_record = NULL; + r->interface = interface; + r->protocol = protocol; + r->flags = 0; + r->retry_with_multicast = 0; + r->key = k; + + r->record_browser = NULL; + AVAHI_LLIST_PREPEND(AvahiSAddressResolver, resolver, server->address_resolvers, r); + + r->time_event = NULL; + + if (!(flags & (AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_USE_WIDE_AREA))) { + + if (!server->wide_area_lookup_engine || !avahi_wide_area_has_servers(server->wide_area_lookup_engine)) + flags |= AVAHI_LOOKUP_USE_MULTICAST; + else { + flags |= AVAHI_LOOKUP_USE_WIDE_AREA; + r->retry_with_multicast = 1; + } + } + + r->record_browser = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, r); + + if (!r->record_browser) { + avahi_s_address_resolver_free(r); + return NULL; + } + + start_timeout(r); + + return r; +} + +void avahi_s_address_resolver_free(AvahiSAddressResolver *r) { + assert(r); + + AVAHI_LLIST_REMOVE(AvahiSAddressResolver, resolver, r->server->address_resolvers, r); + + if (r->record_browser) + avahi_s_record_browser_free(r->record_browser); + + if (r->time_event) + avahi_time_event_free(r->time_event); + + if (r->ptr_record) + avahi_record_unref(r->ptr_record); + + if (r->key) + avahi_key_unref(r->key); + + avahi_free(r); +} diff --git a/trunk/avahi-core/resolve-host-name.c b/trunk/avahi-core/resolve-host-name.c new file mode 100644 index 0000000..59dc3cf --- /dev/null +++ b/trunk/avahi-core/resolve-host-name.c @@ -0,0 +1,299 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/domain.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" + +#define TIMEOUT_MSEC 5000 + +struct AvahiSHostNameResolver { + AvahiServer *server; + char *host_name; + + AvahiSRecordBrowser *record_browser_a; + AvahiSRecordBrowser *record_browser_aaaa; + + AvahiSHostNameResolverCallback callback; + void* userdata; + + AvahiRecord *address_record; + AvahiIfIndex interface; + AvahiProtocol protocol; + AvahiLookupResultFlags flags; + + AvahiTimeEvent *time_event; + + AVAHI_LLIST_FIELDS(AvahiSHostNameResolver, resolver); +}; + +static void finish(AvahiSHostNameResolver *r, AvahiResolverEvent event) { + assert(r); + + if (r->time_event) { + avahi_time_event_free(r->time_event); + r->time_event = NULL; + } + + switch (event) { + case AVAHI_RESOLVER_FOUND: { + AvahiAddress a; + + assert(r->address_record); + + switch (r->address_record->key->type) { + case AVAHI_DNS_TYPE_A: + a.proto = AVAHI_PROTO_INET; + a.data.ipv4 = r->address_record->data.a.address; + break; + + case AVAHI_DNS_TYPE_AAAA: + a.proto = AVAHI_PROTO_INET6; + a.data.ipv6 = r->address_record->data.aaaa.address; + break; + + default: + abort(); + } + + r->callback(r, r->interface, r->protocol, AVAHI_RESOLVER_FOUND, r->address_record->key->name, &a, r->flags, r->userdata); + break; + + } + + case AVAHI_RESOLVER_FAILURE: + + r->callback(r, r->interface, r->protocol, event, r->host_name, NULL, r->flags, r->userdata); + break; + } +} + +static void time_event_callback(AvahiTimeEvent *e, void *userdata) { + AvahiSHostNameResolver *r = userdata; + + assert(e); + assert(r); + + avahi_server_set_errno(r->server, AVAHI_ERR_TIMEOUT); + finish(r, AVAHI_RESOLVER_FAILURE); +} + +static void start_timeout(AvahiSHostNameResolver *r) { + struct timeval tv; + assert(r); + + if (r->time_event) + return; + + avahi_elapse_time(&tv, TIMEOUT_MSEC, 0); + + r->time_event = avahi_time_event_new(r->server->time_event_queue, &tv, time_event_callback, r); +} + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSHostNameResolver *r = userdata; + + assert(rr); + assert(r); + + + switch (event) { + case AVAHI_BROWSER_NEW: + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_A || record->key->type == AVAHI_DNS_TYPE_AAAA); + + if (r->interface > 0 && interface != r->interface) + return; + + if (r->protocol != AVAHI_PROTO_UNSPEC && protocol != r->protocol) + return; + + if (r->interface <= 0) + r->interface = interface; + + if (r->protocol == AVAHI_PROTO_UNSPEC) + r->protocol = protocol; + + if (!r->address_record) { + r->address_record = avahi_record_ref(record); + r->flags = flags; + + finish(r, AVAHI_RESOLVER_FOUND); + } + + break; + + case AVAHI_BROWSER_REMOVE: + assert(record); + assert(record->key->type == AVAHI_DNS_TYPE_A || record->key->type == AVAHI_DNS_TYPE_AAAA); + + if (r->address_record && avahi_record_equal_no_ttl(record, r->address_record)) { + avahi_record_unref(r->address_record); + r->address_record = NULL; + + r->flags = flags; + + + /** Look for a replacement */ + if (r->record_browser_aaaa) + avahi_s_record_browser_restart(r->record_browser_aaaa); + if (r->record_browser_a) + avahi_s_record_browser_restart(r->record_browser_a); + + start_timeout(r); + } + + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + case AVAHI_BROWSER_ALL_FOR_NOW: + /* Ignore */ + break; + + case AVAHI_BROWSER_FAILURE: + + /* Stop browsers */ + + if (r->record_browser_aaaa) + avahi_s_record_browser_free(r->record_browser_aaaa); + if (r->record_browser_a) + avahi_s_record_browser_free(r->record_browser_a); + + r->record_browser_a = r->record_browser_aaaa = NULL; + r->flags = flags; + + finish(r, AVAHI_RESOLVER_FAILURE); + break; + } +} + +AvahiSHostNameResolver *avahi_s_host_name_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *host_name, + AvahiProtocol aprotocol, + AvahiLookupFlags flags, + AvahiSHostNameResolverCallback callback, + void* userdata) { + + AvahiSHostNameResolver *r; + AvahiKey *k; + + assert(server); + assert(host_name); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_is_valid_fqdn(host_name), AVAHI_ERR_INVALID_HOST_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST), AVAHI_ERR_INVALID_FLAGS); + + if (!(r = avahi_new(AvahiSHostNameResolver, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + r->server = server; + r->host_name = avahi_normalize_name_strdup(host_name); + r->callback = callback; + r->userdata = userdata; + r->address_record = NULL; + r->interface = interface; + r->protocol = protocol; + r->flags = 0; + + r->record_browser_a = r->record_browser_aaaa = NULL; + + r->time_event = NULL; + + AVAHI_LLIST_PREPEND(AvahiSHostNameResolver, resolver, server->host_name_resolvers, r); + + r->record_browser_aaaa = r->record_browser_a = NULL; + + if (aprotocol == AVAHI_PROTO_INET || aprotocol == AVAHI_PROTO_UNSPEC) { + k = avahi_key_new(host_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A); + r->record_browser_a = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, r); + avahi_key_unref(k); + + if (!r->record_browser_a) + goto fail; + } + + if (aprotocol == AVAHI_PROTO_INET6 || aprotocol == AVAHI_PROTO_UNSPEC) { + k = avahi_key_new(host_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA); + r->record_browser_aaaa = avahi_s_record_browser_new(server, interface, protocol, k, flags, record_browser_callback, r); + avahi_key_unref(k); + + if (!r->record_browser_aaaa) + goto fail; + } + + assert(r->record_browser_aaaa || r->record_browser_a); + + start_timeout(r); + + return r; + +fail: + avahi_s_host_name_resolver_free(r); + return NULL; +} + +void avahi_s_host_name_resolver_free(AvahiSHostNameResolver *r) { + assert(r); + + AVAHI_LLIST_REMOVE(AvahiSHostNameResolver, resolver, r->server->host_name_resolvers, r); + + if (r->record_browser_a) + avahi_s_record_browser_free(r->record_browser_a); + + if (r->record_browser_aaaa) + avahi_s_record_browser_free(r->record_browser_aaaa); + + if (r->time_event) + avahi_time_event_free(r->time_event); + + if (r->address_record) + avahi_record_unref(r->address_record); + + avahi_free(r->host_name); + avahi_free(r); +} diff --git a/trunk/avahi-core/resolve-service.c b/trunk/avahi-core/resolve-service.c new file mode 100644 index 0000000..1ad6078 --- /dev/null +++ b/trunk/avahi-core/resolve-service.c @@ -0,0 +1,491 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <avahi-common/domain.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "browse.h" +#include "log.h" + +#define TIMEOUT_MSEC 5000 + +struct AvahiSServiceResolver { + AvahiServer *server; + char *service_name; + char *service_type; + char *domain_name; + AvahiProtocol address_protocol; + + AvahiIfIndex interface; + AvahiProtocol protocol; + + AvahiSRecordBrowser *record_browser_srv; + AvahiSRecordBrowser *record_browser_txt; + AvahiSRecordBrowser *record_browser_a; + AvahiSRecordBrowser *record_browser_aaaa; + + AvahiRecord *srv_record, *txt_record, *address_record; + AvahiLookupResultFlags srv_flags, txt_flags, address_flags; + + AvahiSServiceResolverCallback callback; + void* userdata; + AvahiLookupFlags user_flags; + + AvahiTimeEvent *time_event; + + AVAHI_LLIST_FIELDS(AvahiSServiceResolver, resolver); +}; + +static void finish(AvahiSServiceResolver *r, AvahiResolverEvent event) { + AvahiLookupResultFlags flags; + + assert(r); + + if (r->time_event) { + avahi_time_event_free(r->time_event); + r->time_event = NULL; + } + + flags = + r->txt_flags | + r->srv_flags | + r->address_flags; + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + + r->callback( + r, + r->interface, + r->protocol, + event, + r->service_name, + r->service_type, + r->domain_name, + NULL, + NULL, + 0, + NULL, + flags, + r->userdata); + + break; + + case AVAHI_RESOLVER_FOUND: { + AvahiAddress a; + + assert(event == AVAHI_RESOLVER_FOUND); + + assert(r->srv_record); + + if (r->address_record) { + switch (r->address_record->key->type) { + case AVAHI_DNS_TYPE_A: + a.proto = AVAHI_PROTO_INET; + a.data.ipv4 = r->address_record->data.a.address; + break; + + case AVAHI_DNS_TYPE_AAAA: + a.proto = AVAHI_PROTO_INET6; + a.data.ipv6 = r->address_record->data.aaaa.address; + break; + + default: + assert(0); + } + } + + r->callback( + r, + r->interface, + r->protocol, + event, + r->service_name, + r->service_type, + r->domain_name, + r->srv_record->data.srv.name, + r->address_record ? &a : NULL, + r->srv_record->data.srv.port, + r->txt_record ? r->txt_record->data.txt.string_list : NULL, + flags, + r->userdata); + + break; + } + } +} + +static void time_event_callback(AvahiTimeEvent *e, void *userdata) { + AvahiSServiceResolver *r = userdata; + + assert(e); + assert(r); + + avahi_server_set_errno(r->server, AVAHI_ERR_TIMEOUT); + finish(r, AVAHI_RESOLVER_FAILURE); +} + +static void start_timeout(AvahiSServiceResolver *r) { + struct timeval tv; + assert(r); + + if (r->time_event) + return; + + avahi_elapse_time(&tv, TIMEOUT_MSEC, 0); + + r->time_event = avahi_time_event_new(r->server->time_event_queue, &tv, time_event_callback, r); +} + +static void record_browser_callback( + AvahiSRecordBrowser*rr, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + AvahiRecord *record, + AvahiLookupResultFlags flags, + void* userdata) { + + AvahiSServiceResolver *r = userdata; + + assert(rr); + assert(r); + + if (rr == r->record_browser_aaaa || rr == r->record_browser_a) + r->address_flags = flags; + else if (rr == r->record_browser_srv) + r->srv_flags = flags; + else if (rr == r->record_browser_txt) + r->txt_flags = flags; + + switch (event) { + + case AVAHI_BROWSER_NEW: { + int changed = 0; + assert(record); + + if (r->interface > 0 && interface > 0 && interface != r->interface) + return; + + if (r->protocol != AVAHI_PROTO_UNSPEC && protocol != AVAHI_PROTO_UNSPEC && protocol != r->protocol) + return; + + if (r->interface <= 0) + r->interface = interface; + + if (r->protocol == AVAHI_PROTO_UNSPEC) + r->protocol = protocol; + + switch (record->key->type) { + case AVAHI_DNS_TYPE_SRV: + if (!r->srv_record) { + r->srv_record = avahi_record_ref(record); + changed = 1; + + if (r->record_browser_a) { + avahi_s_record_browser_free(r->record_browser_a); + r->record_browser_a = NULL; + } + + if (r->record_browser_aaaa) { + avahi_s_record_browser_free(r->record_browser_aaaa); + r->record_browser_aaaa = NULL; + } + + if (!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)) { + + if (r->address_protocol == AVAHI_PROTO_INET || r->address_protocol == AVAHI_PROTO_UNSPEC) { + AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A); + r->record_browser_a = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); + avahi_key_unref(k); + } + + if (r->address_protocol == AVAHI_PROTO_INET6 || r->address_protocol == AVAHI_PROTO_UNSPEC) { + AvahiKey *k = avahi_key_new(r->srv_record->data.srv.name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA); + r->record_browser_aaaa = avahi_s_record_browser_new(r->server, r->interface, r->protocol, k, r->user_flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); + avahi_key_unref(k); + } + } + } + break; + + case AVAHI_DNS_TYPE_TXT: + + assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT)); + + if (!r->txt_record) { + r->txt_record = avahi_record_ref(record); + changed = 1; + } + break; + + case AVAHI_DNS_TYPE_A: + case AVAHI_DNS_TYPE_AAAA: + + assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)); + + if (!r->address_record) { + r->address_record = avahi_record_ref(record); + changed = 1; + } + break; + + default: + abort(); + } + + + if (changed && + r->srv_record && + (r->txt_record || (r->user_flags & AVAHI_LOOKUP_NO_TXT)) && + (r->address_record || (r->user_flags & AVAHI_LOOKUP_NO_ADDRESS))) + finish(r, AVAHI_RESOLVER_FOUND); + + break; + + } + + case AVAHI_BROWSER_REMOVE: + + assert(record); + + switch (record->key->type) { + case AVAHI_DNS_TYPE_SRV: + + if (r->srv_record && avahi_record_equal_no_ttl(record, r->srv_record)) { + avahi_record_unref(r->srv_record); + r->srv_record = NULL; + + if (r->record_browser_a) { + avahi_s_record_browser_free(r->record_browser_a); + r->record_browser_a = NULL; + } + + if (r->record_browser_aaaa) { + avahi_s_record_browser_free(r->record_browser_aaaa); + r->record_browser_aaaa = NULL; + } + + /** Look for a replacement */ + avahi_s_record_browser_restart(r->record_browser_srv); + start_timeout(r); + } + + break; + + case AVAHI_DNS_TYPE_TXT: + + assert(!(r->user_flags & AVAHI_LOOKUP_NO_TXT)); + + if (r->txt_record && avahi_record_equal_no_ttl(record, r->txt_record)) { + avahi_record_unref(r->txt_record); + r->txt_record = NULL; + + /** Look for a replacement */ + avahi_s_record_browser_restart(r->record_browser_txt); + start_timeout(r); + } + break; + + case AVAHI_DNS_TYPE_A: + case AVAHI_DNS_TYPE_AAAA: + + assert(!(r->user_flags & AVAHI_LOOKUP_NO_ADDRESS)); + + if (r->address_record && avahi_record_equal_no_ttl(record, r->address_record)) { + avahi_record_unref(r->address_record); + r->address_record = NULL; + + /** Look for a replacement */ + if (r->record_browser_aaaa) + avahi_s_record_browser_restart(r->record_browser_aaaa); + if (r->record_browser_a) + avahi_s_record_browser_restart(r->record_browser_a); + start_timeout(r); + } + break; + + default: + abort(); + } + + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + case AVAHI_BROWSER_ALL_FOR_NOW: + break; + + case AVAHI_BROWSER_FAILURE: + + if (rr == r->record_browser_a && r->record_browser_aaaa) { + /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */ + avahi_s_record_browser_free(r->record_browser_a); + r->record_browser_a = NULL; + break; + } + + if (rr == r->record_browser_aaaa && r->record_browser_a) { + /* We were looking for both AAAA and A, and the other query is still living, so we'll not die */ + avahi_s_record_browser_free(r->record_browser_aaaa); + r->record_browser_aaaa = NULL; + break; + } + + /* Hmm, everything's lost, tell the user */ + + if (r->record_browser_srv) + avahi_s_record_browser_free(r->record_browser_srv); + if (r->record_browser_txt) + avahi_s_record_browser_free(r->record_browser_txt); + if (r->record_browser_a) + avahi_s_record_browser_free(r->record_browser_a); + if (r->record_browser_aaaa) + avahi_s_record_browser_free(r->record_browser_aaaa); + + r->record_browser_srv = r->record_browser_txt = r->record_browser_a = r->record_browser_aaaa = NULL; + + finish(r, AVAHI_RESOLVER_FAILURE); + break; + } +} + +AvahiSServiceResolver *avahi_s_service_resolver_new( + AvahiServer *server, + AvahiIfIndex interface, + AvahiProtocol protocol, + const char *name, + const char *type, + const char *domain, + AvahiProtocol aprotocol, + AvahiLookupFlags flags, + AvahiSServiceResolverCallback callback, + void* userdata) { + + AvahiSServiceResolver *r; + AvahiKey *k; + char n[AVAHI_DOMAIN_NAME_MAX]; + int ret; + + assert(server); + assert(type); + assert(callback); + + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_PROTO_VALID(aprotocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, !name || avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); + AVAHI_CHECK_VALIDITY_RETURN_NULL(server, AVAHI_FLAGS_VALID(flags, AVAHI_LOOKUP_USE_WIDE_AREA|AVAHI_LOOKUP_USE_MULTICAST|AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), AVAHI_ERR_INVALID_FLAGS); + + if (!domain) + domain = server->domain_name; + + if ((ret = avahi_service_name_join(n, sizeof(n), name, type, domain)) < 0) { + avahi_server_set_errno(server, ret); + return NULL; + } + + if (!(r = avahi_new(AvahiSServiceResolver, 1))) { + avahi_server_set_errno(server, AVAHI_ERR_NO_MEMORY); + return NULL; + } + + r->server = server; + r->service_name = avahi_strdup(name); + r->service_type = avahi_normalize_name_strdup(type); + r->domain_name = avahi_normalize_name_strdup(domain); + r->callback = callback; + r->userdata = userdata; + r->address_protocol = aprotocol; + r->srv_record = r->txt_record = r->address_record = NULL; + r->srv_flags = r->txt_flags = r->address_flags = 0; + r->interface = interface; + r->protocol = protocol; + r->user_flags = flags; + r->record_browser_a = r->record_browser_aaaa = r->record_browser_srv = r->record_browser_txt = NULL; + r->time_event = NULL; + AVAHI_LLIST_PREPEND(AvahiSServiceResolver, resolver, server->service_resolvers, r); + + k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV); + r->record_browser_srv = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); + avahi_key_unref(k); + + if (!r->record_browser_srv) { + avahi_s_service_resolver_free(r); + return NULL; + } + + if (!(flags & AVAHI_LOOKUP_NO_TXT)) { + k = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT); + r->record_browser_txt = avahi_s_record_browser_new(server, interface, protocol, k, flags & ~(AVAHI_LOOKUP_NO_TXT|AVAHI_LOOKUP_NO_ADDRESS), record_browser_callback, r); + avahi_key_unref(k); + + if (!r->record_browser_txt) { + avahi_s_service_resolver_free(r); + return NULL; + } + } + + start_timeout(r); + + return r; +} + +void avahi_s_service_resolver_free(AvahiSServiceResolver *r) { + assert(r); + + AVAHI_LLIST_REMOVE(AvahiSServiceResolver, resolver, r->server->service_resolvers, r); + + if (r->time_event) + avahi_time_event_free(r->time_event); + + if (r->record_browser_srv) + avahi_s_record_browser_free(r->record_browser_srv); + if (r->record_browser_txt) + avahi_s_record_browser_free(r->record_browser_txt); + if (r->record_browser_a) + avahi_s_record_browser_free(r->record_browser_a); + if (r->record_browser_aaaa) + avahi_s_record_browser_free(r->record_browser_aaaa); + + if (r->srv_record) + avahi_record_unref(r->srv_record); + if (r->txt_record) + avahi_record_unref(r->txt_record); + if (r->address_record) + avahi_record_unref(r->address_record); + + avahi_free(r->service_name); + avahi_free(r->service_type); + avahi_free(r->domain_name); + avahi_free(r); +} diff --git a/trunk/avahi-core/response-sched.c b/trunk/avahi-core/response-sched.c new file mode 100644 index 0000000..ef10eca --- /dev/null +++ b/trunk/avahi-core/response-sched.c @@ -0,0 +1,516 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "response-sched.h" +#include "log.h" +#include "rr-util.h" + +/* Local packets are supressed this long after sending them */ +#define AVAHI_RESPONSE_HISTORY_MSEC 500 + +/* Local packets are deferred this long before sending them */ +#define AVAHI_RESPONSE_DEFER_MSEC 20 + +/* Additional jitter for deferred packets */ +#define AVAHI_RESPONSE_JITTER_MSEC 100 + +/* Remote packets can suppress local traffic as long as this value */ +#define AVAHI_RESPONSE_SUPPRESS_MSEC 700 + +typedef struct AvahiResponseJob AvahiResponseJob; + +typedef enum { + AVAHI_SCHEDULED, + AVAHI_DONE, + AVAHI_SUPPRESSED +} AvahiResponseJobState; + +struct AvahiResponseJob { + AvahiResponseScheduler *scheduler; + AvahiTimeEvent *time_event; + + AvahiResponseJobState state; + struct timeval delivery; + + AvahiRecord *record; + int flush_cache; + AvahiAddress querier; + int querier_valid; + + AVAHI_LLIST_FIELDS(AvahiResponseJob, jobs); +}; + +struct AvahiResponseScheduler { + AvahiInterface *interface; + AvahiTimeEventQueue *time_event_queue; + + AVAHI_LLIST_HEAD(AvahiResponseJob, jobs); + AVAHI_LLIST_HEAD(AvahiResponseJob, history); + AVAHI_LLIST_HEAD(AvahiResponseJob, suppressed); +}; + +static AvahiResponseJob* job_new(AvahiResponseScheduler *s, AvahiRecord *record, AvahiResponseJobState state) { + AvahiResponseJob *rj; + + assert(s); + assert(record); + + if (!(rj = avahi_new(AvahiResponseJob, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; + } + + rj->scheduler = s; + rj->record = avahi_record_ref(record); + rj->time_event = NULL; + rj->flush_cache = 0; + rj->querier_valid = 0; + + if ((rj->state = state) == AVAHI_SCHEDULED) + AVAHI_LLIST_PREPEND(AvahiResponseJob, jobs, s->jobs, rj); + else if (rj->state == AVAHI_DONE) + AVAHI_LLIST_PREPEND(AvahiResponseJob, jobs, s->history, rj); + else /* rj->state == AVAHI_SUPPRESSED */ + AVAHI_LLIST_PREPEND(AvahiResponseJob, jobs, s->suppressed, rj); + + return rj; +} + +static void job_free(AvahiResponseScheduler *s, AvahiResponseJob *rj) { + assert(s); + assert(rj); + + if (rj->time_event) + avahi_time_event_free(rj->time_event); + + if (rj->state == AVAHI_SCHEDULED) + AVAHI_LLIST_REMOVE(AvahiResponseJob, jobs, s->jobs, rj); + else if (rj->state == AVAHI_DONE) + AVAHI_LLIST_REMOVE(AvahiResponseJob, jobs, s->history, rj); + else /* rj->state == AVAHI_SUPPRESSED */ + AVAHI_LLIST_REMOVE(AvahiResponseJob, jobs, s->suppressed, rj); + + avahi_record_unref(rj->record); + avahi_free(rj); +} + +static void elapse_callback(AvahiTimeEvent *e, void* data); + +static void job_set_elapse_time(AvahiResponseScheduler *s, AvahiResponseJob *rj, unsigned msec, unsigned jitter) { + struct timeval tv; + + assert(s); + assert(rj); + + avahi_elapse_time(&tv, msec, jitter); + + if (rj->time_event) + avahi_time_event_update(rj->time_event, &tv); + else + rj->time_event = avahi_time_event_new(s->time_event_queue, &tv, elapse_callback, rj); +} + +static void job_mark_done(AvahiResponseScheduler *s, AvahiResponseJob *rj) { + assert(s); + assert(rj); + + assert(rj->state == AVAHI_SCHEDULED); + + AVAHI_LLIST_REMOVE(AvahiResponseJob, jobs, s->jobs, rj); + AVAHI_LLIST_PREPEND(AvahiResponseJob, jobs, s->history, rj); + + rj->state = AVAHI_DONE; + + job_set_elapse_time(s, rj, AVAHI_RESPONSE_HISTORY_MSEC, 0); + + gettimeofday(&rj->delivery, NULL); +} + +AvahiResponseScheduler *avahi_response_scheduler_new(AvahiInterface *i) { + AvahiResponseScheduler *s; + assert(i); + + if (!(s = avahi_new(AvahiResponseScheduler, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; + } + + s->interface = i; + s->time_event_queue = i->monitor->server->time_event_queue; + + AVAHI_LLIST_HEAD_INIT(AvahiResponseJob, s->jobs); + AVAHI_LLIST_HEAD_INIT(AvahiResponseJob, s->history); + AVAHI_LLIST_HEAD_INIT(AvahiResponseJob, s->suppressed); + + return s; +} + +void avahi_response_scheduler_free(AvahiResponseScheduler *s) { + assert(s); + + avahi_response_scheduler_clear(s); + avahi_free(s); +} + +void avahi_response_scheduler_clear(AvahiResponseScheduler *s) { + assert(s); + + while (s->jobs) + job_free(s, s->jobs); + while (s->history) + job_free(s, s->history); + while (s->suppressed) + job_free(s, s->suppressed); +} + +static void enumerate_aux_records_callback(AVAHI_GCC_UNUSED AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata) { + AvahiResponseJob *rj = userdata; + + assert(r); + assert(rj); + + avahi_response_scheduler_post(rj->scheduler, r, flush_cache, rj->querier_valid ? &rj->querier : NULL, 0); +} + +static int packet_add_response_job(AvahiResponseScheduler *s, AvahiDnsPacket *p, AvahiResponseJob *rj) { + assert(s); + assert(p); + assert(rj); + + /* Try to add this record to the packet */ + if (!avahi_dns_packet_append_record(p, rj->record, rj->flush_cache, 0)) + return 0; + + /* Ok, this record will definitely be sent, so schedule the + * auxilliary packets, too */ + avahi_server_enumerate_aux_records(s->interface->monitor->server, s->interface, rj->record, enumerate_aux_records_callback, rj); + job_mark_done(s, rj); + + return 1; +} + +static void send_response_packet(AvahiResponseScheduler *s, AvahiResponseJob *rj) { + AvahiDnsPacket *p; + unsigned n; + + assert(s); + assert(rj); + + if (!(p = avahi_dns_packet_new_response(s->interface->hardware->mtu, 1))) + return; /* OOM */ + n = 1; + + /* Put it in the packet. */ + if (packet_add_response_job(s, p, rj)) { + + /* Try to fill up packet with more responses, if available */ + while (s->jobs) { + + if (!packet_add_response_job(s, p, s->jobs)) + break; + + n++; + } + + } else { + size_t size; + + avahi_dns_packet_free(p); + + /* OK, the packet was too small, so create one that fits */ + size = avahi_record_get_estimate_size(rj->record) + AVAHI_DNS_PACKET_HEADER_SIZE; + + if (size > AVAHI_DNS_PACKET_SIZE_MAX) + size = AVAHI_DNS_PACKET_SIZE_MAX; + + if (!(p = avahi_dns_packet_new_response(size, 1))) + return; /* OOM */ + + if (!packet_add_response_job(s, p, rj)) { + avahi_dns_packet_free(p); + + avahi_log_warn("Record too large, cannot send"); + job_mark_done(s, rj); + return; + } + } + + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ANCOUNT, n); + avahi_interface_send_packet(s->interface, p); + avahi_dns_packet_free(p); +} + +static void elapse_callback(AVAHI_GCC_UNUSED AvahiTimeEvent *e, void* data) { + AvahiResponseJob *rj = data; + + assert(rj); + + if (rj->state == AVAHI_DONE || rj->state == AVAHI_SUPPRESSED) + job_free(rj->scheduler, rj); /* Lets drop this entry */ + else + send_response_packet(rj->scheduler, rj); +} + +static AvahiResponseJob* find_scheduled_job(AvahiResponseScheduler *s, AvahiRecord *record) { + AvahiResponseJob *rj; + + assert(s); + assert(record); + + for (rj = s->jobs; rj; rj = rj->jobs_next) { + assert(rj->state == AVAHI_SCHEDULED); + + if (avahi_record_equal_no_ttl(rj->record, record)) + return rj; + } + + return NULL; +} + +static AvahiResponseJob* find_history_job(AvahiResponseScheduler *s, AvahiRecord *record) { + AvahiResponseJob *rj; + + assert(s); + assert(record); + + for (rj = s->history; rj; rj = rj->jobs_next) { + assert(rj->state == AVAHI_DONE); + + if (avahi_record_equal_no_ttl(rj->record, record)) { + /* Check whether this entry is outdated */ + +/* avahi_log_debug("history age: %u", (unsigned) (avahi_age(&rj->delivery)/1000)); */ + + if (avahi_age(&rj->delivery)/1000 > AVAHI_RESPONSE_HISTORY_MSEC) { + /* it is outdated, so let's remove it */ + job_free(s, rj); + return NULL; + } + + return rj; + } + } + + return NULL; +} + +static AvahiResponseJob* find_suppressed_job(AvahiResponseScheduler *s, AvahiRecord *record, const AvahiAddress *querier) { + AvahiResponseJob *rj; + + assert(s); + assert(record); + assert(querier); + + for (rj = s->suppressed; rj; rj = rj->jobs_next) { + assert(rj->state == AVAHI_SUPPRESSED); + assert(rj->querier_valid); + + if (avahi_record_equal_no_ttl(rj->record, record) && + avahi_address_cmp(&rj->querier, querier) == 0) { + /* Check whether this entry is outdated */ + + if (avahi_age(&rj->delivery) > AVAHI_RESPONSE_SUPPRESS_MSEC*1000) { + /* it is outdated, so let's remove it */ + job_free(s, rj); + return NULL; + } + + return rj; + } + } + + return NULL; +} + +int avahi_response_scheduler_post(AvahiResponseScheduler *s, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately) { + AvahiResponseJob *rj; + struct timeval tv; +/* char *t; */ + + assert(s); + assert(record); + + assert(!avahi_key_is_pattern(record->key)); + +/* t = avahi_record_to_string(record); */ +/* avahi_log_debug("post %i %s", immediately, t); */ +/* avahi_free(t); */ + + /* Check whether this response is suppressed */ + if (querier && + (rj = find_suppressed_job(s, record, querier)) && + avahi_record_is_goodbye(record) == avahi_record_is_goodbye(rj->record) && + rj->record->ttl >= record->ttl/2) { + +/* avahi_log_debug("Response suppressed by known answer suppression."); */ + return 0; + } + + /* Check if we already sent this response recently */ + if ((rj = find_history_job(s, record))) { + + if (avahi_record_is_goodbye(record) == avahi_record_is_goodbye(rj->record) && + rj->record->ttl >= record->ttl/2 && + (rj->flush_cache || !flush_cache)) { +/* avahi_log_debug("Response suppressed by local duplicate suppression (history)"); */ + return 0; + } + + /* Outdated ... */ + job_free(s, rj); + } + + avahi_elapse_time(&tv, immediately ? 0 : AVAHI_RESPONSE_DEFER_MSEC, immediately ? 0 : AVAHI_RESPONSE_JITTER_MSEC); + + if ((rj = find_scheduled_job(s, record))) { +/* avahi_log_debug("Response suppressed by local duplicate suppression (scheduled)"); */ + + /* Update a little ... */ + + /* Update the time if the new is prior to the old */ + if (avahi_timeval_compare(&tv, &rj->delivery) < 0) { + rj->delivery = tv; + avahi_time_event_update(rj->time_event, &rj->delivery); + } + + /* Update the flush cache bit */ + if (flush_cache) + rj->flush_cache = 1; + + /* Update the querier field */ + if (!querier || (rj->querier_valid && avahi_address_cmp(querier, &rj->querier) != 0)) + rj->querier_valid = 0; + + /* Update record data (just for the TTL) */ + avahi_record_unref(rj->record); + rj->record = avahi_record_ref(record); + + return 1; + } else { +/* avahi_log_debug("Accepted new response job."); */ + + /* Create a new job and schedule it */ + if (!(rj = job_new(s, record, AVAHI_SCHEDULED))) + return 0; /* OOM */ + + rj->delivery = tv; + rj->time_event = avahi_time_event_new(s->time_event_queue, &rj->delivery, elapse_callback, rj); + rj->flush_cache = flush_cache; + + if ((rj->querier_valid = !!querier)) + rj->querier = *querier; + + return 1; + } +} + +void avahi_response_scheduler_incoming(AvahiResponseScheduler *s, AvahiRecord *record, int flush_cache) { + AvahiResponseJob *rj; + assert(s); + + /* This function is called whenever an incoming response was + * receieved. We drop scheduled responses which match here. The + * keyword is "DUPLICATE ANSWER SUPPRESION". */ + + if ((rj = find_scheduled_job(s, record))) { + + if ((!rj->flush_cache || flush_cache) && /* flush cache bit was set correctly */ + avahi_record_is_goodbye(record) == avahi_record_is_goodbye(rj->record) && /* both goodbye packets, or both not */ + record->ttl >= rj->record->ttl/2) { /* sensible TTL */ + + /* A matching entry was found, so let's mark it done */ +/* avahi_log_debug("Response suppressed by distributed duplicate suppression"); */ + job_mark_done(s, rj); + } + + return; + } + + if ((rj = find_history_job(s, record))) { + /* Found a history job, let's update it */ + avahi_record_unref(rj->record); + rj->record = avahi_record_ref(record); + } else + /* Found no existing history job, so let's create a new one */ + if (!(rj = job_new(s, record, AVAHI_DONE))) + return; /* OOM */ + + rj->flush_cache = flush_cache; + rj->querier_valid = 0; + + gettimeofday(&rj->delivery, NULL); + job_set_elapse_time(s, rj, AVAHI_RESPONSE_HISTORY_MSEC, 0); +} + +void avahi_response_scheduler_suppress(AvahiResponseScheduler *s, AvahiRecord *record, const AvahiAddress *querier) { + AvahiResponseJob *rj; + + assert(s); + assert(record); + assert(querier); + + if ((rj = find_scheduled_job(s, record))) { + + if (rj->querier_valid && avahi_address_cmp(querier, &rj->querier) == 0 && /* same originator */ + avahi_record_is_goodbye(record) == avahi_record_is_goodbye(rj->record) && /* both goodbye packets, or both not */ + record->ttl >= rj->record->ttl/2) { /* sensible TTL */ + + /* A matching entry was found, so let's drop it */ +/* avahi_log_debug("Known answer suppression active!"); */ + job_free(s, rj); + } + } + + if ((rj = find_suppressed_job(s, record, querier))) { + + /* Let's update the old entry */ + avahi_record_unref(rj->record); + rj->record = avahi_record_ref(record); + + } else { + + /* Create a new entry */ + if (!(rj = job_new(s, record, AVAHI_SUPPRESSED))) + return; /* OOM */ + rj->querier_valid = 1; + rj->querier = *querier; + } + + gettimeofday(&rj->delivery, NULL); + job_set_elapse_time(s, rj, AVAHI_RESPONSE_SUPPRESS_MSEC, 0); +} + +void avahi_response_scheduler_force(AvahiResponseScheduler *s) { + assert(s); + + /* Send all scheduled responses immediately */ + while (s->jobs) + send_response_packet(s, s->jobs); +} diff --git a/trunk/avahi-core/response-sched.h b/trunk/avahi-core/response-sched.h new file mode 100644 index 0000000..68c4a9d --- /dev/null +++ b/trunk/avahi-core/response-sched.h @@ -0,0 +1,39 @@ +#ifndef fooresponseschedhfoo +#define fooresponseschedhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +typedef struct AvahiResponseScheduler AvahiResponseScheduler; + +#include <avahi-common/address.h> +#include "iface.h" + +AvahiResponseScheduler *avahi_response_scheduler_new(AvahiInterface *i); +void avahi_response_scheduler_free(AvahiResponseScheduler *s); +void avahi_response_scheduler_clear(AvahiResponseScheduler *s); +void avahi_response_scheduler_force(AvahiResponseScheduler *s); + +int avahi_response_scheduler_post(AvahiResponseScheduler *s, AvahiRecord *record, int flush_cache, const AvahiAddress *querier, int immediately); +void avahi_response_scheduler_incoming(AvahiResponseScheduler *s, AvahiRecord *record, int flush_cache); +void avahi_response_scheduler_suppress(AvahiResponseScheduler *s, AvahiRecord *record, const AvahiAddress *querier); + +#endif diff --git a/trunk/avahi-core/rr-util.h b/trunk/avahi-core/rr-util.h new file mode 100644 index 0000000..b3c35b4 --- /dev/null +++ b/trunk/avahi-core/rr-util.h @@ -0,0 +1,64 @@ +#ifndef foorrutilhfoo +#define foorrutilhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "rr.h" + +AVAHI_C_DECL_BEGIN + +/** Creaze new AvahiKey object based on an existing key but replaceing the type by CNAME */ +AvahiKey *avahi_key_new_cname(AvahiKey *key); + +/** Match a key to a key pattern. The pattern has a type of +AVAHI_DNS_CLASS_ANY, the classes are taken to be equal. Same for the +type. If the pattern has neither class nor type with ANY constants, +this function is identical to avahi_key_equal(). In contrast to +avahi_equal() this function is not commutative. */ +int avahi_key_pattern_match(const AvahiKey *pattern, const AvahiKey *k); + +/** Check whether a key is a pattern key, i.e. the class/type has a + * value of AVAHI_DNS_CLASS_ANY/AVAHI_DNS_TYPE_ANY */ +int avahi_key_is_pattern(const AvahiKey *k); + +/** Returns a maximum estimate for the space that is needed to store + * this key in a DNS packet. */ +size_t avahi_key_get_estimate_size(AvahiKey *k); + +/** Returns a maximum estimate for the space that is needed to store + * the record in a DNS packet. */ +size_t avahi_record_get_estimate_size(AvahiRecord *r); + +/** Do a mDNS spec conforming lexicographical comparison of the two + * records. Return a negative value if a < b, a positive if a > b, + * zero if equal. */ +int avahi_record_lexicographical_compare(AvahiRecord *a, AvahiRecord *b); + +/** Return 1 if the specified record is an mDNS goodbye record. i.e. TTL is zero. */ +int avahi_record_is_goodbye(AvahiRecord *r); + +/** Make a deep copy of an AvahiRecord object */ +AvahiRecord *avahi_record_copy(AvahiRecord *r); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/rr.c b/trunk/avahi-core/rr.c new file mode 100644 index 0000000..8b7fab7 --- /dev/null +++ b/trunk/avahi-core/rr.c @@ -0,0 +1,703 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <assert.h> + +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/defs.h> + +#include "rr.h" +#include "log.h" +#include "util.h" +#include "hashmap.h" +#include "domain-util.h" +#include "rr-util.h" + +AvahiKey *avahi_key_new(const char *name, uint16_t class, uint16_t type) { + AvahiKey *k; + assert(name); + + if (!(k = avahi_new(AvahiKey, 1))) { + avahi_log_error("avahi_new() failed."); + return NULL; + } + + if (!(k->name = avahi_normalize_name_strdup(name))) { + avahi_log_error("avahi_normalize_name() failed."); + avahi_free(k); + return NULL; + } + + k->ref = 1; + k->clazz = class; + k->type = type; + + return k; +} + +AvahiKey *avahi_key_new_cname(AvahiKey *key) { + assert(key); + + if (key->clazz != AVAHI_DNS_CLASS_IN) + return NULL; + + if (key->type == AVAHI_DNS_TYPE_CNAME) + return NULL; + + return avahi_key_new(key->name, key->clazz, AVAHI_DNS_TYPE_CNAME); +} + +AvahiKey *avahi_key_ref(AvahiKey *k) { + assert(k); + assert(k->ref >= 1); + + k->ref++; + + return k; +} + +void avahi_key_unref(AvahiKey *k) { + assert(k); + assert(k->ref >= 1); + + if ((--k->ref) <= 0) { + avahi_free(k->name); + avahi_free(k); + } +} + +AvahiRecord *avahi_record_new(AvahiKey *k, uint32_t ttl) { + AvahiRecord *r; + + assert(k); + + if (!(r = avahi_new(AvahiRecord, 1))) { + avahi_log_error("avahi_new() failed."); + return NULL; + } + + r->ref = 1; + r->key = avahi_key_ref(k); + + memset(&r->data, 0, sizeof(r->data)); + + r->ttl = ttl != (uint32_t) -1 ? ttl : AVAHI_DEFAULT_TTL; + + return r; +} + +AvahiRecord *avahi_record_new_full(const char *name, uint16_t class, uint16_t type, uint32_t ttl) { + AvahiRecord *r; + AvahiKey *k; + + assert(name); + + if (!(k = avahi_key_new(name, class, type))) { + avahi_log_error("avahi_key_new() failed."); + return NULL; + } + + r = avahi_record_new(k, ttl); + avahi_key_unref(k); + + if (!r) { + avahi_log_error("avahi_record_new() failed."); + return NULL; + } + + return r; +} + +AvahiRecord *avahi_record_ref(AvahiRecord *r) { + assert(r); + assert(r->ref >= 1); + + r->ref++; + return r; +} + +void avahi_record_unref(AvahiRecord *r) { + assert(r); + assert(r->ref >= 1); + + if ((--r->ref) <= 0) { + switch (r->key->type) { + + case AVAHI_DNS_TYPE_SRV: + avahi_free(r->data.srv.name); + break; + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + avahi_free(r->data.ptr.name); + break; + + case AVAHI_DNS_TYPE_HINFO: + avahi_free(r->data.hinfo.cpu); + avahi_free(r->data.hinfo.os); + break; + + case AVAHI_DNS_TYPE_TXT: + avahi_string_list_free(r->data.txt.string_list); + break; + + case AVAHI_DNS_TYPE_A: + case AVAHI_DNS_TYPE_AAAA: + break; + + default: + avahi_free(r->data.generic.data); + } + + avahi_key_unref(r->key); + avahi_free(r); + } +} + +const char *avahi_dns_class_to_string(uint16_t class) { + if (class & AVAHI_DNS_CACHE_FLUSH) + return "FLUSH"; + + switch (class) { + case AVAHI_DNS_CLASS_IN: + return "IN"; + case AVAHI_DNS_CLASS_ANY: + return "ANY"; + default: + return NULL; + } +} + +const char *avahi_dns_type_to_string(uint16_t type) { + switch (type) { + case AVAHI_DNS_TYPE_CNAME: + return "CNAME"; + case AVAHI_DNS_TYPE_A: + return "A"; + case AVAHI_DNS_TYPE_AAAA: + return "AAAA"; + case AVAHI_DNS_TYPE_PTR: + return "PTR"; + case AVAHI_DNS_TYPE_HINFO: + return "HINFO"; + case AVAHI_DNS_TYPE_TXT: + return "TXT"; + case AVAHI_DNS_TYPE_SRV: + return "SRV"; + case AVAHI_DNS_TYPE_ANY: + return "ANY"; + case AVAHI_DNS_TYPE_SOA: + return "SOA"; + case AVAHI_DNS_TYPE_NS: + return "NS"; + default: + return NULL; + } +} + +char *avahi_key_to_string(const AvahiKey *k) { + char class[16], type[16]; + const char *c, *t; + + assert(k); + assert(k->ref >= 1); + + /* According to RFC3597 */ + + if (!(c = avahi_dns_class_to_string(k->clazz))) { + snprintf(class, sizeof(class), "CLASS%u", k->clazz); + c = class; + } + + if (!(t = avahi_dns_type_to_string(k->type))) { + snprintf(type, sizeof(type), "TYPE%u", k->type); + t = type; + } + + return avahi_strdup_printf("%s\t%s\t%s", k->name, c, t); +} + +char *avahi_record_to_string(const AvahiRecord *r) { + char *p, *s; + char buf[1024], *t = NULL, *d = NULL; + + assert(r); + assert(r->ref >= 1); + + switch (r->key->type) { + case AVAHI_DNS_TYPE_A: + inet_ntop(AF_INET, &r->data.a.address.address, t = buf, sizeof(buf)); + break; + + case AVAHI_DNS_TYPE_AAAA: + inet_ntop(AF_INET6, &r->data.aaaa.address.address, t = buf, sizeof(buf)); + break; + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + + t = r->data.ptr.name; + break; + + case AVAHI_DNS_TYPE_TXT: + t = d = avahi_string_list_to_string(r->data.txt.string_list); + break; + + case AVAHI_DNS_TYPE_HINFO: + + snprintf(t = buf, sizeof(buf), "\"%s\" \"%s\"", r->data.hinfo.cpu, r->data.hinfo.os); + break; + + case AVAHI_DNS_TYPE_SRV: + + snprintf(t = buf, sizeof(buf), "%u %u %u %s", + r->data.srv.priority, + r->data.srv.weight, + r->data.srv.port, + r->data.srv.name); + + break; + + default: { + + uint8_t *c; + uint16_t n; + int i; + char *e; + + /* According to RFC3597 */ + + snprintf(t = buf, sizeof(buf), "\\# %u", r->data.generic.size); + + e = strchr(t, 0); + + for (c = r->data.generic.data, n = r->data.generic.size, i = 0; + n > 0 && i < 20; + c ++, n --, i++) { + + sprintf(e, " %02X", *c); + e = strchr(e, 0); + } + + break; + } + } + + p = avahi_key_to_string(r->key); + s = avahi_strdup_printf("%s %s ; ttl=%u", p, t, r->ttl); + avahi_free(p); + avahi_free(d); + + return s; +} + +int avahi_key_equal(const AvahiKey *a, const AvahiKey *b) { + assert(a); + assert(b); + + if (a == b) + return 1; + + return avahi_domain_equal(a->name, b->name) && + a->type == b->type && + a->clazz == b->clazz; +} + +int avahi_key_pattern_match(const AvahiKey *pattern, const AvahiKey *k) { + assert(pattern); + assert(k); + + assert(!avahi_key_is_pattern(k)); + + if (pattern == k) + return 1; + + return avahi_domain_equal(pattern->name, k->name) && + (pattern->type == k->type || pattern->type == AVAHI_DNS_TYPE_ANY) && + (pattern->clazz == k->clazz || pattern->clazz == AVAHI_DNS_CLASS_ANY); +} + +int avahi_key_is_pattern(const AvahiKey *k) { + assert(k); + + return + k->type == AVAHI_DNS_TYPE_ANY || + k->clazz == AVAHI_DNS_CLASS_ANY; +} + +unsigned avahi_key_hash(const AvahiKey *k) { + assert(k); + + return + avahi_domain_hash(k->name) + + k->type + + k->clazz; +} + +static int rdata_equal(const AvahiRecord *a, const AvahiRecord *b) { + assert(a); + assert(b); + assert(a->key->type == b->key->type); + + switch (a->key->type) { + case AVAHI_DNS_TYPE_SRV: + return + a->data.srv.priority == b->data.srv.priority && + a->data.srv.weight == b->data.srv.weight && + a->data.srv.port == b->data.srv.port && + avahi_domain_equal(a->data.srv.name, b->data.srv.name); + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + return avahi_domain_equal(a->data.ptr.name, b->data.ptr.name); + + case AVAHI_DNS_TYPE_HINFO: + return + !strcmp(a->data.hinfo.cpu, b->data.hinfo.cpu) && + !strcmp(a->data.hinfo.os, b->data.hinfo.os); + + case AVAHI_DNS_TYPE_TXT: + return avahi_string_list_equal(a->data.txt.string_list, b->data.txt.string_list); + + case AVAHI_DNS_TYPE_A: + return memcmp(&a->data.a.address, &b->data.a.address, sizeof(AvahiIPv4Address)) == 0; + + case AVAHI_DNS_TYPE_AAAA: + return memcmp(&a->data.aaaa.address, &b->data.aaaa.address, sizeof(AvahiIPv6Address)) == 0; + + default: + return a->data.generic.size == b->data.generic.size && + (a->data.generic.size == 0 || memcmp(a->data.generic.data, b->data.generic.data, a->data.generic.size) == 0); + } + +} + +int avahi_record_equal_no_ttl(const AvahiRecord *a, const AvahiRecord *b) { + assert(a); + assert(b); + + if (a == b) + return 1; + + return + avahi_key_equal(a->key, b->key) && + rdata_equal(a, b); +} + + +AvahiRecord *avahi_record_copy(AvahiRecord *r) { + AvahiRecord *copy; + + if (!(copy = avahi_new(AvahiRecord, 1))) { + avahi_log_error("avahi_new() failed."); + return NULL; + } + + copy->ref = 1; + copy->key = avahi_key_ref(r->key); + copy->ttl = r->ttl; + + switch (r->key->type) { + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + if (!(copy->data.ptr.name = avahi_strdup(r->data.ptr.name))) + goto fail; + break; + + case AVAHI_DNS_TYPE_SRV: + copy->data.srv.priority = r->data.srv.priority; + copy->data.srv.weight = r->data.srv.weight; + copy->data.srv.port = r->data.srv.port; + if (!(copy->data.srv.name = avahi_strdup(r->data.srv.name))) + goto fail; + break; + + case AVAHI_DNS_TYPE_HINFO: + if (!(copy->data.hinfo.os = avahi_strdup(r->data.hinfo.os))) + goto fail; + + if (!(copy->data.hinfo.cpu = avahi_strdup(r->data.hinfo.cpu))) { + avahi_free(r->data.hinfo.os); + goto fail; + } + break; + + case AVAHI_DNS_TYPE_TXT: + copy->data.txt.string_list = avahi_string_list_copy(r->data.txt.string_list); + break; + + case AVAHI_DNS_TYPE_A: + copy->data.a.address = r->data.a.address; + break; + + case AVAHI_DNS_TYPE_AAAA: + copy->data.aaaa.address = r->data.aaaa.address; + break; + + default: + if (!(copy->data.generic.data = avahi_memdup(r->data.generic.data, r->data.generic.size))) + goto fail; + copy->data.generic.size = r->data.generic.size; + break; + + } + + return copy; + +fail: + avahi_log_error("Failed to allocate memory"); + + avahi_key_unref(copy->key); + avahi_free(copy); + + return NULL; +} + + +size_t avahi_key_get_estimate_size(AvahiKey *k) { + assert(k); + + return strlen(k->name)+1+4; +} + +size_t avahi_record_get_estimate_size(AvahiRecord *r) { + size_t n; + assert(r); + + n = avahi_key_get_estimate_size(r->key) + 4 + 2; + + switch (r->key->type) { + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + n += strlen(r->data.ptr.name) + 1; + break; + + case AVAHI_DNS_TYPE_SRV: + n += 6 + strlen(r->data.srv.name) + 1; + break; + + case AVAHI_DNS_TYPE_HINFO: + n += strlen(r->data.hinfo.os) + 1 + strlen(r->data.hinfo.cpu) + 1; + break; + + case AVAHI_DNS_TYPE_TXT: + n += avahi_string_list_serialize(r->data.txt.string_list, NULL, 0); + break; + + case AVAHI_DNS_TYPE_A: + n += sizeof(AvahiIPv4Address); + break; + + case AVAHI_DNS_TYPE_AAAA: + n += sizeof(AvahiIPv6Address); + break; + + default: + n += r->data.generic.size; + } + + return n; +} + +static int lexicographical_memcmp(const void* a, size_t al, const void* b, size_t bl) { + size_t c; + int ret; + + assert(a); + assert(b); + + c = al < bl ? al : bl; + if ((ret = memcmp(a, b, c))) + return ret; + + if (al == bl) + return 0; + else + return al == c ? 1 : -1; +} + +static int uint16_cmp(uint16_t a, uint16_t b) { + return a == b ? 0 : (a < b ? -1 : 1); +} + +int avahi_record_lexicographical_compare(AvahiRecord *a, AvahiRecord *b) { + int r; +/* char *t1, *t2; */ + + assert(a); + assert(b); + +/* t1 = avahi_record_to_string(a); */ +/* t2 = avahi_record_to_string(b); */ +/* g_message("lexicocmp: %s %s", t1, t2); */ +/* avahi_free(t1); */ +/* avahi_free(t2); */ + + if (a == b) + return 0; + + if ((r = uint16_cmp(a->key->clazz, b->key->clazz)) || + (r = uint16_cmp(a->key->type, b->key->type))) + return r; + + switch (a->key->type) { + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + return avahi_binary_domain_cmp(a->data.ptr.name, b->data.ptr.name); + + case AVAHI_DNS_TYPE_SRV: { + if ((r = uint16_cmp(a->data.srv.priority, b->data.srv.priority)) == 0 && + (r = uint16_cmp(a->data.srv.weight, b->data.srv.weight)) == 0 && + (r = uint16_cmp(a->data.srv.port, b->data.srv.port)) == 0) + r = avahi_binary_domain_cmp(a->data.srv.name, b->data.srv.name); + + return r; + } + + case AVAHI_DNS_TYPE_HINFO: { + + if ((r = strcmp(a->data.hinfo.cpu, b->data.hinfo.cpu)) || + (r = strcmp(a->data.hinfo.os, b->data.hinfo.os))) + return r; + + return 0; + + } + + case AVAHI_DNS_TYPE_TXT: { + + uint8_t *ma = NULL, *mb = NULL; + size_t asize, bsize; + + asize = avahi_string_list_serialize(a->data.txt.string_list, NULL, 0); + bsize = avahi_string_list_serialize(b->data.txt.string_list, NULL, 0); + + if (asize > 0 && !(ma = avahi_new(uint8_t, asize))) + goto fail; + + if (bsize > 0 && !(mb = avahi_new(uint8_t, bsize))) { + avahi_free(ma); + goto fail; + } + + avahi_string_list_serialize(a->data.txt.string_list, ma, asize); + avahi_string_list_serialize(b->data.txt.string_list, mb, bsize); + + if (asize && bsize) + r = lexicographical_memcmp(ma, asize, mb, bsize); + else if (asize && !bsize) + r = 1; + else if (!asize && bsize) + r = -1; + else + r = 0; + + avahi_free(ma); + avahi_free(mb); + + return r; + } + + case AVAHI_DNS_TYPE_A: + return memcmp(&a->data.a.address, &b->data.a.address, sizeof(AvahiIPv4Address)); + + case AVAHI_DNS_TYPE_AAAA: + return memcmp(&a->data.aaaa.address, &b->data.aaaa.address, sizeof(AvahiIPv6Address)); + + default: + return lexicographical_memcmp(a->data.generic.data, a->data.generic.size, + b->data.generic.data, b->data.generic.size); + } + + +fail: + avahi_log_error(__FILE__": Out of memory"); + return -1; /* or whatever ... */ +} + +int avahi_record_is_goodbye(AvahiRecord *r) { + assert(r); + + return r->ttl == 0; +} + +int avahi_key_is_valid(AvahiKey *k) { + assert(k); + + if (!avahi_is_valid_domain_name(k->name)) + return 0; + + return 1; +} + +int avahi_record_is_valid(AvahiRecord *r) { + assert(r); + + if (!avahi_key_is_valid(r->key)) + return 0; + + switch (r->key->type) { + + case AVAHI_DNS_TYPE_PTR: + case AVAHI_DNS_TYPE_CNAME: + case AVAHI_DNS_TYPE_NS: + return avahi_is_valid_domain_name(r->data.ptr.name); + + case AVAHI_DNS_TYPE_SRV: + return avahi_is_valid_domain_name(r->data.srv.name); + + case AVAHI_DNS_TYPE_HINFO: + return + strlen(r->data.hinfo.os) <= 255 && + strlen(r->data.hinfo.cpu) <= 255; + + case AVAHI_DNS_TYPE_TXT: { + + AvahiStringList *strlst; + + for (strlst = r->data.txt.string_list; strlst; strlst = strlst->next) + if (strlst->size > 255 || strlst->size <= 0) + return 0; + + return 1; + } + } + + + return 1; +} diff --git a/trunk/avahi-core/rr.h b/trunk/avahi-core/rr.h new file mode 100644 index 0000000..6bfe0ec --- /dev/null +++ b/trunk/avahi-core/rr.h @@ -0,0 +1,174 @@ +#ifndef foorrhfoo +#define foorrhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/** \file rr.h Functions and definitions for manipulating DNS resource record (RR) data. */ + +#include <inttypes.h> +#include <sys/types.h> + +#include <avahi-common/strlst.h> +#include <avahi-common/address.h> +#include <avahi-common/cdecl.h> + +AVAHI_C_DECL_BEGIN + +/** DNS record types, see RFC 1035, in addition to those defined in defs.h */ +enum { + AVAHI_DNS_TYPE_ANY = 0xFF, /**< Special query type for requesting all records */ + AVAHI_DNS_TYPE_OPT = 41, /**< EDNS0 option */ + AVAHI_DNS_TYPE_TKEY = 249, + AVAHI_DNS_TYPE_TSIG = 250, + AVAHI_DNS_TYPE_IXFR = 251, + AVAHI_DNS_TYPE_AXFR = 252 +}; + +/** DNS record classes, see RFC 1035, in addition to those defined in defs.h */ +enum { + AVAHI_DNS_CLASS_ANY = 0xFF, /**< Special query type for requesting all records */ + AVAHI_DNS_CACHE_FLUSH = 0x8000, /**< Not really a class but a bit which may be set in response packets, see mDNS spec for more information */ + AVAHI_DNS_UNICAST_RESPONSE = 0x8000 /**< Not really a class but a bit which may be set in query packets, see mDNS spec for more information */ +}; + +/** Encapsulates a DNS query key consisting of class, type and + name. Use avahi_key_ref()/avahi_key_unref() for manipulating the + reference counter. The structure is intended to be treated as "immutable", no + changes should be imposed after creation */ +typedef struct AvahiKey { + int ref; /**< Reference counter */ + char *name; /**< Record name */ + uint16_t clazz; /**< Record class, one of the AVAHI_DNS_CLASS_xxx constants */ + uint16_t type; /**< Record type, one of the AVAHI_DNS_TYPE_xxx constants */ +} AvahiKey; + +/** Encapsulates a DNS resource record. The structure is intended to + * be treated as "immutable", no changes should be imposed after + * creation. */ +typedef struct AvahiRecord { + int ref; /**< Reference counter */ + AvahiKey *key; /**< Reference to the query key of this record */ + + uint32_t ttl; /**< DNS TTL of this record */ + + union { + + struct { + void* data; + uint16_t size; + } generic; /**< Generic record data for unknown types */ + + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *name; + } srv; /**< Data for SRV records */ + + struct { + char *name; + } ptr, ns, cname; /**< Data for PTR, NS and CNAME records */ + + struct { + char *cpu; + char *os; + } hinfo; /**< Data for HINFO records */ + + struct { + AvahiStringList *string_list; + } txt; /**< Data for TXT records */ + + struct { + AvahiIPv4Address address; + } a; /**< Data for A records */ + + struct { + AvahiIPv6Address address; + } aaaa; /**< Data for AAAA records */ + + } data; /**< Record data */ + +} AvahiRecord; + +/** Create a new AvahiKey object. The reference counter will be set to 1. */ +AvahiKey *avahi_key_new(const char *name, uint16_t clazz, uint16_t type); + +/** Increase the reference counter of an AvahiKey object by one */ +AvahiKey *avahi_key_ref(AvahiKey *k); + +/** Decrease the reference counter of an AvahiKey object by one */ +void avahi_key_unref(AvahiKey *k); + +/** Check whether two AvahiKey object contain the same + * data. AVAHI_DNS_CLASS_ANY/AVAHI_DNS_TYPE_ANY are treated like any + * other class/type. */ +int avahi_key_equal(const AvahiKey *a, const AvahiKey *b); + +/** Return a numeric hash value for a key for usage in hash tables. */ +unsigned avahi_key_hash(const AvahiKey *k); + +/** Create a new record object. Record data should be filled in right after creation. The reference counter is set to 1. */ +AvahiRecord *avahi_record_new(AvahiKey *k, uint32_t ttl); + +/** Create a new record object. Record data should be filled in right after creation. The reference counter is set to 1. */ +AvahiRecord *avahi_record_new_full(const char *name, uint16_t clazz, uint16_t type, uint32_t ttl); + +/** Increase the reference counter of an AvahiRecord by one. */ +AvahiRecord *avahi_record_ref(AvahiRecord *r); + +/** Decrease the reference counter of an AvahiRecord by one. */ +void avahi_record_unref(AvahiRecord *r); + +/** Return a textual representation of the specified DNS class. The + * returned pointer points to a read only internal string. */ +const char *avahi_dns_class_to_string(uint16_t clazz); + +/** Return a textual representation of the specified DNS class. The + * returned pointer points to a read only internal string. */ +const char *avahi_dns_type_to_string(uint16_t type); + +/** Create a textual representation of the specified key. avahi_free() the + * result! */ +char *avahi_key_to_string(const AvahiKey *k); + +/** Create a textual representation of the specified record, similar + * in style to BIND zone file data. avahi_free() the result! */ +char *avahi_record_to_string(const AvahiRecord *r); + +/** Check whether two records are equal (regardless of the TTL */ +int avahi_record_equal_no_ttl(const AvahiRecord *a, const AvahiRecord *b); + +/** Check whether the specified key is valid */ +int avahi_key_is_valid(AvahiKey *k); + +/** Check whether the specified record is valid */ +int avahi_record_is_valid(AvahiRecord *r); + +/** Parse a binary rdata object and fill it into *record. This function is actually implemented in dns.c */ +int avahi_rdata_parse(AvahiRecord *record, const void* rdata, size_t size); + +/** Serialize an AvahiRecord object into binary rdata. This function is actually implemented in dns.c */ +size_t avahi_rdata_serialize(AvahiRecord *record, void *rdata, size_t max_size); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/rrlist.c b/trunk/avahi-core/rrlist.c new file mode 100644 index 0000000..915ecbb --- /dev/null +++ b/trunk/avahi-core/rrlist.c @@ -0,0 +1,190 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <assert.h> + +#include <avahi-common/llist.h> +#include <avahi-common/malloc.h> + +#include "rrlist.h" +#include "log.h" + +typedef struct AvahiRecordListItem AvahiRecordListItem; + +struct AvahiRecordListItem { + int read; + AvahiRecord *record; + int unicast_response; + int flush_cache; + int auxiliary; + AVAHI_LLIST_FIELDS(AvahiRecordListItem, items); +}; + +struct AvahiRecordList { + AVAHI_LLIST_HEAD(AvahiRecordListItem, read); + AVAHI_LLIST_HEAD(AvahiRecordListItem, unread); + + int all_flush_cache; +}; + +AvahiRecordList *avahi_record_list_new(void) { + AvahiRecordList *l; + + if (!(l = avahi_new(AvahiRecordList, 1))) { + avahi_log_error("avahi_new() failed."); + return NULL; + } + + AVAHI_LLIST_HEAD_INIT(AvahiRecordListItem, l->read); + AVAHI_LLIST_HEAD_INIT(AvahiRecordListItem, l->unread); + + l->all_flush_cache = 1; + return l; +} + +void avahi_record_list_free(AvahiRecordList *l) { + assert(l); + + avahi_record_list_flush(l); + avahi_free(l); +} + +static void item_free(AvahiRecordList *l, AvahiRecordListItem *i) { + assert(l); + assert(i); + + if (i->read) + AVAHI_LLIST_REMOVE(AvahiRecordListItem, items, l->read, i); + else + AVAHI_LLIST_REMOVE(AvahiRecordListItem, items, l->unread, i); + + avahi_record_unref(i->record); + avahi_free(i); +} + +void avahi_record_list_flush(AvahiRecordList *l) { + assert(l); + + while (l->read) + item_free(l, l->read); + while (l->unread) + item_free(l, l->unread); + + l->all_flush_cache = 1; +} + +AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *ret_flush_cache, int *ret_unicast_response, int *ret_auxiliary) { + AvahiRecord *r; + AvahiRecordListItem *i; + + if (!(i = l->unread)) + return NULL; + + assert(!i->read); + + r = avahi_record_ref(i->record); + if (ret_unicast_response) + *ret_unicast_response = i->unicast_response; + if (ret_flush_cache) + *ret_flush_cache = i->flush_cache; + if (ret_auxiliary) + *ret_auxiliary = i->auxiliary; + + AVAHI_LLIST_REMOVE(AvahiRecordListItem, items, l->unread, i); + AVAHI_LLIST_PREPEND(AvahiRecordListItem, items, l->read, i); + + i->read = 1; + + return r; +} + +static AvahiRecordListItem *get(AvahiRecordList *l, AvahiRecord *r) { + AvahiRecordListItem *i; + + assert(l); + assert(r); + + for (i = l->read; i; i = i->items_next) + if (avahi_record_equal_no_ttl(i->record, r)) + return i; + + for (i = l->unread; i; i = i->items_next) + if (avahi_record_equal_no_ttl(i->record, r)) + return i; + + return NULL; +} + +void avahi_record_list_push(AvahiRecordList *l, AvahiRecord *r, int flush_cache, int unicast_response, int auxiliary) { + AvahiRecordListItem *i; + + assert(l); + assert(r); + + if (get(l, r)) + return; + + if (!(i = avahi_new(AvahiRecordListItem, 1))) { + avahi_log_error("avahi_new() failed."); + return; + } + + i->unicast_response = unicast_response; + i->flush_cache = flush_cache; + i->auxiliary = auxiliary; + i->record = avahi_record_ref(r); + i->read = 0; + + l->all_flush_cache = l->all_flush_cache && flush_cache; + + AVAHI_LLIST_PREPEND(AvahiRecordListItem, items, l->unread, i); +} + +void avahi_record_list_drop(AvahiRecordList *l, AvahiRecord *r) { + AvahiRecordListItem *i; + + assert(l); + assert(r); + + if (!(i = get(l, r))) + return; + + item_free(l, i); +} + +int avahi_record_list_is_empty(AvahiRecordList *l) { + assert(l); + + return !l->unread && !l->read; +} + +int avahi_record_list_all_flush_cache(AvahiRecordList *l) { + assert(l); + + /* Return TRUE if all entries in this list have flush_cache set */ + + return l->all_flush_cache; +} diff --git a/trunk/avahi-core/rrlist.h b/trunk/avahi-core/rrlist.h new file mode 100644 index 0000000..9c07ecd --- /dev/null +++ b/trunk/avahi-core/rrlist.h @@ -0,0 +1,42 @@ +#ifndef foorrlisthfoo +#define foorrlisthfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + + +#include "rr.h" + +typedef struct AvahiRecordList AvahiRecordList; + +AvahiRecordList *avahi_record_list_new(void); +void avahi_record_list_free(AvahiRecordList *l); +void avahi_record_list_flush(AvahiRecordList *l); + +AvahiRecord* avahi_record_list_next(AvahiRecordList *l, int *ret_flush_cache, int *ret_unicast_response, int *ret_auxiliary); +void avahi_record_list_push(AvahiRecordList *l, AvahiRecord *r, int flush_cache, int unicast_response, int auxiliary); +void avahi_record_list_drop(AvahiRecordList *l, AvahiRecord *r); + +int avahi_record_list_all_flush_cache(AvahiRecordList *l); + +int avahi_record_list_is_empty(AvahiRecordList *l); + +#endif diff --git a/trunk/avahi-core/server.c b/trunk/avahi-core/server.c new file mode 100644 index 0000000..5bfbc50 --- /dev/null +++ b/trunk/avahi-core/server.c @@ -0,0 +1,1723 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <sys/utsname.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> + +#include <avahi-common/domain.h> +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +#include "internal.h" +#include "iface.h" +#include "socket.h" +#include "browse.h" +#include "log.h" +#include "util.h" +#include "dns-srv-rr.h" +#include "addr-util.h" +#include "domain-util.h" +#include "rr-util.h" + +static void enum_aux_records(AvahiServer *s, AvahiInterface *i, const char *name, uint16_t type, void (*callback)(AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata), void* userdata) { + assert(s); + assert(i); + assert(name); + assert(callback); + + if (type == AVAHI_DNS_TYPE_ANY) { + AvahiEntry *e; + + for (e = s->entries; e; e = e->entries_next) + if (!e->dead && + avahi_entry_is_registered(s, e, i) && + e->record->key->clazz == AVAHI_DNS_CLASS_IN && + avahi_domain_equal(name, e->record->key->name)) + callback(s, e->record, e->flags & AVAHI_PUBLISH_UNIQUE, userdata); + + } else { + AvahiEntry *e; + AvahiKey *k; + + if (!(k = avahi_key_new(name, AVAHI_DNS_CLASS_IN, type))) + return; /** OOM */ + + for (e = avahi_hashmap_lookup(s->entries_by_key, k); e; e = e->by_key_next) + if (!e->dead && avahi_entry_is_registered(s, e, i)) + callback(s, e->record, e->flags & AVAHI_PUBLISH_UNIQUE, userdata); + + avahi_key_unref(k); + } +} + +void avahi_server_enumerate_aux_records(AvahiServer *s, AvahiInterface *i, AvahiRecord *r, void (*callback)(AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata), void* userdata) { + assert(s); + assert(i); + assert(r); + assert(callback); + + /* Call the specified callback far all records referenced by the one specified in *r */ + + if (r->key->clazz == AVAHI_DNS_CLASS_IN) { + if (r->key->type == AVAHI_DNS_TYPE_PTR) { + enum_aux_records(s, i, r->data.ptr.name, AVAHI_DNS_TYPE_SRV, callback, userdata); + enum_aux_records(s, i, r->data.ptr.name, AVAHI_DNS_TYPE_TXT, callback, userdata); + } else if (r->key->type == AVAHI_DNS_TYPE_SRV) { + enum_aux_records(s, i, r->data.srv.name, AVAHI_DNS_TYPE_A, callback, userdata); + enum_aux_records(s, i, r->data.srv.name, AVAHI_DNS_TYPE_AAAA, callback, userdata); + } else if (r->key->type == AVAHI_DNS_TYPE_CNAME) + enum_aux_records(s, i, r->data.cname.name, AVAHI_DNS_TYPE_ANY, callback, userdata); + } +} + +void avahi_server_prepare_response(AvahiServer *s, AvahiInterface *i, AvahiEntry *e, int unicast_response, int auxiliary) { + assert(s); + assert(i); + assert(e); + + avahi_record_list_push(s->record_list, e->record, e->flags & AVAHI_PUBLISH_UNIQUE, unicast_response, auxiliary); +} + +void avahi_server_prepare_matching_responses(AvahiServer *s, AvahiInterface *i, AvahiKey *k, int unicast_response) { + assert(s); + assert(i); + assert(k); + + /* Push all records that match the specified key to the record list */ + + if (avahi_key_is_pattern(k)) { + AvahiEntry *e; + + /* Handle ANY query */ + + for (e = s->entries; e; e = e->entries_next) + if (!e->dead && avahi_key_pattern_match(k, e->record->key) && avahi_entry_is_registered(s, e, i)) + avahi_server_prepare_response(s, i, e, unicast_response, 0); + + } else { + AvahiEntry *e; + + /* Handle all other queries */ + + for (e = avahi_hashmap_lookup(s->entries_by_key, k); e; e = e->by_key_next) + if (!e->dead && avahi_entry_is_registered(s, e, i)) + avahi_server_prepare_response(s, i, e, unicast_response, 0); + } + + /* Look for CNAME records */ + + if ((k->clazz == AVAHI_DNS_CLASS_IN || k->clazz == AVAHI_DNS_CLASS_ANY) + && k->type != AVAHI_DNS_TYPE_CNAME && k->type != AVAHI_DNS_TYPE_ANY) { + + AvahiKey *cname_key; + + if (!(cname_key = avahi_key_new(k->name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_CNAME))) + return; + + avahi_server_prepare_matching_responses(s, i, cname_key, unicast_response); + avahi_key_unref(cname_key); + } +} + +static void withdraw_entry(AvahiServer *s, AvahiEntry *e) { + assert(s); + assert(e); + + /* Withdraw the specified entry, and if is part of an entry group, + * put that into COLLISION state */ + + if (e->dead) + return; + + if (e->group) { + AvahiEntry *k; + + for (k = e->group->entries; k; k = k->by_group_next) + if (!k->dead) { + avahi_goodbye_entry(s, k, 0, 1); + k->dead = 1; + } + + e->group->n_probing = 0; + + avahi_s_entry_group_change_state(e->group, AVAHI_ENTRY_GROUP_COLLISION); + } else { + avahi_goodbye_entry(s, e, 0, 1); + e->dead = 1; + } + + s->need_entry_cleanup = 1; +} + +static void withdraw_rrset(AvahiServer *s, AvahiKey *key) { + AvahiEntry *e; + + assert(s); + assert(key); + + /* Withdraw an entry RRSset */ + + for (e = avahi_hashmap_lookup(s->entries_by_key, key); e; e = e->by_key_next) + withdraw_entry(s, e); +} + +static void incoming_probe(AvahiServer *s, AvahiRecord *record, AvahiInterface *i) { + AvahiEntry *e, *n; + int ours = 0, won = 0, lost = 0; + + assert(s); + assert(record); + assert(i); + + /* Handle incoming probes and check if they conflict our own probes */ + + for (e = avahi_hashmap_lookup(s->entries_by_key, record->key); e; e = n) { + int cmp; + n = e->by_key_next; + + if (e->dead) + continue; + + if ((cmp = avahi_record_lexicographical_compare(e->record, record)) == 0) { + ours = 1; + break; + } else { + + if (avahi_entry_is_probing(s, e, i)) { + if (cmp > 0) + won = 1; + else /* cmp < 0 */ + lost = 1; + } + } + } + + if (!ours) { + char *t = avahi_record_to_string(record); + + if (won) + avahi_log_debug("Recieved conflicting probe [%s]. Local host won.", t); + else if (lost) { + avahi_log_debug("Recieved conflicting probe [%s]. Local host lost. Withdrawing.", t); + withdraw_rrset(s, record->key); + } + + avahi_free(t); + } +} + +static int handle_conflict(AvahiServer *s, AvahiInterface *i, AvahiRecord *record, int unique) { + int valid = 1, ours = 0, conflict = 0, withdraw_immediately = 0; + AvahiEntry *e, *n, *conflicting_entry = NULL; + + assert(s); + assert(i); + assert(record); + + /* Check whether an incoming record conflicts with one of our own */ + + for (e = avahi_hashmap_lookup(s->entries_by_key, record->key); e; e = n) { + n = e->by_key_next; + + if (e->dead) + continue; + + /* Check if the incoming is a goodbye record */ + if (avahi_record_is_goodbye(record)) { + + if (avahi_record_equal_no_ttl(e->record, record)) { + char *t; + + /* Refresh */ + t = avahi_record_to_string(record); + avahi_log_debug("Recieved goodbye record for one of our records [%s]. Refreshing.", t); + avahi_server_prepare_matching_responses(s, i, e->record->key, 0); + + valid = 0; + avahi_free(t); + break; + } + + /* If the goodybe packet doesn't match one of our own RRs, we simply ignore it. */ + continue; + } + + if (!(e->flags & AVAHI_PUBLISH_UNIQUE) && !unique) + continue; + + /* Either our entry or the other is intended to be unique, so let's check */ + + if (avahi_record_equal_no_ttl(e->record, record)) { + ours = 1; /* We have an identical record, so this is no conflict */ + + /* Check wheter there is a TTL conflict */ + if (record->ttl <= e->record->ttl/2 && + avahi_entry_is_registered(s, e, i)) { + char *t; + /* Refresh */ + t = avahi_record_to_string(record); + + avahi_log_debug("Recieved record with bad TTL [%s]. Refreshing.", t); + avahi_server_prepare_matching_responses(s, i, e->record->key, 0); + valid = 0; + + avahi_free(t); + } + + /* There's no need to check the other entries of this RRset */ + break; + + } else { + + if (avahi_entry_is_registered(s, e, i)) { + + /* A conflict => we have to return to probe mode */ + conflict = 1; + conflicting_entry = e; + + } else if (avahi_entry_is_probing(s, e, i)) { + + /* We are currently registering a matching record, but + * someone else already claimed it, so let's + * withdraw */ + conflict = 1; + withdraw_immediately = 1; + } + } + } + + if (!ours && conflict) { + char *t; + + valid = 0; + + t = avahi_record_to_string(record); + + if (withdraw_immediately) { + avahi_log_debug("Recieved conflicting record [%s] with local record to be. Withdrawing.", t); + withdraw_rrset(s, record->key); + } else { + assert(conflicting_entry); + avahi_log_debug("Recieved conflicting record [%s]. Resetting our record.", t); + avahi_entry_return_to_initial_state(s, conflicting_entry, i); + + /* Local unique records are returned to probing + * state. Local shared records are reannounced. */ + } + + avahi_free(t); + } + + return valid; +} + +static void append_aux_callback(AvahiServer *s, AvahiRecord *r, int flush_cache, void* userdata) { + int *unicast_response = userdata; + + assert(s); + assert(r); + assert(unicast_response); + + avahi_record_list_push(s->record_list, r, flush_cache, *unicast_response, 1); +} + +static void append_aux_records_to_list(AvahiServer *s, AvahiInterface *i, AvahiRecord *r, int unicast_response) { + assert(s); + assert(r); + + avahi_server_enumerate_aux_records(s, i, r, append_aux_callback, &unicast_response); +} + +void avahi_server_generate_response(AvahiServer *s, AvahiInterface *i, AvahiDnsPacket *p, const AvahiAddress *a, uint16_t port, int legacy_unicast, int immediately) { + + assert(s); + assert(i); + assert(!legacy_unicast || (a && port > 0 && p)); + + if (legacy_unicast) { + AvahiDnsPacket *reply; + AvahiRecord *r; + + if (!(reply = avahi_dns_packet_new_reply(p, 512 /* unicast DNS maximum packet size is 512 */ , 1, 1))) + return; /* OOM */ + + while ((r = avahi_record_list_next(s->record_list, NULL, NULL, NULL))) { + + append_aux_records_to_list(s, i, r, 0); + + if (avahi_dns_packet_append_record(reply, r, 0, 10)) + avahi_dns_packet_inc_field(reply, AVAHI_DNS_FIELD_ANCOUNT); + else { + char *t = avahi_record_to_string(r); + avahi_log_warn("Record [%s] not fitting in legacy unicast packet, dropping.", t); + avahi_free(t); + } + + avahi_record_unref(r); + } + + if (avahi_dns_packet_get_field(reply, AVAHI_DNS_FIELD_ANCOUNT) != 0) + avahi_interface_send_packet_unicast(i, reply, a, port); + + avahi_dns_packet_free(reply); + + } else { + int unicast_response, flush_cache, auxiliary; + AvahiDnsPacket *reply = NULL; + AvahiRecord *r; + + /* In case the query packet was truncated never respond + immediately, because known answer suppression records might be + contained in later packets */ + int tc = p && !!(avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_TC); + + while ((r = avahi_record_list_next(s->record_list, &flush_cache, &unicast_response, &auxiliary))) { + + int im = immediately; + + /* Only send the response immediately if it contains a + * unique entry AND it is not in reply to a truncated + * packet AND it is not an auxiliary record AND all other + * responses for this record are unique too. */ + + if (flush_cache && !tc && !auxiliary && avahi_record_list_all_flush_cache(s->record_list)) + im = 1; + + if (!avahi_interface_post_response(i, r, flush_cache, a, im) && unicast_response) { + + /* Due to some reasons the record has not been scheduled. + * The client requested an unicast response in that + * case. Therefore we prepare such a response */ + + append_aux_records_to_list(s, i, r, unicast_response); + + for (;;) { + + if (!reply) { + assert(p); + + if (!(reply = avahi_dns_packet_new_reply(p, i->hardware->mtu, 0, 0))) + break; /* OOM */ + } + + if (avahi_dns_packet_append_record(reply, r, flush_cache, 0)) { + + /* Appending this record succeeded, so incremeant + * the specific header field, and return to the caller */ + + avahi_dns_packet_inc_field(reply, AVAHI_DNS_FIELD_ANCOUNT); + + break; + } + + if (avahi_dns_packet_get_field(reply, AVAHI_DNS_FIELD_ANCOUNT) == 0) { + size_t size; + + /* The record is too large for one packet, so create a larger packet */ + + avahi_dns_packet_free(reply); + size = avahi_record_get_estimate_size(r) + AVAHI_DNS_PACKET_HEADER_SIZE; + if (size > AVAHI_DNS_PACKET_SIZE_MAX) + size = AVAHI_DNS_PACKET_SIZE_MAX; + + if (!(reply = avahi_dns_packet_new_reply(p, size, 0, 1))) + break; /* OOM */ + + if (!avahi_dns_packet_append_record(reply, r, flush_cache, 0)) { + char *t; + avahi_dns_packet_free(reply); + t = avahi_record_to_string(r); + avahi_log_warn("Record [%s] too large, doesn't fit in any packet!", t); + avahi_free(t); + break; + } else + avahi_dns_packet_inc_field(reply, AVAHI_DNS_FIELD_ANCOUNT); + } + + /* Appending the record didn't succeeed, so let's send this packet, and create a new one */ + avahi_interface_send_packet_unicast(i, reply, a, port); + avahi_dns_packet_free(reply); + reply = NULL; + } + } + + avahi_record_unref(r); + } + + if (reply) { + if (avahi_dns_packet_get_field(reply, AVAHI_DNS_FIELD_ANCOUNT) != 0) + avahi_interface_send_packet_unicast(i, reply, a, port); + avahi_dns_packet_free(reply); + } + } + + avahi_record_list_flush(s->record_list); +} + +static void reflect_response(AvahiServer *s, AvahiInterface *i, AvahiRecord *r, int flush_cache) { + AvahiInterface *j; + + assert(s); + assert(i); + assert(r); + + if (!s->config.enable_reflector) + return; + + for (j = s->monitor->interfaces; j; j = j->interface_next) + if (j != i && (s->config.reflect_ipv || j->protocol == i->protocol)) + avahi_interface_post_response(j, r, flush_cache, NULL, 1); +} + +static void* reflect_cache_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, void* userdata) { + AvahiServer *s = userdata; + + assert(c); + assert(pattern); + assert(e); + assert(s); + + avahi_record_list_push(s->record_list, e->record, e->cache_flush, 0, 0); + return NULL; +} + +static void reflect_query(AvahiServer *s, AvahiInterface *i, AvahiKey *k) { + AvahiInterface *j; + + assert(s); + assert(i); + assert(k); + + if (!s->config.enable_reflector) + return; + + for (j = s->monitor->interfaces; j; j = j->interface_next) + if (j != i && (s->config.reflect_ipv || j->protocol == i->protocol)) { + /* Post the query to other networks */ + avahi_interface_post_query(j, k, 1, NULL); + + /* Reply from caches of other network. This is needed to + * "work around" known answer suppression. */ + + avahi_cache_walk(j->cache, k, reflect_cache_walk_callback, s); + } +} + +static void reflect_probe(AvahiServer *s, AvahiInterface *i, AvahiRecord *r) { + AvahiInterface *j; + + assert(s); + assert(i); + assert(r); + + if (!s->config.enable_reflector) + return; + + for (j = s->monitor->interfaces; j; j = j->interface_next) + if (j != i && (s->config.reflect_ipv || j->protocol == i->protocol)) + avahi_interface_post_probe(j, r, 1); +} + +static void handle_query_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, uint16_t port, int legacy_unicast, int from_local_iface) { + size_t n; + int is_probe; + + assert(s); + assert(p); + assert(i); + assert(a); + + assert(avahi_record_list_is_empty(s->record_list)); + + is_probe = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) > 0; + + /* Handle the questions */ + for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); n > 0; n --) { + AvahiKey *key; + int unicast_response = 0; + + if (!(key = avahi_dns_packet_consume_key(p, &unicast_response))) { + avahi_log_warn(__FILE__": Packet too short or invalid while reading question key. (Maybe an UTF8 problem?)"); + goto fail; + } + + if (!legacy_unicast && !from_local_iface) { + reflect_query(s, i, key); + avahi_cache_start_poof(i->cache, key, a); + } + + if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0 && + !(avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & AVAHI_DNS_FLAG_TC)) + /* Allow our own queries to be suppressed by incoming + * queries only when they do not include known answers */ + avahi_query_scheduler_incoming(i->query_scheduler, key); + + avahi_server_prepare_matching_responses(s, i, key, unicast_response); + avahi_key_unref(key); + } + + if (!legacy_unicast) { + + /* Known Answer Suppression */ + for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT); n > 0; n --) { + AvahiRecord *record; + int unique = 0; + + if (!(record = avahi_dns_packet_consume_record(p, &unique))) { + avahi_log_warn(__FILE__": Packet too short or invalid while reading known answer record. (Maybe an UTF8 problem?)"); + goto fail; + } + + if (handle_conflict(s, i, record, unique)) { + avahi_response_scheduler_suppress(i->response_scheduler, record, a); + avahi_record_list_drop(s->record_list, record); + avahi_cache_stop_poof(i->cache, record, a); + } + + avahi_record_unref(record); + } + + /* Probe record */ + for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT); n > 0; n --) { + AvahiRecord *record; + int unique = 0; + + if (!(record = avahi_dns_packet_consume_record(p, &unique))) { + avahi_log_warn(__FILE__": Packet too short or invalid while reading probe record. (Maybe an UTF8 problem?)"); + goto fail; + } + + if (!avahi_key_is_pattern(record->key)) { + if (!from_local_iface) + reflect_probe(s, i, record); + incoming_probe(s, record, i); + } + + avahi_record_unref(record); + } + } + + if (!avahi_record_list_is_empty(s->record_list)) + avahi_server_generate_response(s, i, p, a, port, legacy_unicast, is_probe); + + return; + +fail: + avahi_record_list_flush(s->record_list); +} + +static void handle_response_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, int from_local_iface) { + unsigned n; + + assert(s); + assert(p); + assert(i); + assert(a); + + for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) + + avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT); n > 0; n--) { + AvahiRecord *record; + int cache_flush = 0; +/* char *txt; */ + + if (!(record = avahi_dns_packet_consume_record(p, &cache_flush))) { + avahi_log_warn(__FILE__": Packet too short or invalid while reading response record. (Maybe an UTF8 problem?)"); + break; + } + + if (!avahi_key_is_pattern(record->key)) { + + if (handle_conflict(s, i, record, cache_flush)) { + if (!from_local_iface) + reflect_response(s, i, record, cache_flush); + avahi_cache_update(i->cache, record, cache_flush, a); + avahi_response_scheduler_incoming(i->response_scheduler, record, cache_flush); + } + } + + avahi_record_unref(record); + } + + /* If the incoming response contained a conflicting record, some + records have been scheduling for sending. We need to flush them + here. */ + if (!avahi_record_list_is_empty(s->record_list)) + avahi_server_generate_response(s, i, NULL, NULL, 0, 0, 1); +} + +static AvahiLegacyUnicastReflectSlot* allocate_slot(AvahiServer *s) { + unsigned n, idx = (unsigned) -1; + AvahiLegacyUnicastReflectSlot *slot; + + assert(s); + + if (!s->legacy_unicast_reflect_slots) + s->legacy_unicast_reflect_slots = avahi_new0(AvahiLegacyUnicastReflectSlot*, AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX); + + for (n = 0; n < AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX; n++, s->legacy_unicast_reflect_id++) { + idx = s->legacy_unicast_reflect_id % AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX; + + if (!s->legacy_unicast_reflect_slots[idx]) + break; + } + + if (idx == (unsigned) -1 || s->legacy_unicast_reflect_slots[idx]) + return NULL; + + if (!(slot = avahi_new(AvahiLegacyUnicastReflectSlot, 1))) + return NULL; /* OOM */ + + s->legacy_unicast_reflect_slots[idx] = slot; + slot->id = s->legacy_unicast_reflect_id++; + slot->server = s; + + return slot; +} + +static void deallocate_slot(AvahiServer *s, AvahiLegacyUnicastReflectSlot *slot) { + unsigned idx; + + assert(s); + assert(slot); + + idx = slot->id % AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX; + + assert(s->legacy_unicast_reflect_slots[idx] == slot); + + avahi_time_event_free(slot->time_event); + + avahi_free(slot); + s->legacy_unicast_reflect_slots[idx] = NULL; +} + +static void free_slots(AvahiServer *s) { + unsigned idx; + assert(s); + + if (!s->legacy_unicast_reflect_slots) + return; + + for (idx = 0; idx < AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX; idx ++) + if (s->legacy_unicast_reflect_slots[idx]) + deallocate_slot(s, s->legacy_unicast_reflect_slots[idx]); + + avahi_free(s->legacy_unicast_reflect_slots); + s->legacy_unicast_reflect_slots = NULL; +} + +static AvahiLegacyUnicastReflectSlot* find_slot(AvahiServer *s, uint16_t id) { + unsigned idx; + + assert(s); + + if (!s->legacy_unicast_reflect_slots) + return NULL; + + idx = id % AVAHI_LEGACY_UNICAST_REFLECT_SLOTS_MAX; + + if (!s->legacy_unicast_reflect_slots[idx] || s->legacy_unicast_reflect_slots[idx]->id != id) + return NULL; + + return s->legacy_unicast_reflect_slots[idx]; +} + +static void legacy_unicast_reflect_slot_timeout(AvahiTimeEvent *e, void *userdata) { + AvahiLegacyUnicastReflectSlot *slot = userdata; + + assert(e); + assert(slot); + assert(slot->time_event == e); + + deallocate_slot(slot->server, slot); +} + +static void reflect_legacy_unicast_query_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, uint16_t port) { + AvahiLegacyUnicastReflectSlot *slot; + AvahiInterface *j; + + assert(s); + assert(p); + assert(i); + assert(a); + assert(port > 0); + assert(i->protocol == a->proto); + + if (!s->config.enable_reflector) + return; + + /* Reflecting legacy unicast queries is a little more complicated + than reflecting normal queries, since we must route the + responses back to the right client. Therefore we must store + some information for finding the right client contact data for + response packets. In contrast to normal queries legacy + unicast query and response packets are reflected untouched and + are not reassembled into larger packets */ + + if (!(slot = allocate_slot(s))) { + /* No slot available, we drop this legacy unicast query */ + avahi_log_warn("No slot available for legacy unicast reflection, dropping query packet."); + return; + } + + slot->original_id = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID); + slot->address = *a; + slot->port = port; + slot->interface = i->hardware->index; + + avahi_elapse_time(&slot->elapse_time, 2000, 0); + slot->time_event = avahi_time_event_new(s->time_event_queue, &slot->elapse_time, legacy_unicast_reflect_slot_timeout, slot); + + /* Patch the packet with our new locally generatet id */ + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->id); + + for (j = s->monitor->interfaces; j; j = j->interface_next) + if (avahi_interface_is_relevant(j) && + j != i && + (s->config.reflect_ipv || j->protocol == i->protocol)) { + + if (j->protocol == AVAHI_PROTO_INET && s->fd_legacy_unicast_ipv4 >= 0) { + avahi_send_dns_packet_ipv4(s->fd_legacy_unicast_ipv4, j->hardware->index, p, NULL, NULL, 0); + } else if (j->protocol == AVAHI_PROTO_INET6 && s->fd_legacy_unicast_ipv6 >= 0) + avahi_send_dns_packet_ipv6(s->fd_legacy_unicast_ipv6, j->hardware->index, p, NULL, NULL, 0); + } + + /* Reset the id */ + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->original_id); +} + +static int originates_from_local_legacy_unicast_socket(AvahiServer *s, const AvahiAddress *address, uint16_t port) { + assert(s); + assert(address); + assert(port > 0); + + if (!s->config.enable_reflector) + return 0; + + if (!avahi_address_is_local(s->monitor, address)) + return 0; + + if (address->proto == AVAHI_PROTO_INET && s->fd_legacy_unicast_ipv4 >= 0) { + struct sockaddr_in lsa; + socklen_t l = sizeof(lsa); + + if (getsockname(s->fd_legacy_unicast_ipv4, (struct sockaddr*) &lsa, &l) != 0) + avahi_log_warn("getsockname(): %s", strerror(errno)); + else + return lsa.sin_port == port; + + } + + if (address->proto == AVAHI_PROTO_INET6 && s->fd_legacy_unicast_ipv6 >= 0) { + struct sockaddr_in6 lsa; + socklen_t l = sizeof(lsa); + + if (getsockname(s->fd_legacy_unicast_ipv6, (struct sockaddr*) &lsa, &l) != 0) + avahi_log_warn("getsockname(): %s", strerror(errno)); + else + return lsa.sin6_port == port; + } + + return 0; +} + +static int is_mdns_mcast_address(const AvahiAddress *a) { + AvahiAddress b; + assert(a); + + avahi_address_parse(a->proto == AVAHI_PROTO_INET ? AVAHI_IPV4_MCAST_GROUP : AVAHI_IPV6_MCAST_GROUP, a->proto, &b); + return avahi_address_cmp(a, &b) == 0; +} + +static int originates_from_local_iface(AvahiServer *s, AvahiIfIndex iface, const AvahiAddress *a, uint16_t port) { + assert(s); + assert(iface != AVAHI_IF_UNSPEC); + assert(a); + + /* If it isn't the MDNS port it can't be generated by us */ + if (port != AVAHI_MDNS_PORT) + return 0; + + return avahi_interface_has_address(s->monitor, iface, a); +} + +static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, const AvahiAddress *src_address, uint16_t port, const AvahiAddress *dst_address, AvahiIfIndex iface, int ttl) { + AvahiInterface *i; + int from_local_iface = 0; + + assert(s); + assert(p); + assert(src_address); + assert(dst_address); + assert(iface > 0); + assert(src_address->proto == dst_address->proto); + + if (!(i = avahi_interface_monitor_get_interface(s->monitor, iface, src_address->proto)) || + !avahi_interface_is_relevant(i)) { + avahi_log_warn("Recieved packet from invalid interface."); + return; + } + + if (avahi_address_is_ipv4_in_ipv6(src_address)) + /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */ + return; + + if (originates_from_local_legacy_unicast_socket(s, src_address, port)) + /* This originates from our local reflector, so let's ignore it */ + return; + + /* We don't want to reflect local traffic, so we check if this packet is generated locally. */ + if (s->config.enable_reflector) + from_local_iface = originates_from_local_iface(s, iface, src_address, port); + + if (avahi_dns_packet_check_valid_multicast(p) < 0) { + avahi_log_warn("Recieved invalid packet."); + return; + } + + if (avahi_dns_packet_is_query(p)) { + int legacy_unicast = 0; + + if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT) != 0) { + avahi_log_warn("Invalid query packet."); + return; + } + + if (port != AVAHI_MDNS_PORT) { + /* Legacy Unicast */ + + if ((avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) != 0 || + avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0)) { + avahi_log_warn("Invalid legacy unicast query packet."); + return; + } + + legacy_unicast = 1; + } + + if (legacy_unicast) + reflect_legacy_unicast_query_packet(s, p, i, src_address, port); + + handle_query_packet(s, p, i, src_address, port, legacy_unicast, from_local_iface); + + } else { + if (port != AVAHI_MDNS_PORT) { + avahi_log_warn("Recieved repsonse with invalid source port %u on interface '%s.%i'", port, i->hardware->name, i->protocol); + return; + } + + if (ttl != 255 && s->config.check_response_ttl) { + avahi_log_warn("Recieved response with invalid TTL %u on interface '%s.%i'.", ttl, i->hardware->name, i->protocol); + return; + } + + if (!is_mdns_mcast_address(dst_address) && + !avahi_interface_address_on_link(i, src_address)) { + avahi_log_warn("Received non-local response on interface '%s.%i'.", i->hardware->name, i->protocol); + return; + } + + if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT) != 0 || + avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0 || + avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0) { + avahi_log_warn("Invalid response packet."); + return; + } + + handle_response_packet(s, p, i, src_address, from_local_iface); + } +} + +static void dispatch_legacy_unicast_packet(AvahiServer *s, AvahiDnsPacket *p) { + AvahiInterface *j; + AvahiLegacyUnicastReflectSlot *slot; + + assert(s); + assert(p); + + if (avahi_dns_packet_check_valid(p) < 0 || avahi_dns_packet_is_query(p)) { + avahi_log_warn("Recieved invalid packet."); + return; + } + + if (!(slot = find_slot(s, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID)))) { + avahi_log_warn("Recieved legacy unicast response with unknown id"); + return; + } + + if (!(j = avahi_interface_monitor_get_interface(s->monitor, slot->interface, slot->address.proto)) || + !avahi_interface_is_relevant(j)) + return; + + /* Patch the original ID into this response */ + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->original_id); + + /* Forward the response to the correct client */ + avahi_interface_send_packet_unicast(j, p, &slot->address, slot->port); + + /* Undo changes to packet */ + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->id); +} + +static void cleanup_dead(AvahiServer *s) { + assert(s); + + avahi_cleanup_dead_entries(s); + avahi_browser_cleanup(s); +} + +static void mcast_socket_event(AvahiWatch *w, int fd, AvahiWatchEvent events, void *userdata) { + AvahiServer *s = userdata; + AvahiAddress dest, src; + AvahiDnsPacket *p = NULL; + AvahiIfIndex iface; + uint16_t port; + uint8_t ttl; + + assert(w); + assert(fd >= 0); + assert(events & AVAHI_WATCH_IN); + + if (fd == s->fd_ipv4) { + dest.proto = src.proto = AVAHI_PROTO_INET; + p = avahi_recv_dns_packet_ipv4(s->fd_ipv4, &src.data.ipv4, &port, &dest.data.ipv4, &iface, &ttl); + } else { + assert(fd == s->fd_ipv6); + dest.proto = src.proto = AVAHI_PROTO_INET6; + p = avahi_recv_dns_packet_ipv6(s->fd_ipv6, &src.data.ipv6, &port, &dest.data.ipv6, &iface, &ttl); + } + + if (p) { + if (iface == AVAHI_IF_UNSPEC) + iface = avahi_find_interface_for_address(s->monitor, &dest); + + if (iface != AVAHI_IF_UNSPEC) + dispatch_packet(s, p, &src, port, &dest, iface, ttl); + else + avahi_log_error("Incoming packet recieved on address that isn't local."); + + avahi_dns_packet_free(p); + + cleanup_dead(s); + } +} + +static void legacy_unicast_socket_event(AvahiWatch *w, int fd, AvahiWatchEvent events, void *userdata) { + AvahiServer *s = userdata; + AvahiDnsPacket *p = NULL; + + assert(w); + assert(fd >= 0); + assert(events & AVAHI_WATCH_IN); + + if (fd == s->fd_legacy_unicast_ipv4) + p = avahi_recv_dns_packet_ipv4(s->fd_legacy_unicast_ipv4, NULL, NULL, NULL, NULL, NULL); + else { + assert(fd == s->fd_legacy_unicast_ipv6); + p = avahi_recv_dns_packet_ipv6(s->fd_legacy_unicast_ipv6, NULL, NULL, NULL, NULL, NULL); + } + + if (p) { + dispatch_legacy_unicast_packet(s, p); + avahi_dns_packet_free(p); + + cleanup_dead(s); + } +} + +static void server_set_state(AvahiServer *s, AvahiServerState state) { + assert(s); + + if (s->state == state) + return; + + s->state = state; + + avahi_interface_monitor_update_rrs(s->monitor, 0); + + if (s->callback) + s->callback(s, state, s->userdata); +} + +static void withdraw_host_rrs(AvahiServer *s) { + assert(s); + + if (s->hinfo_entry_group) + avahi_s_entry_group_reset(s->hinfo_entry_group); + + if (s->browse_domain_entry_group) + avahi_s_entry_group_reset(s->browse_domain_entry_group); + + avahi_interface_monitor_update_rrs(s->monitor, 1); + s->n_host_rr_pending = 0; +} + +void avahi_server_decrease_host_rr_pending(AvahiServer *s) { + assert(s); + + assert(s->n_host_rr_pending > 0); + + if (--s->n_host_rr_pending == 0) + server_set_state(s, AVAHI_SERVER_RUNNING); +} + +void avahi_host_rr_entry_group_callback(AvahiServer *s, AvahiSEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) { + assert(s); + assert(g); + + if (state == AVAHI_ENTRY_GROUP_REGISTERING && + s->state == AVAHI_SERVER_REGISTERING) + s->n_host_rr_pending ++; + + else if (state == AVAHI_ENTRY_GROUP_COLLISION && + (s->state == AVAHI_SERVER_REGISTERING || s->state == AVAHI_SERVER_RUNNING)) { + withdraw_host_rrs(s); + server_set_state(s, AVAHI_SERVER_COLLISION); + + } else if (state == AVAHI_ENTRY_GROUP_ESTABLISHED && + s->state == AVAHI_SERVER_REGISTERING) + avahi_server_decrease_host_rr_pending(s); +} + +static void register_hinfo(AvahiServer *s) { + struct utsname utsname; + AvahiRecord *r; + + assert(s); + + if (!s->config.publish_hinfo) + return; + + if (s->hinfo_entry_group) + assert(avahi_s_entry_group_is_empty(s->hinfo_entry_group)); + else + s->hinfo_entry_group = avahi_s_entry_group_new(s, avahi_host_rr_entry_group_callback, NULL); + + if (!s->hinfo_entry_group) { + avahi_log_warn("Failed to create HINFO entry group: %s", avahi_strerror(s->error)); + return; + } + + /* Fill in HINFO rr */ + if ((r = avahi_record_new_full(s->host_name_fqdn, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_HINFO, AVAHI_DEFAULT_TTL_HOST_NAME))) { + + if (uname(&utsname) < 0) + avahi_log_warn("uname() failed: %s\n", avahi_strerror(errno)); + else { + + r->data.hinfo.cpu = avahi_strdup(avahi_strup(utsname.machine)); + r->data.hinfo.os = avahi_strdup(avahi_strup(utsname.sysname)); + + avahi_log_info("Registering HINFO record with values '%s'/'%s'.", r->data.hinfo.cpu, r->data.hinfo.os); + + if (avahi_server_add(s, s->hinfo_entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_UNIQUE, r) < 0) { + avahi_log_warn("Failed to add HINFO RR: %s", avahi_strerror(s->error)); + return; + } + } + + avahi_record_unref(r); + } + + if (avahi_s_entry_group_commit(s->hinfo_entry_group) < 0) + avahi_log_warn("Failed to commit HINFO entry group: %s", avahi_strerror(s->error)); + +} + +static void register_localhost(AvahiServer *s) { + AvahiAddress a; + assert(s); + + /* Add localhost entries */ + avahi_address_parse("127.0.0.1", AVAHI_PROTO_INET, &a); + avahi_server_add_address(s, NULL, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_NO_PROBE|AVAHI_PUBLISH_NO_ANNOUNCE, "localhost", &a); + + avahi_address_parse("::1", AVAHI_PROTO_INET6, &a); + avahi_server_add_address(s, NULL, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_NO_PROBE|AVAHI_PUBLISH_NO_ANNOUNCE, "ip6-localhost", &a); +} + +static void register_browse_domain(AvahiServer *s) { + assert(s); + + if (!s->config.publish_domain) + return; + + if (avahi_domain_equal(s->domain_name, "local")) + return; + + if (s->browse_domain_entry_group) + assert(avahi_s_entry_group_is_empty(s->browse_domain_entry_group)); + else + s->browse_domain_entry_group = avahi_s_entry_group_new(s, NULL, NULL); + + if (!s->browse_domain_entry_group) { + avahi_log_warn("Failed to create browse domain entry group: %s", avahi_strerror(s->error)); + return; + } + + if (avahi_server_add_ptr(s, s->browse_domain_entry_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, AVAHI_DEFAULT_TTL, "b._dns-sd._udp.local", s->domain_name) < 0) { + avahi_log_warn("Failed to add browse domain RR: %s", avahi_strerror(s->error)); + return; + } + + if (avahi_s_entry_group_commit(s->browse_domain_entry_group) < 0) + avahi_log_warn("Failed to commit browse domain entry group: %s", avahi_strerror(s->error)); +} + +static void register_stuff(AvahiServer *s) { + assert(s); + + server_set_state(s, AVAHI_SERVER_REGISTERING); + s->n_host_rr_pending ++; /** Make sure that the state isn't changed tp AVAHI_SERVER_RUNNING too early */ + + register_hinfo(s); + register_browse_domain(s); + avahi_interface_monitor_update_rrs(s->monitor, 0); + + s->n_host_rr_pending --; + + if (s->n_host_rr_pending == 0) + server_set_state(s, AVAHI_SERVER_RUNNING); +} + +static void update_fqdn(AvahiServer *s) { + char *n; + + assert(s); + assert(s->host_name); + assert(s->domain_name); + + if (!(n = avahi_strdup_printf("%s.%s", s->host_name, s->domain_name))) + return; /* OOM */ + + avahi_free(s->host_name_fqdn); + s->host_name_fqdn = n; +} + +int avahi_server_set_host_name(AvahiServer *s, const char *host_name) { + char *hn = NULL; + assert(s); + + AVAHI_CHECK_VALIDITY(s, !host_name || avahi_is_valid_host_name(host_name), AVAHI_ERR_INVALID_HOST_NAME); + + if (!host_name) { + hn = avahi_get_host_name_strdup(); + hn[strcspn(hn, ".")] = 0; + host_name = hn; + } + + if (avahi_domain_equal(s->host_name, host_name) && s->state != AVAHI_SERVER_COLLISION) { + avahi_free(hn); + return avahi_server_set_errno(s, AVAHI_ERR_NO_CHANGE); + } + + withdraw_host_rrs(s); + + avahi_free(s->host_name); + s->host_name = hn ? hn : avahi_strdup(host_name); + + update_fqdn(s); + + register_stuff(s); + return AVAHI_OK; +} + +int avahi_server_set_domain_name(AvahiServer *s, const char *domain_name) { + char *dn = NULL; + assert(s); + + AVAHI_CHECK_VALIDITY(s, !domain_name || avahi_is_valid_domain_name(domain_name), AVAHI_ERR_INVALID_DOMAIN_NAME); + + if (!domain_name) { + dn = avahi_strdup("local"); + domain_name = dn; + } + + if (avahi_domain_equal(s->domain_name, domain_name)) { + avahi_free(dn); + return avahi_server_set_errno(s, AVAHI_ERR_NO_CHANGE); + } + + withdraw_host_rrs(s); + + avahi_free(s->domain_name); + s->domain_name = avahi_normalize_name_strdup(domain_name); + update_fqdn(s); + + register_stuff(s); + + avahi_free(dn); + return AVAHI_OK; +} + +static int valid_server_config(const AvahiServerConfig *sc) { + + if (sc->host_name && !avahi_is_valid_host_name(sc->host_name)) + return AVAHI_ERR_INVALID_HOST_NAME; + + if (sc->domain_name && !avahi_is_valid_domain_name(sc->domain_name)) + return AVAHI_ERR_INVALID_DOMAIN_NAME; + + return AVAHI_OK; +} + +static int setup_sockets(AvahiServer *s) { + assert(s); + + s->fd_ipv4 = s->config.use_ipv4 ? avahi_open_socket_ipv4(s->config.disallow_other_stacks) : -1; + s->fd_ipv6 = s->config.use_ipv6 ? avahi_open_socket_ipv6(s->config.disallow_other_stacks) : -1; + + if (s->fd_ipv6 < 0 && s->fd_ipv4 < 0) + return AVAHI_ERR_NO_NETWORK; + + if (s->fd_ipv4 < 0 && s->config.use_ipv4) + avahi_log_notice("Failed to create IPv4 socket, proceeding in IPv6 only mode"); + else if (s->fd_ipv6 < 0 && s->config.use_ipv6) + avahi_log_notice("Failed to create IPv6 socket, proceeding in IPv4 only mode"); + + s->fd_legacy_unicast_ipv4 = s->fd_ipv4 >= 0 && s->config.enable_reflector ? avahi_open_unicast_socket_ipv4() : -1; + s->fd_legacy_unicast_ipv6 = s->fd_ipv6 >= 0 && s->config.enable_reflector ? avahi_open_unicast_socket_ipv6() : -1; + + s->watch_ipv4 = + s->watch_ipv6 = + s->watch_legacy_unicast_ipv4 = + s->watch_legacy_unicast_ipv6 = NULL; + + if (s->fd_ipv4 >= 0) + s->watch_ipv4 = s->poll_api->watch_new(s->poll_api, s->fd_ipv4, AVAHI_WATCH_IN, mcast_socket_event, s); + if (s->fd_ipv6 >= 0) + s->watch_ipv6 = s->poll_api->watch_new(s->poll_api, s->fd_ipv6, AVAHI_WATCH_IN, mcast_socket_event, s); + + if (s->fd_legacy_unicast_ipv4 >= 0) + s->watch_legacy_unicast_ipv4 = s->poll_api->watch_new(s->poll_api, s->fd_legacy_unicast_ipv4, AVAHI_WATCH_IN, legacy_unicast_socket_event, s); + if (s->fd_legacy_unicast_ipv6 >= 0) + s->watch_legacy_unicast_ipv6 = s->poll_api->watch_new(s->poll_api, s->fd_legacy_unicast_ipv6, AVAHI_WATCH_IN, legacy_unicast_socket_event, s); + + return 0; +} + +AvahiServer *avahi_server_new(const AvahiPoll *poll_api, const AvahiServerConfig *sc, AvahiServerCallback callback, void* userdata, int *error) { + AvahiServer *s; + int e; + + if (sc && (e = valid_server_config(sc)) < 0) { + if (error) + *error = e; + return NULL; + } + + if (!(s = avahi_new(AvahiServer, 1))) { + if (error) + *error = AVAHI_ERR_NO_MEMORY; + + return NULL; + } + + s->poll_api = poll_api; + + if (sc) + avahi_server_config_copy(&s->config, sc); + else + avahi_server_config_init(&s->config); + + if ((e = setup_sockets(s)) < 0) { + if (error) + *error = e; + + avahi_server_config_free(&s->config); + avahi_free(s); + + return NULL; + } + + s->n_host_rr_pending = 0; + s->need_entry_cleanup = 0; + s->need_group_cleanup = 0; + s->need_browser_cleanup = 0; + s->hinfo_entry_group = NULL; + s->browse_domain_entry_group = NULL; + s->error = AVAHI_OK; + s->state = AVAHI_SERVER_INVALID; + + s->callback = callback; + s->userdata = userdata; + + s->time_event_queue = avahi_time_event_queue_new(poll_api); + + s->entries_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL); + AVAHI_LLIST_HEAD_INIT(AvahiEntry, s->entries); + AVAHI_LLIST_HEAD_INIT(AvahiGroup, s->groups); + + s->record_browser_hashmap = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, NULL, NULL); + AVAHI_LLIST_HEAD_INIT(AvahiSRecordBrowser, s->record_browsers); + AVAHI_LLIST_HEAD_INIT(AvahiSHostNameResolver, s->host_name_resolvers); + AVAHI_LLIST_HEAD_INIT(AvahiSAddressResolver, s->address_resolvers); + AVAHI_LLIST_HEAD_INIT(AvahiSDomainBrowser, s->domain_browsers); + AVAHI_LLIST_HEAD_INIT(AvahiSServiceTypeBrowser, s->service_type_browsers); + AVAHI_LLIST_HEAD_INIT(AvahiSServiceBrowser, s->service_browsers); + AVAHI_LLIST_HEAD_INIT(AvahiSServiceResolver, s->service_resolvers); + AVAHI_LLIST_HEAD_INIT(AvahiSDNSServerBrowser, s->dns_server_browsers); + + s->legacy_unicast_reflect_slots = NULL; + s->legacy_unicast_reflect_id = 0; + + s->record_list = avahi_record_list_new(); + + /* Get host name */ + s->host_name = s->config.host_name ? avahi_normalize_name_strdup(s->config.host_name) : avahi_get_host_name_strdup(); + s->host_name[strcspn(s->host_name, ".")] = 0; + s->domain_name = s->config.domain_name ? avahi_normalize_name_strdup(s->config.domain_name) : avahi_strdup("local"); + s->host_name_fqdn = NULL; + update_fqdn(s); + + do { + s->local_service_cookie = (uint32_t) rand() * (uint32_t) rand(); + } while (s->local_service_cookie == AVAHI_SERVICE_COOKIE_INVALID); + + if (s->config.enable_wide_area) { + s->wide_area_lookup_engine = avahi_wide_area_engine_new(s); + avahi_wide_area_set_servers(s->wide_area_lookup_engine, s->config.wide_area_servers, s->config.n_wide_area_servers); + } else + s->wide_area_lookup_engine = NULL; + + s->multicast_lookup_engine = avahi_multicast_lookup_engine_new(s); + + s->monitor = avahi_interface_monitor_new(s); + avahi_interface_monitor_sync(s->monitor); + + register_localhost(s); + register_stuff(s); + + return s; +} + +void avahi_server_free(AvahiServer* s) { + assert(s); + + /* Remove all browsers */ + + while (s->dns_server_browsers) + avahi_s_dns_server_browser_free(s->dns_server_browsers); + while (s->host_name_resolvers) + avahi_s_host_name_resolver_free(s->host_name_resolvers); + while (s->address_resolvers) + avahi_s_address_resolver_free(s->address_resolvers); + while (s->domain_browsers) + avahi_s_domain_browser_free(s->domain_browsers); + while (s->service_type_browsers) + avahi_s_service_type_browser_free(s->service_type_browsers); + while (s->service_browsers) + avahi_s_service_browser_free(s->service_browsers); + while (s->service_resolvers) + avahi_s_service_resolver_free(s->service_resolvers); + while (s->record_browsers) + avahi_s_record_browser_destroy(s->record_browsers); + + /* Remove all locally rgeistered stuff */ + + while(s->entries) + avahi_entry_free(s, s->entries); + + avahi_interface_monitor_free(s->monitor); + + while (s->groups) + avahi_entry_group_free(s, s->groups); + + free_slots(s); + + avahi_hashmap_free(s->entries_by_key); + avahi_record_list_free(s->record_list); + avahi_hashmap_free(s->record_browser_hashmap); + + if (s->wide_area_lookup_engine) + avahi_wide_area_engine_free(s->wide_area_lookup_engine); + avahi_multicast_lookup_engine_free(s->multicast_lookup_engine); + + avahi_time_event_queue_free(s->time_event_queue); + + /* Free watches */ + + if (s->watch_ipv4) + s->poll_api->watch_free(s->watch_ipv4); + if (s->watch_ipv6) + s->poll_api->watch_free(s->watch_ipv6); + + if (s->watch_legacy_unicast_ipv4) + s->poll_api->watch_free(s->watch_legacy_unicast_ipv4); + if (s->watch_legacy_unicast_ipv6) + s->poll_api->watch_free(s->watch_legacy_unicast_ipv6); + + /* Free sockets */ + + if (s->fd_ipv4 >= 0) + close(s->fd_ipv4); + if (s->fd_ipv6 >= 0) + close(s->fd_ipv6); + + if (s->fd_legacy_unicast_ipv4 >= 0) + close(s->fd_legacy_unicast_ipv4); + if (s->fd_legacy_unicast_ipv6 >= 0) + close(s->fd_legacy_unicast_ipv6); + + /* Free other stuff */ + + avahi_free(s->host_name); + avahi_free(s->domain_name); + avahi_free(s->host_name_fqdn); + + avahi_server_config_free(&s->config); + + avahi_free(s); +} + +const char* avahi_server_get_domain_name(AvahiServer *s) { + assert(s); + + return s->domain_name; +} + +const char* avahi_server_get_host_name(AvahiServer *s) { + assert(s); + + return s->host_name; +} + +const char* avahi_server_get_host_name_fqdn(AvahiServer *s) { + assert(s); + + return s->host_name_fqdn; +} + +void* avahi_server_get_data(AvahiServer *s) { + assert(s); + + return s->userdata; +} + +void avahi_server_set_data(AvahiServer *s, void* userdata) { + assert(s); + + s->userdata = userdata; +} + +AvahiServerState avahi_server_get_state(AvahiServer *s) { + assert(s); + + return s->state; +} + +AvahiServerConfig* avahi_server_config_init(AvahiServerConfig *c) { + assert(c); + + memset(c, 0, sizeof(AvahiServerConfig)); + c->use_ipv6 = 1; + c->use_ipv4 = 1; + c->host_name = NULL; + c->domain_name = NULL; + c->check_response_ttl = 0; + c->publish_hinfo = 1; + c->publish_addresses = 1; + c->publish_workstation = 1; + c->publish_domain = 1; + c->use_iff_running = 0; + c->enable_reflector = 0; + c->reflect_ipv = 0; + c->add_service_cookie = 1; + c->enable_wide_area = 0; + c->n_wide_area_servers = 0; + c->disallow_other_stacks = 0; + c->browse_domains = NULL; + c->disable_publishing = 0; + c->allow_point_to_point = 0; + + return c; +} + +void avahi_server_config_free(AvahiServerConfig *c) { + assert(c); + + avahi_free(c->host_name); + avahi_free(c->domain_name); + avahi_string_list_free(c->browse_domains); +} + +AvahiServerConfig* avahi_server_config_copy(AvahiServerConfig *ret, const AvahiServerConfig *c) { + char *d = NULL, *h = NULL; + AvahiStringList *l = NULL; + assert(ret); + assert(c); + + if (c->host_name) + if (!(h = avahi_strdup(c->host_name))) + return NULL; + + if (c->domain_name) + if (!(d = avahi_strdup(c->domain_name))) { + avahi_free(h); + return NULL; + } + + if (!(l = avahi_string_list_copy(c->browse_domains)) && c->browse_domains) { + avahi_free(h); + avahi_free(d); + return NULL; + } + + *ret = *c; + ret->host_name = h; + ret->domain_name = d; + ret->browse_domains = l; + + return ret; +} + +int avahi_server_errno(AvahiServer *s) { + assert(s); + + return s->error; +} + +/* Just for internal use */ +int avahi_server_set_errno(AvahiServer *s, int error) { + assert(s); + + return s->error = error; +} + +uint32_t avahi_server_get_local_service_cookie(AvahiServer *s) { + assert(s); + + return s->local_service_cookie; +} + +static AvahiEntry *find_entry(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, AvahiKey *key) { + AvahiEntry *e; + + assert(s); + assert(key); + + for (e = avahi_hashmap_lookup(s->entries_by_key, key); e; e = e->by_key_next) + + if ((e->interface == interface || e->interface <= 0 || interface <= 0) && + (e->protocol == protocol || e->protocol == AVAHI_PROTO_UNSPEC || protocol == AVAHI_PROTO_UNSPEC) && + (!e->group || e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED || e->group->state == AVAHI_ENTRY_GROUP_REGISTERING)) + + return e; + + return NULL; +} + +int avahi_server_get_group_of_service(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain, AvahiSEntryGroup** ret_group) { + AvahiKey *key = NULL; + AvahiEntry *e; + int ret; + char n[AVAHI_DOMAIN_NAME_MAX]; + + assert(s); + assert(name); + assert(type); + assert(ret_group); + + AVAHI_CHECK_VALIDITY(s, AVAHI_IF_VALID(interface), AVAHI_ERR_INVALID_INTERFACE); + AVAHI_CHECK_VALIDITY(s, AVAHI_PROTO_VALID(protocol), AVAHI_ERR_INVALID_PROTOCOL); + AVAHI_CHECK_VALIDITY(s, avahi_is_valid_service_name(name), AVAHI_ERR_INVALID_SERVICE_NAME); + AVAHI_CHECK_VALIDITY(s, avahi_is_valid_service_type_strict(type), AVAHI_ERR_INVALID_SERVICE_TYPE); + AVAHI_CHECK_VALIDITY(s, !domain || avahi_is_valid_domain_name(domain), AVAHI_ERR_INVALID_DOMAIN_NAME); + + if ((ret = avahi_service_name_join(n, sizeof(n), name, type, domain) < 0)) + return avahi_server_set_errno(s, ret); + + if (!(key = avahi_key_new(n, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV))) + return avahi_server_set_errno(s, AVAHI_ERR_NO_MEMORY); + + e = find_entry(s, interface, protocol, key); + avahi_key_unref(key); + + if (e) { + *ret_group = e->group; + return AVAHI_OK; + } + + return avahi_server_set_errno(s, AVAHI_ERR_NOT_FOUND); +} + +int avahi_server_is_service_local(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, const char *name) { + AvahiKey *key = NULL; + AvahiEntry *e; + + assert(s); + assert(name); + + if (!s->host_name_fqdn) + return 0; + + if (!(key = avahi_key_new(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV))) + return 0; + + e = find_entry(s, interface, protocol, key); + avahi_key_unref(key); + + if (!e) + return 0; + + return avahi_domain_equal(s->host_name_fqdn, e->record->data.srv.name); +} + +int avahi_server_is_record_local(AvahiServer *s, AvahiIfIndex interface, AvahiProtocol protocol, AvahiRecord *record) { + AvahiEntry *e; + + assert(s); + assert(record); + + for (e = avahi_hashmap_lookup(s->entries_by_key, record->key); e; e = e->by_key_next) + + if ((e->interface == interface || e->interface <= 0 || interface <= 0) && + (e->protocol == protocol || e->protocol == AVAHI_PROTO_UNSPEC || protocol == AVAHI_PROTO_UNSPEC) && + (!e->group || e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED || e->group->state == AVAHI_ENTRY_GROUP_REGISTERING) && + avahi_record_equal_no_ttl(record, e->record)) + return 1; + + return 0; +} + +/** Set the wide area DNS servers */ +int avahi_server_set_wide_area_servers(AvahiServer *s, const AvahiAddress *a, unsigned n) { + assert(s); + + if (!s->wide_area_lookup_engine) + return avahi_server_set_errno(s, AVAHI_ERR_INVALID_CONFIG); + + avahi_wide_area_set_servers(s->wide_area_lookup_engine, a, n); + return AVAHI_OK; +} diff --git a/trunk/avahi-core/socket.c b/trunk/avahi-core/socket.c new file mode 100644 index 0000000..9291f08 --- /dev/null +++ b/trunk/avahi-core/socket.c @@ -0,0 +1,930 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <sys/uio.h> + +#ifdef IP_RECVIF +#include <net/if_dl.h> +#endif + +#include "dns.h" +#include "fdutil.h" +#include "socket.h" +#include "log.h" +#include "addr-util.h" + +/* this is a portability hack */ +#ifndef IPV6_ADD_MEMBERSHIP +#ifdef IPV6_JOIN_GROUP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif +#endif + +#ifndef IPV6_DROP_MEMBERSHIP +#ifdef IPV6_LEAVE_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif +#endif + +static void mdns_mcast_group_ipv4(struct sockaddr_in *ret_sa) { + assert(ret_sa); + + memset(ret_sa, 0, sizeof(struct sockaddr_in)); + ret_sa->sin_family = AF_INET; + ret_sa->sin_port = htons(AVAHI_MDNS_PORT); + inet_pton(AF_INET, AVAHI_IPV4_MCAST_GROUP, &ret_sa->sin_addr); +} + +static void mdns_mcast_group_ipv6(struct sockaddr_in6 *ret_sa) { + assert(ret_sa); + + memset(ret_sa, 0, sizeof(struct sockaddr_in6)); + ret_sa->sin6_family = AF_INET6; + ret_sa->sin6_port = htons(AVAHI_MDNS_PORT); + inet_pton(AF_INET6, AVAHI_IPV6_MCAST_GROUP, &ret_sa->sin6_addr); +} + +static void ipv4_address_to_sockaddr(struct sockaddr_in *ret_sa, const AvahiIPv4Address *a, uint16_t port) { + assert(ret_sa); + assert(a); + assert(port > 0); + + memset(ret_sa, 0, sizeof(struct sockaddr_in)); + ret_sa->sin_family = AF_INET; + ret_sa->sin_port = htons(port); + memcpy(&ret_sa->sin_addr, a, sizeof(AvahiIPv4Address)); +} + +static void ipv6_address_to_sockaddr(struct sockaddr_in6 *ret_sa, const AvahiIPv6Address *a, uint16_t port) { + assert(ret_sa); + assert(a); + assert(port > 0); + + memset(ret_sa, 0, sizeof(struct sockaddr_in6)); + ret_sa->sin6_family = AF_INET6; + ret_sa->sin6_port = htons(port); + memcpy(&ret_sa->sin6_addr, a, sizeof(AvahiIPv6Address)); +} + +int avahi_mdns_mcast_join_ipv4(int fd, const AvahiIPv4Address *a, int idx, int join) { +#ifdef HAVE_STRUCT_IP_MREQN + struct ip_mreqn mreq; +#else + struct ip_mreq mreq; +#endif + struct sockaddr_in sa; + + assert(fd >= 0); + assert(idx >= 0); + assert(a); + + memset(&mreq, 0, sizeof(mreq)); +#ifdef HAVE_STRUCT_IP_MREQN + mreq.imr_ifindex = idx; + mreq.imr_address.s_addr = a->address; +#else + mreq.imr_interface.s_addr = a->address; +#endif + mdns_mcast_group_ipv4(&sa); + mreq.imr_multiaddr = sa.sin_addr; + + /* Some network drivers have issues with dropping membership of + * mcast groups when the iface is down, but don't allow rejoining + * when it comes back up. This is an ugly workaround */ + if (join) + setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + + if (setsockopt(fd, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + avahi_log_warn("%s failed: %s", join ? "IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP", strerror(errno)); + return -1; + } + + return 0; +} + +int avahi_mdns_mcast_join_ipv6(int fd, const AvahiIPv6Address *a, int idx, int join) { + struct ipv6_mreq mreq6; + struct sockaddr_in6 sa6; + + assert(fd >= 0); + assert(idx >= 0); + assert(a); + + memset(&mreq6, 0, sizeof(mreq6)); + mdns_mcast_group_ipv6 (&sa6); + mreq6.ipv6mr_multiaddr = sa6.sin6_addr; + mreq6.ipv6mr_interface = idx; + + if (join) + setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)); + + if (setsockopt(fd, IPPROTO_IPV6, join ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) { + avahi_log_warn("%s failed: %s", join ? "IPV6_ADD_MEMBERSHIP" : "IPV6_DROP_MEMBERSHIP", strerror(errno)); + return -1; + } + + return 0; +} + +static int reuseaddr(int fd) { + int yes; + + yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + avahi_log_warn("SO_REUSEADDR failed: %s", strerror(errno)); + return -1; + } + +#ifdef SO_REUSEPORT + yes = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) < 0) { + avahi_log_warn("SO_REUSEPORT failed: %s", strerror(errno)); + return -1; + } +#endif + + return 0; +} + +static int bind_with_warn(int fd, const struct sockaddr *sa, socklen_t l) { + + assert(fd >= 0); + assert(sa); + assert(l > 0); + + if (bind(fd, sa, l) < 0) { + + if (errno != EADDRINUSE) { + avahi_log_warn("bind() failed: %s", strerror(errno)); + return -1; + } + + avahi_log_warn("*** WARNING: Detected another %s mDNS stack running on this host. This makes mDNS unreliable and is thus not recommended. ***", + sa->sa_family == AF_INET ? "IPv4" : "IPv6"); + + /* Try again, this time with SO_REUSEADDR set */ + if (reuseaddr(fd) < 0) + return -1; + + if (bind(fd, sa, l) < 0) { + avahi_log_warn("bind() failed: %s", strerror(errno)); + return -1; + } + } else { + + /* We enable SO_REUSEADDR afterwards, to make sure that the + * user may run other mDNS implementations if he really + * wants. */ + + if (reuseaddr(fd) < 0) + return -1; + } + + return 0; +} + +static int ipv4_pktinfo(int fd) { + int yes; + +#ifdef IP_PKTINFO + yes = 1; + if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IP_PKTINFO failed: %s", strerror(errno)); + return -1; + } +#else + +#ifdef IP_RECVINTERFACE + yes = 1; + if (setsockopt (fd, IPPROTO_IP, IP_RECVINTERFACE, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IP_RECVINTERFACE failed: %s", strerror(errno)); + return -1; + } +#elif defined(IP_RECVIF) + yes = 1; + if (setsockopt (fd, IPPROTO_IP, IP_RECVIF, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IP_RECVIF failed: %s", strerror(errno)); + return -1; + } +#endif + +#ifdef IP_RECVDSTADDR + yes = 1; + if (setsockopt (fd, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IP_RECVDSTADDR failed: %s", strerror(errno)); + return -1; + } +#endif + +#endif /* IP_PKTINFO */ + +#ifdef IP_RECVTTL + yes = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IP_RECVTTL failed: %s", strerror(errno)); + return -1; + } +#endif + + return 0; +} + +static int ipv6_pktinfo(int fd) { + int yes; + +#ifdef IPV6_RECVPKTINFO + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_RECVPKTINFO failed: %s", strerror(errno)); + return -1; + } +#elif defined(IPV6_PKTINFO) + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_PKTINFO failed: %s", strerror(errno)); + return -1; + } +#endif + +#ifdef IPV6_RECVHOPS + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPS, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_RECVHOPS failed: %s", strerror(errno)); + return -1; + } +#elif defined(IPV6_RECVHOPLIMIT) + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_RECVHOPLIMIT failed: %s", strerror(errno)); + return -1; + } +#elif defined(IPV6_HOPLIMIT) + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_HOPLIMIT failed: %s", strerror(errno)); + return -1; + } +#endif + + return 0; +} + +int avahi_open_socket_ipv4(int no_reuse) { + struct sockaddr_in local; + int fd = -1, r, ittl; + uint8_t ttl, cyes; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + avahi_log_warn("socket() failed: %s", strerror(errno)); + goto fail; + } + + ttl = 255; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) { + avahi_log_warn("IP_MULTICAST_TTL failed: %s", strerror(errno)); + goto fail; + } + + ittl = 255; + if (setsockopt(fd, IPPROTO_IP, IP_TTL, &ittl, sizeof(ittl)) < 0) { + avahi_log_warn("IP_TTL failed: %s", strerror(errno)); + goto fail; + } + + cyes = 1; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &cyes, sizeof(cyes)) < 0) { + avahi_log_warn("IP_MULTICAST_LOOP failed: %s", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + local.sin_port = htons(AVAHI_MDNS_PORT); + + if (no_reuse) + r = bind(fd, (struct sockaddr*) &local, sizeof(local)); + else + r = bind_with_warn(fd, (struct sockaddr*) &local, sizeof(local)); + + if (r < 0) + goto fail; + + if (ipv4_pktinfo (fd) < 0) + goto fail; + + if (avahi_set_cloexec(fd) < 0) { + avahi_log_warn("FD_CLOEXEC failed: %s", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + avahi_log_warn("O_NONBLOCK failed: %s", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +int avahi_open_socket_ipv6(int no_reuse) { + struct sockaddr_in6 sa, local; + int fd = -1, yes, r; + int ttl; + + mdns_mcast_group_ipv6(&sa); + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + avahi_log_warn("socket() failed: %s", strerror(errno)); + goto fail; + } + + ttl = 255; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0) { + avahi_log_warn("IPV6_MULTICAST_HOPS failed: %s", strerror(errno)); + goto fail; + } + + ttl = 255; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) < 0) { + avahi_log_warn("IPV6_UNICAST_HOPS failed: %s", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_V6ONLY failed: %s", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &yes, sizeof(yes)) < 0) { + avahi_log_warn("IPV6_MULTICAST_LOOP failed: %s", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin6_family = AF_INET6; + local.sin6_port = htons(AVAHI_MDNS_PORT); + + if (no_reuse) + r = bind(fd, (struct sockaddr*) &local, sizeof(local)); + else + r = bind_with_warn(fd, (struct sockaddr*) &local, sizeof(local)); + + if (r < 0) + goto fail; + + if (ipv6_pktinfo(fd) < 0) + goto fail; + + if (avahi_set_cloexec(fd) < 0) { + avahi_log_warn("FD_CLOEXEC failed: %s", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + avahi_log_warn("O_NONBLOCK failed: %s", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +static int sendmsg_loop(int fd, struct msghdr *msg, int flags) { + assert(fd >= 0); + assert(msg); + + for (;;) { + + if (sendmsg(fd, msg, flags) >= 0) + break; + + if (errno != EAGAIN) { + avahi_log_debug("sendmsg() failed: %s", strerror(errno)); + return -1; + } + + if (avahi_wait_for_write(fd) < 0) + return -1; + } + + return 0; +} + +int avahi_send_dns_packet_ipv4( + int fd, + AvahiIfIndex interface, + AvahiDnsPacket *p, + const AvahiIPv4Address *src_address, + const AvahiIPv4Address *dst_address, + uint16_t dst_port) { + + struct sockaddr_in sa; + struct msghdr msg; + struct iovec io; +#ifdef IP_PKTINFO + struct cmsghdr *cmsg; + uint8_t cmsg_data[CMSG_SPACE(sizeof(struct in_pktinfo))]; +#elif defined(IP_SENDSRCADDR) + struct cmsghdr *cmsg; + uint8_t cmsg_data[CMSG_SPACE(sizeof(struct in_addr))]; +#endif + + assert(fd >= 0); + assert(p); + assert(avahi_dns_packet_check_valid(p) >= 0); + assert(!dst_address || dst_port > 0); + + if (!dst_address) + mdns_mcast_group_ipv4(&sa); + else + ipv4_address_to_sockaddr(&sa, dst_address, dst_port); + + memset(&io, 0, sizeof(io)); + io.iov_base = AVAHI_DNS_PACKET_DATA(p); + io.iov_len = p->size; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = NULL; + msg.msg_controllen = 0; + +#ifdef IP_PKTINFO + if (interface > 0 || src_address) { + struct in_pktinfo *pkti; + + memset(cmsg_data, 0, sizeof(cmsg_data)); + msg.msg_control = cmsg_data; + msg.msg_controllen = sizeof(cmsg_data); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + + pkti = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (interface > 0) + pkti->ipi_ifindex = interface; + + if (src_address) + pkti->ipi_spec_dst.s_addr = src_address->address; + + msg.msg_controllen = cmsg->cmsg_len; + } +#elif defined(IP_SENDSRCADDR) + if (src_address) { + struct in_addr *addr; + + memset(cmsg_data, 0, sizeof(cmsg_data)); + msg.msg_control = cmsg_data; + msg.msg_controllen = sizeof(cmsg_data); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_SENDSRCADDR; + + addr = (struct in_addr *)CMSG_DATA(cmsg); + addr->s_addr = src_address->address; + + msg.msg_controllen = cmsg->cmsg_len; +} +#elif defined(IP_MULTICAST_IF) + { + struct in_addr any = { INADDR_ANY }; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, src_address ? (const void*) &src_address->address : (const void*) &any, sizeof(struct in_addr)) < 0) { + avahi_log_warn("IP_MULTICAST_IF failed: %s", strerror(errno)); + return -1; + } + } +#elif defined(__GNUC__) +#warning "FIXME: We need some code to set the outgoing interface/local address here if IP_PKTINFO/IP_MULTICAST_IF is not available" +#endif + + return sendmsg_loop(fd, &msg, 0); +} + +int avahi_send_dns_packet_ipv6(int fd, AvahiIfIndex interface, AvahiDnsPacket *p, const AvahiIPv6Address *src_address, const AvahiIPv6Address *dst_address, uint16_t dst_port) { + struct sockaddr_in6 sa; + struct msghdr msg; + struct iovec io; + struct cmsghdr *cmsg; + uint8_t cmsg_data[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + + assert(fd >= 0); + assert(p); + assert(avahi_dns_packet_check_valid(p) >= 0); + assert(!dst_address || dst_port > 0); + + if (!dst_address) + mdns_mcast_group_ipv6(&sa); + else + ipv6_address_to_sockaddr(&sa, dst_address, dst_port); + + memset(&io, 0, sizeof(io)); + io.iov_base = AVAHI_DNS_PACKET_DATA(p); + io.iov_len = p->size; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + + if (interface > 0 || src_address) { + struct in6_pktinfo *pkti; + + memset(cmsg_data, 0, sizeof(cmsg_data)); + msg.msg_control = cmsg_data; + msg.msg_controllen = sizeof(cmsg_data); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + pkti = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (interface > 0) + pkti->ipi6_ifindex = interface; + + if (src_address) + memcpy(&pkti->ipi6_addr, src_address->address, sizeof(src_address->address)); + + msg.msg_controllen = cmsg->cmsg_len; + } else { + msg.msg_control = NULL; + msg.msg_controllen = 0; + } + + return sendmsg_loop(fd, &msg, 0); +} + +AvahiDnsPacket *avahi_recv_dns_packet_ipv4(int fd, AvahiIPv4Address *ret_src_address, uint16_t *ret_src_port, AvahiIPv4Address *ret_dst_address, AvahiIfIndex *ret_iface, uint8_t *ret_ttl) { + AvahiDnsPacket *p= NULL; + struct msghdr msg; + struct iovec io; + size_t aux[1024 / sizeof(size_t)]; /* for alignment on ia64 ! */ + ssize_t l; + struct cmsghdr *cmsg; + int found_addr = 0; + int ms; + struct sockaddr_in sa; + + assert(fd >= 0); + + if (ioctl(fd, FIONREAD, &ms) < 0) { + avahi_log_warn("ioctl(): %s", strerror(errno)); + goto fail; + } + + p = avahi_dns_packet_new(ms + AVAHI_DNS_PACKET_EXTRA_SIZE); + + io.iov_base = AVAHI_DNS_PACKET_DATA(p); + io.iov_len = p->max_size; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = aux; + msg.msg_controllen = sizeof(aux); + msg.msg_flags = 0; + + if ((l = recvmsg(fd, &msg, 0)) < 0) { + avahi_log_warn("recvmsg(): %s", strerror(errno)); + goto fail; + } + + if (sa.sin_addr.s_addr == INADDR_ANY) { + /* Linux 2.4 behaves very strangely sometimes! */ + goto fail; + } + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + if (ret_src_port) + *ret_src_port = avahi_port_from_sockaddr((struct sockaddr*) &sa); + + if (ret_src_address) { + AvahiAddress a; + avahi_address_from_sockaddr((struct sockaddr*) &sa, &a); + *ret_src_address = a.data.ipv4; + } + + if (ret_ttl) + *ret_ttl = 255; + + if (ret_iface) + *ret_iface = AVAHI_IF_UNSPEC; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP) { + + switch (cmsg->cmsg_type) { +#ifdef IP_RECVTTL + case IP_RECVTTL: +#endif + case IP_TTL: + if (ret_ttl) + *ret_ttl = (uint8_t) (*(int *) CMSG_DATA(cmsg)); + + break; + +#ifdef IP_PKTINFO + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (ret_iface) + *ret_iface = (int) i->ipi_ifindex; + + if (ret_dst_address) + ret_dst_address->address = i->ipi_addr.s_addr; + + found_addr = 1; + + break; + } +#endif + +#ifdef IP_RECVIF + case IP_RECVIF: { + struct sockaddr_dl *sdl = (struct sockaddr_dl *) CMSG_DATA (cmsg); + + if (ret_iface) +#ifdef __sun + *ret_iface = *(uint_t*) sdl; +#else + *ret_iface = (int) sdl->sdl_index; +#endif + + break; + } +#endif + +#ifdef IP_RECVDSTADDR + case IP_RECVDSTADDR: + if (ret_dst_address) + memcpy(&ret_dst_address->address, CMSG_DATA (cmsg), 4); + + found_addr = 1; + break; +#endif + + default: + avahi_log_warn("Unhandled cmsg_type : %d", cmsg->cmsg_type); + break; + } + } + } + + assert(found_addr); + + return p; + +fail: + if (p) + avahi_dns_packet_free(p); + + return NULL; +} + +AvahiDnsPacket *avahi_recv_dns_packet_ipv6(int fd, AvahiIPv6Address *ret_src_address, uint16_t *ret_src_port, AvahiIPv6Address *ret_dst_address, AvahiIfIndex *ret_iface, uint8_t *ret_ttl) { + AvahiDnsPacket *p = NULL; + struct msghdr msg; + struct iovec io; + size_t aux[1024 / sizeof(size_t)]; + ssize_t l; + int ms; + struct cmsghdr *cmsg; + int found_ttl = 0, found_iface = 0; + struct sockaddr_in6 sa; + + assert(fd >= 0); + + if (ioctl(fd, FIONREAD, &ms) < 0) { + avahi_log_warn("ioctl(): %s", strerror(errno)); + goto fail; + } + + p = avahi_dns_packet_new(ms + AVAHI_DNS_PACKET_EXTRA_SIZE); + + io.iov_base = AVAHI_DNS_PACKET_DATA(p); + io.iov_len = p->max_size; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr*) &sa; + msg.msg_namelen = sizeof(sa); + + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = aux; + msg.msg_controllen = sizeof(aux); + msg.msg_flags = 0; + + if ((l = recvmsg(fd, &msg, 0)) < 0) { + avahi_log_warn("recvmsg(): %s", strerror(errno)); + goto fail; + } + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + if (ret_src_port) + *ret_src_port = avahi_port_from_sockaddr((struct sockaddr*) &sa); + + if (ret_src_address) { + AvahiAddress a; + avahi_address_from_sockaddr((struct sockaddr*) &sa, &a); + *ret_src_address = a.data.ipv6; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + + switch (cmsg->cmsg_type) { + + case IPV6_HOPLIMIT: + + if (ret_ttl) + *ret_ttl = (uint8_t) (*(int *) CMSG_DATA(cmsg)); + + found_ttl = 1; + + break; + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (ret_iface) + *ret_iface = i->ipi6_ifindex; + + if (ret_dst_address) + memcpy(ret_dst_address->address, i->ipi6_addr.s6_addr, 16); + + found_iface = 1; + break; + } + + default: + avahi_log_warn("Unhandled cmsg_type : %d", cmsg->cmsg_type); + break; + } + } + } + + assert(found_iface); + assert(found_ttl); + + return p; + +fail: + if (p) + avahi_dns_packet_free(p); + + return NULL; +} + +int avahi_open_unicast_socket_ipv4(void) { + struct sockaddr_in local; + int fd = -1; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + avahi_log_warn("socket() failed: %s", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + + if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0) { + avahi_log_warn("bind() failed: %s", strerror(errno)); + goto fail; + } + + if (ipv4_pktinfo(fd) < 0) { + goto fail; + } + + if (avahi_set_cloexec(fd) < 0) { + avahi_log_warn("FD_CLOEXEC failed: %s", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + avahi_log_warn("O_NONBLOCK failed: %s", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +int avahi_open_unicast_socket_ipv6(void) { + struct sockaddr_in6 local; + int fd = -1; + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + avahi_log_warn("socket() failed: %s", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin6_family = AF_INET6; + + if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0) { + avahi_log_warn("bind() failed: %s", strerror(errno)); + goto fail; + } + + if (ipv6_pktinfo(fd) < 0) + goto fail; + + if (avahi_set_cloexec(fd) < 0) { + avahi_log_warn("FD_CLOEXEC failed: %s", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + avahi_log_warn("O_NONBLOCK failed: %s", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} diff --git a/trunk/avahi-core/socket.h b/trunk/avahi-core/socket.h new file mode 100644 index 0000000..237b534 --- /dev/null +++ b/trunk/avahi-core/socket.h @@ -0,0 +1,49 @@ +#ifndef foosockethfoo +#define foosockethfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <inttypes.h> + +#include "dns.h" + +#define AVAHI_MDNS_PORT 5353 +#define AVAHI_DNS_PORT 53 +#define AVAHI_IPV4_MCAST_GROUP "224.0.0.251" +#define AVAHI_IPV6_MCAST_GROUP "ff02::fb" + +int avahi_open_socket_ipv4(int no_reuse); +int avahi_open_socket_ipv6(int no_reuse); + +int avahi_open_unicast_socket_ipv4(void); +int avahi_open_unicast_socket_ipv6(void); + +int avahi_send_dns_packet_ipv4(int fd, AvahiIfIndex iface, AvahiDnsPacket *p, const AvahiIPv4Address *src_address, const AvahiIPv4Address *dst_address, uint16_t dst_port); +int avahi_send_dns_packet_ipv6(int fd, AvahiIfIndex iface, AvahiDnsPacket *p, const AvahiIPv6Address *src_address, const AvahiIPv6Address *dst_address, uint16_t dst_port); + +AvahiDnsPacket *avahi_recv_dns_packet_ipv4(int fd, AvahiIPv4Address *ret_src_address, uint16_t *ret_src_port, AvahiIPv4Address *ret_dst_address, AvahiIfIndex *ret_iface, uint8_t *ret_ttl); +AvahiDnsPacket *avahi_recv_dns_packet_ipv6(int fd, AvahiIPv6Address *ret_src_address, uint16_t *ret_src_port, AvahiIPv6Address *ret_dst_address, AvahiIfIndex *ret_iface, uint8_t *ret_ttl); + +int avahi_mdns_mcast_join_ipv4(int fd, const AvahiIPv4Address *local_address, int iface, int join); +int avahi_mdns_mcast_join_ipv6(int fd, const AvahiIPv6Address *local_address, int iface, int join); + +#endif diff --git a/trunk/avahi-core/timeeventq-test.c b/trunk/avahi-core/timeeventq-test.c new file mode 100644 index 0000000..c66a55a --- /dev/null +++ b/trunk/avahi-core/timeeventq-test.c @@ -0,0 +1,69 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/simple-watch.h> + +#include "timeeventq.h" +#include "log.h" + +#define POINTER_TO_INT(p) ((int) (p)) +#define INT_TO_POINTER(i) ((void*) (i)) + +static AvahiTimeEventQueue *q = NULL; + +static void callback(AvahiTimeEvent*e, void* userdata) { + struct timeval tv = {0, 0}; + assert(e); + avahi_log_info("callback(%i)", POINTER_TO_INT(userdata)); + avahi_elapse_time(&tv, 1000, 100); + avahi_time_event_update(e, &tv); +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + struct timeval tv; + AvahiSimplePoll *s; + + s = avahi_simple_poll_new(); + + q = avahi_time_event_queue_new(avahi_simple_poll_get(s)); + + avahi_time_event_new(q, avahi_elapse_time(&tv, 5000, 100), callback, INT_TO_POINTER(1)); + avahi_time_event_new(q, avahi_elapse_time(&tv, 5000, 100), callback, INT_TO_POINTER(2)); + + avahi_log_info("starting"); + + for (;;) + if (avahi_simple_poll_iterate(s, -1) != 0) + break; + + avahi_time_event_queue_free(q); + avahi_simple_poll_free(s); + + return 0; +} diff --git a/trunk/avahi-core/timeeventq.c b/trunk/avahi-core/timeeventq.c new file mode 100644 index 0000000..17334ee --- /dev/null +++ b/trunk/avahi-core/timeeventq.c @@ -0,0 +1,227 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> + +#include <avahi-common/timeval.h> +#include <avahi-common/malloc.h> + +#include "timeeventq.h" +#include "log.h" + +struct AvahiTimeEvent { + AvahiTimeEventQueue *queue; + AvahiPrioQueueNode *node; + struct timeval expiry; + struct timeval last_run; + AvahiTimeEventCallback callback; + void* userdata; +}; + +struct AvahiTimeEventQueue { + const AvahiPoll *poll_api; + AvahiPrioQueue *prioq; + AvahiTimeout *timeout; +}; + +static int compare(const void* _a, const void* _b) { + const AvahiTimeEvent *a = _a, *b = _b; + int ret; + + if ((ret = avahi_timeval_compare(&a->expiry, &b->expiry)) != 0) + return ret; + + /* If both exevents are scheduled for the same time, put the entry + * that has been run earlier the last time first. */ + return avahi_timeval_compare(&a->last_run, &b->last_run); +} + +static AvahiTimeEvent* time_event_queue_root(AvahiTimeEventQueue *q) { + assert(q); + + return q->prioq->root ? q->prioq->root->data : NULL; +} + +static void update_timeout(AvahiTimeEventQueue *q) { + AvahiTimeEvent *e; + assert(q); + + if ((e = time_event_queue_root(q))) + q->poll_api->timeout_update(q->timeout, &e->expiry); + else + q->poll_api->timeout_update(q->timeout, NULL); +} + +static void expiration_event(AVAHI_GCC_UNUSED AvahiTimeout *timeout, void *userdata) { + AvahiTimeEventQueue *q = userdata; + AvahiTimeEvent *e; + + if ((e = time_event_queue_root(q))) { + struct timeval now; + + gettimeofday(&now, NULL); + + /* Check if expired */ + if (avahi_timeval_compare(&now, &e->expiry) >= 0) { + + /* Make sure to move the entry away from the front */ + e->last_run = now; + avahi_prio_queue_shuffle(q->prioq, e->node); + + /* Run it */ + assert(e->callback); + e->callback(e, e->userdata); + + update_timeout(q); + return; + } + } + + avahi_log_debug(__FILE__": Strange, expiration_event() called, but nothing really happened."); + update_timeout(q); +} + +static void fix_expiry_time(AvahiTimeEvent *e) { + struct timeval now; + assert(e); + + return; /*** DO WE REALLY NEED THIS? ***/ + + gettimeofday(&now, NULL); + + if (avahi_timeval_compare(&now, &e->expiry) > 0) + e->expiry = now; +} + +AvahiTimeEventQueue* avahi_time_event_queue_new(const AvahiPoll *poll_api) { + AvahiTimeEventQueue *q; + + if (!(q = avahi_new(AvahiTimeEventQueue, 1))) { + avahi_log_error(__FILE__": Out of memory"); + goto oom; + } + + q->poll_api = poll_api; + + if (!(q->prioq = avahi_prio_queue_new(compare))) + goto oom; + + if (!(q->timeout = poll_api->timeout_new(poll_api, NULL, expiration_event, q))) + goto oom; + + return q; + +oom: + + if (q) { + avahi_free(q); + + if (q->prioq) + avahi_prio_queue_free(q->prioq); + } + + return NULL; +} + +void avahi_time_event_queue_free(AvahiTimeEventQueue *q) { + AvahiTimeEvent *e; + + assert(q); + + while ((e = time_event_queue_root(q))) + avahi_time_event_free(e); + avahi_prio_queue_free(q->prioq); + + q->poll_api->timeout_free(q->timeout); + + avahi_free(q); +} + +AvahiTimeEvent* avahi_time_event_new( + AvahiTimeEventQueue *q, + const struct timeval *timeval, + AvahiTimeEventCallback callback, + void* userdata) { + + AvahiTimeEvent *e; + + assert(q); + assert(callback); + assert(userdata); + + if (!(e = avahi_new(AvahiTimeEvent, 1))) { + avahi_log_error(__FILE__": Out of memory"); + return NULL; /* OOM */ + } + + e->queue = q; + e->callback = callback; + e->userdata = userdata; + + if (timeval) + e->expiry = *timeval; + else { + e->expiry.tv_sec = 0; + e->expiry.tv_usec = 0; + } + + fix_expiry_time(e); + + e->last_run.tv_sec = 0; + e->last_run.tv_usec = 0; + + if (!(e->node = avahi_prio_queue_put(q->prioq, e))) { + avahi_free(e); + return NULL; + } + + update_timeout(q); + return e; +} + +void avahi_time_event_free(AvahiTimeEvent *e) { + AvahiTimeEventQueue *q; + assert(e); + + q = e->queue; + + avahi_prio_queue_remove(q->prioq, e->node); + avahi_free(e); + + update_timeout(q); +} + +void avahi_time_event_update(AvahiTimeEvent *e, const struct timeval *timeval) { + assert(e); + assert(timeval); + + e->expiry = *timeval; + fix_expiry_time(e); + avahi_prio_queue_shuffle(e->queue->prioq, e->node); + + update_timeout(e->queue); +} + diff --git a/trunk/avahi-core/timeeventq.h b/trunk/avahi-core/timeeventq.h new file mode 100644 index 0000000..cdfa5e6 --- /dev/null +++ b/trunk/avahi-core/timeeventq.h @@ -0,0 +1,48 @@ +#ifndef footimeeventqhfoo +#define footimeeventqhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <sys/types.h> + +typedef struct AvahiTimeEventQueue AvahiTimeEventQueue; +typedef struct AvahiTimeEvent AvahiTimeEvent; + +#include <avahi-common/watch.h> + +#include "prioq.h" + +typedef void (*AvahiTimeEventCallback)(AvahiTimeEvent *e, void* userdata); + +AvahiTimeEventQueue* avahi_time_event_queue_new(const AvahiPoll *poll_api); +void avahi_time_event_queue_free(AvahiTimeEventQueue *q); + +AvahiTimeEvent* avahi_time_event_new( + AvahiTimeEventQueue *q, + const struct timeval *timeval, + AvahiTimeEventCallback callback, + void* userdata); + +void avahi_time_event_free(AvahiTimeEvent *e); +void avahi_time_event_update(AvahiTimeEvent *e, const struct timeval *timeval); + +#endif diff --git a/trunk/avahi-core/update-test.c b/trunk/avahi-core/update-test.c new file mode 100644 index 0000000..bdffc0c --- /dev/null +++ b/trunk/avahi-core/update-test.c @@ -0,0 +1,92 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <stdlib.h> + +#include <avahi-common/error.h> +#include <avahi-common/watch.h> +#include <avahi-common/simple-watch.h> +#include <avahi-common/malloc.h> +#include <avahi-common/alternative.h> +#include <avahi-common/timeval.h> + +#include <avahi-core/core.h> +#include <avahi-core/log.h> +#include <avahi-core/publish.h> +#include <avahi-core/lookup.h> + +static AvahiSEntryGroup *group = NULL; + +static void server_callback(AvahiServer *s, AvahiServerState state, AVAHI_GCC_UNUSED void* userdata) { + + avahi_log_debug("server state: %i", state); + + if (state == AVAHI_SERVER_RUNNING) { + int ret; + + group = avahi_s_entry_group_new(s, NULL, NULL); + assert(group); + + ret = avahi_server_add_service(s, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, "foo", "_http._tcp", NULL, NULL, 80, "test1", NULL); + assert(ret == AVAHI_OK); + + avahi_s_entry_group_commit(group); + } +} + +static void modify_txt_callback(AVAHI_GCC_UNUSED AvahiTimeout *e, void *userdata) { + int ret; + AvahiServer *s = userdata; + + avahi_log_debug("modifying"); + + ret = avahi_server_update_service_txt(s, group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, "foo", "_http._tcp", NULL, "test2", NULL); + assert(ret == AVAHI_OK); +} + +int main(AVAHI_GCC_UNUSED int argc, AVAHI_GCC_UNUSED char *argv[]) { + AvahiSimplePoll *simple_poll; + const AvahiPoll *poll_api; + AvahiServer *server; + struct timeval tv; + AvahiServerConfig config; + + simple_poll = avahi_simple_poll_new(); + assert(simple_poll); + + poll_api = avahi_simple_poll_get(simple_poll); + assert(poll_api); + + avahi_server_config_init(&config); + config.publish_domain = config.publish_workstation = config.use_ipv6 = config.publish_hinfo = 0; + server = avahi_server_new(poll_api, &config, server_callback, NULL, NULL); + assert(server); + avahi_server_config_free(&config); + + poll_api->timeout_new(poll_api, avahi_elapse_time(&tv, 1000*10, 0), modify_txt_callback, server); + + avahi_simple_poll_loop(simple_poll); +} diff --git a/trunk/avahi-core/util.c b/trunk/avahi-core/util.c new file mode 100644 index 0000000..b1925f0 --- /dev/null +++ b/trunk/avahi-core/util.c @@ -0,0 +1,122 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <ctype.h> + +#include <avahi-common/malloc.h> +#include "util.h" + +void avahi_hexdump(const void* p, size_t size) { + const uint8_t *c = p; + assert(p); + + printf("Dumping %lu bytes from %p:\n", (unsigned long) size, p); + + while (size > 0) { + unsigned i; + + for (i = 0; i < 16; i++) { + if (i < size) + printf("%02x ", c[i]); + else + printf(" "); + } + + for (i = 0; i < 16; i++) { + if (i < size) + printf("%c", c[i] >= 32 && c[i] < 127 ? c[i] : '.'); + else + printf(" "); + } + + printf("\n"); + + c += 16; + + if (size <= 16) + break; + + size -= 16; + } +} + +char *avahi_format_mac_address(char *r, size_t l, const uint8_t* mac, size_t size) { + char *t = r; + unsigned i; + static const char hex[] = "0123456789abcdef"; + + assert(r); + assert(l > 0); + assert(mac); + + if (size <= 0) { + *r = 0; + return r; + } + + for (i = 0; i < size; i++) { + if (l < 3) + break; + + *(t++) = hex[*mac >> 4]; + *(t++) = hex[*mac & 0xF]; + *(t++) = ':'; + + l -= 3; + + mac++; + } + + if (t > r) + *(t-1) = 0; + else + *r = 0; + + return r; +} + +char *avahi_strup(char *s) { + char *c; + assert(s); + + for (c = s; *c; c++) + *c = (char) toupper(*c); + + return s; +} + +char *avahi_strdown(char *s) { + char *c; + assert(s); + + for (c = s; *c; c++) + *c = (char) tolower(*c); + + return s; +} diff --git a/trunk/avahi-core/util.h b/trunk/avahi-core/util.h new file mode 100644 index 0000000..8d2caee --- /dev/null +++ b/trunk/avahi-core/util.h @@ -0,0 +1,43 @@ +#ifndef fooutilhfoo +#define fooutilhfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <inttypes.h> + +#include <avahi-common/cdecl.h> + +AVAHI_C_DECL_BEGIN + +void avahi_hexdump(const void *p, size_t size); + +char *avahi_format_mac_address(char *t, size_t l, const uint8_t* mac, size_t size); + +/** Change every character in the string to upper case (ASCII), return a pointer to the string */ +char *avahi_strup(char *s); + +/** Change every character in the string to lower case (ASCII), return a pointer to the string */ +char *avahi_strdown(char *s); + +AVAHI_C_DECL_END + +#endif diff --git a/trunk/avahi-core/wide-area.c b/trunk/avahi-core/wide-area.c new file mode 100644 index 0000000..4072f83 --- /dev/null +++ b/trunk/avahi-core/wide-area.c @@ -0,0 +1,725 @@ +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <netinet/in.h> + +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/timeval.h> + +#include "internal.h" +#include "browse.h" +#include "socket.h" +#include "log.h" +#include "hashmap.h" +#include "wide-area.h" +#include "addr-util.h" +#include "rr-util.h" + +#define CACHE_ENTRIES_MAX 500 + +typedef struct AvahiWideAreaCacheEntry AvahiWideAreaCacheEntry; + +struct AvahiWideAreaCacheEntry { + AvahiWideAreaLookupEngine *engine; + + AvahiRecord *record; + struct timeval timestamp; + struct timeval expiry; + + AvahiTimeEvent *time_event; + + AVAHI_LLIST_FIELDS(AvahiWideAreaCacheEntry, by_key); + AVAHI_LLIST_FIELDS(AvahiWideAreaCacheEntry, cache); +}; + +struct AvahiWideAreaLookup { + AvahiWideAreaLookupEngine *engine; + int dead; + + uint32_t id; /* effectively just an uint16_t, but we need it as an index for a hash table */ + AvahiTimeEvent *time_event; + + AvahiKey *key, *cname_key; + + int n_send; + AvahiDnsPacket *packet; + + AvahiWideAreaLookupCallback callback; + void *userdata; + + AvahiAddress dns_server_used; + + AVAHI_LLIST_FIELDS(AvahiWideAreaLookup, lookups); + AVAHI_LLIST_FIELDS(AvahiWideAreaLookup, by_key); +}; + +struct AvahiWideAreaLookupEngine { + AvahiServer *server; + + int fd_ipv4, fd_ipv6; + AvahiWatch *watch_ipv4, *watch_ipv6; + + uint16_t next_id; + + /* Cache */ + AVAHI_LLIST_HEAD(AvahiWideAreaCacheEntry, cache); + AvahiHashmap *cache_by_key; + unsigned cache_n_entries; + + /* Lookups */ + AVAHI_LLIST_HEAD(AvahiWideAreaLookup, lookups); + AvahiHashmap *lookups_by_id; + AvahiHashmap *lookups_by_key; + + int cleanup_dead; + + AvahiAddress dns_servers[AVAHI_WIDE_AREA_SERVERS_MAX]; + unsigned n_dns_servers; + unsigned current_dns_server; +}; + +static AvahiWideAreaLookup* find_lookup(AvahiWideAreaLookupEngine *e, uint16_t id) { + AvahiWideAreaLookup *l; + int i = (int) id; + + assert(e); + + if (!(l = avahi_hashmap_lookup(e->lookups_by_id, &i))) + return NULL; + + assert(l->id == id); + + if (l->dead) + return NULL; + + return l; +} + +static int send_to_dns_server(AvahiWideAreaLookup *l, AvahiDnsPacket *p) { + AvahiAddress *a; + + assert(l); + assert(p); + + if (l->engine->n_dns_servers <= 0) + return -1; + + assert(l->engine->current_dns_server < l->engine->n_dns_servers); + + a = &l->engine->dns_servers[l->engine->current_dns_server]; + l->dns_server_used = *a; + + if (a->proto == AVAHI_PROTO_INET) { + + if (l->engine->fd_ipv4 < 0) + return -1; + + return avahi_send_dns_packet_ipv4(l->engine->fd_ipv4, AVAHI_IF_UNSPEC, p, NULL, &a->data.ipv4, AVAHI_DNS_PORT); + + } else { + assert(a->proto == AVAHI_PROTO_INET6); + + if (l->engine->fd_ipv6 < 0) + return -1; + + return avahi_send_dns_packet_ipv6(l->engine->fd_ipv6, AVAHI_IF_UNSPEC, p, NULL, &a->data.ipv6, AVAHI_DNS_PORT); + } +} + +static void next_dns_server(AvahiWideAreaLookupEngine *e) { + assert(e); + + e->current_dns_server++; + + if (e->current_dns_server >= e->n_dns_servers) + e->current_dns_server = 0; +} + +static void lookup_stop(AvahiWideAreaLookup *l) { + assert(l); + + l->callback = NULL; + + if (l->time_event) { + avahi_time_event_free(l->time_event); + l->time_event = NULL; + } +} + +static void sender_timeout_callback(AvahiTimeEvent *e, void *userdata) { + AvahiWideAreaLookup *l = userdata; + struct timeval tv; + + assert(l); + + /* Try another DNS server after three retries */ + if (l->n_send >= 3 && avahi_address_cmp(&l->engine->dns_servers[l->engine->current_dns_server], &l->dns_server_used) == 0) { + next_dns_server(l->engine); + + if (avahi_address_cmp(&l->engine->dns_servers[l->engine->current_dns_server], &l->dns_server_used) == 0) + /* There is no other DNS server, fail */ + l->n_send = 1000; + } + + if (l->n_send >= 6) { + avahi_log_warn(__FILE__": Query timed out."); + avahi_server_set_errno(l->engine->server, AVAHI_ERR_TIMEOUT); + l->callback(l->engine, AVAHI_BROWSER_FAILURE, AVAHI_LOOKUP_RESULT_WIDE_AREA, NULL, l->userdata); + lookup_stop(l); + return; + } + + assert(l->packet); + send_to_dns_server(l, l->packet); + l->n_send++; + + avahi_time_event_update(e, avahi_elapse_time(&tv, 1000, 0)); +} + +AvahiWideAreaLookup *avahi_wide_area_lookup_new( + AvahiWideAreaLookupEngine *e, + AvahiKey *key, + AvahiWideAreaLookupCallback callback, + void *userdata) { + + struct timeval tv; + AvahiWideAreaLookup *l, *t; + uint8_t *p; + + assert(e); + assert(key); + assert(callback); + assert(userdata); + + l = avahi_new(AvahiWideAreaLookup, 1); + l->engine = e; + l->dead = 0; + l->key = avahi_key_ref(key); + l->cname_key = avahi_key_new_cname(l->key); + l->callback = callback; + l->userdata = userdata; + + /* If more than 65K wide area quries are issued simultaneously, + * this will break. This should be limited by some higher level */ + + for (;; e->next_id++) + if (!find_lookup(e, e->next_id)) + break; /* This ID is not yet used. */ + + l->id = e->next_id++; + + /* We keep the packet around in case we need to repeat our query */ + l->packet = avahi_dns_packet_new(0); + + avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_ID, (uint16_t) l->id); + avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_FLAGS, AVAHI_DNS_FLAGS(0, 0, 0, 0, 1, 0, 0, 0, 0, 0)); + + p = avahi_dns_packet_append_key(l->packet, key, 0); + assert(p); + + avahi_dns_packet_set_field(l->packet, AVAHI_DNS_FIELD_QDCOUNT, 1); + + if (send_to_dns_server(l, l->packet) < 0) { + avahi_log_error(__FILE__": Failed to send packet."); + avahi_dns_packet_free(l->packet); + avahi_key_unref(l->key); + if (l->cname_key) + avahi_key_unref(l->cname_key); + avahi_free(l); + return NULL; + } + + l->n_send = 1; + + l->time_event = avahi_time_event_new(e->server->time_event_queue, avahi_elapse_time(&tv, 500, 0), sender_timeout_callback, l); + + avahi_hashmap_insert(e->lookups_by_id, &l->id, l); + + t = avahi_hashmap_lookup(e->lookups_by_key, l->key); + AVAHI_LLIST_PREPEND(AvahiWideAreaLookup, by_key, t, l); + avahi_hashmap_replace(e->lookups_by_key, avahi_key_ref(l->key), t); + + AVAHI_LLIST_PREPEND(AvahiWideAreaLookup, lookups, e->lookups, l); + + return l; +} + +static void lookup_destroy(AvahiWideAreaLookup *l) { + AvahiWideAreaLookup *t; + assert(l); + + lookup_stop(l); + + t = avahi_hashmap_lookup(l->engine->lookups_by_key, l->key); + AVAHI_LLIST_REMOVE(AvahiWideAreaLookup, by_key, t, l); + if (t) + avahi_hashmap_replace(l->engine->lookups_by_key, avahi_key_ref(l->key), t); + else + avahi_hashmap_remove(l->engine->lookups_by_key, l->key); + + AVAHI_LLIST_REMOVE(AvahiWideAreaLookup, lookups, l->engine->lookups, l); + + avahi_hashmap_remove(l->engine->lookups_by_id, &l->id); + avahi_dns_packet_free(l->packet); + + if (l->key) + avahi_key_unref(l->key); + + if (l->cname_key) + avahi_key_unref(l->cname_key); + + avahi_free(l); +} + +void avahi_wide_area_lookup_free(AvahiWideAreaLookup *l) { + assert(l); + + if (l->dead) + return; + + l->dead = 1; + l->engine->cleanup_dead = 1; + lookup_stop(l); +} + +void avahi_wide_area_cleanup(AvahiWideAreaLookupEngine *e) { + AvahiWideAreaLookup *l, *n; + assert(e); + + while (e->cleanup_dead) { + e->cleanup_dead = 0; + + for (l = e->lookups; l; l = n) { + n = l->lookups_next; + + if (l->dead) + lookup_destroy(l); + } + } +} + +static void cache_entry_free(AvahiWideAreaCacheEntry *c) { + AvahiWideAreaCacheEntry *t; + assert(c); + + if (c->time_event) + avahi_time_event_free(c->time_event); + + AVAHI_LLIST_REMOVE(AvahiWideAreaCacheEntry, cache, c->engine->cache, c); + + t = avahi_hashmap_lookup(c->engine->cache_by_key, c->record->key); + AVAHI_LLIST_REMOVE(AvahiWideAreaCacheEntry, by_key, t, c); + if (t) + avahi_hashmap_replace(c->engine->cache_by_key, avahi_key_ref(c->record->key), t); + else + avahi_hashmap_remove(c->engine->cache_by_key, c->record->key); + + c->engine->cache_n_entries --; + + avahi_record_unref(c->record); + avahi_free(c); +} + +static void expiry_event(AvahiTimeEvent *te, void *userdata) { + AvahiWideAreaCacheEntry *e = userdata; + + assert(te); + assert(e); + + cache_entry_free(e); +} + +static AvahiWideAreaCacheEntry* find_record_in_cache(AvahiWideAreaLookupEngine *e, AvahiRecord *r) { + AvahiWideAreaCacheEntry *c; + + assert(e); + assert(r); + + for (c = avahi_hashmap_lookup(e->cache_by_key, r->key); c; c = c->by_key_next) + if (avahi_record_equal_no_ttl(r, c->record)) + return c; + + return NULL; +} + +static void run_callbacks(AvahiWideAreaLookupEngine *e, AvahiRecord *r) { + AvahiWideAreaLookup *l; + + assert(e); + assert(r); + + for (l = avahi_hashmap_lookup(e->lookups_by_key, r->key); l; l = l->by_key_next) { + if (l->dead || !l->callback) + continue; + + l->callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA, r, l->userdata); + } + + if (r->key->clazz == AVAHI_DNS_CLASS_IN && r->key->type == AVAHI_DNS_TYPE_CNAME) { + /* It's a CNAME record, so we have to scan the all lookups to see if one matches */ + + for (l = e->lookups; l; l = l->lookups_next) { + AvahiKey *key; + + if (l->dead || !l->callback) + continue; + + if ((key = avahi_key_new_cname(l->key))) { + if (avahi_key_equal(r->key, key)) + l->callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA, r, l->userdata); + + avahi_key_unref(key); + } + } + } +} + +static void add_to_cache(AvahiWideAreaLookupEngine *e, AvahiRecord *r) { + AvahiWideAreaCacheEntry *c; + int is_new; + + assert(e); + assert(r); + + if ((c = find_record_in_cache(e, r))) { + is_new = 0; + + /* Update the existing entry */ + avahi_record_unref(c->record); + } else { + AvahiWideAreaCacheEntry *t; + + is_new = 1; + + /* Enforce cache size */ + if (e->cache_n_entries >= CACHE_ENTRIES_MAX) + /* Eventually we should improve the caching algorithm here */ + goto finish; + + c = avahi_new(AvahiWideAreaCacheEntry, 1); + c->engine = e; + c->time_event = NULL; + + AVAHI_LLIST_PREPEND(AvahiWideAreaCacheEntry, cache, e->cache, c); + + /* Add the new entry to the cache entry hash table */ + t = avahi_hashmap_lookup(e->cache_by_key, r->key); + AVAHI_LLIST_PREPEND(AvahiWideAreaCacheEntry, by_key, t, c); + avahi_hashmap_replace(e->cache_by_key, avahi_key_ref(r->key), t); + + e->cache_n_entries ++; + } + + c->record = avahi_record_ref(r); + + gettimeofday(&c->timestamp, NULL); + c->expiry = c->timestamp; + avahi_timeval_add(&c->expiry, r->ttl * 1000000); + + if (c->time_event) + avahi_time_event_update(c->time_event, &c->expiry); + else + c->time_event = avahi_time_event_new(e->server->time_event_queue, &c->expiry, expiry_event, c); + +finish: + + if (is_new) + run_callbacks(e, r); +} + +static int map_dns_error(uint16_t error) { + static const int table[16] = { + AVAHI_OK, + AVAHI_ERR_DNS_FORMERR, + AVAHI_ERR_DNS_SERVFAIL, + AVAHI_ERR_DNS_NXDOMAIN, + AVAHI_ERR_DNS_NOTIMP, + AVAHI_ERR_DNS_REFUSED, + AVAHI_ERR_DNS_YXDOMAIN, + AVAHI_ERR_DNS_YXRRSET, + AVAHI_ERR_DNS_NXRRSET, + AVAHI_ERR_DNS_NOTAUTH, + AVAHI_ERR_DNS_NOTZONE, + AVAHI_ERR_INVALID_DNS_ERROR, + AVAHI_ERR_INVALID_DNS_ERROR, + AVAHI_ERR_INVALID_DNS_ERROR, + AVAHI_ERR_INVALID_DNS_ERROR, + AVAHI_ERR_INVALID_DNS_ERROR + }; + + assert(error <= 15); + + return table[error]; +} + +static void handle_packet(AvahiWideAreaLookupEngine *e, AvahiDnsPacket *p) { + AvahiWideAreaLookup *l = NULL; + int i, r; + + AvahiBrowserEvent final_event = AVAHI_BROWSER_ALL_FOR_NOW; + + assert(e); + assert(p); + + /* Some superficial validity tests */ + if (avahi_dns_packet_check_valid(p) < 0 || avahi_dns_packet_is_query(p)) { + avahi_log_warn(__FILE__": Ignoring invalid response for wide area datagram."); + goto finish; + } + + /* Look for the lookup that issued this query */ + if (!(l = find_lookup(e, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID))) || l->dead) + goto finish; + + /* Check whether this a packet indicating a failure */ + if ((r = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_FLAGS) & 15) != 0 || + avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0) { + + avahi_server_set_errno(e->server, r == 0 ? AVAHI_ERR_NOT_FOUND : map_dns_error(r)); + /* Tell the user about the failure */ + final_event = AVAHI_BROWSER_FAILURE; + + /* We go on here, since some of the records contained in the + reply might be interesting in some way */ + } + + /* Skip over the question */ + for (i = (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); i > 0; i--) { + AvahiKey *k; + + if (!(k = avahi_dns_packet_consume_key(p, NULL))) { + avahi_log_warn(__FILE__": Wide area response packet too short or invalid while reading question key. (Maybe an UTF8 problem?)"); + avahi_server_set_errno(e->server, AVAHI_ERR_INVALID_PACKET); + final_event = AVAHI_BROWSER_FAILURE; + goto finish; + } + + avahi_key_unref(k); + } + + /* Process responses */ + for (i = (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) + + (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) + + (int) avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT); i > 0; i--) { + + AvahiRecord *rr; + + if (!(rr = avahi_dns_packet_consume_record(p, NULL))) { + avahi_log_warn(__FILE__": Wide area response packet too short or invalid while reading response ecord. (Maybe an UTF8 problem?)"); + avahi_server_set_errno(e->server, AVAHI_ERR_INVALID_PACKET); + final_event = AVAHI_BROWSER_FAILURE; + goto finish; + } + + add_to_cache(e, rr); + avahi_record_unref(rr); + } + +finish: + + if (l && !l->dead) { + if (l->callback) + l->callback(e, final_event, AVAHI_LOOKUP_RESULT_WIDE_AREA, NULL, l->userdata); + + lookup_stop(l); + } +} + +static void socket_event(AVAHI_GCC_UNUSED AvahiWatch *w, int fd, AVAHI_GCC_UNUSED AvahiWatchEvent events, void *userdata) { + AvahiWideAreaLookupEngine *e = userdata; + AvahiDnsPacket *p = NULL; + + if (fd == e->fd_ipv4) + p = avahi_recv_dns_packet_ipv4(e->fd_ipv4, NULL, NULL, NULL, NULL, NULL); + else { + assert(fd == e->fd_ipv6); + p = avahi_recv_dns_packet_ipv6(e->fd_ipv6, NULL, NULL, NULL, NULL, NULL); + } + + if (p) { + handle_packet(e, p); + avahi_dns_packet_free(p); + } +} + +AvahiWideAreaLookupEngine *avahi_wide_area_engine_new(AvahiServer *s) { + AvahiWideAreaLookupEngine *e; + + assert(s); + + e = avahi_new(AvahiWideAreaLookupEngine, 1); + e->server = s; + e->cleanup_dead = 0; + + /* Create sockets */ + e->fd_ipv4 = avahi_open_unicast_socket_ipv4(); + e->fd_ipv6 = avahi_open_unicast_socket_ipv6(); + + if (e->fd_ipv4 < 0 && e->fd_ipv6 < 0) { + avahi_log_error(__FILE__": Failed to create wide area sockets: %s", strerror(errno)); + + if (e->fd_ipv6 >= 0) + close(e->fd_ipv6); + + if (e->fd_ipv4 >= 0) + close(e->fd_ipv4); + + avahi_free(e); + return NULL; + } + + /* Create watches */ + + e->watch_ipv4 = e->watch_ipv6 = NULL; + + if (e->fd_ipv4 >= 0) + e->watch_ipv4 = s->poll_api->watch_new(e->server->poll_api, e->fd_ipv4, AVAHI_WATCH_IN, socket_event, e); + if (e->fd_ipv6 >= 0) + e->watch_ipv6 = s->poll_api->watch_new(e->server->poll_api, e->fd_ipv6, AVAHI_WATCH_IN, socket_event, e); + + e->n_dns_servers = e->current_dns_server = 0; + e->next_id = (uint16_t) rand(); + + /* Initialize cache */ + AVAHI_LLIST_HEAD_INIT(AvahiWideAreaCacheEntry, e->cache); + e->cache_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL); + e->cache_n_entries = 0; + + /* Initialize lookup list */ + e->lookups_by_id = avahi_hashmap_new((AvahiHashFunc) avahi_int_hash, (AvahiEqualFunc) avahi_int_equal, NULL, NULL); + e->lookups_by_key = avahi_hashmap_new((AvahiHashFunc) avahi_key_hash, (AvahiEqualFunc) avahi_key_equal, (AvahiFreeFunc) avahi_key_unref, NULL); + AVAHI_LLIST_HEAD_INIT(AvahiWideAreaLookup, e->lookups); + + return e; +} + +void avahi_wide_area_engine_free(AvahiWideAreaLookupEngine *e) { + assert(e); + + avahi_wide_area_clear_cache(e); + + while (e->lookups) + lookup_destroy(e->lookups); + + avahi_hashmap_free(e->cache_by_key); + avahi_hashmap_free(e->lookups_by_id); + avahi_hashmap_free(e->lookups_by_key); + + if (e->watch_ipv4) + e->server->poll_api->watch_free(e->watch_ipv4); + + if (e->watch_ipv6) + e->server->poll_api->watch_free(e->watch_ipv6); + + if (e->fd_ipv6 >= 0) + close(e->fd_ipv6); + + if (e->fd_ipv4 >= 0) + close(e->fd_ipv4); + + avahi_free(e); +} + +void avahi_wide_area_clear_cache(AvahiWideAreaLookupEngine *e) { + assert(e); + + while (e->cache) + cache_entry_free(e->cache); + + assert(e->cache_n_entries == 0); +} + +void avahi_wide_area_set_servers(AvahiWideAreaLookupEngine *e, const AvahiAddress *a, unsigned n) { + assert(e); + + if (a) { + for (e->n_dns_servers = 0; n > 0 && e->n_dns_servers < AVAHI_WIDE_AREA_SERVERS_MAX; a++, n--) + if ((a->proto == AVAHI_PROTO_INET && e->fd_ipv4 >= 0) || (a->proto == AVAHI_PROTO_INET6 && e->fd_ipv6 >= 0)) + e->dns_servers[e->n_dns_servers++] = *a; + } else { + assert(n == 0); + e->n_dns_servers = 0; + } + + e->current_dns_server = 0; + + avahi_wide_area_clear_cache(e); +} + +void avahi_wide_area_cache_dump(AvahiWideAreaLookupEngine *e, AvahiDumpCallback callback, void* userdata) { + AvahiWideAreaCacheEntry *c; + + assert(e); + assert(callback); + + callback(";; WIDE AREA CACHE ;;; ", userdata); + + for (c = e->cache; c; c = c->cache_next) { + char *t = avahi_record_to_string(c->record); + callback(t, userdata); + avahi_free(t); + } +} + +unsigned avahi_wide_area_scan_cache(AvahiWideAreaLookupEngine *e, AvahiKey *key, AvahiWideAreaLookupCallback callback, void *userdata) { + AvahiWideAreaCacheEntry *c; + AvahiKey *cname_key; + unsigned n = 0; + + assert(e); + assert(key); + assert(callback); + + for (c = avahi_hashmap_lookup(e->cache_by_key, key); c; c = c->by_key_next) { + callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA|AVAHI_LOOKUP_RESULT_CACHED, c->record, userdata); + n++; + } + + if ((cname_key = avahi_key_new_cname(key))) { + + for (c = avahi_hashmap_lookup(e->cache_by_key, cname_key); c; c = c->by_key_next) { + callback(e, AVAHI_BROWSER_NEW, AVAHI_LOOKUP_RESULT_WIDE_AREA|AVAHI_LOOKUP_RESULT_CACHED, c->record, userdata); + n++; + } + + avahi_key_unref(cname_key); + } + + return n; +} + +int avahi_wide_area_has_servers(AvahiWideAreaLookupEngine *e) { + assert(e); + + return e->n_dns_servers > 0; +} + + + diff --git a/trunk/avahi-core/wide-area.h b/trunk/avahi-core/wide-area.h new file mode 100644 index 0000000..1af613b --- /dev/null +++ b/trunk/avahi-core/wide-area.h @@ -0,0 +1,54 @@ +#ifndef foowideareahfoo +#define foowideareahfoo + +/* $Id$ */ + +/*** + This file is part of avahi. + + avahi 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. + + avahi 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 avahi; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include "lookup.h" +#include "browse.h" + +typedef struct AvahiWideAreaLookupEngine AvahiWideAreaLookupEngine; +typedef struct AvahiWideAreaLookup AvahiWideAreaLookup; + +typedef void (*AvahiWideAreaLookupCallback)( + AvahiWideAreaLookupEngine *e, + AvahiBrowserEvent event, + AvahiLookupResultFlags flags, + AvahiRecord *r, + void *userdata); + +AvahiWideAreaLookupEngine *avahi_wide_area_engine_new(AvahiServer *s); +void avahi_wide_area_engine_free(AvahiWideAreaLookupEngine *e); + +unsigned avahi_wide_area_scan_cache(AvahiWideAreaLookupEngine *e, AvahiKey *key, AvahiWideAreaLookupCallback callback, void *userdata); +void avahi_wide_area_cache_dump(AvahiWideAreaLookupEngine *e, AvahiDumpCallback callback, void* userdata); +void avahi_wide_area_set_servers(AvahiWideAreaLookupEngine *e, const AvahiAddress *a, unsigned n); +void avahi_wide_area_clear_cache(AvahiWideAreaLookupEngine *e); +void avahi_wide_area_cleanup(AvahiWideAreaLookupEngine *e); +int avahi_wide_area_has_servers(AvahiWideAreaLookupEngine *e); + +AvahiWideAreaLookup *avahi_wide_area_lookup_new(AvahiWideAreaLookupEngine *e, AvahiKey *key, AvahiWideAreaLookupCallback callback, void *userdata); +void avahi_wide_area_lookup_free(AvahiWideAreaLookup *q); + + + +#endif + |