]> git.meshlink.io Git - catta/blob - dns.c
add support for dots and backslashes in domain names (required for DNS-SD)
[catta] / dns.c
1 #include <netinet/in.h>
2
3 #include <stdlib.h>
4 #include <string.h>
5 #include <stdio.h>
6
7 #include "dns.h"
8 #include "util.h"
9
10 flxDnsPacket* flx_dns_packet_new(guint max_size) {
11     flxDnsPacket *p;
12
13     if (max_size <= 0)
14         max_size = FLX_DNS_PACKET_MAX_SIZE;
15     else if (max_size < FLX_DNS_PACKET_HEADER_SIZE)
16         max_size = FLX_DNS_PACKET_HEADER_SIZE;
17     
18     p = g_malloc(sizeof(flxDnsPacket) + max_size);
19     p->size = p->rindex = FLX_DNS_PACKET_HEADER_SIZE;
20     p->max_size = max_size;
21     p->name_table = NULL;
22
23     memset(FLX_DNS_PACKET_DATA(p), 0, p->size);
24     return p;
25 }
26
27 flxDnsPacket* flx_dns_packet_new_query(guint max_size) {
28     flxDnsPacket *p;
29
30     p = flx_dns_packet_new(max_size);
31     flx_dns_packet_set_field(p, FLX_DNS_FIELD_FLAGS, FLX_DNS_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
32     return p;
33 }
34
35 flxDnsPacket* flx_dns_packet_new_response(guint max_size) {
36     flxDnsPacket *p;
37
38     p = flx_dns_packet_new(max_size);
39     flx_dns_packet_set_field(p, FLX_DNS_FIELD_FLAGS, FLX_DNS_FLAGS(1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
40     return p;
41 }
42
43 void flx_dns_packet_free(flxDnsPacket *p) {
44     g_assert(p);
45
46     if (p->name_table)
47         g_hash_table_destroy(p->name_table);
48     
49     g_free(p);
50 }
51
52 void flx_dns_packet_set_field(flxDnsPacket *p, guint index, guint16 v) {
53     g_assert(p);
54     g_assert(index < FLX_DNS_PACKET_HEADER_SIZE);
55     
56     ((guint16*) FLX_DNS_PACKET_DATA(p))[index] = g_htons(v);
57 }
58
59 guint16 flx_dns_packet_get_field(flxDnsPacket *p, guint index) {
60     g_assert(p);
61     g_assert(index < FLX_DNS_PACKET_HEADER_SIZE);
62
63     return g_ntohs(((guint16*) FLX_DNS_PACKET_DATA(p))[index]);
64 }
65
66 /* Read the first label from string dest, unescape "\" and append it to *name */
67 static gchar *unescape_label(gchar *dest, guint size, const gchar **name) {
68     guint i = 0;
69     gchar *d;
70     
71     g_assert(dest);
72     g_assert(size > 0);
73     g_assert(name);
74     g_assert(*name);
75
76     d = dest;
77     
78     for (;;) {
79         if (i >= size)
80             return NULL;
81
82         if (**name == '.') {
83             (*name)++;
84             break;
85         }
86         
87         if (**name == 0)
88             break;
89         
90         if (**name == '\\') {
91             (*name) ++;
92             
93             if (**name == 0)
94                 break;
95         }
96         
97         *(d++) = *((*name) ++);
98         i++;
99     }
100
101     g_assert(i < size);
102
103     *d = 0;
104
105     return dest;
106 }
107
108 guint8* flx_dns_packet_append_name(flxDnsPacket *p, const gchar *name) {
109     guint8 *d, *f = NULL;
110     guint saved_size;
111     
112     g_assert(p);
113     g_assert(name);
114
115     saved_size = p->size;
116
117     while (*name) {
118         guint n;
119         guint8* prev;
120         const gchar *pname;
121         char label[64];
122
123         /* Check whether we can compress this name. */
124
125         if (p->name_table && (prev = g_hash_table_lookup(p->name_table, name))) {
126             guint index;
127             
128             g_assert(prev >= FLX_DNS_PACKET_DATA(p));
129             index = (guint) (prev - FLX_DNS_PACKET_DATA(p));
130
131             g_assert(index < p->size);
132
133             if (index < 0x4000) {
134                 guint16 *t;
135                 if (!(t = (guint16*) flx_dns_packet_extend(p, sizeof(guint16))))
136                     return NULL;
137
138                 if (!f)
139                     f = (guint8*) t;
140     
141                 *t = g_htons((0xC000 | index));
142                 return f;
143             }
144         }
145
146         pname = name;
147         
148         if (!(unescape_label(label, sizeof(label), &name)))
149             goto fail;
150
151         if (!(d = flx_dns_packet_append_string(p, label)))
152             goto fail;
153         
154         if (!p->name_table)
155             p->name_table = g_hash_table_new_full((GHashFunc) flx_domain_hash, (GEqualFunc) flx_domain_equal, g_free, NULL);
156
157         g_hash_table_insert(p->name_table, g_strdup(pname), d);
158     }
159
160     if (!(d = flx_dns_packet_extend(p, 1)))
161         goto fail;
162     
163     *d = 0;
164
165     return f;
166
167 fail:
168     p->size = saved_size;
169     return NULL;
170 }
171
172 guint8* flx_dns_packet_append_uint16(flxDnsPacket *p, guint16 v) {
173     guint8 *d;
174     g_assert(p);
175     
176     if (!(d = flx_dns_packet_extend(p, sizeof(guint16))))
177         return NULL;
178     
179     *((guint16*) d) = g_htons(v);
180     return d;
181 }
182
183 guint8 *flx_dns_packet_append_uint32(flxDnsPacket *p, guint32 v) {
184     guint8 *d;
185     g_assert(p);
186
187     if (!(d = flx_dns_packet_extend(p, sizeof(guint32))))
188         return NULL;
189     
190     *((guint32*) d) = g_htonl(v);
191
192     return d;
193 }
194
195 guint8 *flx_dns_packet_append_bytes(flxDnsPacket  *p, gconstpointer b, guint l) {
196     guint8* d;
197
198     g_assert(p);
199     g_assert(b);
200     g_assert(l);
201
202     if (!(d = flx_dns_packet_extend(p, l)))
203         return NULL;
204
205     memcpy(d, b, l);
206     return d;
207 }
208
209 guint8* flx_dns_packet_append_string(flxDnsPacket *p, const gchar *s) {
210     guint8* d;
211     guint k;
212     
213     g_assert(p);
214     g_assert(s);
215
216     if ((k = strlen(s)) >= 255)
217         k = 255;
218     
219     if (!(d = flx_dns_packet_extend(p, k+1)))
220         return NULL;
221
222     *d = (guint8) k;
223     memcpy(d+1, s, k);
224
225     return d;
226 }
227
228 guint8 *flx_dns_packet_extend(flxDnsPacket *p, guint l) {
229     guint8 *d;
230     
231     g_assert(p);
232
233     if (p->size+l > p->max_size)
234         return NULL;
235     
236     d = FLX_DNS_PACKET_DATA(p) + p->size;
237     p->size += l;
238     
239     return d;
240 }
241
242 gint flx_dns_packet_check_valid(flxDnsPacket *p) {
243     guint16 flags;
244     g_assert(p);
245
246     if (p->size < 12)
247         return -1;
248
249     flags = flx_dns_packet_get_field(p, FLX_DNS_FIELD_FLAGS);
250
251     if (flags & FLX_DNS_FLAG_OPCODE || flags & FLX_DNS_FLAG_RCODE)
252         return -1;
253
254     return 0;
255 }
256
257 gint flx_dns_packet_is_query(flxDnsPacket *p) {
258     g_assert(p);
259     
260     return !(flx_dns_packet_get_field(p, FLX_DNS_FIELD_FLAGS) & FLX_DNS_FLAG_QR);
261 }
262
263 /* Read a label from a DNS packet, escape "\" and ".", append \0 */
264 static gchar *escape_label(guint8* src, guint src_length, gchar **ret_name, guint *ret_name_length) {
265     gchar *r;
266
267     g_assert(src);
268     g_assert(ret_name);
269     g_assert(*ret_name);
270     g_assert(ret_name_length);
271     g_assert(*ret_name_length > 0);
272
273     r = *ret_name;
274
275     while (src_length > 0) {
276         if (*src == '.' || *src == '\\') {
277             if (*ret_name_length < 3)
278                 return NULL;
279             
280             *((*ret_name) ++) = '\\';
281             (*ret_name_length) --;
282         }
283
284         if (*ret_name_length < 2)
285             return NULL;
286         
287         *((*ret_name)++) = *src;
288         (*ret_name_length) --;
289
290         src_length --;
291         src++;
292     }
293
294     **ret_name = 0;
295
296     return r;
297 }
298
299 static gint consume_labels(flxDnsPacket *p, guint index, gchar *ret_name, guint l) {
300     gint ret = 0;
301     int compressed = 0;
302     int first_label = 1;
303     g_assert(p && ret_name && l);
304     
305     for (;;) {
306         guint8 n;
307
308         if (index+1 > p->size)
309             return -1;
310
311         n = FLX_DNS_PACKET_DATA(p)[index];
312
313         if (!n) {
314             index++;
315             if (!compressed)
316                 ret++;
317
318             if (l < 1)
319                 return -1;
320             *ret_name = 0;
321             
322             return ret;
323             
324         } else if (n <= 63) {
325             /* Uncompressed label */
326             index++;
327             if (!compressed)
328                 ret++;
329         
330             if (index + n > p->size)
331                 return -1;
332
333             if ((guint) n + 1 > l)
334                 return -1;
335
336             if (!first_label) {
337                 *(ret_name++) = '.';
338                 l--;
339             } else
340                 first_label = 0;
341
342             if (!(escape_label(FLX_DNS_PACKET_DATA(p) + index, n, &ret_name, &l)))
343                 return -1;
344
345             index += n;
346             
347             if (!compressed)
348                 ret += n;
349         } else if ((n & 0xC0) == 0xC0) {
350             /* Compressed label */
351
352             if (index+2 > p->size)
353                 return -1;
354
355             index = ((guint) (FLX_DNS_PACKET_DATA(p)[index] & ~0xC0)) << 8 | FLX_DNS_PACKET_DATA(p)[index+1];
356
357             if (!compressed)
358                 ret += 2;
359             
360             compressed = 1;
361         } else
362             return -1;
363     }
364 }
365
366 gint flx_dns_packet_consume_name(flxDnsPacket *p, gchar *ret_name, guint l) {
367     gint r;
368     
369     if ((r = consume_labels(p, p->rindex, ret_name, l)) < 0)
370         return -1;
371
372     p->rindex += r;
373     return 0;
374 }
375
376 gint flx_dns_packet_consume_uint16(flxDnsPacket *p, guint16 *ret_v) {
377     g_assert(p);
378     g_assert(ret_v);
379
380     if (p->rindex + sizeof(guint16) > p->size)
381         return -1;
382
383     *ret_v = g_ntohs(*((guint16*) (FLX_DNS_PACKET_DATA(p) + p->rindex)));
384     p->rindex += sizeof(guint16);
385
386     return 0;
387 }
388
389 gint flx_dns_packet_consume_uint32(flxDnsPacket *p, guint32 *ret_v) {
390     g_assert(p);
391     g_assert(ret_v);
392
393     if (p->rindex + sizeof(guint32) > p->size)
394         return -1;
395
396     *ret_v = g_ntohl(*((guint32*) (FLX_DNS_PACKET_DATA(p) + p->rindex)));
397     p->rindex += sizeof(guint32);
398     
399     return 0;
400 }
401
402 gint flx_dns_packet_consume_bytes(flxDnsPacket *p, gpointer ret_data, guint l) {
403     g_assert(p);
404     g_assert(ret_data);
405     g_assert(l > 0);
406     
407     if (p->rindex + l > p->size)
408         return -1;
409
410     memcpy(ret_data, FLX_DNS_PACKET_DATA(p) + p->rindex, l);
411     p->rindex += l;
412
413     return 0;
414 }
415
416 gint flx_dns_packet_consume_string(flxDnsPacket *p, gchar *ret_string, guint l) {
417     guint k;
418     
419     g_assert(p);
420     g_assert(ret_string);
421     g_assert(l > 0);
422
423     if (p->rindex >= p->size)
424         return -1;
425
426     k = FLX_DNS_PACKET_DATA(p)[p->rindex];
427
428     if (p->rindex+1+k > p->size)
429         return -1;
430
431     if (l > k+1)
432         l = k+1;
433
434     memcpy(ret_string, FLX_DNS_PACKET_DATA(p)+p->rindex+1, l-1);
435     ret_string[l-1] = 0;
436
437     
438     p->rindex += 1+k;
439
440     return 0;
441     
442 }
443
444 gconstpointer flx_dns_packet_get_rptr(flxDnsPacket *p) {
445     g_assert(p);
446     
447     if (p->rindex > p->size)
448         return NULL;
449
450     return FLX_DNS_PACKET_DATA(p) + p->rindex;
451 }
452
453 gint flx_dns_packet_skip(flxDnsPacket *p, guint length) {
454     g_assert(p);
455
456     if (p->rindex + length > p->size)
457         return -1;
458
459     p->rindex += length;
460     return 0;
461 }
462
463 flxRecord* flx_dns_packet_consume_record(flxDnsPacket *p, gboolean *ret_cache_flush) {
464     gchar name[257], buf[257];
465     guint16 type, class;
466     guint32 ttl;
467     guint16 rdlength;
468     gconstpointer data;
469     flxRecord *r = NULL;
470     gconstpointer start;
471
472     g_assert(p);
473     g_assert(ret_cache_flush);
474
475 /*     g_message("consume_record()"); */
476
477     if (flx_dns_packet_consume_name(p, name, sizeof(name)) < 0 ||
478         flx_dns_packet_consume_uint16(p, &type) < 0 ||
479         flx_dns_packet_consume_uint16(p, &class) < 0 ||
480         flx_dns_packet_consume_uint32(p, &ttl) < 0 ||
481         flx_dns_packet_consume_uint16(p, &rdlength) < 0 ||
482         p->rindex + rdlength > p->size)
483         
484         goto fail;
485
486 /*     g_message("name = %s, rdlength = %u", name, rdlength); */
487     
488     start = flx_dns_packet_get_rptr(p);
489
490     r = flx_record_new_full(name, class, type);
491     
492     switch (type) {
493         case FLX_DNS_TYPE_PTR:
494         case FLX_DNS_TYPE_CNAME:
495
496 /*             g_message("ptr"); */
497             
498             if (flx_dns_packet_consume_name(p, buf, sizeof(buf)) < 0)
499                 goto fail;
500
501             r->data.ptr.name = g_strdup(buf);
502             break;
503
504             
505         case FLX_DNS_TYPE_SRV:
506
507 /*             g_message("srv"); */
508             
509             if (flx_dns_packet_consume_uint16(p, &r->data.srv.priority) < 0 ||
510                 flx_dns_packet_consume_uint16(p, &r->data.srv.weight) < 0 ||
511                 flx_dns_packet_consume_uint16(p, &r->data.srv.port) < 0 ||
512                 flx_dns_packet_consume_name(p, buf, sizeof(buf)) < 0)
513                 goto fail;
514             
515             r->data.srv.name = g_strdup(buf);
516             break;
517
518         case FLX_DNS_TYPE_HINFO:
519             
520 /*             g_message("hinfo"); */
521
522             if (flx_dns_packet_consume_string(p, buf, sizeof(buf)) < 0)
523                 goto fail;
524
525             r->data.hinfo.cpu = g_strdup(buf);
526
527             if (flx_dns_packet_consume_string(p, buf, sizeof(buf)) < 0)
528                 goto fail;
529
530             r->data.hinfo.os = g_strdup(buf);
531             break;
532
533         case FLX_DNS_TYPE_TXT:
534
535 /*             g_message("txt"); */
536
537             if (rdlength > 0) {
538                 r->data.txt.string_list = flx_string_list_parse(flx_dns_packet_get_rptr(p), rdlength);
539                 
540                 if (flx_dns_packet_skip(p, rdlength) < 0)
541                     goto fail;
542             } else
543                 r->data.txt.string_list = NULL;
544             
545             break;
546
547         case FLX_DNS_TYPE_A:
548
549 /*             g_message("A"); */
550
551             if (flx_dns_packet_consume_bytes(p, &r->data.a.address, sizeof(flxIPv4Address)) < 0)
552                 goto fail;
553             
554             break;
555
556         case FLX_DNS_TYPE_AAAA:
557
558 /*             g_message("aaaa"); */
559             
560             if (flx_dns_packet_consume_bytes(p, &r->data.aaaa.address, sizeof(flxIPv6Address)) < 0)
561                 goto fail;
562             
563             break;
564             
565         default:
566
567 /*             g_message("generic"); */
568             
569             if (rdlength > 0) {
570
571                 r->data.generic.data = g_memdup(flx_dns_packet_get_rptr(p), rdlength);
572                 
573                 if (flx_dns_packet_skip(p, rdlength) < 0)
574                     goto fail;
575             }
576
577             break;
578     }
579
580 /*     g_message("%i == %u ?", (guint8*) flx_dns_packet_get_rptr(p) - (guint8*) start, rdlength); */
581     
582     /* Check if we read enough data */
583     if ((guint8*) flx_dns_packet_get_rptr(p) - (guint8*) start != rdlength)
584         goto fail;
585     
586     *ret_cache_flush = !!(class & FLX_DNS_CACHE_FLUSH);
587     class &= ~ FLX_DNS_CACHE_FLUSH;
588
589     r->ttl = ttl;
590
591     return r;
592
593 fail:
594     if (r)
595         flx_record_unref(r);
596
597     return NULL;
598 }
599
600 flxKey* flx_dns_packet_consume_key(flxDnsPacket *p) {
601     gchar name[256];
602     guint16 type, class;
603
604     g_assert(p);
605
606     if (flx_dns_packet_consume_name(p, name, sizeof(name)) < 0 ||
607         flx_dns_packet_consume_uint16(p, &type) < 0 ||
608         flx_dns_packet_consume_uint16(p, &class) < 0)
609         return NULL;
610
611     class &= ~ FLX_DNS_CACHE_FLUSH;
612
613     return flx_key_new(name, class, type);
614 }
615
616 guint8* flx_dns_packet_append_key(flxDnsPacket *p, flxKey *k) {
617     guint8 *t;
618     guint size;
619     
620     g_assert(p);
621     g_assert(k);
622
623     size = p->size;
624     
625     if (!(t = flx_dns_packet_append_name(p, k->name)) ||
626         !flx_dns_packet_append_uint16(p, k->type) ||
627         !flx_dns_packet_append_uint16(p, k->class)) {
628         p->size = size;
629         return NULL;
630     }
631
632     return t;
633 }
634
635 guint8* flx_dns_packet_append_record(flxDnsPacket *p, flxRecord *r, gboolean cache_flush) {
636     guint8 *t, *l, *start;
637     guint size;
638
639     g_assert(p);
640     g_assert(r);
641
642     size = p->size;
643
644     if (!(t = flx_dns_packet_append_name(p, r->key->name)) ||
645         !flx_dns_packet_append_uint16(p, r->key->type) ||
646         !flx_dns_packet_append_uint16(p, cache_flush ? (r->key->class | FLX_DNS_CACHE_FLUSH) : (r->key->class &~ FLX_DNS_CACHE_FLUSH)) ||
647         !flx_dns_packet_append_uint32(p, r->ttl) ||
648         !(l = flx_dns_packet_append_uint16(p, 0)))
649         goto fail;
650
651     start = flx_dns_packet_extend(p, 0);
652
653     switch (r->key->type) {
654         
655         case FLX_DNS_TYPE_PTR:
656         case FLX_DNS_TYPE_CNAME :
657
658             if (!(flx_dns_packet_append_name(p, r->data.ptr.name)))
659                 goto fail;
660             
661             break;
662
663         case FLX_DNS_TYPE_SRV:
664
665             if (!flx_dns_packet_append_uint16(p, r->data.srv.priority) ||
666                 !flx_dns_packet_append_uint16(p, r->data.srv.weight) ||
667                 !flx_dns_packet_append_uint16(p, r->data.srv.port) ||
668                 !flx_dns_packet_append_name(p, r->data.srv.name))
669                 goto fail;
670
671             break;
672
673         case FLX_DNS_TYPE_HINFO:
674             if (!flx_dns_packet_append_string(p, r->data.hinfo.cpu) ||
675                 !flx_dns_packet_append_string(p, r->data.hinfo.os))
676                 goto fail;
677
678             break;
679
680         case FLX_DNS_TYPE_TXT: {
681
682             guint8 *data;
683             guint size;
684
685             size = flx_string_list_serialize(r->data.txt.string_list, NULL, 0);
686
687 /*             g_message("appending string: %u %p", size, r->data.txt.string_list); */
688
689             if (!(data = flx_dns_packet_extend(p, size)))
690                 goto fail;
691
692             flx_string_list_serialize(r->data.txt.string_list, data, size);
693             break;
694         }
695
696
697         case FLX_DNS_TYPE_A:
698
699             if (!flx_dns_packet_append_bytes(p, &r->data.a.address, sizeof(r->data.a.address)))
700                 goto fail;
701             
702             break;
703
704         case FLX_DNS_TYPE_AAAA:
705             
706             if (!flx_dns_packet_append_bytes(p, &r->data.aaaa.address, sizeof(r->data.aaaa.address)))
707                 goto fail;
708             
709             break;
710             
711         default:
712
713             if (r->data.generic.size &&
714                 flx_dns_packet_append_bytes(p, r->data.generic.data, r->data.generic.size))
715                 goto fail;
716
717             break;
718     }
719
720
721
722     
723     size = flx_dns_packet_extend(p, 0) - start;
724     g_assert(size <= 0xFFFF);
725
726 /*     g_message("appended %u", size); */
727
728     * (guint16*) l = g_htons((guint16) size);
729     
730     return t;
731
732
733 fail:
734     p->size = size;
735     return NULL;
736 }
737
738 gboolean flx_dns_packet_is_empty(flxDnsPacket *p) {
739     g_assert(p);
740
741     return p->size <= FLX_DNS_PACKET_HEADER_SIZE;
742 }
743
744 guint flx_dns_packet_space(flxDnsPacket *p) {
745     g_assert(p);
746
747     g_assert(p->size <= p->max_size);
748     
749     return p->max_size - p->size;
750 }