全部例程

NetBIOS

W5500 其他标签

2025/02/17 更新

本篇文章我们将详细介绍如何在W5500芯片上面实现NetBIOS功能,并通过实战例程,为大家讲解如何通过名称进行PING测试。

该例程用到的其他网络协议,例如DHCP, 请参考相关章节。有关 W5500 的初始化过程,请参考Network install,这里将不再赘述。

NetBIOS简介

NetBIOS(Network Basic Input/Output System,网络基本输入输出系统)即网络基本输入输出系统,由 IBM 在 1983 年开发,用于局域网通信。它为网络资源分配唯一名称,支持可靠的会话服务和快速的无连接数据报服务,计算机通过向名称服务器注册和查询,实现基于名称的通信,在局域网共享、网络管理等场景广泛应用。

NetBIOS特点

  • 唯一命名规则:NetBIOS为网络中的每个节点分配一个唯一的名称,长度为16个字符。这一名称在网络中作为节点的标识,方便用户和应用程序识别和访问特定的资源。
  • 动态注册与解析:节点在接入网络时,会动态地将其NetBIOS名称注册到网络中。当一个节点需要与另一个节点通信时,它会通过名称解析机制将NetBIOS名称转换为对应的以太网地址(MAC地址)。这种动态的注册和解析过程使得网络配置更加灵活,节点可以随时加入或离开网络,而无需复杂的手动配置。
  • 面向连接(TCP)和无连接(UDP)通信均支持:它支持广播和组播,支持三个分开的服务:名字、会话和数据报。
  • 较好的兼容性好:该协议具有较好的兼容性,能够与其他网络协议(如TCP/IP)共存。在现代网络环境中,虽然TCP/IP协议占据主导地位,但NetBIOS仍然可以在某些特定的应用场景中发挥作用,并且可以与基于TCP/IP的应用程序进行交互。

NetBIOS应用场景

在实际应用中,我们通常会使用DHCP的方式来配置设备的网络地址,因此,当我们想访问该设备时,还需先去查看IP地址后再进行访问。当我们的设备使用上NetBIOS之后,我们可以直接通过NetBIOS名称来访问设备,解决了设备IP不固定难以访问的问题。

NetBIOS的基本工作流程

接下来,我们以PC端PING一个支持NetBIOS的设备为例,讲解下NetBIOS的工作流程。

  1. 当PC端PING的是一个NetBIOS 名称时,首先会查询自身的 NetBIOS 远程缓存名称表中是否存在记录,存在则将NetBIOS名称替代为IP地址,不存在则PC 端发出 NetBIOS 广播请求。
  2. 当设备端接收到NetBIOS请求后,会检查该请求中的名称是否与自身的名称相符。若相符,设备端会向请求端回复自身的IP地址。
  3. PC端在收到设备端的响应后,会将该响应中包含的 IP 地址和NetBIOS名称建立映射关系存储到 NetBIOS 远程缓存名称表中。
  4. PC端根据NetBIOS 远程缓存名称表中的映射关系,将NetBIOS名称替换成IP进行PING操作。

NetBIOS报文解析

NetBIOS(Network Basic Input/Output System)报文用于局域网内计算机的设备发现与名称解析。它工作在会话层,通过UDP 137端口进行名称服务,用于主机名与IP地址的映射;UDP 138端口用于数据报服务,支持无连接消息传输;TCP 139端口用于会话服务,支持面向连接的通信。

NetBIOS报文格式如下:

Blog Image

字段解释

  1. Transaction ID (事务 ID):
    用于标识请求与响应的唯一事务 ID,便于匹配查询和应答报文。
  2. Flags (标志位):
    指示报文类型(请求/响应)。
    包含广播标志、操作码及其他控制信息。
  3. Questions (查询数量):
    表示当前查询的名称数量,通常为 1。
  4. Answer RRs (回答记录数):
    表示响应中返回的资源记录数。
  5. Authority RRs (授权记录数):
    表示提供的授权名称服务器记录数。
  6. Additional RRs (额外记录数):
    提供额外的附加信息,如 IP 地址或其他补充数据。
  7. Question Name (查询名称):
    查询的 NetBIOS 名称,经过特殊编码,占用 16 字节,末尾以 0x00 结束。
  8. Question Type (查询类型):
    指定查询的类型,如 0x20 表示 NetBIOS 名称查询。
  9. Question Class (查询类):
    指定查询的类,0x01 表示 IN(互联网类查询)。

报文示例


|报文解析|
NetBIOS Name Service
  Transaction ID: 0xa753    	(唯一标识此查询,用于匹配请求与响应)
  Flags: 0x0110, Opcode: Name query, Recursion desired, Broadcast   	(表示这是一个 广播 查询请求)
  Questions: 1     	(字段说明仅查询一个设备名称)
  Answer RRs: 0     	(在响应报文中,该字段会显示解析到的记录数)
  Authority RRs: 0    	(在响应报文中,用于指示哪些服务器可以授权回答该查询)
  Additional RRs: 0   	(在某些NetBIOS响应中可能用于携带更多解析信息)

|报文原文|
a7 54 01 10 00 01 00 00 00 00 00 00 

实现过程

接下来,我们看看如何在W5500上实现NetBIOS功能。

在主循环调用do_netbios()函数,如下所示:


while (1)
{
    do_netbios(SOCKET_ID);
}

do_netbios()函数需要传入socket号作为参数,具体内容如下:


