]> git.meshlink.io Git - catta/blob - server.c
b17b14293806e1197eae655b582971993fec773a
[catta] / server.c
1 #include <sys/socket.h>
2 #include <arpa/inet.h>
3 #include <string.h>
4 #include <sys/utsname.h>
5 #include <unistd.h>
6
7 #include "server.h"
8 #include "util.h"
9 #include "iface.h"
10 #include "socket.h"
11 #include "subscribe.h"
12
13 static void free_entry(AvahiServer*s, AvahiEntry *e) {
14     AvahiEntry *t;
15
16     g_assert(s);
17     g_assert(e);
18
19     avahi_goodbye_entry(s, e, TRUE);
20
21     /* Remove from linked list */
22     AVAHI_LLIST_REMOVE(AvahiEntry, entries, s->entries, e);
23
24     /* Remove from hash table indexed by name */
25     t = g_hash_table_lookup(s->entries_by_key, e->record->key);
26     AVAHI_LLIST_REMOVE(AvahiEntry, by_key, t, e);
27     if (t)
28         g_hash_table_replace(s->entries_by_key, t->record->key, t);
29     else
30         g_hash_table_remove(s->entries_by_key, e->record->key);
31
32     /* Remove from associated group */
33     if (e->group)
34         AVAHI_LLIST_REMOVE(AvahiEntry, by_group, e->group->entries, e);
35
36     avahi_record_unref(e->record);
37     g_free(e);
38 }
39
40 static void free_group(AvahiServer *s, AvahiEntryGroup *g) {
41     g_assert(s);
42     g_assert(g);
43
44     while (g->entries)
45         free_entry(s, g->entries);
46
47     AVAHI_LLIST_REMOVE(AvahiEntryGroup, groups, s->groups, g);
48     g_free(g);
49 }
50
51 static void cleanup_dead(AvahiServer *s) {
52     AvahiEntryGroup *g, *ng;
53     AvahiEntry *e, *ne;
54     g_assert(s);
55
56
57     if (s->need_group_cleanup) {
58         for (g = s->groups; g; g = ng) {
59             ng = g->groups_next;
60             
61             if (g->dead)
62                 free_group(s, g);
63         }
64
65         s->need_group_cleanup = FALSE;
66     }
67
68     if (s->need_entry_cleanup) {
69         for (e = s->entries; e; e = ne) {
70             ne = e->entries_next;
71             
72             if (e->dead)
73                 free_entry(s, e);
74         }
75
76         s->need_entry_cleanup = FALSE;
77     }
78 }
79
80 static void handle_query_key(AvahiServer *s, AvahiKey *k, AvahiInterface *i, const AvahiAddress *a, guint16 port, gboolean legacy_unicast, gboolean unicast_response) {
81     AvahiEntry *e;
82     gchar *txt;
83     
84     g_assert(s);
85     g_assert(k);
86     g_assert(i);
87     g_assert(a);
88
89     g_message("Handling query: %s", txt = avahi_key_to_string(k));
90     g_free(txt);
91
92     avahi_packet_scheduler_incoming_query(i->scheduler, k);
93
94     if (k->type == AVAHI_DNS_TYPE_ANY) {
95
96         /* Handle ANY query */
97         
98         for (e = s->entries; e; e = e->entries_next)
99             if (!e->dead && avahi_key_pattern_match(k, e->record->key) && avahi_entry_registered(s, e, i))
100                 avahi_interface_post_response(i, a, e->record, e->flags & AVAHI_ENTRY_UNIQUE, FALSE);
101     } else {
102
103         /* Handle all other queries */
104         
105         for (e = g_hash_table_lookup(s->entries_by_key, k); e; e = e->by_key_next)
106             if (!e->dead && avahi_entry_registered(s, e, i))
107                 avahi_interface_post_response(i, a, e->record, e->flags & AVAHI_ENTRY_UNIQUE, FALSE);
108     }
109 }
110
111 static void withdraw_entry(AvahiServer *s, AvahiEntry *e) {
112     g_assert(s);
113     g_assert(e);
114
115     
116     if (e->group) {
117         AvahiEntry *k;
118         
119         for (k = e->group->entries; k; k = k->by_group_next) {
120             avahi_goodbye_entry(s, k, FALSE);
121             k->dead = TRUE;
122         }
123         
124         avahi_entry_group_change_state(e->group, AVAHI_ENTRY_GROUP_COLLISION);
125     } else {
126         avahi_goodbye_entry(s, e, FALSE);
127         e->dead = TRUE;
128     }
129
130     s->need_entry_cleanup = TRUE;
131 }
132
133 static void incoming_probe(AvahiServer *s, AvahiRecord *record, AvahiInterface *i) {
134     AvahiEntry *e, *n;
135     gchar *t;
136     
137     g_assert(s);
138     g_assert(record);
139     g_assert(i);
140
141     t = avahi_record_to_string(record);
142
143 /*     g_message("PROBE: [%s]", t); */
144     
145     for (e = g_hash_table_lookup(s->entries_by_key, record->key); e; e = n) {
146         n = e->by_key_next;
147
148         if (e->dead || avahi_record_equal_no_ttl(record, e->record))
149             continue;
150
151         if (avahi_entry_registering(s, e, i)) {
152             gint cmp;
153
154             if ((cmp = avahi_record_lexicographical_compare(record, e->record)) > 0) {
155                 withdraw_entry(s, e);
156                 g_message("Recieved conflicting probe [%s]. Local host lost. Withdrawing.", t);
157             } else if (cmp < 0)
158                 g_message("Recieved conflicting probe [%s]. Local host won.", t);
159
160         }
161     }
162
163     g_free(t);
164 }
165
166 static void handle_query(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a, guint16 port, gboolean legacy_unicast) {
167     guint n;
168     
169     g_assert(s);
170     g_assert(p);
171     g_assert(i);
172     g_assert(a);
173
174     /* Handle the questions */
175     for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT); n > 0; n --) {
176         AvahiKey *key;
177         gboolean unicast_response = FALSE;
178
179         if (!(key = avahi_dns_packet_consume_key(p, &unicast_response))) {
180             g_warning("Packet too short (1)");
181             return;
182         }
183
184         handle_query_key(s, key, i, a, port, legacy_unicast, unicast_response);
185         avahi_key_unref(key);
186     }
187
188     /* Known Answer Suppression */
189     for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT); n > 0; n --) {
190         AvahiRecord *record;
191         gboolean unique = FALSE;
192
193         if (!(record = avahi_dns_packet_consume_record(p, &unique))) {
194             g_warning("Packet too short (2)");
195             return;
196         }
197
198         avahi_packet_scheduler_incoming_known_answer(i->scheduler, record, a);
199         avahi_record_unref(record);
200     }
201
202     /* Probe record */
203     for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT); n > 0; n --) {
204         AvahiRecord *record;
205         gboolean unique = FALSE;
206
207         if (!(record = avahi_dns_packet_consume_record(p, &unique))) {
208             g_warning("Packet too short (3)");
209             return;
210         }
211
212         if (record->key->type != AVAHI_DNS_TYPE_ANY)
213             incoming_probe(s, record, i);
214         
215         avahi_record_unref(record);
216     }
217 }
218
219 static gboolean handle_conflict(AvahiServer *s, AvahiInterface *i, AvahiRecord *record, gboolean unique, const AvahiAddress *a) {
220     gboolean valid = TRUE;
221     AvahiEntry *e, *n;
222     gchar *t;
223     
224     g_assert(s);
225     g_assert(i);
226     g_assert(record);
227
228     t = avahi_record_to_string(record);
229
230 /*     g_message("CHECKING FOR CONFLICT: [%s]", t); */
231
232     for (e = g_hash_table_lookup(s->entries_by_key, record->key); e; e = n) {
233         n = e->by_key_next;
234
235         if (e->dead)
236             continue;
237         
238         if (avahi_entry_registered(s, e, i)) {
239
240             gboolean equal = avahi_record_equal_no_ttl(record, e->record);
241                 
242             /* Check whether there is a unique record conflict */
243             if (!equal && ((e->flags & AVAHI_ENTRY_UNIQUE) || unique)) {
244                 gint cmp;
245                 
246                 /* The lexicographically later data wins. */
247                 if ((cmp = avahi_record_lexicographical_compare(record, e->record)) > 0) {
248                     g_message("Recieved conflicting record [%s]. Local host lost. Withdrawing.", t);
249                     withdraw_entry(s, e);
250                 } else if (cmp < 0) {
251                     /* Tell the other host that our entry is lexicographically later */
252
253                     g_message("Recieved conflicting record [%s]. Local host won. Refreshing.", t);
254
255                     valid = FALSE;
256                     avahi_interface_post_response(i, a, e->record, e->flags & AVAHI_ENTRY_UNIQUE, TRUE);
257                 }
258                 
259                 /* Check wheter there is a TTL conflict */
260             } else if (equal && record->ttl <= e->record->ttl/2) {
261                 /* Correct the TTL */
262                 valid = FALSE;
263                 avahi_interface_post_response(i, a, e->record, e->flags & AVAHI_ENTRY_UNIQUE, TRUE);
264                 g_message("Recieved record with bad TTL [%s]. Refreshing.", t);
265             }
266             
267         } else if (avahi_entry_registering(s, e, i)) {
268
269             if (!avahi_record_equal_no_ttl(record, e->record) && ((e->flags & AVAHI_ENTRY_UNIQUE) || unique)) {
270
271                 /* We are currently registering a matching record, but
272                  * someone else already claimed it, so let's
273                  * withdraw */
274                 
275                 g_message("Recieved conflicting record [%s] with local record to be. Withdrawing.", t);
276                 withdraw_entry(s, e);
277             }
278         }
279     }
280
281     g_free(t);
282
283     return valid;
284 }
285
286 static void handle_response(AvahiServer *s, AvahiDnsPacket *p, AvahiInterface *i, const AvahiAddress *a) {
287     guint n;
288     
289     g_assert(s);
290     g_assert(p);
291     g_assert(i);
292     g_assert(a);
293     
294     for (n = avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) +
295              avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT); n > 0; n--) {
296         AvahiRecord *record;
297         gboolean cache_flush = FALSE;
298         gchar *txt;
299         
300         if (!(record = avahi_dns_packet_consume_record(p, &cache_flush))) {
301             g_warning("Packet too short (4)");
302             return;
303         }
304
305         if (record->key->type != AVAHI_DNS_TYPE_ANY) {
306
307             g_message("Handling response: %s", txt = avahi_record_to_string(record));
308             g_free(txt);
309             
310             if (handle_conflict(s, i, record, cache_flush, a)) {
311                 avahi_cache_update(i->cache, record, cache_flush, a);
312                 avahi_packet_scheduler_incoming_response(i->scheduler, record);
313             }
314         }
315             
316         avahi_record_unref(record);
317     }
318 }
319
320 static void dispatch_packet(AvahiServer *s, AvahiDnsPacket *p, struct sockaddr *sa, gint iface, gint ttl) {
321     AvahiInterface *i;
322     AvahiAddress a;
323     guint16 port;
324     
325     g_assert(s);
326     g_assert(p);
327     g_assert(sa);
328     g_assert(iface > 0);
329
330     if (!(i = avahi_interface_monitor_get_interface(s->monitor, iface, sa->sa_family))) {
331         g_warning("Recieved packet from invalid interface.");
332         return;
333     }
334
335     g_message("new packet recieved on interface '%s.%i'.", i->hardware->name, i->protocol);
336
337     if (sa->sa_family == AF_INET6) {
338         static const guint8 ipv4_in_ipv6[] = {
339             0x00, 0x00, 0x00, 0x00,
340             0x00, 0x00, 0x00, 0x00,
341             0xFF, 0xFF, 0xFF, 0xFF };
342
343         /* This is an IPv4 address encapsulated in IPv6, so let's ignore it. */
344
345         if (memcmp(((struct sockaddr_in6*) sa)->sin6_addr.s6_addr, ipv4_in_ipv6, sizeof(ipv4_in_ipv6)) == 0)
346             return;
347     }
348
349     if (avahi_dns_packet_check_valid(p) < 0) {
350         g_warning("Recieved invalid packet.");
351         return;
352     }
353
354     port = avahi_port_from_sockaddr(sa);
355     avahi_address_from_sockaddr(sa, &a);
356
357     if (avahi_dns_packet_is_query(p)) {
358         gboolean legacy_unicast = FALSE;
359
360         if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT) == 0 ||
361             avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ARCOUNT) != 0) {
362             g_warning("Invalid query packet.");
363             return;
364         }
365
366         if (port != AVAHI_MDNS_PORT) {
367             /* Legacy Unicast */
368
369             if ((avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) != 0 ||
370                  avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0)) {
371                 g_warning("Invalid legacy unicast query packet.");
372                 return;
373             }
374         
375             legacy_unicast = TRUE;
376         }
377
378         handle_query(s, p, i, &a, port, legacy_unicast);
379         
380         g_message("Handled query");
381     } else {
382
383         if (port != AVAHI_MDNS_PORT) {
384             g_warning("Recieved repsonse with invalid source port %u on interface '%s.%i'", port, i->hardware->name, i->protocol);
385             return;
386         }
387
388         if (ttl != 255) {
389             g_warning("Recieved response with invalid TTL %u on interface '%s.%i'.", ttl, i->hardware->name, i->protocol);
390             if (!s->ignore_bad_ttl)
391                 return;
392         }
393
394         if (avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_QDCOUNT) != 0 ||
395             avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_ANCOUNT) == 0 ||
396             avahi_dns_packet_get_field(p, AVAHI_DNS_FIELD_NSCOUNT) != 0) {
397             g_warning("Invalid response packet.");
398             return;
399         }
400
401         handle_response(s, p, i, &a);
402         g_message("Handled response");
403     }
404 }
405
406 static void work(AvahiServer *s) {
407     struct sockaddr_in6 sa6;
408     struct sockaddr_in sa;
409     AvahiDnsPacket *p;
410     gint iface = -1;
411     guint8 ttl;
412         
413     g_assert(s);
414
415     if (s->pollfd_ipv4.revents & G_IO_IN) {
416         if ((p = avahi_recv_dns_packet_ipv4(s->fd_ipv4, &sa, &iface, &ttl))) {
417             dispatch_packet(s, p, (struct sockaddr*) &sa, iface, ttl);
418             avahi_dns_packet_free(p);
419         }
420     }
421
422     if (s->pollfd_ipv6.revents & G_IO_IN) {
423         if ((p = avahi_recv_dns_packet_ipv6(s->fd_ipv6, &sa6, &iface, &ttl))) {
424             dispatch_packet(s, p, (struct sockaddr*) &sa6, iface, ttl);
425             avahi_dns_packet_free(p);
426         }
427     }
428 }
429
430 static gboolean prepare_func(GSource *source, gint *timeout) {
431     g_assert(source);
432     g_assert(timeout);
433     
434     *timeout = -1;
435     return FALSE;
436 }
437
438 static gboolean check_func(GSource *source) {
439     AvahiServer* s;
440     g_assert(source);
441
442     s = *((AvahiServer**) (((guint8*) source) + sizeof(GSource)));
443     g_assert(s);
444     
445     return (s->pollfd_ipv4.revents | s->pollfd_ipv6.revents) & (G_IO_IN | G_IO_HUP | G_IO_ERR);
446 }
447
448 static gboolean dispatch_func(GSource *source, GSourceFunc callback, gpointer user_data) {
449     AvahiServer* s;
450     g_assert(source);
451
452     s = *((AvahiServer**) (((guint8*) source) + sizeof(GSource)));
453     g_assert(s);
454
455     work(s);
456     cleanup_dead(s);
457
458     return TRUE;
459 }
460
461 static void add_default_entries(AvahiServer *s) {
462     gint length = 0;
463     struct utsname utsname;
464     gchar *hinfo;
465     AvahiAddress a;
466     AvahiRecord *r;
467     
468     g_assert(s);
469     
470     /* Fill in HINFO rr */
471     r = avahi_record_new_full(s->hostname, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_HINFO);
472     uname(&utsname);
473     r->data.hinfo.cpu = g_strdup(g_strup(utsname.machine));
474     r->data.hinfo.os = g_strdup(g_strup(utsname.sysname));
475     avahi_server_add(s, NULL, 0, AF_UNSPEC, AVAHI_ENTRY_UNIQUE, r);
476     avahi_record_unref(r);
477
478     /* Add localhost entries */
479     avahi_address_parse("127.0.0.1", AF_INET, &a);
480     avahi_server_add_address(s, NULL, 0, AF_UNSPEC, AVAHI_ENTRY_UNIQUE|AVAHI_ENTRY_NOPROBE|AVAHI_ENTRY_NOANNOUNCE, "localhost", &a);
481
482     avahi_address_parse("::1", AF_INET6, &a);
483     avahi_server_add_address(s, NULL, 0, AF_UNSPEC, AVAHI_ENTRY_UNIQUE|AVAHI_ENTRY_NOPROBE|AVAHI_ENTRY_NOANNOUNCE, "ip6-localhost", &a);
484 }
485
486 AvahiServer *avahi_server_new(GMainContext *c) {
487     gchar *hn, *e;
488     AvahiServer *s;
489     
490     static GSourceFuncs source_funcs = {
491         prepare_func,
492         check_func,
493         dispatch_func,
494         NULL,
495         NULL,
496         NULL
497     };
498
499     s = g_new(AvahiServer, 1);
500
501     s->ignore_bad_ttl = FALSE;
502     s->need_entry_cleanup = s->need_group_cleanup = FALSE;
503     
504     s->fd_ipv4 = avahi_open_socket_ipv4();
505     s->fd_ipv6 = avahi_open_socket_ipv6();
506     
507     if (s->fd_ipv6 < 0 && s->fd_ipv4 < 0) {
508         g_critical("Failed to create IP sockets.\n");
509         g_free(s);
510         return NULL;
511     }
512
513     if (s->fd_ipv4 < 0)
514         g_message("Failed to create IPv4 socket, proceeding in IPv6 only mode");
515     else if (s->fd_ipv6 < 0)
516         g_message("Failed to create IPv6 socket, proceeding in IPv4 only mode");
517     
518     if (c)
519         g_main_context_ref(s->context = c);
520     else
521         s->context = g_main_context_default();
522     
523     AVAHI_LLIST_HEAD_INIT(AvahiEntry, s->entries);
524     s->entries_by_key = g_hash_table_new((GHashFunc) avahi_key_hash, (GEqualFunc) avahi_key_equal);
525     AVAHI_LLIST_HEAD_INIT(AvahiGroup, s->groups);
526
527     AVAHI_LLIST_HEAD_INIT(AvahiSubscription, s->subscriptions);
528     s->subscription_hashtable = g_hash_table_new((GHashFunc) avahi_key_hash, (GEqualFunc) avahi_key_equal);
529
530     /* Get host name */
531     hn = avahi_get_host_name();
532     hn[strcspn(hn, ".")] = 0;
533
534     s->hostname = g_strdup_printf("%s.local.", hn);
535     g_free(hn);
536
537     s->time_event_queue = avahi_time_event_queue_new(s->context, G_PRIORITY_DEFAULT+10); /* Slightly less priority than the FDs */
538     s->monitor = avahi_interface_monitor_new(s);
539     avahi_interface_monitor_sync(s->monitor);
540     add_default_entries(s);
541     
542     /* Prepare IO source registration */
543     s->source = g_source_new(&source_funcs, sizeof(GSource) + sizeof(AvahiServer*));
544     *((AvahiServer**) (((guint8*) s->source) + sizeof(GSource))) = s;
545
546     memset(&s->pollfd_ipv4, 0, sizeof(s->pollfd_ipv4));
547     s->pollfd_ipv4.fd = s->fd_ipv4;
548     s->pollfd_ipv4.events = G_IO_IN|G_IO_ERR|G_IO_HUP;
549     g_source_add_poll(s->source, &s->pollfd_ipv4);
550     
551     memset(&s->pollfd_ipv6, 0, sizeof(s->pollfd_ipv6));
552     s->pollfd_ipv6.fd = s->fd_ipv6;
553     s->pollfd_ipv6.events = G_IO_IN|G_IO_ERR|G_IO_HUP;
554     g_source_add_poll(s->source, &s->pollfd_ipv6);
555
556     g_source_attach(s->source, s->context);
557
558     return s;
559 }
560
561 void avahi_server_free(AvahiServer* s) {
562     g_assert(s);
563
564     while(s->entries)
565         free_entry(s, s->entries);
566
567     avahi_interface_monitor_free(s->monitor);
568
569     while (s->groups)
570         free_group(s, s->groups);
571
572     while (s->subscriptions)
573         avahi_subscription_free(s->subscriptions);
574     g_hash_table_destroy(s->subscription_hashtable);
575
576     g_hash_table_destroy(s->entries_by_key);
577
578     avahi_time_event_queue_free(s->time_event_queue);
579
580     if (s->fd_ipv4 >= 0)
581         close(s->fd_ipv4);
582     if (s->fd_ipv6 >= 0)
583         close(s->fd_ipv6);
584     
585     g_free(s->hostname);
586
587     g_source_destroy(s->source);
588     g_source_unref(s->source);
589     g_main_context_unref(s->context);
590
591     g_free(s);
592 }
593
594 void avahi_server_add(
595     AvahiServer *s,
596     AvahiEntryGroup *g,
597     gint interface,
598     guchar protocol,
599     AvahiEntryFlags flags,
600     AvahiRecord *r) {
601     
602     AvahiEntry *e, *t;
603     g_assert(s);
604     g_assert(r);
605
606     g_assert(r->key->type != AVAHI_DNS_TYPE_ANY);
607
608     e = g_new(AvahiEntry, 1);
609     e->server = s;
610     e->record = avahi_record_ref(r);
611     e->group = g;
612     e->interface = interface;
613     e->protocol = protocol;
614     e->flags = flags;
615     e->dead = FALSE;
616
617     AVAHI_LLIST_HEAD_INIT(AvahiAnnouncement, e->announcements);
618
619     AVAHI_LLIST_PREPEND(AvahiEntry, entries, s->entries, e);
620
621     /* Insert into hash table indexed by name */
622     t = g_hash_table_lookup(s->entries_by_key, e->record->key);
623     AVAHI_LLIST_PREPEND(AvahiEntry, by_key, t, e);
624     g_hash_table_replace(s->entries_by_key, e->record->key, t);
625
626     /* Insert into group list */
627     if (g)
628         AVAHI_LLIST_PREPEND(AvahiEntry, by_group, g->entries, e); 
629
630     avahi_announce_entry(s, e);
631 }
632 const AvahiRecord *avahi_server_iterate(AvahiServer *s, AvahiEntryGroup *g, void **state) {
633     AvahiEntry **e = (AvahiEntry**) state;
634     g_assert(s);
635     g_assert(e);
636
637     if (!*e)
638         *e = g ? g->entries : s->entries;
639     
640     while (*e && (*e)->dead)
641         *e = g ? (*e)->by_group_next : (*e)->entries_next;
642         
643     if (!*e)
644         return NULL;
645
646     return avahi_record_ref((*e)->record);
647 }
648
649 void avahi_server_dump(AvahiServer *s, FILE *f) {
650     AvahiEntry *e;
651     g_assert(s);
652     g_assert(f);
653
654     fprintf(f, "\n;;; ZONE DUMP FOLLOWS ;;;\n");
655
656     for (e = s->entries; e; e = e->entries_next) {
657         gchar *t;
658
659         if (e->dead)
660             continue;
661         
662         t = avahi_record_to_string(e->record);
663         fprintf(f, "%s ; iface=%i proto=%i\n", t, e->interface, e->protocol);
664         g_free(t);
665     }
666
667     avahi_dump_caches(s->monitor, f);
668 }
669
670 void avahi_server_add_ptr(
671     AvahiServer *s,
672     AvahiEntryGroup *g,
673     gint interface,
674     guchar protocol,
675     AvahiEntryFlags flags,
676     const gchar *name,
677     const gchar *dest) {
678
679     AvahiRecord *r;
680
681     g_assert(dest);
682
683     r = avahi_record_new_full(name ? name : s->hostname, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_PTR);
684     r->data.ptr.name = avahi_normalize_name(dest);
685     avahi_server_add(s, g, interface, protocol, flags, r);
686     avahi_record_unref(r);
687 }
688
689 void avahi_server_add_address(
690     AvahiServer *s,
691     AvahiEntryGroup *g,
692     gint interface,
693     guchar protocol,
694     AvahiEntryFlags flags,
695     const gchar *name,
696     AvahiAddress *a) {
697
698     gchar *n = NULL;
699     g_assert(s);
700     g_assert(a);
701
702     name = name ? (n = avahi_normalize_name(name)) : s->hostname;
703     
704     if (a->family == AF_INET) {
705         gchar *reverse;
706         AvahiRecord  *r;
707
708         r = avahi_record_new_full(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_A);
709         r->data.a.address = a->data.ipv4;
710         avahi_server_add(s, g, interface, protocol, flags, r);
711         avahi_record_unref(r);
712         
713         reverse = avahi_reverse_lookup_name_ipv4(&a->data.ipv4);
714         g_assert(reverse);
715         avahi_server_add_ptr(s, g, interface, protocol, flags, reverse, name);
716         g_free(reverse);
717         
718     } else {
719         gchar *reverse;
720         AvahiRecord *r;
721             
722         r = avahi_record_new_full(name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA);
723         r->data.aaaa.address = a->data.ipv6;
724         avahi_server_add(s, g, interface, protocol, flags, r);
725         avahi_record_unref(r);
726
727         reverse = avahi_reverse_lookup_name_ipv6_arpa(&a->data.ipv6);
728         g_assert(reverse);
729         avahi_server_add_ptr(s, g, interface, protocol, flags, reverse, name);
730         g_free(reverse);
731     
732         reverse = avahi_reverse_lookup_name_ipv6_int(&a->data.ipv6);
733         g_assert(reverse);
734         avahi_server_add_ptr(s, g, interface, protocol, flags, reverse, name);
735         g_free(reverse);
736     }
737     
738     g_free(n);
739 }
740
741 void avahi_server_add_text_strlst(
742     AvahiServer *s,
743     AvahiEntryGroup *g,
744     gint interface,
745     guchar protocol,
746     AvahiEntryFlags flags,
747     const gchar *name,
748     AvahiStringList *strlst) {
749
750     AvahiRecord *r;
751     
752     g_assert(s);
753     
754     r = avahi_record_new_full(name ? name : s->hostname, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_TXT);
755     r->data.txt.string_list = strlst;
756     avahi_server_add(s, g, interface, protocol, flags, r);
757     avahi_record_unref(r);
758 }
759
760 void avahi_server_add_text_va(
761     AvahiServer *s,
762     AvahiEntryGroup *g,
763     gint interface,
764     guchar protocol,
765     AvahiEntryFlags flags,
766     const gchar *name,
767     va_list va) {
768     
769     g_assert(s);
770
771     avahi_server_add_text_strlst(s, g, interface, protocol, flags, name, avahi_string_list_new_va(va));
772 }
773
774 void avahi_server_add_text(
775     AvahiServer *s,
776     AvahiEntryGroup *g,
777     gint interface,
778     guchar protocol,
779     AvahiEntryFlags flags,
780     const gchar *name,
781     ...) {
782
783     va_list va;
784     
785     g_assert(s);
786
787     va_start(va, name);
788     avahi_server_add_text_va(s, g, interface, protocol, flags, name, va);
789     va_end(va);
790 }
791
792 static void escape_service_name(gchar *d, guint size, const gchar *s) {
793     g_assert(d);
794     g_assert(size);
795     g_assert(s);
796
797     while (*s && size >= 2) {
798         if (*s == '.' || *s == '\\') {
799             if (size < 3)
800                 break;
801
802             *(d++) = '\\';
803             size--;
804         }
805             
806         *(d++) = *(s++);
807         size--;
808     }
809
810     g_assert(size > 0);
811     *(d++) = 0;
812 }
813
814 void avahi_server_add_service_strlst(
815     AvahiServer *s,
816     AvahiEntryGroup *g,
817     gint interface,
818     guchar protocol,
819     const gchar *type,
820     const gchar *name,
821     const gchar *domain,
822     const gchar *host,
823     guint16 port,
824     AvahiStringList *strlst) {
825
826     gchar ptr_name[256], svc_name[256], ename[64], enum_ptr[256];
827     AvahiRecord *r;
828     
829     g_assert(s);
830     g_assert(type);
831     g_assert(name);
832
833     escape_service_name(ename, sizeof(ename), name);
834
835     if (domain) {
836         while (domain[0] == '.')
837             domain++;
838     } else
839         domain = "local";
840
841     if (!host)
842         host = s->hostname;
843
844     snprintf(ptr_name, sizeof(ptr_name), "%s.%s", type, domain);
845     snprintf(svc_name, sizeof(svc_name), "%s.%s.%s", ename, type, domain);
846     
847     avahi_server_add_ptr(s, g, interface, protocol, AVAHI_ENTRY_NULL, ptr_name, svc_name);
848
849     r = avahi_record_new_full(svc_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_SRV);
850     r->data.srv.priority = 0;
851     r->data.srv.weight = 0;
852     r->data.srv.port = port;
853     r->data.srv.name = avahi_normalize_name(host);
854     avahi_server_add(s, g, interface, protocol, AVAHI_ENTRY_UNIQUE, r);
855     avahi_record_unref(r);
856
857     avahi_server_add_text_strlst(s, g, interface, protocol, AVAHI_ENTRY_UNIQUE, svc_name, strlst);
858
859     snprintf(enum_ptr, sizeof(enum_ptr), "_services._dns-sd._udp.%s", domain);
860     avahi_server_add_ptr(s, g, interface, protocol, AVAHI_ENTRY_NULL, enum_ptr, ptr_name);
861 }
862
863 void avahi_server_add_service_va(
864     AvahiServer *s,
865     AvahiEntryGroup *g,
866     gint interface,
867     guchar protocol,
868     const gchar *type,
869     const gchar *name,
870     const gchar *domain,
871     const gchar *host,
872     guint16 port,
873     va_list va){
874
875     g_assert(s);
876     g_assert(type);
877     g_assert(name);
878
879     avahi_server_add_service(s, g, interface, protocol, type, name, domain, host, port, avahi_string_list_new_va(va));
880 }
881
882 void avahi_server_add_service(
883     AvahiServer *s,
884     AvahiEntryGroup *g,
885     gint interface,
886     guchar protocol,
887     const gchar *type,
888     const gchar *name,
889     const gchar *domain,
890     const gchar *host,
891     guint16 port,
892     ... ){
893
894     va_list va;
895     
896     g_assert(s);
897     g_assert(type);
898     g_assert(name);
899
900     va_start(va, port);
901     avahi_server_add_service_va(s, g, interface, protocol, type, name, domain, host, port, va);
902     va_end(va);
903 }
904
905 static void post_query_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, gpointer userdata) {
906     AvahiKey *k = userdata;
907
908     g_assert(m);
909     g_assert(i);
910     g_assert(k);
911
912     avahi_interface_post_query(i, k, FALSE);
913 }
914
915 void avahi_server_post_query(AvahiServer *s, gint interface, guchar protocol, AvahiKey *key) {
916     g_assert(s);
917     g_assert(key);
918
919     avahi_interface_monitor_walk(s->monitor, interface, protocol, post_query_callback, key);
920 }
921
922 struct tmpdata {
923     AvahiRecord *record;
924     gboolean flush_cache;
925 };
926
927 static void post_response_callback(AvahiInterfaceMonitor *m, AvahiInterface *i, gpointer userdata) {
928     struct tmpdata *tmpdata = userdata;
929
930     g_assert(m);
931     g_assert(i);
932     g_assert(tmpdata);
933
934     avahi_interface_post_response(i, NULL, tmpdata->record, tmpdata->flush_cache, FALSE);
935 }
936
937 void avahi_server_post_response(AvahiServer *s, gint interface, guchar protocol, AvahiRecord *record, gboolean flush_cache) {
938     struct tmpdata tmpdata;
939     
940     g_assert(s);
941     g_assert(record);
942
943     tmpdata.record = record;
944     tmpdata.flush_cache = flush_cache;
945
946     avahi_interface_monitor_walk(s->monitor, interface, protocol, post_response_callback, &tmpdata);
947 }
948
949 void avahi_entry_group_change_state(AvahiEntryGroup *g, AvahiEntryGroupState state) {
950     g_assert(g);
951
952     g->state = state;
953     
954     if (g->callback) {
955         g->callback(g->server, g, state, g->userdata);
956         return;
957     }
958 }
959
960 AvahiEntryGroup *avahi_entry_group_new(AvahiServer *s, AvahiEntryGroupCallback callback, gpointer userdata) {
961     AvahiEntryGroup *g;
962     
963     g_assert(s);
964
965     g = g_new(AvahiEntryGroup, 1);
966     g->server = s;
967     g->callback = callback;
968     g->userdata = userdata;
969     g->dead = FALSE;
970     g->state = AVAHI_ENTRY_GROUP_UNCOMMITED;
971     g->n_probing = 0;
972     AVAHI_LLIST_HEAD_INIT(AvahiEntry, g->entries);
973
974     AVAHI_LLIST_PREPEND(AvahiEntryGroup, groups, s->groups, g);
975     return g;
976 }
977
978 void avahi_entry_group_free(AvahiEntryGroup *g) {
979     g_assert(g);
980     g_assert(g->server);
981
982     g->dead = TRUE;
983     g->server->need_group_cleanup = TRUE;
984 }
985
986 void avahi_entry_group_commit(AvahiEntryGroup *g) {
987     AvahiEntry *e;
988     
989     g_assert(g);
990     g_assert(!g->dead);
991
992     if (g->state != AVAHI_ENTRY_GROUP_UNCOMMITED)
993         return;
994
995     avahi_entry_group_change_state(g, AVAHI_ENTRY_GROUP_REGISTERING);
996     avahi_announce_group(g->server, g);
997     avahi_entry_group_check_probed(g, FALSE);
998 }
999
1000 gboolean avahi_entry_commited(AvahiEntry *e) {
1001     g_assert(e);
1002     g_assert(!e->dead);
1003
1004     return !e->group ||
1005         e->group->state == AVAHI_ENTRY_GROUP_REGISTERING ||
1006         e->group->state == AVAHI_ENTRY_GROUP_ESTABLISHED;
1007 }
1008
1009 AvahiEntryGroupState avahi_entry_group_get_state(AvahiEntryGroup *g) {
1010     g_assert(g);
1011     g_assert(!g->dead);
1012
1013     return g->state;
1014 }