Skip to content

Commit 1f7989b

Browse files
authored
Implement Async NBNS (NetBIOS) name resolution for Windows (#2275)
* Implement Async NBNS (NetBIOS) Name resolution for windows Source: http://www.xpablo.cz/?p=751#more-751
1 parent e35ebf1 commit 1f7989b

File tree

5 files changed

+385
-0
lines changed

5 files changed

+385
-0
lines changed
+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/* Klient sluzby NBNS
2+
*/
3+
4+
#include "ESP8266NetBIOS.h"
5+
6+
#include <functional>
7+
8+
extern "C" {
9+
#include "osapi.h"
10+
#include "ets_sys.h"
11+
#include "user_interface.h"
12+
}
13+
14+
#include "lwip/opt.h"
15+
#include "lwip/inet.h"
16+
#include "lwip/udp.h"
17+
18+
#define NBNSQ_TYPE_NB (0x0020)
19+
#define NBNSQ_CLASS_IN (0x0001)
20+
21+
// Definice struktury NBNS dotazu (alespon veci, ktere jsem vypozoroval):
22+
struct NBNSQUESTION {
23+
uint16_t NBNSQ_ID; // ID dotazu
24+
uint8_t NBNSQ_FLAGS1;
25+
uint8_t NBNSQ_FLAGS2;
26+
uint16_t NBNSQ_QUESTIONCOUNT;
27+
uint16_t NBNSQ_ANSWERCOUNT;
28+
uint16_t NBNSQ_AUTHORITYCOUNT;
29+
uint16_t NBNSQ_ADDITIONALRECORDCOUNT;
30+
uint8_t NBNSQ_NAMESIZE; // delka nasledujiciho retezce
31+
char NBNSQ_NAME[32+1]; // POZOR!!! mozna tato polozka muze byt ruzne dlouha
32+
uint16_t NBNSQ_TYPE;
33+
uint16_t NBNSQ_CLASS;
34+
} __attribute__((packed));
35+
36+
// Definice struktury NBNS odpovedi (stejne jako u dotazu)
37+
struct NBNSANSWER {
38+
uint16_t NBNSA_ID; // ID dotazu
39+
uint8_t NBNSA_FLAGS1;
40+
uint8_t NBNSA_FLAGS2;
41+
uint16_t NBNSA_QUESTIONCOUNT;
42+
uint16_t NBNSA_ANSWERCOUNT;
43+
uint16_t NBNSA_AUTHORITYCOUNT;
44+
uint16_t NBNSA_ADDITIONALRECORDCOUNT;
45+
uint8_t NBNSA_NAMESIZE; // delka nasledujiciho retezce
46+
char NBNSA_NAME[32 + 1]; // POZOR!!! mozna tato polozka muze byt ruzne dlouha
47+
uint16_t NBNSA_TYPE;
48+
uint16_t NBNSA_CLASS;
49+
uint32_t NBNSA_TIMETOLIVE;
50+
uint16_t NBNSA_LENGTH;
51+
uint16_t NBNSA_NODEFLAGS; // POZOR!!! tady si nejsem moc jisty
52+
uint32_t NBNSA_NODEADDRESS;
53+
} __attribute__((packed));
54+
55+
// Definice struktury NBNS odpovedi na dotaz na jmeno
56+
struct NBNSANSWERN {
57+
uint16_t NBNSAN_ID; // ID dotazu
58+
uint8_t NBNSAN_FLAGS1;
59+
uint8_t NBNSAN_FLAGS2;
60+
uint16_t NBNSAN_QUESTIONCOUNT;
61+
uint16_t NBNSAN_ANSWERCOUNT;
62+
uint16_t NBNSAN_AUTHORITYCOUNT;
63+
uint16_t NBNSAN_ADDITIONALRECORDCOUNT;
64+
uint8_t NBNSAN_NAMESIZE; // delka nasledujiciho retezce
65+
char NBNSAN_NAME[32 + 1]; // POZOR!!! mozna tato polozka muze byt ruzne dlouha
66+
uint16_t NBNSAN_TYPE;
67+
uint16_t NBNSAN_CLASS;
68+
uint32_t NBNSAN_TIMETOLIVE;
69+
uint16_t NBNSAN_LENGTH;
70+
uint8_t NBNSAN_NUMBER; // number of names
71+
char NBNSAN_NNAME[15]; // jmeno nodu
72+
uint8_t NBNSAN_NTYPE; // typ jmena
73+
uint16_t NBNSAN_NFLAGS; // node flags
74+
} __attribute__((packed));
75+
76+
/** Metoda pro ziskani jmena z kodovani NETBIOS.
77+
* \param nbname Ukazatel na jmeno v NETBIOS kodovani.
78+
* \param name Ukazatel na misto, kam prevadime jmeno.
79+
* \param maxlen Maximalni pocet znaku v nbname.
80+
*/
81+
void ESP8266NetBIOS::_getnbname(char *nbname, char *name, uint8_t maxlen)
82+
{
83+
uint8_t b;
84+
uint8_t c = 0;
85+
86+
while ((*nbname != 0x0) && (c < maxlen)) {
87+
b = (*nbname++ - 'A') << 4; // opravime nibble a prevedeme ho do vyssich bitu
88+
c++; // pocitame pocet odebranych bytu
89+
if (*nbname != 0x0) {
90+
b |= *nbname++ - 'A'; // pridame nizsi nibble
91+
c++; // opet spocitame pocet odebranych znaku
92+
}
93+
*name++ = b; // ulozime znak do vysledku a posuneme ukazatel
94+
}
95+
*name = 0x0; // ulozime ukoncovaci 0
96+
}
97+
98+
/** Prevod zadaneho textu do NETBIOS kodovani
99+
* \param name Ukazatel na prevadene jmeno.
100+
* \param nbname Ukazatel na misto, kam vytvarime jmeno.
101+
* \param outlen Pocet vystupnich znaku (mimo ukoncovaci 0) musi byt delitelne 2
102+
*/
103+
void ESP8266NetBIOS::_makenbname(char *name, char *nbname, uint8_t outlen)
104+
{
105+
uint8_t b;
106+
uint8_t c = 0;
107+
108+
while (c < (outlen - 2)) {
109+
b = *name; // prevadeny znak
110+
if (b) {
111+
name++; // zatim se posunujeme
112+
} else {
113+
b = 0x20; // konec retezce je nahrazeny mezerou
114+
}
115+
*nbname++ = (b >> 4) + 'A'; // jeden nibble ze znaku
116+
*nbname++ = (b & 0xf) + 'A'; // druhy nibble ze znaku
117+
c += 2; // pocet prevedenych znaku
118+
}
119+
*nbname++ = 'A';
120+
*nbname++ = 'A'; // ulozime ukoncovaci 0 v NBNS kodovani
121+
*nbname = 0; // ulozime ukoncovaci 0 retezce
122+
}
123+
124+
ESP8266NetBIOS::ESP8266NetBIOS():_pcb(NULL)
125+
{
126+
127+
}
128+
ESP8266NetBIOS::~ESP8266NetBIOS()
129+
{
130+
end();
131+
}
132+
133+
// Vytvoreni a otevreni UDP soketu, pokud jeste neni...
134+
bool ESP8266NetBIOS::begin(const char *name)
135+
{
136+
size_t n = strlen(name);
137+
if (n > sizeof(_name)) {
138+
// prilis dlouhe jmeno
139+
return false;
140+
}
141+
142+
// presuneme jmeno zarizeni se soucasnou upravou na UPPER case
143+
for (int i = 0; i < n; ++i) {
144+
_name[i] = toupper(name[i]);
145+
}
146+
_name[n] = '\0';
147+
148+
if(_pcb != NULL) {
149+
return true;
150+
}
151+
ip_addr_t addr;
152+
addr.addr = INADDR_ANY;
153+
_pcb = udp_new();
154+
udp_recv(_pcb, &_s_recv, (void *) this);
155+
err_t err = udp_bind(_pcb, &addr, NBNS_PORT);
156+
if(err != ERR_OK) {
157+
end();
158+
return false;
159+
}
160+
return true;
161+
}
162+
163+
void ESP8266NetBIOS::end()
164+
{
165+
if(_pcb != NULL) {
166+
udp_remove(_pcb);
167+
_pcb = NULL;
168+
}
169+
}
170+
171+
void ESP8266NetBIOS::_recv(udp_pcb *upcb, pbuf *pb, ip_addr_t *addr, u16_t port)
172+
{
173+
while(pb != NULL) {
174+
uint8_t * data = (uint8_t*)((pb)->payload);
175+
size_t len = pb->len;
176+
ip_hdr* iphdr = reinterpret_cast<ip_hdr*>(data - UDP_HLEN - IP_HLEN);
177+
ip_addr_t saddr;
178+
saddr.addr = iphdr->src.addr;
179+
180+
if (len >= sizeof(struct NBNSQUESTION)) {
181+
struct NBNSQUESTION * question = (struct NBNSQUESTION *)data;
182+
if (0 == (question->NBNSQ_FLAGS1 & 0x80)) {
183+
char name[ NBNS_MAX_HOSTNAME_LEN + 1 ]; // dekodovane dotazovane jmeno
184+
char *str; // pomocna promenna, pouze pro praci s retezcem
185+
186+
_getnbname(&question->NBNSQ_NAME[0], (char *)&name, question->NBNSQ_NAMESIZE); // prevedeme dotazovane jmeno
187+
if ((str = strchr(name, ' ')) != NULL) { // jmeno hledaneho zarizeni v tomto pripade ukoncuje i mezera
188+
*str = '\0'; // ukoncime retezec na vyskytu prvni mezery
189+
}
190+
191+
if (0 == strcmp(name, _name)) {
192+
// dotaz primo na nas
193+
struct NBNSANSWER nbnsa; // buffer, do ktereho je sestavena odpoved na dotaz
194+
195+
nbnsa.NBNSA_ID = question->NBNSQ_ID;// ID dotazu kopirujeme do ID odpovedi
196+
nbnsa.NBNSA_FLAGS1 = 0x85; // priznak odpovedi
197+
nbnsa.NBNSA_FLAGS2 = 0; // vlajky 2 a response code
198+
nbnsa.NBNSA_QUESTIONCOUNT = LWIP_PLATFORM_HTONS(0);
199+
nbnsa.NBNSA_ANSWERCOUNT = LWIP_PLATFORM_HTONS(1);// poradove cislo odpovedi
200+
nbnsa.NBNSA_AUTHORITYCOUNT = LWIP_PLATFORM_HTONS(0);
201+
nbnsa.NBNSA_ADDITIONALRECORDCOUNT = LWIP_PLATFORM_HTONS(0);
202+
nbnsa.NBNSA_NAMESIZE = sizeof(nbnsa.NBNSA_NAME) - 1; // prekopirujeme delku jmena stanice
203+
_makenbname(_name, &nbnsa.NBNSA_NAME[0], sizeof(nbnsa.NBNSA_NAME) - 1); // prevedeme jmeno
204+
nbnsa.NBNSA_TYPE = LWIP_PLATFORM_HTONS(0x20); // NetBIOS name
205+
nbnsa.NBNSA_CLASS = LWIP_PLATFORM_HTONS(1); // Internet name
206+
nbnsa.NBNSA_TIMETOLIVE = LWIP_PLATFORM_HTONL(300000UL);// Time to live (30000 sekund)
207+
nbnsa.NBNSA_LENGTH = LWIP_PLATFORM_HTONS(6);
208+
nbnsa.NBNSA_NODEFLAGS = LWIP_PLATFORM_HTONS(0);
209+
nbnsa.NBNSA_NODEADDRESS = WiFi.localIP(); // ulozime nasi IP adresu
210+
211+
pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, sizeof(nbnsa), PBUF_RAM);
212+
if(pbt != NULL) {
213+
uint8_t* dst = reinterpret_cast<uint8_t*>(pbt->payload);
214+
memcpy(dst, (uint8_t *)&nbnsa, sizeof(nbnsa));
215+
udp_sendto(_pcb, pbt, &saddr, NBNS_PORT);
216+
pbuf_free(pbt);
217+
}
218+
} else if (0 == strcmp(name, "*")) {
219+
// obecny dotaz - mireny nejspis na nasi IP adresu
220+
struct NBNSANSWERN nbnsan; // buffer, do ktereho je sestavena odpoved na dotaz
221+
222+
nbnsan.NBNSAN_ID = question->NBNSQ_ID;// ID dotazu kopirujeme do ID odpovedi
223+
nbnsan.NBNSAN_FLAGS1 = 0x84; // priznak odpovedi
224+
nbnsan.NBNSAN_FLAGS2 = 0; // vlajky 2 a response code
225+
nbnsan.NBNSAN_QUESTIONCOUNT = LWIP_PLATFORM_HTONS(0);
226+
nbnsan.NBNSAN_ANSWERCOUNT = LWIP_PLATFORM_HTONS(1);// poradove cislo odpovedi
227+
nbnsan.NBNSAN_AUTHORITYCOUNT = LWIP_PLATFORM_HTONS(0);
228+
nbnsan.NBNSAN_ADDITIONALRECORDCOUNT = LWIP_PLATFORM_HTONS(0);
229+
nbnsan.NBNSAN_NAMESIZE = question->NBNSQ_NAMESIZE; // prekopirujeme delku jmena stanice
230+
memcpy(nbnsan.NBNSAN_NAME, question->NBNSQ_NAME, sizeof(nbnsan.NBNSAN_NAME)); // prekopirujeme dotazovane jmeno
231+
nbnsan.NBNSAN_TYPE = LWIP_PLATFORM_HTONS(0x21); // NBSTAT
232+
nbnsan.NBNSAN_CLASS = LWIP_PLATFORM_HTONS(1); // Internet name
233+
nbnsan.NBNSAN_TIMETOLIVE = LWIP_PLATFORM_HTONL(0);
234+
nbnsan.NBNSAN_LENGTH = LWIP_PLATFORM_HTONS(4 + sizeof(nbnsan.NBNSAN_NNAME));
235+
nbnsan.NBNSAN_NUMBER = 1; // Number of names
236+
memset(nbnsan.NBNSAN_NNAME, 0x20, sizeof(nbnsan.NBNSAN_NNAME));
237+
memcpy(nbnsan.NBNSAN_NNAME, _name, strlen(_name));
238+
nbnsan.NBNSAN_NTYPE = 0; // Workstation/Redirector
239+
nbnsan.NBNSAN_NFLAGS = LWIP_PLATFORM_HTONS(0x400); // b-node, unique, active
240+
241+
pbuf* pbt = pbuf_alloc(PBUF_TRANSPORT, sizeof(nbnsan), PBUF_RAM);
242+
if(pbt != NULL) {
243+
uint8_t* dst = reinterpret_cast<uint8_t*>(pbt->payload);
244+
memcpy(dst, (uint8_t *)&nbnsan, sizeof(nbnsan));
245+
udp_sendto(_pcb, pbt, &saddr, NBNS_PORT);
246+
pbuf_free(pbt);
247+
}
248+
}
249+
}
250+
}
251+
252+
pbuf * this_pb = pb;
253+
pb = pb->next;
254+
this_pb->next = NULL;
255+
pbuf_free(this_pb);
256+
}
257+
}
258+
259+
void ESP8266NetBIOS::_s_recv(void *arg, udp_pcb *upcb, pbuf *p, struct ip_addr *addr, uint16_t port)
260+
{
261+
reinterpret_cast<ESP8266NetBIOS*>(arg)->_recv(upcb, p, addr, port);
262+
}
263+
264+
ESP8266NetBIOS NBNS;
265+
// EOF
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
#ifndef __ESPNBNS_h__
3+
#define __ESPNBNS_h__
4+
5+
#include <ESP8266WiFi.h>
6+
7+
#define NBNS_PORT 137
8+
/**
9+
* @def NBNS_MAX_HOSTNAME_LEN
10+
* @brief maximalni delka NBNS jmena zarizeni
11+
* @remarks
12+
* Jmeno zarizeni musi byt uvedeno VELKYMI pismenami a nesmi obsahovat mezery (whitespaces).
13+
*/
14+
#define NBNS_MAX_HOSTNAME_LEN 16
15+
16+
struct udp_pcb;
17+
struct pbuf;
18+
struct ip_addr;
19+
20+
class ESP8266NetBIOS
21+
{
22+
protected:
23+
udp_pcb* _pcb;
24+
char _name[NBNS_MAX_HOSTNAME_LEN + 1];
25+
void _getnbname(char *nbname, char *name, uint8_t maxlen);
26+
void _makenbname(char *name, char *nbname, uint8_t outlen);
27+
void _recv(udp_pcb *upcb, pbuf *pb, struct ip_addr *addr, uint16_t port);
28+
static void _s_recv(void *arg, udp_pcb *upcb, pbuf *p, struct ip_addr *addr, uint16_t port);
29+
public:
30+
ESP8266NetBIOS();
31+
~ESP8266NetBIOS();
32+
bool begin(const char *name);
33+
void end();
34+
};
35+
36+
extern ESP8266NetBIOS NBNS;
37+
38+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <ESP8266WiFi.h>
2+
#include <ESP8266WebServer.h>
3+
#include <ESP8266NetBIOS.h>
4+
5+
const char* ssid = "............";
6+
const char* password = "..............";
7+
8+
ESP8266WebServer wwwserver(80);
9+
String content;
10+
11+
static void handleRoot(void)
12+
{
13+
content = F("<!DOCTYPE HTML>\n<html>Hello world from ESP8266");
14+
content += F("<p>");
15+
content += F("</html>");
16+
17+
wwwserver.send(200, F("text/html"), content);
18+
}
19+
20+
void setup()
21+
{
22+
Serial.begin(115200);
23+
24+
// Connect to WiFi network
25+
WiFi.begin(ssid, password);
26+
Serial.println("");
27+
28+
// Wait for connection
29+
while (WiFi.status() != WL_CONNECTED) {
30+
delay(500);
31+
Serial.print(".");
32+
}
33+
Serial.println("");
34+
Serial.print("Connected to ");
35+
Serial.println(ssid);
36+
Serial.print("IP address: ");
37+
Serial.println(WiFi.localIP());
38+
39+
40+
wwwserver.on("/", handleRoot);
41+
wwwserver.begin();
42+
43+
NBNS.begin("ESP");
44+
}
45+
46+
void loop()
47+
{
48+
wwwserver.handleClient();
49+
}

libraries/ESP8266NetBIOS/keywords.txt

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#######################################
2+
# Syntax Coloring Map For ESPNBNS
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
NBNS KEYWORD1
10+
11+
#######################################
12+
# Methods and Functions (KEYWORD2)
13+
#######################################
14+
15+
begin KEYWORD2
16+
17+
#######################################
18+
# Instances (KEYWORD2)
19+
#######################################
20+
21+
22+
#######################################
23+
# Constants (LITERAL1)
24+
#######################################
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=ESP8266NetBIOS
2+
version=1.0
3+
author=Pablo@xpablo.cz
4+
maintainer=Hristo Gochkov<hristo@espressif.com>
5+
sentence=Enables NBNS (NetBIOS) name resolution.
6+
paragraph=With this library you can connect to your ESP from Windows using a short name
7+
category=Communication
8+
url=http://www.xpablo.cz/?p=751#more-751
9+
architectures=esp8266

0 commit comments

Comments
 (0)