/**
*@brief  Execute the NetBIOS name resolver
*@param  socket number
*@return no
*/
void do_netbios(uint8_t sn)
{
    unsigned char state;
    unsigned int  len;
    state = getSn_SR(sn);
    switch (state)
    {
    case SOCK_UDP:
        if ((len = getSn_RX_RSR(sn)) > 0)
        {
            unsigned char     rem_ip_addr[4];
            uint16_t          rem_udp_port;
            char              netbios_name[NETBIOS_NAME_LEN + 1];
            NETBIOS_HDR      *netbios_hdr;
            NETBIOS_NAME_HDR *netbios_name_hdr;
            len = recvfrom(sn, (unsigned char *)&netbios_rx_buf, len, rem_ip_addr, &rem_udp_port);
            printf("rem_ip_addr=%d.%d.%d.%d:%d\r\n", rem_ip_addr[0], rem_ip_addr[1], rem_ip_addr[2], rem_ip_addr[3], rem_udp_port);
            netbios_hdr      = (NETBIOS_HDR *)netbios_rx_buf;
            netbios_name_hdr = (NETBIOS_NAME_HDR *)(netbios_hdr + 1);
            // If the packet is a NetBIOS query packet
            if (((netbios_hdr->flags & ntohs(NETB_HFLAG_OPCODE)) == ntohs(NETB_HFLAG_OPCODE_NAME_QUERY)) && ((netbios_hdr->flags & ntohs(NETB_HFLAG_RESPONSE)) == 0) && (netbios_hdr->questions == ntohs(1)))
            {
                printf("netbios name query question\r\n");
                // Decode the NetBIOS package
                netbios_name_decoding((char *)(netbios_name_hdr->encname), netbios_name, sizeof(netbios_name));
                printf("name is %s\r\n", netbios_name);
                // If the query is made against the native Netbios
                if (strcmp(netbios_name, NETBIOS_W5500_NAME) == 0)
                {
                    uint8_t       ip_addr[4];
                    NETBIOS_RESP *resp = (NETBIOS_RESP *)netbios_tx_buf;
                    // Handle the header of the NetBIOS response packet
                    resp->resp_hdr.trans_id      = netbios_hdr->trans_id;
                    resp->resp_hdr.flags         = htons(NETB_HFLAG_RESPONSE | NETB_HFLAG_OPCODE_NAME_QUERY | NETB_HFLAG_AUTHORATIVE | NETB_HFLAG_RECURS_DESIRED);
                    resp->resp_hdr.questions     = 0;
                    resp->resp_hdr.answerRRs     = htons(1);
                    resp->resp_hdr.authorityRRs  = 0;
                    resp->resp_hdr.additionalRRs = 0;
                    // Process the header data of the NetBIOS response packet
                    memcpy(resp->resp_name.encname, netbios_name_hdr->encname, sizeof(netbios_name_hdr->encname));
                    resp->resp_name.nametype = netbios_name_hdr->nametype;
                    resp->resp_name.type     = netbios_name_hdr->type;
                    resp->resp_name.cls      = netbios_name_hdr->cls;
                    resp->resp_name.ttl      = htonl(NETBIOS_NAME_TTL);
                    resp->resp_name.datalen  = htons(sizeof(resp->resp_name.flags) + sizeof(resp->resp_name.addr));
                    resp->resp_name.flags    = htons(NETB_NFLAG_NODETYPE_BNODE);
                    getSIPR(ip_addr);
                    memcpy(resp->resp_name.addr, ip_addr, 4);
                    // Send a response packet
                    sendto(sn, (unsigned char *)resp, sizeof(NETBIOS_RESP), rem_ip_addr, rem_udp_port);
                    printf("send response\r\n");
                }
            }
        }
        break;

    case SOCK_CLOSED:
        close(sn);
        socket(sn, Sn_MR_UDP, NETBIOS_PORT, 0);
        break;

    default:
        break;
    }
}

进入do_netbios()函数会执行一个UDP协议的状态机,当收到消息后,首先会判断是否为NetBIOS报文,如果为NetBIOS报文则会进入netbios_name_decoding()函数解析NetBIOS名称,当名称与W5500的NetBIOS名称一致时,则返回响应报文。

netbios_name_decoding()函数如下:


static int netbios_name_decoding(char *name_enc, char *name_dec, int name_dec_len)
{
  char *pname;
  char  cname;
  char  cnbname;
  int   index = 0;
  // Decode the name of the former NetBIOS
  pname = name_enc;
  for (;;)
  {
      /* Every two characters of the first level-encoded name
    * turn into one character in the decoded name. */
      cname = *pname;
      if (cname == '\0')
          break; // no more characters
      if (cname == '.')
          break; // scope ID follows
      if (cname < 'A' || cname > 'Z')
      {
          // Not legal.
          return -1;
      }
      cname   -= 'A';
      cnbname  = cname << 4;
      pname++;

      cname = *pname;
      if (cname == '\0' || cname == '.')
      {
          /* No more characters in the name - but we're in
      * the middle of a pair.  Not legal. */
          return -1;
      }
      if (cname < 'A' || cname > 'Z')
      {
          // Not legal.
          return -1;
      }
      cname   -= 'A';
      cnbname |= cname;
      pname++;

      // Do we have room to store the character?
      if (index < NETBIOS_NAME_LEN)
      {
          // Yes - store the character.
          name_dec[index++] = (cnbname != ' ' ? cnbname : '\0');
      }
  }
  return 0;
}

运行结果

请注意:

测试实例需要PC端和W5500处于同一网段

烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息,最后程序开始持续接收和响应 NetBIOS 请求。如下图所示:

Blog Image
Blog Image

总结

本文讲解了如何在 W5500 芯片上实现 NetBIOS 功能,通过实战例程展示了利用 NetBIOS 进行名称 PING 测试的具体过程,包括 NetBIOS 功能的调用、请求处理、名称解析和响应发送等关键步骤。

下一篇文章将聚焦UPnP,解析其核心原理及在网络设备互联互通中的应用,同时讲解如何在相关设备上实现 UPnP 端口转发功能,敬请期待!

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板