这段代码是 lwIP 协议栈中 DNS 模块的核心实现,主要完成两件事:
1. dns_recv :收到 DNS 响应后,解析报文、校验事务 ID、提取 IP 地址并回调用户函数。
2. dns_enqueue :把新的域名查询请求加入队列,并立即触发 DNS 查询报文的发送。
下面分步拆解:
1. dns_recv —— 处理来自 DNS 服务器的响应
• 入口:UDP 收到数据后,由 udp_recv 回调进入此函数。
• 流程:
• 先检查 pbuf 长度是否至少包含 DNS 头 + 一条查询,否则丢弃。
• 把 DNS 头复制到栈上,提取事务 ID(txid)。
• 遍历 dns_table ,找到状态为 DNS_STATE_ASKING 且 txid 匹配的条目。
• 校验响应标志、问题数、源地址(防止伪造响应)。
• 跳过问题部分,逐条检查答案:
• 如果是 A 记录(IPv4)或 AAAA 记录(IPv6),就把 IP 地址写入对应表项。
• 调用 dns_correct_response 更新缓存、停止重传定时器,并触发用户回调。
• 若响应出错或无匹配记录,则调用 dns_call_found 通知失败,清理表项。
• 关键点:
• 支持 IPv4/IPv6 双栈,通过 LWIP_DNS_ADDRTYPE_IS_IPV6 切换。
• 支持 mDNS(若宏开启),可区分普通 DNS 与 mDNS 查询。
• 采用“事务 ID + 源地址”双重校验,防止 DNS 劫持。
2. dns_enqueue —— 提交新的域名查询请求
• 入口:用户调用 dns_gethostbyname 时,最终会走到这里。
• 流程:
• 先检查是否已有相同域名的请求正在处理,若有则复用(去重)。
• 在 dns_table 中找空闲条目,或淘汰最旧的已完成条目。
• 若表满且无可淘汰,则返回 ERR_MEM 。
• 分配 dns_requests 中的请求槽位,保存用户回调和参数。
• 填充表项:域名、事务状态、地址类型、序列号。
• 若开启随机源端口,还会分配独立的 UDP PCB。
• 立即调用 dns_check_entry 发送 DNS 查询报文,不等待定时器。
• 关键点:
• 支持并发多个请求,通过 DNS_MAX_REQUESTS 限制。
• 支持安全选项:禁止同一域名多条未完成请求、随机源端口防伪造。
• 序列号 dns_seqno 递增,用于淘汰策略。
3. 整体协作关系
• 用户 → dns_gethostbyname → dns_enqueue → 发送 DNS 查询。
• 网络 ← DNS 响应 ← dns_recv → 解析 → 回调用户函数。
• 重传、超时、缓存淘汰由 dns_check_entry 和定时器驱动。
4. 可裁剪宏
• LWIP_DNS :整体开关。
• LWIP_IPV4 / LWIP_IPV6 :决定支持的 IP 版本。
• LWIP_DNS_SECURE :启用安全选项(去重、随机端口等)。
• LWIP_DNS_SUPPORT_MDNS_QUERIES :支持 mDNS。
一句话总结:
这段代码把“域名 → IP”的解析过程拆成“发查询 + 收响应”两步,通过状态机与回调机制,在极小内存占用下实现了安全、并发的 DNS 客户端功能。
代码:
dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
u8_t i;
u16_t txid;
u16_t res_idx;
struct dns_hdr hdr;
struct dns_answer ans;
struct dns_query qry;
u16_t nquestions, nanswers;
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(pcb);
LWIP_UNUSED_ARG(port);
/* is the dns message big enough ? */
if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY)) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too small\n"));
/* free pbuf and return */
goto memerr;
}
/* copy dns payload inside static buffer for processing */
if (pbuf_copy_partial(p, &hdr, SIZEOF_DNS_HDR, 0) == SIZEOF_DNS_HDR) {
/* Match the ID in the DNS header with the name table. */
txid = lwip_htons(hdr.id);
for (i = 0; i < DNS_TABLE_SIZE; i++) {
const struct dns_table_entry *entry = &dns_table[i];
if ((entry->state == DNS_STATE_ASKING) &&
(entry->txid == txid)) {
/* We only care about the question(s) and the answers. The authrr
and the extrarr are simply discarded. */
nquestions = lwip_htons(hdr.numquestions);
nanswers = lwip_htons(hdr.numanswers);
/* Check for correct response. */
if ((hdr.flags1 & DNS_FLAG1_RESPONSE) == 0) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": not a response\n", entry->name));
goto memerr; /* ignore this packet */
}
if (nquestions != 1) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
goto memerr; /* ignore this packet */
}
#if LWIP_DNS_SUPPORT_MDNS_QUERIES
if (!entry->is_mdns)
#endif /* LWIP_DNS_SUPPORT_MDNS_QUERIES */
{
/* Check whether response comes from the same network address to which the
question was sent. (RFC 5452) */
if (!ip_addr_cmp(addr, &dns_servers[entry->server_idx])) {
goto memerr; /* ignore this packet */
}
}
/* Check if the name in the "question" part match with the name in the entry and
skip it if equal. */
res_idx = dns_compare_name(entry->name, p, SIZEOF_DNS_HDR);
if (res_idx == 0xFFFF) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
goto memerr; /* ignore this packet */
}
/* check if "question" part matches the request */
if (pbuf_copy_partial(p, &qry, SIZEOF_DNS_QUERY, res_idx) != SIZEOF_DNS_QUERY) {
goto memerr; /* ignore this packet */
}
if ((qry.cls != PP_HTONS(DNS_RRCLASS_IN)) ||
(LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_AAAA))) ||
(!LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype) && (qry.type != PP_HTONS(DNS_RRTYPE_A)))) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": response not match to query\n", entry->name));
goto memerr; /* ignore this packet */
}
/* skip the rest of the "question" part */
res_idx += SIZEOF_DNS_QUERY;
/* Check for error. If so, call callback to inform. */
if (hdr.flags2 & DNS_FLAG2_ERR_MASK) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in flags\n", entry->name));
} else {
while ((nanswers > 0) && (res_idx < p->tot_len)) {
/* skip answer resource record's host name */
res_idx = dns_skip_name(p, res_idx);
if (res_idx == 0xFFFF) {
goto memerr; /* ignore this packet */
}
/* Check for IP address type and Internet class. Others are discarded. */
if (pbuf_copy_partial(p, &ans, SIZEOF_DNS_ANSWER, res_idx) != SIZEOF_DNS_ANSWER) {
goto memerr; /* ignore this packet */
}
res_idx += SIZEOF_DNS_ANSWER;
if (ans.cls == PP_HTONS(DNS_RRCLASS_IN)) {
#if LWIP_IPV4
if ((ans.type == PP_HTONS(DNS_RRTYPE_A)) && (ans.len == PP_HTONS(sizeof(ip4_addr_t)))) {
#if LWIP_IPV4 && LWIP_IPV6
if (!LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype))
#endif /* LWIP_IPV4 && LWIP_IPV6 */
{
ip4_addr_t ip4addr;
/* read the IP address after answer resource record's header */
if (pbuf_copy_partial(p, &ip4addr, sizeof(ip4_addr_t), res_idx) != sizeof(ip4_addr_t)) {
goto memerr; /* ignore this packet */
}
ip_addr_copy_from_ip4(dns_table[i].ipaddr, ip4addr);
pbuf_free(p);
/* handle correct response */
dns_correct_response(i, lwip_ntohl(ans.ttl));
return;
}
}
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
if ((ans.type == PP_HTONS(DNS_RRTYPE_AAAA)) && (ans.len == PP_HTONS(sizeof(ip6_addr_t)))) {
#if LWIP_IPV4 && LWIP_IPV6
if (LWIP_DNS_ADDRTYPE_IS_IPV6(entry->reqaddrtype))
#endif /* LWIP_IPV4 && LWIP_IPV6 */
{
ip6_addr_t ip6addr;
/* read the IP address after answer resource record's header */
if (pbuf_copy_partial(p, &ip6addr, sizeof(ip6_addr_t), res_idx) != sizeof(ip6_addr_t)) {
goto memerr; /* ignore this packet */
}
ip_addr_copy_from_ip6(dns_table[i].ipaddr, ip6addr);
pbuf_free(p);
/* handle correct response */
dns_correct_response(i, lwip_ntohl(ans.ttl));
return;
}
}
#endif /* LWIP_IPV6 */
}
/* skip this answer */
if ((int)(res_idx + lwip_htons(ans.len)) > 0xFFFF) {
goto memerr; /* ignore this packet */
}
res_idx += lwip_htons(ans.len);
--nanswers;
}
#if LWIP_IPV4 && LWIP_IPV6
if ((entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) ||
(entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV6_IPV4)) {
if (entry->reqaddrtype == LWIP_DNS_ADDRTYPE_IPV4_IPV6) {
/* IPv4 failed, try IPv6 */
dns_table[i].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV6;
} else {
/* IPv6 failed, try IPv4 */
dns_table[i].reqaddrtype = LWIP_DNS_ADDRTYPE_IPV4;
}
pbuf_free(p);
dns_table[i].state = DNS_STATE_NEW;
dns_check_entry(i);
return;
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: \"%s\": error in response\n", entry->name));
}
/* call callback to indicate error, clean up memory and return */
pbuf_free(p);
dns_call_found(i, NULL);
dns_table[i].state = DNS_STATE_UNUSED;
return;
}
}
}
memerr:
/* deallocate memory and return */
pbuf_free(p);
return;
}
/**
* Queues a new hostname to resolve and sends out a DNS query for that hostname
*
* @param name the hostname that is to be queried
* @param hostnamelen length of the hostname
* @param found a callback function to be called on success, failure or timeout
* @param callback_arg argument to pass to the callback function
* @return err_t return code.
*/
static err_t
dns_enqueue(const char *name, size_t hostnamelen, dns_found_callback found,
void *callback_arg LWIP_DNS_ADDRTYPE_ARG(u8_t dns_addrtype) LWIP_DNS_ISMDNS_ARG(u8_t is_mdns))
{
u8_t i;
u8_t lseq, lseqi;
struct dns_table_entry *entry = NULL;
size_t namelen;
struct dns_req_entry* req;
#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
u8_t r;
/* check for duplicate entries */
for (i = 0; i < DNS_TABLE_SIZE; i++) {
if ((dns_table[i].state == DNS_STATE_ASKING) &&
(lwip_strnicmp(name, dns_table[i].name, sizeof(dns_table[i].name)) == 0)) {
#if LWIP_IPV4 && LWIP_IPV6
if (dns_table[i].reqaddrtype != dns_addrtype) {
/* requested address types don't match
this can lead to 2 concurrent requests, but mixing the address types
for the same host should not be that common */
continue;
}
#endif /* LWIP_IPV4 && LWIP_IPV6 */
/* this is a duplicate entry, find a free request entry */
for (r = 0; r < DNS_MAX_REQUESTS; r++) {
if (dns_requests[r].found == 0) {
dns_requests[r].found = found;
dns_requests[r].arg = callback_arg;
dns_requests[r].dns_table_idx = i;
LWIP_DNS_SET_ADDRTYPE(dns_requests[r].reqaddrtype, dns_addrtype);
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": duplicate request\n", name));
return ERR_INPROGRESS;
}
}
}
}
/* no duplicate entries found */
#endif
/* search an unused entry, or the oldest one */
lseq = 0;
lseqi = DNS_TABLE_SIZE;
for (i = 0; i < DNS_TABLE_SIZE; ++i) {
entry = &dns_table[i];
/* is it an unused entry ? */
if (entry->state == DNS_STATE_UNUSED) {
break;
}
/* check if this is the oldest completed entry */
if (entry->state == DNS_STATE_DONE) {
u8_t age = dns_seqno - entry->seqno;
if (age > lseq) {
lseq = age;
lseqi = i;
}
}
}
/* if we don't have found an unused entry, use the oldest completed one */
if (i == DNS_TABLE_SIZE) {
if ((lseqi >= DNS_TABLE_SIZE) || (dns_table[lseqi].state != DNS_STATE_DONE)) {
/* no entry can be used now, table is full */
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": DNS entries table is full\n", name));
return ERR_MEM;
} else {
/* use the oldest completed one */
i = lseqi;
entry = &dns_table[i];
}
}
#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_NO_MULTIPLE_OUTSTANDING) != 0)
/* find a free request entry */
req = NULL;
for (r = 0; r < DNS_MAX_REQUESTS; r++) {
if (dns_requests[r].found == NULL) {
req = &dns_requests[r];
break;
}
}
if (req == NULL) {
/* no request entry can be used now, table is full */
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": DNS request entries table is full\n", name));
return ERR_MEM;
}
req->dns_table_idx = i;
#else
/* in this configuration, the entry index is the same as the request index */
req = &dns_requests[i];
#endif
/* use this entry */
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": use DNS entry %"U16_F"\n", name, (u16_t)(i)));
/* fill the entry */
entry->state = DNS_STATE_NEW;
entry->seqno = dns_seqno;
LWIP_DNS_SET_ADDRTYPE(entry->reqaddrtype, dns_addrtype);
LWIP_DNS_SET_ADDRTYPE(req->reqaddrtype, dns_addrtype);
req->found = found;
req->arg = callback_arg;
namelen = LWIP_MIN(hostnamelen, DNS_MAX_NAME_LENGTH-1);
MEMCPY(entry->name, name, namelen);
entry->name[namelen] = 0;
#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) != 0)
entry->pcb_idx = dns_alloc_pcb();
if (entry->pcb_idx >= DNS_MAX_SOURCE_PORTS) {
/* failed to get a UDP pcb */
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": failed to allocate a pcb\n", name));
entry->state = DNS_STATE_UNUSED;
req->found = NULL;
return ERR_MEM;
}
LWIP_DEBUGF(DNS_DEBUG, ("dns_enqueue: \"%s\": use DNS pcb %"U16_F"\n", name, (u16_t)(entry->pcb_idx)));
#endif
#if LWIP_DNS_SUPPORT_MDNS_QUERIES
entry->is_mdns = is_mdns;
#endif
dns_seqno++;
/* force to send query without waiting timer */
dns_check_entry(i);
/* dns query is enqueued */
return ERR_INPROGRESS;
}