/* The daemon forks before calling this: it should deal with one connection, blocking as neccessary, and then return. Note, need to be a bit careful about resources for debug mode, when the fork is suppressed: that's done by the caller. */ unsigned char *tcp_request(int confd, time_t now, struct in_addr local_addr, struct in_addr netmask) { int size = 0; size_t m; unsigned short qtype, gotname; unsigned char c1, c2; /* Max TCP packet + slop */ unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ); HEADER *header; struct server *last_server; while (1) { if (!packet || !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) || !(size = c1 << 8 | c2) || !read_write(confd, packet, size, 1)) return packet; if (size < (int)sizeof(HEADER)) continue; header = (HEADER *)packet; if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype))) { union mysockaddr peer_addr; socklen_t peer_len = sizeof(union mysockaddr); if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) != -1) { char types[20]; querystr(types, qtype); if (peer_addr.sa.sa_family == AF_INET) log_query(F_QUERY | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&peer_addr.in.sin_addr, types); #ifdef HAVE_IPV6 else log_query(F_QUERY | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&peer_addr.in6.sin6_addr, types); #endif } } /* m > 0 if answered from cache */ m = answer_request(header, ((char *) header) + 65536, (unsigned int)size, local_addr, netmask, now); /* Do this by steam now we're not in the select() loop */ check_log_writer(NULL); if (m == 0) { unsigned short flags = 0; struct all_addr *addrp = NULL; int type = 0; char *domain = NULL; if (gotname) flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain); if (type != 0 || (daemon->options & OPT_ORDER) || !daemon->last_server) last_server = daemon->servers; else last_server = daemon->last_server; if (!flags && last_server) { struct server *firstsendto = NULL; unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); /* Loop round available servers until we succeed in connecting to one. Note that this code subtley ensures that consecutive queries on this connection which can go to the same server, do so. */ while (1) { if (!firstsendto) firstsendto = last_server; else { if (!(last_server = last_server->next)) last_server = daemon->servers; if (last_server == firstsendto) break; } /* server for wrong domain */ if (type != (last_server->flags & SERV_TYPE) || (type == SERV_HAS_DOMAIN && !hostname_isequal(domain, last_server->domain))) continue; if ((last_server->tcpfd == -1) && (last_server->tcpfd = socket(last_server->addr.sa.sa_family, SOCK_STREAM, 0)) != -1 && (!local_bind(last_server->tcpfd, &last_server->source_addr, last_server->interface, last_server->mark, 1) || connect(last_server->tcpfd, &last_server->addr.sa, sa_len(&last_server->addr)) == -1)) { close(last_server->tcpfd); last_server->tcpfd = -1; } if (last_server->tcpfd == -1) continue; c1 = size >> 8; c2 = size; if (!read_write(last_server->tcpfd, &c1, 1, 0) || !read_write(last_server->tcpfd, &c2, 1, 0) || !read_write(last_server->tcpfd, packet, size, 0) || !read_write(last_server->tcpfd, &c1, 1, 1) || !read_write(last_server->tcpfd, &c2, 1, 1)) { close(last_server->tcpfd); last_server->tcpfd = -1; continue; } m = (c1 << 8) | c2; if (!read_write(last_server->tcpfd, packet, m, 1)) return packet; if (!gotname) strcpy(daemon->namebuff, "query"); if (last_server->addr.sa.sa_family == AF_INET) log_query(F_SERVER | F_IPV4 | F_FORWARD, daemon->namebuff, (struct all_addr *)&last_server->addr.in.sin_addr, NULL); #ifdef HAVE_IPV6 else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&last_server->addr.in6.sin6_addr, NULL); #endif /* There's no point in updating the cache, since this process will exit and lose the information after a few queries. We make this call for the alias and bogus-nxdomain side-effects. */ /* If the crc of the question section doesn't match the crc we sent, then someone might be attempting to insert bogus values into the cache by sending replies containing questions and bogus answers. */ if (crc == questions_crc(header, (unsigned int)m, daemon->namebuff)) m = process_reply(header, now, last_server, (unsigned int)m); break; } } /* In case of local answer or no connections made. */ if (m == 0) m = setup_reply(header, (unsigned int)size, addrp, flags, daemon->local_ttl); } check_log_writer(NULL); c1 = m>>8; c2 = m; if (!read_write(confd, &c1, 1, 0) || !read_write(confd, &c2, 1, 0) || !read_write(confd, packet, m, 0)) return packet; }
/* 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, 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; }
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; }
static size_t process_reply(struct daemon *daemon, HEADER *header, time_t now, unsigned int query_crc, struct server *server, size_t n) { unsigned char *pheader, *sizep; int munged = 0; size_t plen; /* If upstream is advertising a larger UDP packet size than we allow, trim it so that we don't get overlarge requests for the client. */ if ((pheader = find_pseudoheader(header, n, &plen, &sizep))) { unsigned short udpsz; unsigned char *psave = sizep; GETSHORT(udpsz, sizep); if (udpsz > daemon->edns_pktsz) PUTSHORT(daemon->edns_pktsz, psave); } if (header->opcode != QUERY || (header->rcode != NOERROR && header->rcode != NXDOMAIN)) return n; /* Complain loudly if the upstream server is non-recursive. */ if (!header->ra && header->rcode == NOERROR && ntohs(header->ancount) == 0 && server && !(server->flags & SERV_WARNED_RECURSIVE)) { prettyprint_addr(&server->addr, daemon->namebuff); syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff); if (!(daemon->options & OPT_LOG)) server->flags |= SERV_WARNED_RECURSIVE; } if (daemon->bogus_addr && header->rcode != NXDOMAIN && check_for_bogus_wildcard(header, n, daemon->namebuff, daemon->bogus_addr, now)) { munged = 1; header->rcode = NXDOMAIN; header->aa = 0; } else { if (header->rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) && check_for_local_domain(daemon->namebuff, now, daemon)) { /* if we forwarded a query for a locally known name (because it was for an unknown type) and the answer is NXDOMAIN, convert that to NODATA, since we know that the domain exists, even if upstream doesn't */ munged = 1; header->aa = 1; header->rcode = NOERROR; } /* If the crc of the question section doesn't match the crc we sent, then someone might be attempting to insert bogus values into the cache by sending replies containing questions and bogus answers. */ if (query_crc == questions_crc(header, n, daemon->namebuff)) extract_addresses(header, n, daemon->namebuff, now, daemon); } /* do this after extract_addresses. Ensure NODATA reply and remove nameserver info. */ if (munged) { header->ancount = htons(0); header->nscount = htons(0); header->arcount = htons(0); } /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide sections of the packet. Find the new length here and put back pseudoheader if it was removed. */ return resize_packet(header, n, pheader, plen); }
/* returns new last_server */ static void forward_query(struct daemon *daemon, 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; 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(daemon, now, &addrp, gotname, daemon->namebuff, &type, &domain); if (!flags && !(forward = get_new_frec(daemon, now, NULL))) /* table full - server failure. */ flags = F_NEG; if (forward) { forward->source = *udpaddr; forward->dest = *dst_addr; forward->iface = dst_iface; forward->new_id = get_id(); forward->fd = udpfd; forward->orig_id = ntohs(header->id); 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)) { start = daemon->servers; 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; 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)) { if (sendto(start->sfd->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, 0, NULL, 0); #ifdef HAVE_IPV6 else log_query(F_SERVER | F_IPV6 | F_FORWARD, daemon->namebuff, (struct all_addr *)&start->addr.in6.sin6_addr, 0, NULL, 0); #endif 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; /* could not send on, prepare to return */ header->id = htons(forward->orig_id); forward->new_id = 0; /* 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; }