From 4ff0807c04fcc239de52a793bceb88e7f3408f3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 3 Jun 2005 12:45:47 +0000 Subject: * implement reflection (including legacy unicast reflection) * implement a history in the probe scheduler git-svn-id: file:///home/lennart/svn/public/avahi/trunk@92 941a03a8-eaeb-0310-b9a0-b1bbd8fe43fe --- avahi-core/address.c | 15 ++ avahi-core/address.h | 2 + avahi-core/cache.c | 7 +- avahi-core/cache.h | 3 +- avahi-core/core.h | 1 + avahi-core/dns.c | 2 - avahi-core/dns.h | 1 - avahi-core/iface.c | 14 ++ avahi-core/iface.h | 2 + avahi-core/probe-sched.c | 128 +++++++++++++++-- avahi-core/server.c | 360 ++++++++++++++++++++++++++++++++++++++++++----- avahi-core/server.h | 25 +++- avahi-core/socket.c | 95 +++++++++++++ avahi-core/socket.h | 3 + 14 files changed, 604 insertions(+), 54 deletions(-) (limited to 'avahi-core') diff --git a/avahi-core/address.c b/avahi-core/address.c index 34f8581..3484707 100644 --- a/avahi-core/address.c +++ b/avahi-core/address.c @@ -149,3 +149,18 @@ guint16 avahi_port_from_sockaddr(const struct sockaddr* sa) { else return ntohs(((struct sockaddr_in6*) sa)->sin6_port); } + +gboolean avahi_address_is_ipv4_in_ipv6(const AvahiAddress *a) { + static const guint8 ipv4_in_ipv6[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF }; + + g_assert(a); + + if (a->family != AF_INET6) + return FALSE; + + return memcmp(a->data.ipv6.address, ipv4_in_ipv6, sizeof(ipv4_in_ipv6)) == 0; +} + diff --git a/avahi-core/address.h b/avahi-core/address.h index b88aba2..22cb74b 100644 --- a/avahi-core/address.h +++ b/avahi-core/address.h @@ -57,4 +57,6 @@ gchar* avahi_reverse_lookup_name_ipv4(const AvahiIPv4Address *a); gchar* avahi_reverse_lookup_name_ipv6_arpa(const AvahiIPv6Address *a); gchar* avahi_reverse_lookup_name_ipv6_int(const AvahiIPv6Address *a); +gboolean avahi_address_is_ipv4_in_ipv6(const AvahiAddress *a); + #endif diff --git a/avahi-core/cache.c b/avahi-core/cache.c index c5e7e31..433f5a8 100644 --- a/avahi-core/cache.c +++ b/avahi-core/cache.c @@ -230,7 +230,7 @@ static void expire_in_one_second(AvahiCache *c, AvahiCacheEntry *e) { update_time_event(c, e); } -void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const AvahiAddress *a) { +void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean cache_flush, const AvahiAddress *a) { /* gchar *txt; */ g_assert(c); @@ -257,7 +257,7 @@ void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const Av if ((first = avahi_cache_lookup_key(c, r->key))) { - if (unique) { + if (cache_flush) { /* For unique entries drop all entries older than one second */ for (e = first; e; e = e->by_key_next) { @@ -288,7 +288,7 @@ void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const Av /* Update the record */ avahi_record_unref(e->record); e->record = avahi_record_ref(r); - + } else { /* No entry found, therefore we create a new one */ @@ -314,6 +314,7 @@ void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const Av e->timestamp = now; next_expiry(c, e, 80); e->state = AVAHI_CACHE_VALID; + e->cache_flush = cache_flush; } } diff --git a/avahi-core/cache.h b/avahi-core/cache.h index 4415c3c..7496568 100644 --- a/avahi-core/cache.h +++ b/avahi-core/cache.h @@ -46,6 +46,7 @@ struct AvahiCacheEntry { AvahiRecord *record; GTimeVal timestamp; GTimeVal expiry; + gboolean cache_flush; AvahiAddress origin; @@ -72,7 +73,7 @@ void avahi_cache_free(AvahiCache *c); AvahiCacheEntry *avahi_cache_lookup_key(AvahiCache *c, AvahiKey *k); AvahiCacheEntry *avahi_cache_lookup_record(AvahiCache *c, AvahiRecord *r); -void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean unique, const AvahiAddress *a); +void avahi_cache_update(AvahiCache *c, AvahiRecord *r, gboolean cache_flush, const AvahiAddress *a); void avahi_cache_dump(AvahiCache *c, FILE *f); diff --git a/avahi-core/core.h b/avahi-core/core.h index addfebe..9e2f66b 100644 --- a/avahi-core/core.h +++ b/avahi-core/core.h @@ -80,6 +80,7 @@ typedef struct AvahiServerConfig { gboolean announce_domain; /**< Announce the local domain for browsing */ gboolean 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. */ gboolean enable_reflector; /**< Reflect incoming mDNS traffic to all local networks. This allows mDNS based network browsing beyond ethernet borders */ + gboolean ipv_reflect; /**< if enable_reflector is TRUE, enable/disable reflecting between IPv4 and IPv6 */ } AvahiServerConfig; /** Allocate a new mDNS responder object. */ diff --git a/avahi-core/dns.c b/avahi-core/dns.c index 41d645f..4294c1e 100644 --- a/avahi-core/dns.c +++ b/avahi-core/dns.c @@ -137,8 +137,6 @@ void avahi_dns_packet_inc_field(AvahiDnsPacket *p, guint index) { avahi_dns_packet_set_field(p, index, avahi_dns_packet_get_field(p, index) + 1); } - - guint8* avahi_dns_packet_append_name(AvahiDnsPacket *p, const gchar *name) { guint8 *d, *saved_ptr = NULL; guint saved_size; diff --git a/avahi-core/dns.h b/avahi-core/dns.h index 24f4ced..a6d7895 100644 --- a/avahi-core/dns.h +++ b/avahi-core/dns.h @@ -34,7 +34,6 @@ typedef struct AvahiDnsPacket { GHashTable *name_table; /* for name compression */ } AvahiDnsPacket; - #define AVAHI_DNS_PACKET_DATA(p) (((guint8*) p) + sizeof(AvahiDnsPacket)) AvahiDnsPacket* avahi_dns_packet_new(guint mtu); diff --git a/avahi-core/iface.c b/avahi-core/iface.c index ff7106b..d74a96d 100644 --- a/avahi-core/iface.c +++ b/avahi-core/iface.c @@ -646,3 +646,17 @@ void avahi_update_host_rrs(AvahiInterfaceMonitor *m, gboolean remove) { for (i = m->interfaces; i; i = i->interface_next) update_interface_rr(m, i, remove); } + +gboolean avahi_address_is_local(AvahiInterfaceMonitor *m, const AvahiAddress *a) { + AvahiInterface *i; + AvahiInterfaceAddress *ia; + g_assert(m); + g_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 TRUE; + + return FALSE; +} diff --git a/avahi-core/iface.h b/avahi-core/iface.h index d18bbe6..50c130e 100644 --- a/avahi-core/iface.h +++ b/avahi-core/iface.h @@ -127,4 +127,6 @@ void avahi_interface_monitor_walk(AvahiInterfaceMonitor *m, gint index, guchar p void avahi_update_host_rrs(AvahiInterfaceMonitor *m, gboolean remove); +gboolean avahi_address_is_local(AvahiInterfaceMonitor *m, const AvahiAddress *a); + #endif diff --git a/avahi-core/probe-sched.c b/avahi-core/probe-sched.c index 9089bba..a162dd7 100644 --- a/avahi-core/probe-sched.c +++ b/avahi-core/probe-sched.c @@ -26,7 +26,8 @@ #include "probe-sched.h" #include "util.h" -#define AVAHI_PROBE_DEFER_MSEC 70 +#define AVAHI_PROBE_HISTORY_MSEC 150 +#define AVAHI_PROBE_DEFER_MSEC 50 typedef struct AvahiProbeJob AvahiProbeJob; @@ -35,6 +36,7 @@ struct AvahiProbeJob { AvahiTimeEvent *time_event; gboolean chosen; /* Use for packet assembling */ + gboolean done; GTimeVal delivery; AvahiRecord *record; @@ -47,9 +49,10 @@ struct AvahiProbeScheduler { AvahiTimeEventQueue *time_event_queue; AVAHI_LLIST_HEAD(AvahiProbeJob, jobs); + AVAHI_LLIST_HEAD(AvahiProbeJob, history); }; -static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record) { +static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record, gboolean done) { AvahiProbeJob *pj; g_assert(s); @@ -60,8 +63,11 @@ static AvahiProbeJob* job_new(AvahiProbeScheduler *s, AvahiRecord *record) { pj->record = avahi_record_ref(record); pj->time_event = NULL; pj->chosen = FALSE; - - AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->jobs, pj); + + if ((pj->done = done)) + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj); + else + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->jobs, pj); return pj; } @@ -72,12 +78,45 @@ static void job_free(AvahiProbeScheduler *s, AvahiProbeJob *pj) { if (pj->time_event) avahi_time_event_queue_remove(s->time_event_queue, pj->time_event); - AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj); + 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); g_free(pj); } +static void elapse_callback(AvahiTimeEvent *e, gpointer data); + +static void job_set_elapse_time(AvahiProbeScheduler *s, AvahiProbeJob *pj, guint msec, guint jitter) { + GTimeVal tv; + + g_assert(s); + g_assert(pj); + + avahi_elapse_time(&tv, msec, jitter); + + if (pj->time_event) + avahi_time_event_queue_update(s->time_event_queue, pj->time_event, &tv); + else + pj->time_event = avahi_time_event_queue_add(s->time_event_queue, &tv, elapse_callback, pj); +} + +static void job_mark_done(AvahiProbeScheduler *s, AvahiProbeJob *pj) { + g_assert(s); + g_assert(pj); + + g_assert(!pj->done); + + AVAHI_LLIST_REMOVE(AvahiProbeJob, jobs, s->jobs, pj); + AVAHI_LLIST_PREPEND(AvahiProbeJob, jobs, s->history, pj); + + pj->done = TRUE; + + job_set_elapse_time(s, pj, AVAHI_PROBE_HISTORY_MSEC, 0); + g_get_current_time(&pj->delivery); +} AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *i) { AvahiProbeScheduler *s; @@ -89,6 +128,7 @@ AvahiProbeScheduler *avahi_probe_scheduler_new(AvahiInterface *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; } @@ -105,6 +145,8 @@ void avahi_probe_scheduler_clear(AvahiProbeScheduler *s) { while (s->jobs) job_free(s, s->jobs); + while (s->history) + job_free(s, s->history); } static gboolean packet_add_probe_query(AvahiProbeScheduler *s, AvahiDnsPacket *p, AvahiProbeJob *pj) { @@ -166,6 +208,12 @@ static void elapse_callback(AvahiTimeEvent *e, gpointer data) { g_assert(pj); s = pj->scheduler; + if (pj->done) { + /* Lets remove it from the history */ + job_free(s, pj); + return; + } + p = avahi_dns_packet_new_query(s->interface->hardware->mtu); n = 1; @@ -201,7 +249,7 @@ static void elapse_callback(AvahiTimeEvent *e, gpointer data) { g_warning("Probe record too large, cannot send"); avahi_dns_packet_free(p); - job_free(s, pj); + job_mark_done(s, pj); return; } @@ -240,7 +288,7 @@ static void elapse_callback(AvahiTimeEvent *e, gpointer data) { break; } - job_free(s, pj); + job_mark_done(s, pj); n ++; } @@ -252,6 +300,47 @@ static void elapse_callback(AvahiTimeEvent *e, gpointer data) { avahi_dns_packet_free(p); } +static AvahiProbeJob* find_scheduled_job(AvahiProbeScheduler *s, AvahiRecord *record) { + AvahiProbeJob *pj; + + g_assert(s); + g_assert(record); + + for (pj = s->jobs; pj; pj = pj->jobs_next) { + g_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; + + g_assert(s); + g_assert(record); + + for (pj = s->history; pj; pj = pj->jobs_next) { + g_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; +} + gboolean avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, gboolean immediately) { AvahiProbeJob *pj; GTimeVal tv; @@ -259,15 +348,30 @@ gboolean avahi_probe_scheduler_post(AvahiProbeScheduler *s, AvahiRecord *record, g_assert(s); g_assert(record); g_assert(!avahi_key_is_pattern(record->key)); + + if ((pj = find_history_job(s, record))) + return FALSE; avahi_elapse_time(&tv, immediately ? 0 : AVAHI_PROBE_DEFER_MSEC, 0); - /* Create a new job and schedule it */ - pj = job_new(s, record); - pj->delivery = tv; - pj->time_event = avahi_time_event_queue_add(s->time_event_queue, &pj->delivery, elapse_callback, pj); + 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_queue_update(s->time_event_queue, pj->time_event, &pj->delivery); + } + + return TRUE; + } else { + /* Create a new job and schedule it */ + pj = job_new(s, record, FALSE); + pj->delivery = tv; + pj->time_event = avahi_time_event_queue_add(s->time_event_queue, &pj->delivery, elapse_callback, pj); + /* g_message("Accepted new probe job."); */ - return TRUE; + return TRUE; + } } diff --git a/avahi-core/server.c b/avahi-core/server.c index e95fcca..3ccaddc 100644 --- a/avahi-core/server.c +++ b/avahi-core/server.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "server.h" #include "util.h" @@ -477,10 +478,22 @@ static void reflect_response(AvahiServer *s, AvahiInterface *i, AvahiRecord *r, return; for (j = s->monitor->interfaces; j; j = j->interface_next) - if (j != i) + if (j != i && (s->config.ipv_reflect || j->protocol == i->protocol)) avahi_interface_post_response(j, r, flush_cache, NULL, TRUE); } +static gpointer reflect_cache_walk_callback(AvahiCache *c, AvahiKey *pattern, AvahiCacheEntry *e, gpointer userdata) { + AvahiServer *s = userdata; + + g_assert(c); + g_assert(pattern); + g_assert(e); + g_assert(s); + + avahi_record_list_push(s->record_list, e->record, e->cache_flush, FALSE, FALSE); + return NULL; +} + static void reflect_query(AvahiServer *s, AvahiInterface *i, AvahiKey *k) { AvahiInterface *j; @@ -492,8 +505,15 @@ static void reflect_query(AvahiServer *s, AvahiInterface *i, AvahiKey *k) { return; for (j = s->monitor->interfaces; j; j = j->interface_next) - if (j != i) + if (j != i && (s->config.ipv_reflect || j->protocol == i->protocol)) { + /* Post the query to other networks */ avahi_interface_post_query(j, k, TRUE); + + /* 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) { @@ -507,11 +527,11 @@ static void reflect_probe(AvahiServer *s, AvahiInterface *i, AvahiRecord *r) { return; for (j = s->monitor->interfaces; j; j = j->interface_next) - if (j != i) + if (j != i && (s->config.ipv_reflect || j->protocol == i->protocol)) avahi_interface_post_probe(j, r, TRUE); } -static void handle_query(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, guint16 port, gboolean legacy_unicast) { +static void handle_query_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, guint16 port, gboolean legacy_unicast) { guint n; g_assert(s); @@ -533,7 +553,8 @@ static void handle_query(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, c goto fail; } - reflect_query(s, i, key); + if (!legacy_unicast) + reflect_query(s, i, key); avahi_query_scheduler_incoming(i->query_scheduler, key); avahi_server_prepare_matching_responses(s, i, key, unicast_response); avahi_key_unref(key); @@ -585,7 +606,7 @@ fail: } -static void handle_response(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a) { +static void handle_response_packet(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a) { guint n; g_assert(s); @@ -622,7 +643,182 @@ static void handle_response(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i } } -static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, struct sockaddr *sa, gint iface, gint ttl) { +static AvahiLegacyUnicastReflectSlot* allocate_slot(AvahiServer *s) { + guint n, index = (guint) -1; + AvahiLegacyUnicastReflectSlot *slot; + + g_assert(s); + + if (!s->legacy_unicast_reflect_slots) + s->legacy_unicast_reflect_slots = g_new0(AvahiLegacyUnicastReflectSlot*, AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS); + + for (n = 0; n < AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS; n++, s->legacy_unicast_reflect_id++) { + index = s->legacy_unicast_reflect_id % AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS; + + if (!s->legacy_unicast_reflect_slots[index]) + break; + } + + if (index == (guint) -1 || s->legacy_unicast_reflect_slots[index]) + return NULL; + + slot = s->legacy_unicast_reflect_slots[index] = g_new(AvahiLegacyUnicastReflectSlot, 1); + slot->id = s->legacy_unicast_reflect_id++; + slot->server = s; + return slot; +} + +static void deallocate_slot(AvahiServer *s, AvahiLegacyUnicastReflectSlot *slot) { + guint index; + + g_assert(s); + g_assert(slot); + + index = slot->id % AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS; + + g_assert(s->legacy_unicast_reflect_slots[index] == slot); + + avahi_time_event_queue_remove(s->time_event_queue, slot->time_event); + + g_free(slot); + s->legacy_unicast_reflect_slots[index] = NULL; +} + +static void free_slots(AvahiServer *s) { + guint index; + g_assert(s); + + if (!s->legacy_unicast_reflect_slots) + return; + + for (index = 0; index < AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS; index ++) + if (s->legacy_unicast_reflect_slots[index]) + deallocate_slot(s, s->legacy_unicast_reflect_slots[index]); + + g_free(s->legacy_unicast_reflect_slots); + s->legacy_unicast_reflect_slots = NULL; +} + +static AvahiLegacyUnicastReflectSlot* find_slot(AvahiServer *s, guint16 id) { + guint index; + + g_assert(s); + + if (!s->legacy_unicast_reflect_slots) + return NULL; + + index = id % AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS; + + if (!s->legacy_unicast_reflect_slots[index] || s->legacy_unicast_reflect_slots[index]->id != id) + return NULL; + + return s->legacy_unicast_reflect_slots[index]; +} + +static void legacy_unicast_reflect_slot_timeout(AvahiTimeEvent *e, void *userdata) { + AvahiLegacyUnicastReflectSlot *slot = userdata; + + g_assert(e); + g_assert(slot); + g_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, guint16 port) { + AvahiLegacyUnicastReflectSlot *slot; + AvahiInterface *j; + + g_assert(s); + g_assert(p); + g_assert(i); + g_assert(a); + g_assert(port > 0); + g_assert(i->protocol == a->family); + + if (!s->config.enable_reflector) + return; + +/* g_message("legacy unicast reflectr"); */ + + /* 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 */ + g_warning("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_queue_add(s->time_event_queue, &slot->elapse_time, legacy_unicast_reflect_slot_timeout, slot); + + /* Patch the packet with our new locally generatedt 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_relevant(j) && + j != i && + (s->config.ipv_reflect || j->protocol == i->protocol)) { + + if (j->protocol == AF_INET && s->fd_legacy_unicast_ipv4 >= 0) { + avahi_send_dns_packet_ipv4(s->fd_legacy_unicast_ipv4, j->hardware->index, p, NULL, 0); + } else if (j->protocol == AF_INET6 && s->fd_legacy_unicast_ipv6 >= 0) + avahi_send_dns_packet_ipv6(s->fd_legacy_unicast_ipv6, j->hardware->index, p, NULL, 0); + } + + /* Reset the id */ + avahi_dns_packet_set_field(p, AVAHI_DNS_FIELD_ID, slot->original_id); +} + +static gboolean originates_from_local_legacy_unicast_socket(AvahiServer *s, const struct sockaddr *sa) { + AvahiAddress a; + g_assert(s); + g_assert(sa); + + if (!s->config.enable_reflector) + return FALSE; + + avahi_address_from_sockaddr(sa, &a); + + if (!avahi_address_is_local(s->monitor, &a)) + return FALSE; + + if (sa->sa_family == AF_INET && s->fd_legacy_unicast_ipv4 >= 0) { + struct sockaddr_in lsa; + socklen_t l = sizeof(lsa); + + if (getsockname(s->fd_legacy_unicast_ipv4, &lsa, &l) != 0) + g_warning("getsockname(): %s", strerror(errno)); + else + return lsa.sin_port == ((struct sockaddr_in*) sa)->sin_port; + + } + + if (sa->sa_family == AF_INET6 && s->fd_legacy_unicast_ipv6 >= 0) { + struct sockaddr_in6 lsa; + socklen_t l = sizeof(lsa); + + if (getsockname(s->fd_legacy_unicast_ipv6, &lsa, &l) != 0) + g_warning("getsockname(): %s", strerror(errno)); + else + return lsa.sin6_port == ((struct sockaddr_in6*) sa)->sin6_port; + } + + return FALSE; +} + +static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, const struct sockaddr *sa, gint iface, gint ttl) { AvahiInterface *i; AvahiAddress a; guint16 port; @@ -640,26 +836,22 @@ static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, struct sockaddr * /* g_message("new packet recieved on interface '%s.%i'.", i->hardware->name, i->protocol); */ - if (sa->sa_family == AF_INET6) { - static const guint8 ipv4_in_ipv6[] = { - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF }; - + port = avahi_port_from_sockaddr(sa); + avahi_address_from_sockaddr(sa, &a); + + if (avahi_address_is_ipv4_in_ipv6(&a)) /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */ + return; - if (memcmp(((struct sockaddr_in6*) sa)->sin6_addr.s6_addr, ipv4_in_ipv6, sizeof(ipv4_in_ipv6)) == 0) - return; - } + if (originates_from_local_legacy_unicast_socket(s, sa)) + /* This originates from our local reflector, so let's ignore it */ + return; if (avahi_dns_packet_check_valid(p) < 0) { g_warning("Recieved invalid packet."); return; } - port = avahi_port_from_sockaddr(sa); - avahi_address_from_sockaddr(sa, &a); - if (avahi_dns_packet_is_query(p)) { gboolean legacy_unicast = FALSE; @@ -680,7 +872,10 @@ static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, struct sockaddr * legacy_unicast = TRUE; } - handle_query(s, p, i, &a, port, legacy_unicast); + if (legacy_unicast) + reflect_legacy_unicast_query_packet(s, p, i, &a, port); + + handle_query_packet(s, p, i, &a, port, legacy_unicast); /* g_message("Handled query"); */ } else { @@ -703,11 +898,61 @@ static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, struct sockaddr * return; } - handle_response(s, p, i, &a); + handle_response_packet(s, p, i, &a); /* g_message("Handled response"); */ } } +static void dispatch_legacy_unicast_packet(AvahiServer *s, AvahiDnsPacket *p, const struct sockaddr *sa, gint iface, gint ttl) { + AvahiInterface *i, *j; + AvahiAddress a; + guint16 port; + AvahiLegacyUnicastReflectSlot *slot; + + g_assert(s); + g_assert(p); + g_assert(sa); + g_assert(iface > 0); + + if (!(i = avahi_interface_monitor_get_interface(s->monitor, iface, sa->sa_family)) || + !avahi_interface_relevant(i)) { + g_warning("Recieved packet from invalid interface."); + return; + } + +/* g_message("new legacy unicast packet recieved on interface '%s.%i'.", i->hardware->name, i->protocol); */ + + port = avahi_port_from_sockaddr(sa); + avahi_address_from_sockaddr(sa, &a); + + if (avahi_address_is_ipv4_in_ipv6(&a)) + /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */ + return; + + if (avahi_dns_packet_check_valid(p) < 0 || avahi_dns_packet_is_query(p)) { + g_warning("Recieved invalid packet."); + return; + } + + if (!(slot = find_slot(s, avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ID)))) { + g_warning("Recieved legacy unicast response with unknown id"); + return; + } + + if (!(j = avahi_interface_monitor_get_interface(s->monitor, slot->interface, slot->address.family)) || + !avahi_interface_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 work(AvahiServer *s) { struct sockaddr_in6 sa6; struct sockaddr_in sa; @@ -717,19 +962,33 @@ static void work(AvahiServer *s) { g_assert(s); - if (s->pollfd_ipv4.revents & G_IO_IN) { + if (s->fd_ipv4 >= 0 && (s->pollfd_ipv4.revents & G_IO_IN)) { if ((p = avahi_recv_dns_packet_ipv4(s->fd_ipv4, &sa, &iface, &ttl))) { dispatch_packet(s, p, (struct sockaddr*) &sa, iface, ttl); avahi_dns_packet_free(p); } } - if (s->pollfd_ipv6.revents & G_IO_IN) { + if (s->fd_ipv6 >= 0 && (s->pollfd_ipv6.revents & G_IO_IN)) { if ((p = avahi_recv_dns_packet_ipv6(s->fd_ipv6, &sa6, &iface, &ttl))) { dispatch_packet(s, p, (struct sockaddr*) &sa6, iface, ttl); avahi_dns_packet_free(p); } } + + if (s->fd_legacy_unicast_ipv4 >= 0 && (s->pollfd_legacy_unicast_ipv4.revents & G_IO_IN)) { + if ((p = avahi_recv_dns_packet_ipv4(s->fd_legacy_unicast_ipv4, &sa, &iface, &ttl))) { + dispatch_legacy_unicast_packet(s, p, (struct sockaddr*) &sa, iface, ttl); + avahi_dns_packet_free(p); + } + } + + if (s->fd_legacy_unicast_ipv6 >= 0 && (s->pollfd_legacy_unicast_ipv6.revents & G_IO_IN)) { + if ((p = avahi_recv_dns_packet_ipv6(s->fd_legacy_unicast_ipv6, &sa6, &iface, &ttl))) { + dispatch_legacy_unicast_packet(s, p, (struct sockaddr*) &sa6, iface, ttl); + avahi_dns_packet_free(p); + } + } } static gboolean prepare_func(GSource *source, gint *timeout) { @@ -742,12 +1001,23 @@ static gboolean prepare_func(GSource *source, gint *timeout) { static gboolean check_func(GSource *source) { AvahiServer* s; + gushort revents = 0; + g_assert(source); s = *((AvahiServer**) (((guint8*) source) + sizeof(GSource))); g_assert(s); + + if (s->fd_ipv4 >= 0) + revents |= s->pollfd_ipv4.revents; + if (s->fd_ipv6 >= 0) + revents |= s->pollfd_ipv6.revents; + if (s->fd_legacy_unicast_ipv4 >= 0) + revents |= s->pollfd_legacy_unicast_ipv4.revents; + if (s->fd_legacy_unicast_ipv6 >= 0) + revents |= s->pollfd_legacy_unicast_ipv6.revents; - return (s->pollfd_ipv4.revents | s->pollfd_ipv6.revents) & (G_IO_IN | G_IO_HUP | G_IO_ERR); + return !!(revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)); } static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer user_data) { @@ -946,6 +1216,18 @@ void avahi_server_set_domain_name(AvahiServer *s, const gchar *domain_name) { delayed_register_stuff(s); } + +static void prepare_pollfd(AvahiServer *s, GPollFD *pollfd, gint fd) { + g_assert(s); + g_assert(pollfd); + g_assert(fd >= 0); + + memset(pollfd, 0, sizeof(GPollFD)); + pollfd->fd = fd; + pollfd->events = G_IO_IN|G_IO_ERR|G_IO_HUP; + g_source_add_poll(s->source, pollfd); +} + AvahiServer *avahi_server_new(GMainContext *c, const AvahiServerConfig *sc, AvahiServerCallback callback, gpointer userdata) { AvahiServer *s; @@ -981,6 +1263,9 @@ AvahiServer *avahi_server_new(GMainContext *c, const AvahiServerConfig *sc, Avah g_message("Failed to create IPv4 socket, proceeding in IPv6 only mode"); else if (s->fd_ipv6 < 0 && s->config.use_ipv6) g_message("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_legacy_unicast_socket_ipv4() : -1; + s->fd_legacy_unicast_ipv6 = s->fd_ipv6 >= 0 && s->config.enable_reflector ? avahi_open_legacy_unicast_socket_ipv6() : -1; if (c) g_main_context_ref(s->context = c); @@ -991,16 +1276,15 @@ AvahiServer *avahi_server_new(GMainContext *c, const AvahiServerConfig *sc, Avah s->source = g_source_new(&source_funcs, sizeof(GSource) + sizeof(AvahiServer*)); *((AvahiServer**) (((guint8*) s->source) + sizeof(GSource))) = s; - memset(&s->pollfd_ipv4, 0, sizeof(s->pollfd_ipv4)); - s->pollfd_ipv4.fd = s->fd_ipv4; - s->pollfd_ipv4.events = G_IO_IN|G_IO_ERR|G_IO_HUP; - g_source_add_poll(s->source, &s->pollfd_ipv4); + if (s->fd_ipv4 >= 0) + prepare_pollfd(s, &s->pollfd_ipv4, s->fd_ipv4); + if (s->fd_ipv6 >= 0) + prepare_pollfd(s, &s->pollfd_ipv6, s->fd_ipv6); + if (s->fd_legacy_unicast_ipv4 >= 0) + prepare_pollfd(s, &s->pollfd_legacy_unicast_ipv4, s->fd_legacy_unicast_ipv4); + if (s->fd_legacy_unicast_ipv6 >= 0) + prepare_pollfd(s, &s->pollfd_legacy_unicast_ipv6, s->fd_legacy_unicast_ipv6); - memset(&s->pollfd_ipv6, 0, sizeof(s->pollfd_ipv6)); - s->pollfd_ipv6.fd = s->fd_ipv6; - s->pollfd_ipv6.events = G_IO_IN|G_IO_ERR|G_IO_HUP; - g_source_add_poll(s->source, &s->pollfd_ipv6); - g_source_attach(s->source, s->context); s->callback = callback; @@ -1019,6 +1303,9 @@ AvahiServer *avahi_server_new(GMainContext *c, const AvahiServerConfig *sc, Avah AVAHI_LLIST_HEAD_INIT(AvahiServiceBrowser, s->service_browsers); AVAHI_LLIST_HEAD_INIT(AvahiServiceResolver, s->service_resolvers); + s->legacy_unicast_reflect_slots = NULL; + s->legacy_unicast_reflect_id = 0; + /* Get host name */ s->host_name = s->config.host_name ? avahi_normalize_name(s->config.host_name) : avahi_get_host_name(); s->host_name[strcspn(s->host_name, ".")] = 0; @@ -1056,6 +1343,8 @@ void avahi_server_free(AvahiServer* s) { while (s->groups) free_group(s, s->groups); + free_slots(s); + while (s->host_name_resolvers) avahi_host_name_resolver_free(s->host_name_resolvers); while (s->address_resolvers) @@ -1084,6 +1373,10 @@ void avahi_server_free(AvahiServer* s) { 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); g_free(s->host_name); g_free(s->domain_name); @@ -1562,6 +1855,7 @@ AvahiServerConfig* avahi_server_config_init(AvahiServerConfig *c) { c->announce_domain = TRUE; c->use_iff_running = FALSE; c->enable_reflector = FALSE; + c->ipv_reflect = FALSE; return c; } diff --git a/avahi-core/server.h b/avahi-core/server.h index fb137b0..0da7c34 100644 --- a/avahi-core/server.h +++ b/avahi-core/server.h @@ -32,6 +32,21 @@ #include "dns.h" #include "rrlist.h" +#define AVAHI_MAX_LEGACY_UNICAST_REFLECT_SLOTS 100 + +typedef struct AvahiLegacyUnicastReflectSlot AvahiLegacyUnicastReflectSlot; + +struct AvahiLegacyUnicastReflectSlot { + AvahiServer *server; + + guint16 id, original_id; + AvahiAddress address; + guint16 port; + gint interface; + GTimeVal elapse_time; + AvahiTimeEvent *time_event; +}; + struct AvahiEntry { AvahiServer *server; AvahiEntryGroup *group; @@ -90,9 +105,11 @@ struct AvahiServer { gchar *host_name, *host_name_fqdn, *domain_name; - gint fd_ipv4, fd_ipv6; + gint fd_ipv4, fd_ipv6, + /* The following two sockets two are used for reflection only */ + fd_legacy_unicast_ipv4, fd_legacy_unicast_ipv6; - GPollFD pollfd_ipv4, pollfd_ipv6; + GPollFD pollfd_ipv4, pollfd_ipv6, pollfd_legacy_unicast_ipv4, pollfd_legacy_unicast_ipv6; GSource *source; AvahiServerState state; @@ -107,6 +124,10 @@ struct AvahiServer { /* Used for assembling responses */ AvahiRecordList *record_list; + + /* Used for reflection of legacy unicast packets */ + AvahiLegacyUnicastReflectSlot **legacy_unicast_reflect_slots; + guint16 legacy_unicast_reflect_id; }; gboolean avahi_server_entry_match_interface(AvahiEntry *e, AvahiInterface *i); diff --git a/avahi-core/socket.c b/avahi-core/socket.c index 2833d27..a2264ce 100644 --- a/avahi-core/socket.c +++ b/avahi-core/socket.c @@ -555,3 +555,98 @@ fail: return NULL; } +gint avahi_open_legacy_unicast_socket_ipv4(void) { + struct sockaddr_in local; + int fd = -1, yes; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + g_warning("socket() failed: %s\n", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + + if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0) { + g_warning("bind() failed: %s\n", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, SOL_IP, IP_RECVTTL, &yes, sizeof(yes)) < 0) { + g_warning("IP_RECVTTL failed: %s\n", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, SOL_IP, IP_PKTINFO, &yes, sizeof(yes)) < 0) { + g_warning("IP_PKTINFO failed: %s\n", strerror(errno)); + goto fail; + } + + if (avahi_set_cloexec(fd) < 0) { + g_warning("FD_CLOEXEC failed: %s\n", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + g_warning("O_NONBLOCK failed: %s\n", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +gint avahi_open_legacy_unicast_socket_ipv6(void) { + struct sockaddr_in local; + int fd = -1, yes; + + if ((fd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + g_warning("socket() failed: %s\n", strerror(errno)); + goto fail; + } + + memset(&local, 0, sizeof(local)); + local.sin_family = AF_INET; + + if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0) { + g_warning("bind() failed: %s\n", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, SOL_IPV6, IPV6_HOPLIMIT, &yes, sizeof(yes)) < 0) { + g_warning("IPV6_HOPLIMIT failed: %s\n", strerror(errno)); + goto fail; + } + + yes = 1; + if (setsockopt(fd, SOL_IPV6, IPV6_PKTINFO, &yes, sizeof(yes)) < 0) { + g_warning("IPV6_PKTINFO failed: %s\n", strerror(errno)); + goto fail; + } + + if (avahi_set_cloexec(fd) < 0) { + g_warning("FD_CLOEXEC failed: %s\n", strerror(errno)); + goto fail; + } + + if (avahi_set_nonblock(fd) < 0) { + g_warning("O_NONBLOCK failed: %s\n", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} diff --git a/avahi-core/socket.h b/avahi-core/socket.h index fe41254..03eafb0 100644 --- a/avahi-core/socket.h +++ b/avahi-core/socket.h @@ -31,6 +31,9 @@ gint avahi_open_socket_ipv4(void); gint avahi_open_socket_ipv6(void); +gint avahi_open_legacy_unicast_socket_ipv4(void); +gint avahi_open_legacy_unicast_socket_ipv6(void); + gint avahi_send_dns_packet_ipv4(gint fd, gint iface, AvahiDnsPacket *p, const AvahiIPv4Address *a, guint16 port); gint avahi_send_dns_packet_ipv6(gint fd, gint iface, AvahiDnsPacket *p, const AvahiIPv6Address *a, guint16 port); -- cgit