int ares_create_query(const char *name, int dnsclass, int type, unsigned short id, int rd, unsigned char **buf, int *buflen, int max_udp_size) { int len; unsigned char *q; const char *p; /* Set our results early, in case we bail out early with an error. */ *buflen = 0; *buf = NULL; /* Compute the length of the encoded name so we can check buflen. * Start counting at 1 for the zero-length label at the end. */ len = 1; for (p = name; *p; p++) { if (*p == '\\' && *(p + 1) != 0) p++; len++; } /* If there are n periods in the name, there are n + 1 labels, and * thus n + 1 length fields, unless the name is empty or ends with a * period. So add 1 unless name is empty or ends with a period. */ if (*name && *(p - 1) != '.') len++; /* Immediately reject names that are longer than the maximum of 255 * bytes that's specified in RFC 1035 ("To simplify implementations, * the total length of a domain name (i.e., label octets and label * length octets) is restricted to 255 octets or less."). We aren't * doing this just to be a stickler about RFCs. For names that are * too long, 'dnscache' closes its TCP connection to us immediately * (when using TCP) and ignores the request when using UDP, and * BIND's named returns ServFail (TCP or UDP). Sending a request * that we know will cause 'dnscache' to close the TCP connection is * painful, since that makes any other outstanding requests on that * connection fail. And sending a UDP request that we know * 'dnscache' will ignore is bad because resources will be tied up * until we time-out the request. */ if (len > MAXCDNAME) return ARES_EBADNAME; *buflen = len + HFIXEDSZ + QFIXEDSZ + (max_udp_size ? EDNSFIXEDSZ : 0); *buf = malloc(*buflen); if (!*buf) return ARES_ENOMEM; /* Set up the header. */ q = *buf; memset(q, 0, HFIXEDSZ); DNS_HEADER_SET_QID(q, id); DNS_HEADER_SET_OPCODE(q, QUERY); if (rd) { DNS_HEADER_SET_RD(q, 1); } else { DNS_HEADER_SET_RD(q, 0); } DNS_HEADER_SET_QDCOUNT(q, 1); if (max_udp_size) { DNS_HEADER_SET_ARCOUNT(q, 1); } /* A name of "." is a screw case for the loop below, so adjust it. */ if (strcmp(name, ".") == 0) name++; /* Start writing out the name after the header. */ q += HFIXEDSZ; while (*name) { if (*name == '.') return ARES_EBADNAME; /* Count the number of bytes in this label. */ len = 0; for (p = name; *p && *p != '.'; p++) { if (*p == '\\' && *(p + 1) != 0) p++; len++; } if (len > MAXLABEL) return ARES_EBADNAME; /* Encode the length and copy the data. */ *q++ = (unsigned char)len; for (p = name; *p && *p != '.'; p++) { if (*p == '\\' && *(p + 1) != 0) p++; *q++ = *p; } /* Go to the next label and repeat, unless we hit the end. */ if (!*p) break; name = p + 1; } /* Add the zero-length label at the end. */ *q++ = 0; /* Finish off the question with the type and class. */ DNS_QUESTION_SET_TYPE(q, type); DNS_QUESTION_SET_CLASS(q, dnsclass); if (max_udp_size) { q += QFIXEDSZ; memset(q, 0, EDNSFIXEDSZ); q++; DNS_RR_SET_TYPE(q, T_OPT); DNS_RR_SET_CLASS(q, max_udp_size); } return ARES_SUCCESS; }
/* Handle an answer from a server. */ static void process_answer(ares_channel channel, unsigned char *abuf, int alen, int whichserver, int tcp, struct timeval *now) { int tc, rcode, packetsz; unsigned short id; struct query *query; struct list_node* list_head; struct list_node* list_node; /* If there's no room in the answer for a header, we can't do much * with it. */ if (alen < HFIXEDSZ) return; /* Grab the query ID, truncate bit, and response code from the packet. */ id = DNS_HEADER_QID(abuf); tc = DNS_HEADER_TC(abuf); rcode = DNS_HEADER_RCODE(abuf); /* Find the query corresponding to this packet. The queries are * hashed/bucketed by query id, so this lookup should be quick. Note that * both the query id and the questions must be the same; when the query id * wraps around we can have multiple outstanding queries with the same query * id, so we need to check both the id and question. */ query = NULL; list_head = &(channel->queries_by_qid[id % ARES_QID_TABLE_SIZE]); for (list_node = list_head->next; list_node != list_head; list_node = list_node->next) { struct query *q = list_node->data; if ((q->qid == id) && same_questions(q->qbuf, q->qlen, abuf, alen)) { query = q; break; } } if (!query) return; packetsz = PACKETSZ; /* If we use EDNS and server answers with one of these RCODES, the protocol * extension is not understood by the responder. We must retry the query * without EDNS enabled. */ if (channel->flags & ARES_FLAG_EDNS) { packetsz = channel->ednspsz; if (rcode == NOTIMP || rcode == FORMERR || rcode == SERVFAIL) { int qlen = alen - EDNSFIXEDSZ; channel->flags ^= ARES_FLAG_EDNS; query->tcplen -= EDNSFIXEDSZ; query->qlen -= EDNSFIXEDSZ; query->tcpbuf[0] = (unsigned char)((qlen >> 8) & 0xff); query->tcpbuf[1] = (unsigned char)(qlen & 0xff); DNS_HEADER_SET_ARCOUNT(query->tcpbuf + 2, 0); query->tcpbuf = realloc(query->tcpbuf, query->tcplen); ares__send_query(channel, query, now); return; }