一 實例背景

最近一個做智能家居的朋友面臨這樣的一個煩惱,他想讓用戶通過智能手機在家裡方便地控制家居設備,又想讓用戶免除下載安裝App的麻煩,通過瀏覽器直接打開設備內嵌的網頁便可實現控制。但是設備的IP地址都是通過家裡的路由器自動獲得的,設備上又沒有屏幕來顯示其IP地址。問我有沒有辦法不輸入IP地址來實現瀏覽器訪問該設備網頁的辦法,就是類似DNS之類,但是無需連外網,只在家庭網路內能訪問即可。

這使我想起一個古老的協議,NetBIOS(Network Basic Input/Output System)。這個在上世紀80年代由IBM開發的協議,主要用於數十臺左右計算機組成的小型局域網,該協議的主要用途之一就是把計算機名稱解析為相應IP地址。如果每個設備有一個固定名字,在實現了NetBIOS的前提下,用戶在瀏覽器里輸入該設備的名字,然後通過NetBIOS解析,便可實現訪問該設備網頁的這個功能了。而且NetBIOS占用系統資源少,在單片機上運行不成問題。於是推薦這個朋友在他的設備上實現了NetBIOS協議,解決了他的煩惱。

除了智能家居,在當下物聯網時代,想必還有其他應用也會遇到類似問題,就拿手頭的WIZnet-W5500評估板實現了一下NetBIOS,希望能對做網路設備開發的朋友有所幫助。在用W5500實現之前,我們還是先在PC上看一下NetBIOS到底是一個什麼東西。

 二 NetBIOS協議

我們知道在DOS 命令下可以通過PING主機名獲得另外一臺電腦的IP地址,實際上就是通過 NETBIOS進行的。在Windows操作系統中,默認情況下在安裝TCP/IP協議後會自動安裝NetBIOS。查看方法如下:本地連接屬性的中“高級TCP/IP設置”視窗中選擇“WINS”選項卡,在“NetBIOS設置”區域中就可以設置相應的NetBIOS,如圖1:

 

image

圖1 WINS下的NetBIOS設置

Ping主機名的第一個數據包就是NBNS(NetBIOS Name Server),協議包,它是 TCP/IP 上的 NetBIOS (NetBT) 協議族的一部分,它在基於 NetBIOS 名稱訪問的網路上提供主機名和地址映射方法。NBNS是動態DNS的一種,Microsoft的NBNS實現稱為WINS。NetBIOS的報文類型較多、結構復雜,不同的網路環境及不同的用途中,會使用不同報文,可用端口進行區分,WINS協議中,NetBIOS名字報文、數據報報文及會話報文分別使用TCP 137、138和139埠。

NetBIOS 數據報有很多不同格式,主要取決於服務和信息類型,以及用以傳送 NetBIOS 數據報的傳輸協議。 NetBIOS 協議架構可見圖2,其中包含三種基本服務: NAME、SESSION 和 DATAGRAM ,其中NAME所用協議就是NBNS協議。

image

圖2:NetBIOS協議架構

下麵看一下WINS協議使用的報文NETBIOS的名字報文(NAME)的總體格式如表1:

image

報文的前12位元組總稱為NETBIOS名字報文的首部,通過首部我們可以判斷出是否為名字查詢的報文。

NETBIOS名字報文中最常見的是攜帶問題記錄的報文,問題記錄的格式如表2:

image

通過攜帶問題記錄的報文,我們可以得到要查詢的名字字元,如果和本機名相符,就發送報文響應,響應中帶有IP地址,發送廣播的主機就會得到該IP地址

三 W5500EVB實現NETBIOS名字報文解析

瞭解了NETBIOS協議之後,下麵就讓我們通過W5500EVB做一個嵌入NetBIOS的簡單實驗。
1.實驗目的:通過在DOS下ping該設備名“WIZNRTW5500”,可以得到開發板的IP地址。
2.硬體環境
1.單片機:STM32F103RC,256K位元組Flash,48K位元組SRAM,2K位元組EEPROM
2.以太網控制器:W5500,SPI介面與單片機相連
3.電源:USB供電
4.硬體外設:板載LED
1.開發工具: Keil
2.測試軟體:串口調試助手,網路調試助手看代碼之前,我們還是先來瞭解一下整個的程式流程,如圖3所示整個程式採用查詢方式,通過DHCP子程式成功獲取IP後可執行NBNS服務。同時W5500EVB設置成HTTP Server,可以接收,並處理TCP Client發來的數據

 

image

 

圖3:主程式流程圖

本文主要討論如何在單片機上實現NETBIOS名字解析服務,DHCP和TCP Server相關部分子程式在此不再詳細介紹,根據NETBIOS名字解析服務子程式流程圖(如圖4示),我們可以得知當查詢到137埠收到網路的UDP數據包時,讀取數據包並進行判斷是否為NETBIOS名字報文,如果是就將解析出的名字與本機名比較,如果一致就回復報文。

 

image

