diff --git a/announce.c b/announce.c index a562277..cd404fe 100644 --- a/announce.c +++ b/announce.c @@ -65,9 +65,9 @@ announce_timer(struct uloop_timeout *timeout) /* Fall through */ case STATE_ANNOUNCE: - dns_reply_a(iface, NULL, announce_ttl, NULL); - dns_reply_a_additional(iface, NULL, announce_ttl); - service_announce_services(iface, NULL, announce_ttl); + dns_reply_a(iface, NULL, announce_ttl, NULL, false); + dns_reply_a_additional(iface, NULL, announce_ttl, false); + service_announce_services(iface, NULL, announce_ttl, false); uloop_timeout_set(timeout, announce_ttl * 800); break; } diff --git a/dns.c b/dns.c index e2db851..b2fbec5 100644 --- a/dns.c +++ b/dns.c @@ -157,6 +157,9 @@ void dns_packet_answer(const char *name, int type, const uint8_t *rdata, uint16_ pkt.h.flags |= cpu_to_be16(0x8400); a = dns_packet_record_add(sizeof(*a) + rdlength, name); + if (!a) + return; + memset(a, 0, sizeof(*a)); a->type = cpu_to_be16(type); a->class = cpu_to_be16(1); @@ -266,7 +269,7 @@ void dns_query(const char *name, uint16_t type) } void -dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname) +dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname, bool append) { struct ifaddrs *ifap, *ifa; struct sockaddr_in *sa; @@ -277,7 +280,9 @@ dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *h getifaddrs(&ifap); - dns_packet_init(); + if (!append) + dns_packet_init(); + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, iface->name)) continue; @@ -292,16 +297,17 @@ dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *h } freeifaddrs(ifap); - dns_packet_send(iface, to, 0, 0); + if(!append) + dns_packet_send(iface, to, 0, 0); } void -dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl) +dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl, bool append) { struct hostname *h; vlist_for_each_element(&hostnames, h, node) - dns_reply_a(iface, to, ttl, h->hostname); + dns_reply_a(iface, to, ttl, h->hostname, append); } static int @@ -484,7 +490,8 @@ match_ip_addresses(char *reverse_ip, char *intf_ip) } static void -dns_reply_reverse_ip6_mapping(struct interface *iface, struct sockaddr *to, int ttl, char *name, char *reverse_ip) +dns_reply_reverse_ip6_mapping(struct interface *iface, struct sockaddr *to, int ttl, char *name, char *reverse_ip, + bool append) { struct ifaddrs *ifap, *ifa; struct sockaddr_in6 *sa6; @@ -494,7 +501,10 @@ dns_reply_reverse_ip6_mapping(struct interface *iface, struct sockaddr *to, int int len; getifaddrs(&ifap); - dns_packet_init(); + + if (!append) + dns_packet_init(); + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, iface->name)) continue; @@ -513,13 +523,16 @@ dns_reply_reverse_ip6_mapping(struct interface *iface, struct sockaddr *to, int } } } - dns_packet_send(iface, to, 0, 0); + + if (!append) + dns_packet_send(iface, to, 0, 0); freeifaddrs(ifap); } static void -dns_reply_reverse_ip4_mapping(struct interface *iface, struct sockaddr *to, int ttl, char *name, char *reverse_ip) +dns_reply_reverse_ip4_mapping(struct interface *iface, struct sockaddr *to, int ttl, char *name, char *reverse_ip, + bool append) { struct ifaddrs *ifap, *ifa; struct sockaddr_in *sa; @@ -529,7 +542,10 @@ dns_reply_reverse_ip4_mapping(struct interface *iface, struct sockaddr *to, int int len; getifaddrs(&ifap); - dns_packet_init(); + + if (!append) + dns_packet_init(); + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, iface->name)) continue; @@ -548,7 +564,8 @@ dns_reply_reverse_ip4_mapping(struct interface *iface, struct sockaddr *to, int } } } - dns_packet_send(iface, to, 0, 0); + if (!append) + dns_packet_send(iface, to, 0, 0); freeifaddrs(ifap); } @@ -572,7 +589,7 @@ is_reverse_dns_query(const char *name, const char *suffix) } static void -parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q) +parse_question(struct interface *iface, struct sockaddr *from, char *name, struct dns_question *q, bool append) { int is_unicast = (q->class & CLASS_UNICAST) != 0; struct sockaddr *to = NULL; @@ -581,9 +598,12 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc /* TODO: Multicast if more than one quarter of TTL has passed */ if (is_unicast) { - to = from; - if (interface_multicast(iface)) - iface = interface_get(iface->name, iface->type | SOCKTYPE_BIT_UNICAST); + /* if append is true we have already done this */ + if (!append) { + to = from; + if (interface_multicast(iface)) + iface = interface_get(iface->name, iface->type | SOCKTYPE_BIT_UNICAST); + } } DBG(1, "Q -> %s %s\n", dns_type_string(q->type), name); @@ -591,9 +611,9 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc switch (q->type) { case TYPE_ANY: if (!strcasecmp(name, mdns_hostname_local)) { - dns_reply_a(iface, to, announce_ttl, NULL); - dns_reply_a_additional(iface, to, announce_ttl); - service_reply(iface, to, NULL, NULL, announce_ttl, is_unicast); + dns_reply_a(iface, to, announce_ttl, NULL, append); + dns_reply_a_additional(iface, to, announce_ttl, append); + service_reply(iface, to, NULL, NULL, announce_ttl, is_unicast, append); } break; @@ -603,7 +623,7 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc char name_buf[256]; strcpy(name_buf, name); *host = '\0'; - dns_reply_reverse_ip4_mapping(iface, to, announce_ttl, name_buf, name); + dns_reply_reverse_ip4_mapping(iface, to, announce_ttl, name_buf, name, append); break; } @@ -612,22 +632,22 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc char name_buf6[256]; strcpy(name_buf6, name); *host6 = '\0'; - dns_reply_reverse_ip6_mapping(iface, to, announce_ttl, name_buf6, name); + dns_reply_reverse_ip6_mapping(iface, to, announce_ttl, name_buf6, name, append); break; } if (!strcasecmp(name, C_DNS_SD)) { - service_announce_services(iface, to, announce_ttl); + service_announce_services(iface, to, announce_ttl, append); } else { if (name[0] == '_') { - service_reply(iface, to, NULL, name, announce_ttl, is_unicast); + service_reply(iface, to, NULL, name, announce_ttl, is_unicast, append); } else { /* First dot separates instance name from the rest */ char *dot = strchr(name, '.'); if (dot) { *dot = '\0'; - service_reply(iface, to, name, dot + 1, announce_ttl, is_unicast); + service_reply(iface, to, name, dot + 1, announce_ttl, is_unicast, append); *dot = '.'; } } @@ -640,24 +660,58 @@ parse_question(struct interface *iface, struct sockaddr *from, char *name, struc if (host) *host = '\0'; if (!strcasecmp(umdns_host_label, name)) { - dns_reply_a(iface, to, announce_ttl, NULL); + dns_reply_a(iface, to, announce_ttl, NULL, append); } else { if (host) *host = '.'; vlist_for_each_element(&hostnames, h, node) if (!strcasecmp(h->hostname, name)) - dns_reply_a(iface, to, announce_ttl, h->hostname); + dns_reply_a(iface, to, announce_ttl, h->hostname, append); } break; }; } +static void +dns_append_questions(uint8_t *orig_buffer, int orig_len) +{ + /* Construct original question section */ + const struct dns_header *orig_h; + uint8_t *ptr = orig_buffer; + int len = orig_len; + + orig_h = dns_consume_header(&ptr, &len); + if (orig_h) { + pkt.h.id = cpu_to_be16(orig_h->id); + + uint16_t q_count = be16_to_cpu(orig_h->questions); + while (q_count-- > 0 && len > 0) { + char *qname = dns_consume_name(orig_buffer, orig_len, &ptr, &len); + if (!qname || len < (int)sizeof(struct dns_question)) + break; + + struct dns_question *q = dns_consume_question(&ptr, &len); + if (!q) + break; + + dns_packet_question(qname, q->type); + } + } +} + void dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, uint8_t *buffer, int len) { struct dns_header *h; uint8_t *b = buffer; int rlen = len; + uint8_t orig_buffer[len]; + struct sockaddr *to = NULL; + bool append = false; + + /* make a copy of the original buffer since it might be needed to construct the answer + * in case the query is received from a one-shot multicast dns querier */ + memcpy(orig_buffer, buffer, len); h = dns_consume_header(&b, &rlen); if (!h) { @@ -665,9 +719,22 @@ dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, return; } - if (h->questions && !interface_multicast(iface) && port != MCAST_PORT) - /* silently drop unicast questions that dont originate from port 5353 */ - return; + /* legacy querier */ + if (port != MCAST_PORT) { + /* aggregate answers and send, instead of sending separately */ + append = true; + + /* packet construction starts here */ + dns_packet_init(); + + /* add original questions, as outlined by RFC 6762 Section 6.7 */ + dns_append_questions(orig_buffer, len); + + /* to return a unicast response */ + to = from; + if (interface_multicast(iface)) + iface = interface_get(iface->name, iface->type | SOCKTYPE_BIT_UNICAST); + } while (h->questions-- > 0) { char *name = dns_consume_name(buffer, len, &b, &rlen); @@ -685,9 +752,13 @@ dns_handle_packet(struct interface *iface, struct sockaddr *from, uint16_t port, } if (!(h->flags & FLAG_RESPONSE)) - parse_question(iface, from, name, q); + parse_question(iface, from, name, q, append); } + /* if append is true, then answers have only been appended to the packet, not sent, so we do that here */ + if (append && pkt.h.answers > 0) + dns_packet_send(iface, to, 0, 0); + if (!(h->flags & FLAG_RESPONSE)) return; diff --git a/dns.h b/dns.h index fde049f..bb09341 100644 --- a/dns.h +++ b/dns.h @@ -82,8 +82,8 @@ void dns_query(const char *name, uint16_t type); void dns_send_question(struct interface *iface, struct sockaddr *to, const char *question, int type, int multicast); -void dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname); -void dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl); +void dns_reply_a(struct interface *iface, struct sockaddr *to, int ttl, const char *hostname, bool append); +void dns_reply_a_additional(struct interface *iface, struct sockaddr *to, int ttl, bool append); const char* dns_type_string(uint16_t type); void dns_handle_packet(struct interface *iface, struct sockaddr *s, uint16_t port, uint8_t *buf, int len); diff --git a/interface.c b/interface.c index 340878e..ee94d45 100644 --- a/interface.c +++ b/interface.c @@ -666,9 +666,9 @@ void interface_shutdown(void) vlist_for_each_element(&interfaces, iface, node) if (interface_multicast(iface)) { - dns_reply_a(iface, NULL, 0, NULL); - dns_reply_a_additional(iface, NULL, 0); - service_announce_services(iface, NULL, 0); + dns_reply_a(iface, NULL, 0, NULL, false); + dns_reply_a_additional(iface, NULL, 0, false); + service_announce_services(iface, NULL, 0, false); } for (size_t i = 0; i < ARRAY_SIZE(ufd); i++) { diff --git a/service.c b/service.c index 441ec2e..93c1f1c 100644 --- a/service.c +++ b/service.c @@ -118,7 +118,8 @@ service_timeout(struct service *s) } static void -service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force) +service_reply_single(struct interface *iface, struct sockaddr *to, struct service *s, int ttl, int force, + bool append) { const char *host = service_instance_name(s); char *service = strstr(host, "._"); @@ -131,16 +132,21 @@ service_reply_single(struct interface *iface, struct sockaddr *to, struct servic s->t = t; - dns_packet_init(); + if (!append) + dns_packet_init(); + service_add_ptr(service, service_instance_name(s), ttl); service_add_srv(host, s, ttl); if (s->txt && s->txt_len) dns_packet_answer(host, TYPE_TXT, (uint8_t *) s->txt, s->txt_len, ttl); - dns_packet_send(iface, to, 0, 0); + + if (!append) + dns_packet_send(iface, to, 0, 0); } void -service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force) +service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force, + bool append) { struct service *s; @@ -149,17 +155,19 @@ service_reply(struct interface *iface, struct sockaddr *to, const char *instance continue; if (service_domain && strcmp(s->service, service_domain)) continue; - service_reply_single(iface, to, s, ttl, force); + service_reply_single(iface, to, s, ttl, force, append); } } void -service_announce_services(struct interface *iface, struct sockaddr *to, int ttl) +service_announce_services(struct interface *iface, struct sockaddr *to, int ttl, bool append) { struct service *s; int count = 0; - dns_packet_init(); + if (!append) + dns_packet_init(); + vlist_for_each_element(&announced_services, s, node) { s->t = 0; if (ttl) { @@ -168,7 +176,8 @@ service_announce_services(struct interface *iface, struct sockaddr *to, int ttl) } } if (count) - dns_packet_send(iface, to, 0, 0); + if (!append) + dns_packet_send(iface, to, 0, 0); } void @@ -183,7 +192,7 @@ service_update(struct vlist_tree *tree, struct vlist_node *node_new, if (service_init_announce) vlist_for_each_element(&interfaces, iface, node) { s->t = 0; - service_reply_single(iface, NULL, s, announce_ttl, 1); + service_reply_single(iface, NULL, s, announce_ttl, 1, false); } return; } @@ -191,7 +200,7 @@ service_update(struct vlist_tree *tree, struct vlist_node *node_new, s = container_of(node_old, struct service, node); if (!node_new && service_init_announce) vlist_for_each_element(&interfaces, iface, node) - service_reply_single(iface, NULL, s, 0, 1); + service_reply_single(iface, NULL, s, 0, 1, false); free(s); } @@ -205,14 +214,14 @@ hostname_update(struct vlist_tree *tree, struct vlist_node *node_new, if (!node_old) { h = container_of(node_new, struct hostname, node); vlist_for_each_element(&interfaces, iface, node) - dns_reply_a(iface, NULL, announce_ttl, h->hostname); + dns_reply_a(iface, NULL, announce_ttl, h->hostname, false); return; } h = container_of(node_old, struct hostname, node); if (!node_new) vlist_for_each_element(&interfaces, iface, node) - dns_reply_a(iface, NULL, 0, h->hostname); + dns_reply_a(iface, NULL, 0, h->hostname, false); free(h); } diff --git a/service.h b/service.h index 7149672..f256269 100644 --- a/service.h +++ b/service.h @@ -42,8 +42,8 @@ extern struct vlist_tree announced_services; extern void service_init(int announce); extern void service_cleanup(void); -extern void service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force); -extern void service_announce_services(struct interface *iface, struct sockaddr *to, int ttl); +extern void service_reply(struct interface *iface, struct sockaddr *to, const char *instance, const char *service_domain, int ttl, int force, bool append); +extern void service_announce_services(struct interface *iface, struct sockaddr *to, int ttl, bool append); extern void service_update(struct vlist_tree *tree, struct vlist_node *node_new, struct vlist_node *node_old); #endif