+#include "iface.h"
+#include "socket.h"
+#include "subscribe.h"
+
+static void free_entry(flxServer*s, flxEntry *e) {
+ flxEntry *t;
+
+ g_assert(s);
+ g_assert(e);
+
+ flx_goodbye_entry(s, e, TRUE);
+
+ /* Remove from linked list */
+ FLX_LLIST_REMOVE(flxEntry, entries, s->entries, e);
+
+ /* Remove from hash table indexed by name */
+ t = g_hash_table_lookup(s->entries_by_key, e->record->key);
+ FLX_LLIST_REMOVE(flxEntry, by_key, t, e);
+ if (t)
+ g_hash_table_replace(s->entries_by_key, t->record->key, t);
+ else
+ g_hash_table_remove(s->entries_by_key, e->record->key);
+
+ /* Remove from associated group */
+ if (e->group)
+ FLX_LLIST_REMOVE(flxEntry, by_group, e->group->entries, e);
+
+ flx_record_unref(e->record);
+ g_free(e);
+}
+
+static void free_group(flxServer *s, flxEntryGroup *g) {
+ g_assert(s);
+ g_assert(g);
+
+ while (g->entries)
+ free_entry(s, g->entries);
+
+ FLX_LLIST_REMOVE(flxEntryGroup, groups, s->groups, g);
+ g_free(g);
+}
+
+static void cleanup_dead(flxServer *s) {
+ flxEntryGroup *g, *ng;
+ flxEntry *e, *ne;
+ g_assert(s);
+
+
+ if (s->need_group_cleanup) {
+ for (g = s->groups; g; g = ng) {
+ ng = g->groups_next;
+
+ if (g->dead)
+ free_group(s, g);
+ }
+
+ s->need_group_cleanup = FALSE;
+ }
+
+ if (s->need_entry_cleanup) {
+ for (e = s->entries; e; e = ne) {
+ ne = e->entries_next;
+
+ if (e->dead)
+ free_entry(s, e);
+ }
+
+ s->need_entry_cleanup = FALSE;
+ }
+}
+
+static void handle_query_key(flxServer *s, flxKey *k, flxInterface *i, const flxAddress *a) {
+ flxEntry *e;
+ gchar *txt;
+
+ g_assert(s);
+ g_assert(k);
+ g_assert(i);
+ g_assert(a);
+
+ g_message("Handling query: %s", txt = flx_key_to_string(k));
+ g_free(txt);
+
+ flx_packet_scheduler_incoming_query(i->scheduler, k);
+
+ if (k->type == FLX_DNS_TYPE_ANY) {
+
+ /* Handle ANY query */
+
+ for (e = s->entries; e; e = e->entries_next)
+ if (!e->dead && flx_key_pattern_match(k, e->record->key) && flx_entry_registered(s, e, i))
+ flx_interface_post_response(i, a, e->record, e->flags & FLX_ENTRY_UNIQUE, FALSE);
+ } else {
+
+ /* Handle all other queries */
+
+ for (e = g_hash_table_lookup(s->entries_by_key, k); e; e = e->by_key_next)
+ if (!e->dead && flx_entry_registered(s, e, i))
+ flx_interface_post_response(i, a, e->record, e->flags & FLX_ENTRY_UNIQUE, FALSE);
+ }
+}
+
+static void withdraw_entry(flxServer *s, flxEntry *e) {
+ g_assert(s);
+ g_assert(e);
+
+ e->dead = TRUE;
+ s->need_entry_cleanup = TRUE;
+
+ flx_goodbye_entry(s, e, FALSE);
+
+ if (e->group)
+ flx_entry_group_run_callback(e->group, FLX_ENTRY_GROUP_COLLISION);
+}
+
+static void incoming_probe(flxServer *s, flxRecord *record, flxInterface *i) {
+ flxEntry *e, *n;
+ gchar *t;
+
+ g_assert(s);
+ g_assert(record);
+ g_assert(i);
+
+ t = flx_record_to_string(record);
+
+ for (e = g_hash_table_lookup(s->entries_by_key, record->key); e; e = n) {
+ n = e->by_key_next;
+
+ if (e->dead || !flx_record_equal_no_ttl(record, e->record))
+ continue;
+
+ if (flx_entry_registering(s, e, i)) {
+
+ if (flx_record_lexicographical_compare(record, e->record) > 0) {
+ withdraw_entry(s, e);
+ g_message("Recieved conflicting probe [%s]. Local host lost. Withdrawing.", t);
+ } else
+ g_message("Recieved conflicting probe [%s]. Local host won.", t);
+ }
+ }
+
+ g_free(t);
+}
+
+static void handle_query(flxServer *s, flxDnsPacket *p, flxInterface *i, const flxAddress *a) {
+ guint n;
+
+ g_assert(s);
+ g_assert(p);
+ g_assert(i);
+ g_assert(a);
+
+ /* Handle the questions */
+ for (n = flx_dns_packet_get_field(p, FLX_DNS_FIELD_QDCOUNT); n > 0; n --) {
+ flxKey *key;
+
+ if (!(key = flx_dns_packet_consume_key(p))) {
+ g_warning("Packet too short (1)");
+ return;
+ }
+
+ handle_query_key(s, key, i, a);
+ flx_key_unref(key);
+ }
+
+ /* Known Answer Suppresion */
+ for (n = flx_dns_packet_get_field(p, FLX_DNS_FIELD_ANCOUNT); n > 0; n --) {
+ flxRecord *record;
+ gboolean unique = FALSE;
+
+ if (!(record = flx_dns_packet_consume_record(p, &unique))) {
+ g_warning("Packet too short (2)");
+ return;
+ }
+
+ flx_packet_scheduler_incoming_known_answer(i->scheduler, record, a);
+ flx_record_unref(record);
+ }
+
+ /* Probe record */
+ for (n = flx_dns_packet_get_field(p, FLX_DNS_FIELD_NSCOUNT); n > 0; n --) {
+ flxRecord *record;
+ gboolean unique = FALSE;
+
+ if (!(record = flx_dns_packet_consume_record(p, &unique))) {
+ g_warning("Packet too short (3)");
+ return;
+ }
+
+ if (record->key->type != FLX_DNS_TYPE_ANY)
+ incoming_probe(s, record, i);
+
+ flx_record_unref(record);
+ }
+}
+
+static gboolean handle_conflict(flxServer *s, flxInterface *i, flxRecord *record, const flxAddress *a) {
+ gboolean valid = TRUE;
+ flxEntry *e, *n;
+ gchar *t;
+
+ g_assert(s);
+ g_assert(i);
+ g_assert(record);
+
+ t = flx_record_to_string(record);
+
+ for (e = g_hash_table_lookup(s->entries_by_key, record->key); e; e = n) {
+ n = e->by_key_next;
+
+ if (e->dead)
+ continue;
+
+ if (flx_entry_registered(s, e, i)) {
+
+ gboolean equal = flx_record_equal_no_ttl(record, e->record);
+
+ /* Check whether there is a unique record conflict */
+ if (!equal && (e->flags & FLX_ENTRY_UNIQUE)) {
+
+ /* The lexicographically later data wins. */
+ if (flx_record_lexicographical_compare(record, e->record) > 0) {
+ withdraw_entry(s, e);
+ g_message("Recieved conflicting record [%s]. Local host lost. Withdrawing.", t);
+ } else {
+ /* Tell the other host that our entry is lexicographically later */
+ valid = FALSE;
+ flx_interface_post_response(i, a, e->record, e->flags & FLX_ENTRY_UNIQUE, TRUE);
+ g_message("Recieved conflicting record [%s]. Local host won. Refreshing.", t);
+ }
+
+ /* Check wheter there is a TTL conflict */
+ } else if (equal && record->ttl <= e->record->ttl/2) {
+ /* Correct the TTL */
+ valid = FALSE;
+ flx_interface_post_response(i, a, e->record, e->flags & FLX_ENTRY_UNIQUE, TRUE);
+ g_message("Recieved record with bad TTL [%s]. Refreshing.", t);
+ }
+ }
+ }
+
+ g_free(t);
+
+ return valid;
+}
+
+static void handle_response(flxServer *s, flxDnsPacket *p, flxInterface *i, const flxAddress *a) {
+ guint n;
+
+ g_assert(s);
+ g_assert(p);
+ g_assert(i);
+ g_assert(a);
+
+ for (n = flx_dns_packet_get_field(p, FLX_DNS_FIELD_ANCOUNT) +
+ flx_dns_packet_get_field(p, FLX_DNS_FIELD_ARCOUNT); n > 0; n--) {
+ flxRecord *record;
+ gboolean cache_flush = FALSE;
+ gchar *txt;
+
+ if (!(record = flx_dns_packet_consume_record(p, &cache_flush))) {
+ g_warning("Packet too short (4)");
+ return;
+ }
+
+ if (record->key->type != FLX_DNS_TYPE_ANY) {
+
+ g_message("Handling response: %s", txt = flx_record_to_string(record));
+ g_free(txt);
+
+ if (handle_conflict(s, i, record, a)) {
+ flx_cache_update(i->cache, record, cache_flush, a);
+ flx_packet_scheduler_incoming_response(i->scheduler, record);
+ }
+ }
+
+ flx_record_unref(record);
+ }
+}
+
+static void dispatch_packet(flxServer *s, flxDnsPacket *p, struct sockaddr *sa, gint iface, gint ttl) {
+ flxInterface *i;
+ flxAddress a;
+
+ g_assert(s);
+ g_assert(p);
+ g_assert(sa);
+ g_assert(iface > 0);
+
+ g_message("new packet recieved.");
+
+ if (!(i = flx_interface_monitor_get_interface(s->monitor, iface, sa->sa_family))) {
+ g_warning("Recieved packet from invalid interface.");
+ return;
+ }
+
+ if (ttl != 255) {
+ g_warning("Recieved packet with invalid TTL on interface '%s.%i'.", i->hardware->name, i->protocol);
+ if (!s->ignore_bad_ttl)
+ return;
+ }
+
+ if (sa->sa_family == AF_INET6) {
+ static const unsigned char ipv4_in_ipv6[] = {
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0xFF, 0xFF, 0xFF };
+
+ if (memcmp(((struct sockaddr_in6*) sa)->sin6_addr.s6_addr, ipv4_in_ipv6, sizeof(ipv4_in_ipv6)) == 0) {
+
+ /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */
+ return;
+ }
+ }
+
+ if (flx_dns_packet_check_valid(p) < 0) {
+ g_warning("Recieved invalid packet.");
+ return;
+ }
+
+ flx_address_from_sockaddr(sa, &a);
+
+ if (flx_dns_packet_is_query(p)) {
+
+ if (flx_dns_packet_get_field(p, FLX_DNS_FIELD_QDCOUNT) == 0 ||
+ flx_dns_packet_get_field(p, FLX_DNS_FIELD_ARCOUNT) != 0) {
+ g_warning("Invalid query packet.");
+ return;
+ }
+
+ handle_query(s, p, i, &a);
+ g_message("Handled query");
+ } else {
+ if (flx_dns_packet_get_field(p, FLX_DNS_FIELD_QDCOUNT) != 0 ||
+ flx_dns_packet_get_field(p, FLX_DNS_FIELD_ANCOUNT) == 0 ||
+ flx_dns_packet_get_field(p, FLX_DNS_FIELD_NSCOUNT) != 0) {
+ g_warning("Invalid response packet.");
+ return;
+ }
+
+ handle_response(s, p, i, &a);
+ g_message("Handled response");
+ }
+}
+
+static void work(flxServer *s) {
+ struct sockaddr_in6 sa6;
+ struct sockaddr_in sa;
+ flxDnsPacket *p;
+ gint iface = -1;
+ guint8 ttl;
+
+ g_assert(s);
+
+ if (s->pollfd_ipv4.revents & G_IO_IN) {
+ if ((p = flx_recv_dns_packet_ipv4(s->fd_ipv4, &sa, &iface, &ttl))) {
+ dispatch_packet(s, p, (struct sockaddr*) &sa, iface, ttl);
+ flx_dns_packet_free(p);
+ }
+ }
+
+ if (s->pollfd_ipv6.revents & G_IO_IN) {
+ if ((p = flx_recv_dns_packet_ipv6(s->fd_ipv6, &sa6, &iface, &ttl))) {
+ dispatch_packet(s, p, (struct sockaddr*) &sa6, iface, ttl);
+ flx_dns_packet_free(p);
+ }
+ }
+}
+
+static gboolean prepare_func(GSource *source, gint *timeout) {
+ g_assert(source);
+ g_assert(timeout);
+
+ *timeout = -1;
+ return FALSE;
+}
+
+static gboolean check_func(GSource *source) {
+ flxServer* s;
+ g_assert(source);
+
+ s = *((flxServer**) (((guint8*) source) + sizeof(GSource)));
+ g_assert(s);
+
+ return (s->pollfd_ipv4.revents | s->pollfd_ipv6.revents) & (G_IO_IN | G_IO_HUP | G_IO_ERR);
+}
+
+static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer user_data) {
+ flxServer* s;
+ g_assert(source);
+
+ s = *((flxServer**) (((guint8*) source) + sizeof(GSource)));
+ g_assert(s);
+
+ work(s);
+ cleanup_dead(s);
+
+ return TRUE;
+}
+
+static void add_default_entries(flxServer *s) {
+ gint length = 0;
+ struct utsname utsname;
+ gchar *hinfo;
+ flxAddress a;
+ flxRecord *r;
+
+ g_assert(s);
+
+ /* Fill in HINFO rr */
+ r = flx_record_new_full(s->hostname, FLX_DNS_CLASS_IN, FLX_DNS_TYPE_HINFO);
+ uname(&utsname);
+ r->data.hinfo.cpu = g_strdup(g_strup(utsname.machine));
+ r->data.hinfo.os = g_strdup(g_strup(utsname.sysname));
+ flx_server_add(s, NULL, 0, AF_UNSPEC, FLX_ENTRY_UNIQUE | FLX_ENTRY_NOANNOUNCE | FLX_ENTRY_NOPROBE, r);
+ flx_record_unref(r);
+
+ /* Add localhost entries */
+ flx_address_parse("127.0.0.1", AF_INET, &a);
+ flx_server_add_address(s, NULL, 0, AF_UNSPEC, FLX_ENTRY_UNIQUE|FLX_ENTRY_NOPROBE|FLX_ENTRY_NOANNOUNCE, "localhost", &a);
+
+ flx_address_parse("::1", AF_INET6, &a);
+ flx_server_add_address(s, NULL, 0, AF_UNSPEC, FLX_ENTRY_UNIQUE|FLX_ENTRY_NOPROBE|FLX_ENTRY_NOANNOUNCE, "ip6-localhost", &a);
+}