lwip 协议栈DNS核心代码

这段代码是 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;

}

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容