I have cleaned up the mDNS request parser and completed some local tests.
There are still some things open to fullfill the mDNS RFC but for now it works.
So I am posting a nodeMcu firmware built with the bonjour module and a simple demo.
It was tested on a nodeMcu Devkit V1.0 based on the ESP-12e:
What you will need:
- a esp8266 board capable to run nodeMCU
- a PC with an mDNS client installed (i.e. an Mac or a windows PC with iTunes)
- a Browser
- ESP Flasher Tool
- the LUA Loader Tool
- the mDNS demo firmware
- the demo LUA files
Step 1:
Extract the .bin files from the .zip
Load the firmware on your NodeMCU (see Setting up a ESP8266 Build environment)
Step 2: Upload the LUA files
Extract the two Demo Files from the .zip
Use the LUA Loader to copy both files onto the nodeMCU
Step 3: Execute the LUA files
For mDNS resolving you need only to execute (dofile) the mdns_server.lua. If you want to see a html page opening in a browser, also execute the httpsrv.lua
Step 4: Showtime
You can now either ping your esp under the address esp-8266.local or open your favorite web-browser and open the webpage http://esp-8266.local/
The sources:
[mdns_server.lua]
srv = net.createServer(net.UDP) conn = net.createConnection(net.UDP, 0) conn:connect(5353,"224.0.0.251") srv:on("receive", function (s, c) requests = bonjour:parse_request(c) if (requests ~= nil) then for index, request in pairs(requests) do print("mDNS["..index.."]: "..request.name.."(Type: "..request.type..")") if ((request.name == "esp-8266.local") and (request.type == bonjour.TYPE.A)) then conn:send ( bonjour:create_response_v4(request.name, wifi.sta.getip()) ) end end end end ) srv:listen(5353) net.multicastJoin(wifi.sta.getip(), "224.0.0.251")
[bonjour.c] Note: There are still a lot of dirty coding / probable bugs in there
#include "module.h" #include "lauxlib.h" #include "platform.h" #include "c_stdlib.h" #include "c_string.h" #include "user_interface.h" typedef struct lmdns_request { u16_t id; u16_t flags; u16_t qd_count; u16_t an_count; u16_t ns_count; u16_t ar_count; u8_t payload[]; } lmdns_request, *plmdns_request; #define SWAPBYTES_U16(a) ((((a) >> 8) & 0xFF) | (((a) & 0xFF) << 8)) static int ICACHE_FLASH_ATTR bonjour_create_response_v4(lua_State* L) { // parameters: // 1 - bonjour // 2 - string name as requested // 3 - ip string size_t length; if (!lua_isstring( L, 2 )) return luaL_error( L, "wrong parameter type (mdns name)" ); if (!lua_isstring( L, 3 )) return luaL_error( L, "wrong parameter type (ip)" ); char *name = (char *)luaL_checklstring(L, 2, &length) ; // create header char buffer[256] ; lmdns_request *request = (lmdns_request *)buffer ; request->id = SWAPBYTES_U16(0) ; request->flags = SWAPBYTES_U16(0x8400) ; request->qd_count = SWAPBYTES_U16(0) ; request->an_count = SWAPBYTES_U16(1) ; request->ns_count = SWAPBYTES_U16(0) ; request->ar_count = SWAPBYTES_U16(0) ; // add the name u16_t namePartIndex = 0 ; u16_t namePartPosition = 0 ; u16_t namePosition = 0 ; while (name[namePosition]) { if (name[namePosition] == '.') { request->payload[namePartIndex] = namePartPosition ; namePartIndex += namePartPosition + 1 ; namePartPosition = 0 ; namePosition++ ; } else { request->payload[namePartIndex+namePartPosition+1] = name[namePosition] ; namePosition++ ; namePartPosition++ ; } } request->payload[namePartIndex] = namePartPosition ; request->payload[namePartIndex+namePartPosition+1] = 0 ; // add type: A (host address) request->payload[namePartIndex+namePartPosition+2] = 0 ; request->payload[namePartIndex+namePartPosition+3] = 1 ; // add class: 1 (IN) request->payload[namePartIndex+namePartPosition+4] = 0 ; request->payload[namePartIndex+namePartPosition+5] = 1 ; // add ttl: 64 request->payload[namePartIndex+namePartPosition+6] = 0 ; request->payload[namePartIndex+namePartPosition+7] = 0 ; request->payload[namePartIndex+namePartPosition+8] = 0 ; request->payload[namePartIndex+namePartPosition+9] = 64 ; // add IP v4 length request->payload[namePartIndex+namePartPosition+10] = 0 ; request->payload[namePartIndex+namePartPosition+11] = 4 ; // add IP bytes uint32_t ip = ipaddr_addr((char *)luaL_checklstring(L, 3, &length)); request->payload[namePartIndex+namePartPosition+15] = (ip >> 24) & 0xFF ; request->payload[namePartIndex+namePartPosition+14] = (ip >> 16) & 0xFF ; request->payload[namePartIndex+namePartPosition+13] = (ip >> 8) & 0xFF ; request->payload[namePartIndex+namePartPosition+12] = (ip >> 0) & 0xFF ; lua_pushlstring(L, &buffer[0], sizeof(lmdns_request) + namePartIndex + namePartPosition + 16) ; return 1 ; } static int ICACHE_FLASH_ATTR bonjour_push_request_to_lua(lua_State* L, char *name, uint16_t requestType, uint16_t classType) { lua_createtable(L, 0, 3) ; lua_pushstring(L, "name") ; lua_pushstring(L, name) ; lua_settable(L, -3) ; lua_pushstring(L, "type") ; lua_pushnumber(L, requestType) ; lua_settable(L, -3) ; lua_pushstring(L, "class") ; lua_pushnumber(L, classType) ; lua_settable(L, -3) ; } static uint16_t bonjour_getNameLength(lmdns_request *request, uint16_t requestLength, uint16_t startOffset, uint16_t *highestByteOfName) { // sanitize arguments if (!request) return 0 ; if (startOffset < sizeof(lmdns_request)) return 0 ; if (startOffset >= requestLength) return 0 ; // init uint16_t namePartLengthIndex = startOffset - sizeof(lmdns_request) ; if (highestByteOfName) *highestByteOfName = startOffset ; uint16_t nameLength = 0; uint8_t numberOfNameParts = 0 ; // iterate through part names while ((namePartLengthIndex < (requestLength - sizeof(lmdns_request))) && (request->payload[namePartLengthIndex])) { if (request->payload[namePartLengthIndex] < 0xC0) // this is a text chunk { nameLength += request->payload[namePartLengthIndex] + 1; namePartLengthIndex += request->payload[namePartLengthIndex]+1 ; if (highestByteOfName) { if (namePartLengthIndex > *highestByteOfName - sizeof(lmdns_request)) *highestByteOfName = namePartLengthIndex + sizeof(lmdns_request); } } else // this is a reference to another name / namepart { if (namePartLengthIndex + 1 + sizeof(lmdns_request) >= requestLength) return 0 ; if (highestByteOfName) { if (namePartLengthIndex+1 > *highestByteOfName - sizeof(lmdns_request)) *highestByteOfName = namePartLengthIndex + sizeof(lmdns_request) + 1; } namePartLengthIndex = request->payload[namePartLengthIndex+1] - sizeof(lmdns_request) ; } // names could possible loop with references, so prevent an infinite loop by limiting the number of parts numberOfNameParts++ ; if (numberOfNameParts > 128) return 0; } return nameLength ; } static int bonjour_getName(lmdns_request *request, uint16_t requestLength, uint16_t startOffset, char *buffer, uint16_t bufferSize) { // sanitize arguments if (!request) return 0 ; if (!buffer) return 0 ; if (startOffset < sizeof(lmdns_request)) return 0 ; if (startOffset >= requestLength) return 0 ; uint16_t namePartLengthIndex = startOffset - sizeof(lmdns_request); uint16_t namePos = 0 ; while ((namePartLengthIndex < (requestLength - sizeof(lmdns_request))) && (request->payload[namePartLengthIndex])) { if (request->payload[namePartLengthIndex] < 0xC0) // this is a text chunk { memcpy(buffer + namePos, &request->payload[namePartLengthIndex + 1], request->payload[namePartLengthIndex]) ; namePos += request->payload[namePartLengthIndex] ; namePartLengthIndex += request->payload[namePartLengthIndex]+1 ; if (request->payload[namePartLengthIndex]) buffer[namePos++] = '.' ; else buffer[namePos++] = 0 ; } else { namePartLengthIndex = request->payload[namePartLengthIndex+1] - sizeof(lmdns_request) ; } } return 1 ; } static int ICACHE_FLASH_ATTR bonjour_parse_request(lua_State* L) { // get the frame size_t length; const char *buffer = luaL_checklstring(L, 2, &length); // maximum and minimum length we process if (length < sizeof(lmdns_request)) { return 0; } lmdns_request *request = (lmdns_request *)buffer ; uint16_t id = SWAPBYTES_U16(request->id) ; uint16_t flags = SWAPBYTES_U16(request->flags) ; uint16_t qd_count = SWAPBYTES_U16(request->qd_count) ; // check id & flags for request if ((id) || (flags)) { return 0; } // create all requests uint16_t index = sizeof(lmdns_request) ; int i; uint16_t farestReadByte = 0 ; //prepare returned array lua_createtable(L, qd_count, 0) ; for (i=0;ipayload[namePartLengthIndex + 1] * 0x100 + request->payload[namePartLengthIndex + 2] ; uint16_t requestClass = request->payload[namePartLengthIndex + 3] * 0x100 + request->payload[namePartLengthIndex + 4] ; bonjour_push_request_to_lua( L, name, requestType, requestClass ) ; lua_rawseti(L, -2, i) ; } else { name[0] = 0 ; } c_free(name) ; index = farestReadByte + 1 + 4; } return 1; } static const LUA_REG_TYPE bonjour_requesttype_map[] = { { LSTRKEY( "A" ), LNUMVAL( 1 ) }, { LSTRKEY( "NS" ), LNUMVAL( 2 ) }, { LSTRKEY( "MD" ), LNUMVAL( 3 ) }, { LSTRKEY( "MF" ), LNUMVAL( 4 ) }, { LSTRKEY( "CNAME" ), LNUMVAL( 5 ) }, { LSTRKEY( "SOA" ), LNUMVAL( 6 ) }, { LSTRKEY( "MB" ), LNUMVAL( 7 ) }, { LSTRKEY( "MG" ), LNUMVAL( 8 ) }, { LSTRKEY( "MR" ), LNUMVAL( 9 ) }, { LSTRKEY( "NULL" ), LNUMVAL( 10 ) }, { LSTRKEY( "WKS" ), LNUMVAL( 11 ) }, { LSTRKEY( "PTR" ), LNUMVAL( 12 ) }, { LSTRKEY( "HINFO" ), LNUMVAL( 13 ) }, { LSTRKEY( "MINFO" ), LNUMVAL( 14 ) }, { LSTRKEY( "MX" ), LNUMVAL( 15 ) }, { LSTRKEY( "TXT" ), LNUMVAL( 16 ) }, { LSTRKEY( "AAAA" ), LNUMVAL( 28 ) }, { LSTRKEY( "ANY" ), LNUMVAL( 255 ) }, { LNILKEY, LNILVAL} }; static const LUA_REG_TYPE bonjour_map[] = { { LSTRKEY( "create_response_v4" ), LFUNCVAL( bonjour_create_response_v4)}, { LSTRKEY( "parse_request" ), LFUNCVAL( bonjour_parse_request )}, { LSTRKEY( "TYPE" ), LROVAL( bonjour_requesttype_map )}, { LSTRKEY( "PORT" ), LNUMVAL( 5353 ) }, { LNILKEY, LNILVAL} }; int luaopen_bonjour(lua_State *L) { return 0; } NODEMCU_MODULE(BONJOUR, "bonjour", bonjour_map, luaopen_bonjour);