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);