static int forward_query(int udpfd, union mysockaddr *udpaddr, struct all_addr *dst_addr, unsigned int dst_iface, HEADER *header, size_t plen, time_t now, struct frec *forward) { char *domain = NULL; int type = 0; struct all_addr *addrp = NULL; unsigned int crc = questions_crc(header, plen, daemon->namebuff); unsigned short flags = 0; unsigned short gotname = extract_request(header, plen, daemon->namebuff, NULL); struct server *start = NULL; /* may be no servers available. */ if (!daemon->servers) forward = NULL; else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) { /* retry on existing query, send to all available servers */ domain = forward->sentto->domain; forward->sentto->failed_queries++; if (!(daemon->options & OPT_ORDER)) { forward->forwardall = 1; daemon->last_server = NULL; } type = forward->sentto->flags & SERV_TYPE; if (!(start = forward->sentto->next)) start = daemon->servers; /* at end of list, recycle */ header->id = htons(forward->new_id); } else { if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain); if (!flags && !(forward = get_new_frec(now, NULL))) /* table full - server failure. */ flags = F_NEG; if (forward) { /* force unchanging id for signed packets */ int is_sign; find_pseudoheader(header, plen, NULL, NULL, &is_sign); forward->source = *udpaddr; forward->dest = *dst_addr; forward->iface = dst_iface; forward->orig_id = ntohs(header->id); forward->new_id = get_id(is_sign, forward->orig_id, crc); forward->fd = udpfd; forward->crc = crc; forward->forwardall = 0; header->id = htons(forward->new_id); /* In strict_order mode, or when using domain specific servers always try servers in the order specified in resolv.conf, otherwise, use the one last known to work. */ if (type != 0 || (daemon->options & OPT_ORDER)) start = daemon->servers; else if (!(start = daemon->last_server) || daemon->forwardcount++ > FORWARD_TEST || difftime(now, daemon->forwardtime) > FORWARD_TIME) { start = daemon->servers; forward->forwardall = 1; daemon->forwardcount = 0; daemon->forwardtime = now; } } } /* check for send errors here (no route to host) if we fail to send to all nameservers, send back an error packet straight away (helps modem users when offline) */ if (!flags && forward) { struct server *firstsentto = start; int forwarded = 0; while (1) { /* only send to servers dealing with our domain. domain may be NULL, in which case server->domain must be NULL also. */ if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && !(start->flags & SERV_LITERAL_ADDRESS)) { int fd; /* find server socket to use, may need to get random one. */ if (start->sfd) fd = start->sfd->fd; else { #ifdef HAVE_IPV6 if (start->addr.sa.sa_family == AF_INET6) { if (!forward->rfd6 && !(forward->rfd6 = allocate_rfd(AF_INET6))) break; daemon->rfd_save = forward->rfd6; fd = forward->rfd6->fd; } else #endif { if (!forward->rfd4 && !(forward->rfd4 = allocate_rfd(AF_INET))) break; daemon->rfd_save = forward->rfd4; fd = forward->rfd4->fd; } #ifdef ANDROID // Mark the socket so it goes out on the correct network. Note // that we never clear the mark, only re-set it the next time we // allocate a new random fd. This is because we buffer DNS // queries (in daemon->srv_save, daemon->packet_len) and socket // file descriptors (in daemon->rfd_save) with the expectation of // being able to use them again. // // Server fds are marked separately in allocate_sfd. setsockopt(fd, SOL_SOCKET, SO_MARK, &start->mark, sizeof(start->mark)); #endif } if (sendto(fd, (char *)header, plen, 0, &start->addr.sa, sa_len(&start->addr)) == -1) { if (retry_send()) continue; } else { /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; daemon->packet_len = plen; if (!gotname) strcpy(daemon->namebuff, "query"); if (start->addr.sa.sa_family == AF_INET) log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&start->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&start->addr.in6.sin6_addr, NULL); #endif start->queries++; forwarded = 1; forward->sentto = start; if (!forward->forwardall) break; forward->forwardall++; } } if (!(start = start->next)) start = daemon->servers; if (start == firstsentto) break; } if (forwarded) return 1; /* could not send on, prepare to return */ header->id = htons(forward->orig_id); free_frec(forward); /* cancel */ } /* could not send on, return empty answer or address if known for whole domain */ if (udpfd != -1) { plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); send_from(udpfd, daemon->options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr, dst_iface); } return 0; }
/* sets new last_server */ void reply_query(int fd, int family, time_t now) { /* packet from peer server, extract data for cache, and send to original requester */ HEADER *header; union mysockaddr serveraddr; struct frec *forward; socklen_t addrlen = sizeof(serveraddr); ssize_t n = recvfrom(fd, daemon->packet, daemon->edns_pktsz, 0, &serveraddr.sa, &addrlen); size_t nn; struct server *server; /* packet buffer overwritten */ daemon->srv_save = NULL; /* Determine the address of the server replying so that we can mark that as good */ serveraddr.sa.sa_family = family; #ifdef HAVE_IPV6 if (serveraddr.sa.sa_family == AF_INET6) serveraddr.in6.sin6_flowinfo = 0; #endif /* spoof check: answer must come from known server, */ for (server = daemon->servers; server; server = server->next) if (!(server->flags & (SERV_LITERAL_ADDRESS | SERV_NO_ADDR)) && sockaddr_isequal(&server->addr, &serveraddr)) break; header = (HEADER *)daemon->packet; if (!server || n < (int)sizeof(HEADER) || !header->qr || !(forward = lookup_frec(ntohs(header->id), questions_crc(header, n, daemon->namebuff)))) return; server = forward->sentto; if ((header->rcode == SERVFAIL || header->rcode == REFUSED) && !(daemon->options & OPT_ORDER) && forward->forwardall == 0) /* for broken servers, attempt to send to another one. */ { unsigned char *pheader; size_t plen; int is_sign; /* recreate query from reply */ pheader = find_pseudoheader(header, (size_t)n, &plen, NULL, &is_sign); if (!is_sign) { header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); if ((nn = resize_packet(header, (size_t)n, pheader, plen))) { header->qr = 0; header->tc = 0; forward_query(-1, NULL, NULL, 0, header, nn, now, forward); return; } } } if ((forward->sentto->flags & SERV_TYPE) == 0) { if (header->rcode == SERVFAIL || header->rcode == REFUSED) server = NULL; else { struct server *last_server; /* find good server by address if possible, otherwise assume the last one we sent to */ for (last_server = daemon->servers; last_server; last_server = last_server->next) if (!(last_server->flags & (SERV_LITERAL_ADDRESS | SERV_HAS_DOMAIN | SERV_FOR_NODOTS | SERV_NO_ADDR)) && sockaddr_isequal(&last_server->addr, &serveraddr)) { server = last_server; break; } } if (!(daemon->options & OPT_ALL_SERVERS)) daemon->last_server = server; } /* If the answer is an error, keep the forward record in place in case we get a good reply from another server. Kill it when we've had replies from all to avoid filling the forwarding table when everything is broken */ if (forward->forwardall == 0 || --forward->forwardall == 1 || (header->rcode != REFUSED && header->rcode != SERVFAIL)) { if ((nn = process_reply(header, now, server, (size_t)n))) { header->id = htons(forward->orig_id); header->ra = 1; /* recursion if available */ send_from(forward->fd, daemon->options & OPT_NOWILD, daemon->packet, nn, &forward->source, &forward->dest, forward->iface); } free_frec(forward); /* cancel */ } }
static int forward_query(int udpfd, union mysockaddr *udpaddr, struct all_addr *dst_addr, unsigned int dst_iface, struct dns_header *header, size_t plen, time_t now, struct frec *forward) { char *domain = NULL; int type = 0, norebind = 0; struct all_addr *addrp = NULL; unsigned int crc = questions_crc(header, plen, daemon->namebuff); unsigned int flags = 0; unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); struct server *start = NULL; /* RFC 4035: sect 4.6 para 2 */ header->hb4 &= ~HB4_AD; /* may be no servers available. */ if (!daemon->servers) forward = NULL; else if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, crc))) { /* retry on existing query, send to all available servers */ domain = forward->sentto->domain; forward->sentto->failed_queries++; if (!option_bool(OPT_ORDER)) { forward->forwardall = 1; daemon->last_server = NULL; } type = forward->sentto->flags & SERV_TYPE; if (!(start = forward->sentto->next)) start = daemon->servers; /* at end of list, recycle */ header->id = htons(forward->new_id); } else { if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); if (!flags && !(forward = get_new_frec(now, NULL))) /* table full - server failure. */ flags = F_NEG; if (forward) { forward->source = *udpaddr; forward->dest = *dst_addr; forward->iface = dst_iface; forward->orig_id = ntohs(header->id); forward->new_id = get_id(crc); forward->fd = udpfd; forward->crc = crc; forward->forwardall = 0; if (norebind) forward->flags |= FREC_NOREBIND; if (header->hb4 & HB4_CD) forward->flags |= FREC_CHECKING_DISABLED; header->id = htons(forward->new_id); /* In strict_order mode, always try servers in the order specified in resolv.conf, if a domain is given always try all the available servers, otherwise, use the one last known to work. */ if (type == 0) { if (option_bool(OPT_ORDER)) start = daemon->servers; else if (!(start = daemon->last_server) || daemon->forwardcount++ > FORWARD_TEST || difftime(now, daemon->forwardtime) > FORWARD_TIME) { start = daemon->servers; forward->forwardall = 1; daemon->forwardcount = 0; daemon->forwardtime = now; } } else { start = daemon->servers; if (!option_bool(OPT_ORDER)) forward->forwardall = 1; } } } /* check for send errors here (no route to host) if we fail to send to all nameservers, send back an error packet straight away (helps modem users when offline) */ if (!flags && forward) { struct server *firstsentto = start; int forwarded = 0; if (udpaddr && option_bool(OPT_ADD_MAC)) plen = add_mac(header, plen, ((char *) header) + PACKETSZ, udpaddr); while (1) { /* only send to servers dealing with our domain. domain may be NULL, in which case server->domain must be NULL also. */ if (type == (start->flags & SERV_TYPE) && (type != SERV_HAS_DOMAIN || hostname_isequal(domain, start->domain)) && !(start->flags & SERV_LITERAL_ADDRESS)) { int fd; /* find server socket to use, may need to get random one. */ if (start->sfd) fd = start->sfd->fd; else { #ifdef HAVE_IPV6 if (start->addr.sa.sa_family == AF_INET6) { if (!forward->rfd6 && !(forward->rfd6 = allocate_rfd(AF_INET6))) break; daemon->rfd_save = forward->rfd6; fd = forward->rfd6->fd; } else #endif { if (!forward->rfd4 && !(forward->rfd4 = allocate_rfd(AF_INET))) break; daemon->rfd_save = forward->rfd4; fd = forward->rfd4->fd; } #ifdef HAVE_CONNTRACK /* Copy connection mark of incoming query to outgoing connection. */ if (option_bool(OPT_CONNTRACK)) { unsigned int mark; if (get_incoming_mark(udpaddr, dst_addr, 0, &mark)) setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); } #endif } if (sendto(fd, (char *)header, plen, 0, &start->addr.sa, sa_len(&start->addr)) == -1) { if (retry_send()) continue; } else { /* Keep info in case we want to re-send this packet */ daemon->srv_save = start; daemon->packet_len = plen; if (!gotname) strcpy(daemon->namebuff, "query"); if (start->addr.sa.sa_family == AF_INET) log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&start->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&start->addr.in6.sin6_addr, NULL); #endif start->queries++; forwarded = 1; forward->sentto = start; if (!forward->forwardall) break; forward->forwardall++; } } if (!(start = start->next)) start = daemon->servers; if (start == firstsentto) break; } if (forwarded) return 1; /* could not send on, prepare to return */ header->id = htons(forward->orig_id); free_frec(forward); /* cancel */ } /* could not send on, return empty answer or address if known for whole domain */ if (udpfd != -1) { plen = setup_reply(header, plen, addrp, flags, daemon->local_ttl); send_from(udpfd, option_bool(OPT_NOWILD), (char *)header, plen, udpaddr, dst_addr, dst_iface); } return 0; }