在此貼出NETBIOS部分代碼,要獲取完整代碼,請到這裡上下載。

void do_netbios(void)
{
  unsigned char state;
  unsigned int len;
1  state = getSn_SR(NETBIOS_SOCK);
  switch(state)
  {
  case SOCK_UDP:
2    if((len=getSn_RX_RSR(NETBIOS_SOCK))>0)
    {
      unsigned char rem_ip_addr[4];
      uint16 rem_udp_port;
3     char netbios_name[NETBIOS_NAME_LEN+1];
4     NETBIOS_HDR* netbios_hdr;
5     NETBIOS_NAME_HDR* netbios_name_hdr;
6      len=recvfrom(NETBIOS_SOCK,(unsignedchar*)&netbios_rx_buf,len,rem_ip_addr,&rem_udp_port);
      printf(“rem_ip_addr=%d.%d.%d.%d:%drn”,rem_ip_addr[0],rem_ip_addr[1],rem_ip_addr[2],rem_ip_addr[3],rem_udp_port);
7      netbios_hdr = (NETBIOS_HDR*)netbios_rx_buf;
8  netbios_name_hdr = (NETBIOS_NAME_HDR*)(netbios_hdr+1);
      /* if the packet is a NetBIOS name query question */
9      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 questionrn”);
        /* decode the NetBIOS name */
10       netbios_name_decoding( (char*)(netbios_name_hdr->encname), netbios_name, sizeof(netbios_name));
        printf(“name is %srn”,netbios_name);
        /* if the packet is for us */
11    if (strcmp(netbios_name, NETBIOS_W5500_NAME) == 0)
        {
          uint8 ip_addr[4];
          NETBIOS_RESP *resp = (NETBIOS_RESP*)netbios_tx_buf;
          /* prepare NetBIOS header response */
12    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;
 
          /* prepare NetBIOS header datas */
          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 the NetBIOS response */
13         sendto(NETBIOS_SOCK, (unsigned char*)resp, sizeof(NETBIOS_RESP), rem_ip_addr, rem_udp_port);
          printf(“send responsern”);
        }
      }
 
    }
    break;
14   case SOCK_CLOSED:
    close(NETBIOS_SOCK);
  socket(NETBIOS_SOCK,Sn_MR_UDP,NETBIOS_PORT,0);
    break;
  default:
    break;
  }
}

主要代碼解釋:

第1、2段程式功能為通過SPI介面讀取NBNS Socket寄存器狀態,如果檢測建立了UDP連接,並且收到數據則進行NBNS服務。第3段定義了NetBIOS name緩存區,Netbios name長度為16。第4、5段定義了NetBIOS 包頭和其name部分結構體變量。第6段為讀取137埠的UDP數據)netbios_rx_buf。接下來NBNS核心分:

第7、8兩段將接受緩存區數據對定義的包頭進行賦值,第9,10段,判斷數據NetBIOS 包頭是否為名字查詢,如果是名字查詢則進行名字解析。第11行進行NetBIOS名字進一步比較。比較一致後,第12段程式準備回復NetBIOS包頭和內容。第13段,發送NetBIOS回復響應。第14段為檢測到NBNS Socket 為SOCK_CLOSED,則打開137埠的UDP Socket。

四 實驗測試

試驗中,我們通過W5500EVB對NetBIOS的解析,並用瀏覽器直接訪問設備名稱,來實現對設備的遠程訪問,以達實驗目的。下麵就來看一下實驗測試全過程。

1.首先,打開串口調試助手,運行DHCP相關程式。可看到圖5中所示,W5500EVB成功通過DHCP獲得可用IP地址。

image

  1. 在DOS下,ping W5500EVB設備名:WIZNET5500,可看到如圖6中,獲取設備IP地址為:192.168.1.100 。
    image

圖6:通過Ping命令獲取W5500的IP地址

  1. 運行NetBIOS解析程式,在串口調試助手中看到解析過程,如圖7所示:

image

圖7:W5500 EVB解析本地網路中的NetBIOS廣播包

  1. 最後,我們在瀏覽器中輸入要訪問的設備名稱:wiznet5500,可以看到順利訪問到設備中的內置網頁,瀏覽到設備的配置信息。NetBIOS解析成功!如圖8所示:

image

圖8:在IE瀏覽器中輸入設備名稱,可以訪問內置網頁

五 總結

隨著物聯網事業的發展,越來越多的網路設備走進千家萬戶。對於普通用戶來說,設定和查詢IP地址顯得稍微麻煩,即插即用的設備將會受到人們的青睞。本次實驗,我們在STM32上實現了NetBIOS name Server,通過在瀏覽器中輸入設備的名字就可以登錄到設備中的網頁中,然後就可以進行查看和控制。這樣以來,只要知道該設備的名字,無需用客戶端軟體就能對設備進行控制。例子很簡單,但希望能給大家提供一個思路,將其應用到你的智能家居DIY製作中,讓智能家居真正的“智能”起來!

通過:iwiznet.cn

來源:Instructables

刊登在《無線電》2月刊