Take 2: Add regular sends to mdns. Check for (some) buffer overflows. Make it handle unicast

Merging as suggested by @TerryE (and squashing at the same time. Turns out that this feature is enabled for this repo).

* Squashed commit of the following:

commit f985f10d9d2ee035f5a6ee6245c60d9904d98cc1
Author: philip <philip@gladstonefamily.net>
Date:   Sun Mar 27 21:52:46 2016 -0400

    Better mdns code

commit 6ee49ee106
Author: philip <philip@gladstonefamily.net>
Date:   Fri Mar 25 23:25:11 2016 -0400

    Update the docs

commit 7e455541c6
Author: philip <philip@gladstonefamily.net>
Date:   Thu Mar 24 21:58:16 2016 -0400

    Add retries and buffer checking to mdns

    Get the length right

    Now it seems to work

* Might work for combined mode

* Fix crash

* Simplified various bits of code. Changed the LUA interface

Added checking (to some degree) incoming quyery types

Move the defaults to the right place

Added reference to the RFC`
This commit is contained in:
Philip Gladstone 2016-04-03 14:10:52 -04:00
parent eccb2e4a53
commit 3a5e5f10e2
8 changed files with 1151 additions and 104 deletions

View File

@ -41,6 +41,7 @@ SUBDIRS= \
crypto \
dhtlib \
tsl2561 \
net \
http
endif # } PDIR
@ -86,6 +87,7 @@ COMPONENTS_eagle.app.v6 = \
dhtlib/libdhtlib.a \
tsl2561/tsl2561lib.a \
http/libhttp.a \
net/libnodemcu_net.a \
modules/libmodules.a \
# Inspect the modules library and work out which modules need to be linked.

View File

@ -0,0 +1,16 @@
#ifndef _NODEMCU_MDNS_H
#define _NODEMCU_MDNS_H
struct nodemcu_mdns_info {
const char *host_name;
const char *host_desc;
const char *service_name;
uint16 service_port;
const char *txt_data[10];
};
void nodemcu_mdns_close(void);
bool nodemcu_mdns_init(struct nodemcu_mdns_info *);
#endif

View File

@ -1,4 +1,5 @@
#include "c_string.h"
#include "c_stdlib.h"
// const char *c_strstr(const char * __s1, const char * __s2){
// }
@ -14,3 +15,115 @@
// int c_strcoll(const char * s1, const char * s2){
// }
//
char *c_strdup(const char *c) {
int len = os_strlen(c) + 1;
char *ret = os_malloc(len);
if (ret) {
memcpy(ret, c, len);
}
return ret;
}
/* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns strlen(src); if retval >= siz, truncation occurred.
*/
size_t
c_strlcpy(char *dst, const char *src, size_t siz)
{
register char *d = dst;
register const char *s = src;
register size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0 && --n != 0) {
do {
if ((*d++ = *s++) == 0)
break;
} while (--n != 0);
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
/* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
c_strlcat(char *dst, const char *src, size_t siz)
{
register char *d = dst;
register const char *s = src;
register size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}

View File

@ -39,5 +39,11 @@
// size_t c_strcspn(const char * s1, const char * s2);
// const char *c_strpbrk(const char * /*s1*/, const char * /*s2*/);
// int c_strcoll(const char * /*s1*/, const char * /*s2*/);
//
extern size_t c_strlcpy(char *dst, const char *src, size_t siz);
extern size_t c_strlcat(char *dst, const char *src, size_t siz);
extern char *c_strdup(const char *src);
#endif /* _C_STRING_H_ */

View File

@ -1,4 +1,4 @@
// Module for access to the espconn_mdns functions
// Module for access to the nodemcu_mdns functions
#include "module.h"
#include "lauxlib.h"
@ -9,115 +9,59 @@
#include "c_types.h"
#include "mem.h"
#include "lwip/ip_addr.h"
#include "espconn.h"
#include "nodemcu_mdns.h"
#include "user_interface.h"
typedef struct wrapper {
struct mdns_info mdns_info;
char data;
} wrapper_t;
static wrapper_t *info;
typedef enum phase {
PHASE_CALCULATE_LENGTH,
PHASE_COPY_DATA
} phase_t;
static char *advance_over_string(char *s)
{
while (*s++) {
}
// s now points after the null
return s;
}
//
// mdns.close()
//
static int mdns_close(lua_State *L)
{
if (info) {
espconn_mdns_close();
c_free(info);
info = NULL;
}
nodemcu_mdns_close();
return 0;
}
//
// this handles all the arguments. Two passes are necessary --
// one to calculate the size of the block, and the other to
// copy the data. It is vitally important that these two
// passes are kept in step.
//
static wrapper_t *process_args(lua_State *L, phase_t phase, size_t *sizep)
// mdns.register(hostname [, { attributes} ])
//
static int mdns_register(lua_State *L)
{
wrapper_t *result = NULL;
char *p = NULL;
struct nodemcu_mdns_info info;
if (phase == PHASE_COPY_DATA) {
result = (wrapper_t *) c_zalloc(sizeof(wrapper_t) + *sizep);
if (!result) {
return NULL;
}
p = &result->data;
}
memset(&info, 0, sizeof(info));
info.host_name = luaL_checkstring(L, 1);
info.service_name = "http";
info.service_port = 80;
info.host_desc = info.host_name;
if (phase == PHASE_CALCULATE_LENGTH) {
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 2, LUA_TSTRING);
(void) luaL_checkinteger(L, 3);
*sizep += c_strlen(luaL_checkstring(L, 1)) + 1;
*sizep += c_strlen(luaL_checkstring(L, 2)) + 1;
} else {
c_strcpy(p, luaL_checkstring(L, 1));
result->mdns_info.host_name = p;
p = advance_over_string(p);
c_strcpy(p, luaL_checkstring(L, 2));
result->mdns_info.server_name = p;
p = advance_over_string(p);
result->mdns_info.server_port = luaL_checkinteger(L, 3);
}
if (lua_gettop(L) >= 4) {
luaL_checktype(L, 4, LUA_TTABLE);
if (lua_gettop(L) >= 2) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L); // first key
int slot = 0;
while (lua_next(L, 4) != 0 && slot < sizeof(result->mdns_info.txt_data) / sizeof(result->mdns_info.txt_data[0])) {
if (phase == PHASE_CALCULATE_LENGTH) {
luaL_checktype(L, -2, LUA_TSTRING);
*sizep += c_strlen(luaL_checkstring(L, -2)) + 1;
*sizep += c_strlen(luaL_checkstring(L, -1)) + 1;
} else {
// put in the key
c_strcpy(p, luaL_checkstring(L, -2));
result->mdns_info.txt_data[slot] = p;
p = advance_over_string(p);
while (lua_next(L, 2) != 0 && slot < sizeof(info.txt_data) / sizeof(info.txt_data[0])) {
luaL_checktype(L, -2, LUA_TSTRING);
const char *key = luaL_checkstring(L, -2);
// now smash in the value
if (c_strcmp(key, "port") == 0) {
info.service_port = luaL_checknumber(L, -1);
} else if (c_strcmp(key, "service") == 0) {
info.service_name = luaL_checkstring(L, -1);
} else if (c_strcmp(key, "description") == 0) {
info.host_desc = luaL_checkstring(L, -1);
} else {
int len = c_strlen(key) + 1;
const char *value = luaL_checkstring(L, -1);
p[-1] = '=';
c_strcpy(p, value);
p = advance_over_string(p);
char *p = alloca(len + c_strlen(value) + 1);
strcpy(p, key);
strcat(p, "=");
strcat(p, value);
info.txt_data[slot++] = p;
}
lua_pop(L, 1);
}
}
return result;
}
//
// mdns.register(hostname, servicename, port [, attributes])
//
static int mdns_register(lua_State *L)
{
size_t len = 0;
(void) process_args(L, PHASE_CALCULATE_LENGTH, &len);
struct ip_info ipconfig;
@ -127,25 +71,18 @@ static int mdns_register(lua_State *L)
return luaL_error(L, "No network connection");
}
wrapper_t *result = process_args(L, PHASE_COPY_DATA, &len);
if (!result) {
return luaL_error( L, "failed to allocate info block" );
}
result->mdns_info.ipAddr = ipconfig.ip.addr;
// Close up the old session (if any). This cannot fail
// so no chance of losing the memory in 'result'
mdns_close(L);
// Save the result as it appears that espconn_mdns_init needs
// Save the result as it appears that nodemcu_mdns_init needs
// to have the data valid while it is running.
info = result;
espconn_mdns_init(&(info->mdns_info));
if (!nodemcu_mdns_init(&info)) {
mdns_close(L);
return luaL_error(L, "Unable to start mDns daemon");
}
return 0;
}

44
app/net/Makefile Normal file
View File

@ -0,0 +1,44 @@
#############################################################
# Required variables for each makefile
# Discard this section from all parent makefiles
# Expected variables (with automatic defaults):
# CSRCS (all "C" files in the dir)
# SUBDIRS (all subdirs with a Makefile)
# GEN_LIBS - list of libs to be generated ()
# GEN_IMAGES - list of images to be generated ()
# COMPONENTS_xxx - a list of libs/objs in the form
# subdir/lib to be extracted and rolled up into
# a generated lib/image xxx.a ()
#
ifndef PDIR
GEN_LIBS = libnodemcu_net.a
endif
#############################################################
# Configuration i.e. compile options etc.
# Target specific stuff (defines etc.) goes in here!
# Generally values applying to a tree are captured in the
# makefile at its root level - these are then overridden
# for a subtree within the makefile rooted therein
#
#DEFINES +=
#############################################################
# Recursion Magic - Don't touch this!!
#
# Each subtree potentially has an include directory
# corresponding to the common APIs applicable to modules
# rooted at that subtree. Accordingly, the INCLUDE PATH
# of a module can only contain the include directories up
# its parent path, and not its siblings
#
# Required for each makefile to inherit from the parent
#
INCLUDES := $(INCLUDES) -I $(PDIR)include
INCLUDES += -I ./
INCLUDES += -I ../libc
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

919
app/net/nodemcu_mdns.c Normal file
View File

@ -0,0 +1,919 @@
/**
* lwip MDNS resolver file.
*
* Created on: Jul 29, 2010
* Author: Daniel Toma
*
* ported from uIP resolv.c Copyright (c) 2002-2003, Adam Dunkels.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* This file implements a MDNS host name and PUCK service registration.
*-----------------------------------------------------------------------------
* Includes
*----------------------------------------------------------------------------*/
#include "lwip/opt.h"
#if LWIP_MDNS /* don't build if not configured for use in lwipopts.h */
#include "lwip/mdns.h"
#include "lwip/udp.h"
#include "lwip/mem.h"
#include "lwip/igmp.h"
#include "osapi.h"
#include "os_type.h"
#include "user_interface.h"
#include "c_string.h"
#include "nodemcu_mdns.h"
#if 0
#define MDNS_DBG(...) os_printf(...)
#else
#define MDNS_DBG(...) do {} while (0)
#endif
#define MDNS_NAME_LENGTH 68 //68
static const char* service_name_with_suffix = NULL;
#define DNS_SD_SERVICE "_services._dns-sd._udp.local"
#define PUCK_SERVICE_LENGTH 30
#define PUCK_DATASHEET_SIZE 96
#ifdef MEMLEAK_DEBUG
static const char mem_debug_file[] ICACHE_RODATA_ATTR = __FILE__;
#endif
/** DNS server IP address */
#ifndef DNS_MULTICAST_ADDRESS
#define DNS_MULTICAST_ADDRESS ipaddr_addr("224.0.0.251") /* resolver1.opendns.com */
#endif
/** DNS server IP address */
#ifndef MDNS_LOCAL
#define MDNS_LOCAL "local" /* resolver1.opendns.com */
#endif
/** DNS server port address */
#ifndef DNS_MDNS_PORT
#define DNS_MDNS_PORT 5353
#endif
/** DNS maximum number of retries when asking for a name, before "timeout". */
#ifndef DNS_MAX_RETRIES
#define DNS_MAX_RETRIES 4
#endif
/** DNS resource record max. TTL (one week as default) */
#ifndef DNS_MAX_TTL
#define DNS_MAX_TTL 604800
#endif
/* DNS protocol flags */
#define DNS_FLAG1_RESPONSE 0x84
#define DNS_FLAG1_OPCODE_STATUS 0x10
#define DNS_FLAG1_OPCODE_INVERSE 0x08
#define DNS_FLAG1_OPCODE_STANDARD 0x00
#define DNS_FLAG1_AUTHORATIVE 0x04
#define DNS_FLAG1_TRUNC 0x02
#define DNS_FLAG1_RD 0x01
#define DNS_FLAG2_RA 0x80
#define DNS_FLAG2_ERR_MASK 0x0f
#define DNS_FLAG2_ERR_NONE 0x00
#define DNS_FLAG2_ERR_NAME 0x03
/* DNS protocol states */
#define DNS_STATE_UNUSED 0
#define DNS_STATE_NEW 1
#define DNS_STATE_ASKING 2
#define DNS_STATE_DONE 3
/* MDNS registration type */
#define MDNS_HOSTNAME_REG 0
#define MDNS_SERVICE_REG 1
/* MDNS registration type */
#define MDNS_REG_ANSWER 1
#define MDNS_SD_ANSWER 2
#define MDNS_SERVICE_REG_ANSWER 3
/* MDNS registration time */
#define MDNS_HOST_TIME 120
#define MDNS_SERVICE_TIME 3600
/** MDNS name length with "." at the beginning and end of name*/
#ifndef MDNS_LENGTH_ADD
#define MDNS_LENGTH_ADD 2
#endif
#ifdef MDNS_MAX_NAME_LENGTH
#undef MDNS_MAX_NAME_LENGTH
#endif
#define MDNS_MAX_NAME_LENGTH (256)
PACK_STRUCT_BEGIN
/** DNS message header */
struct mdns_hdr {
PACK_STRUCT_FIELD(u16_t id);
PACK_STRUCT_FIELD(u8_t flags1);
PACK_STRUCT_FIELD(u8_t flags2);
PACK_STRUCT_FIELD(u16_t numquestions);
PACK_STRUCT_FIELD(u16_t numanswers);
PACK_STRUCT_FIELD(u16_t numauthrr);
PACK_STRUCT_FIELD(u16_t numextrarr);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_DNS_HDR 12
PACK_STRUCT_BEGIN
/** MDNS query message structure */
struct mdns_query {
/* MDNS query record starts with either a domain name or a pointer
to a name already present somewhere in the packet. */PACK_STRUCT_FIELD(u16_t type);
PACK_STRUCT_FIELD(u16_t class);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_DNS_QUERY 4
PACK_STRUCT_BEGIN
/** MDNS answer message structure */
struct mdns_answer {
/* MDNS answer record starts with either a domain name or a pointer
to a name already present somewhere in the packet. */PACK_STRUCT_FIELD(u16_t type);
PACK_STRUCT_FIELD(u16_t class);
PACK_STRUCT_FIELD(u32_t ttl);
PACK_STRUCT_FIELD(u16_t len);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_DNS_ANSWER 10
PACK_STRUCT_BEGIN
/** MDNS answer message structure */
struct mdns_auth {
PACK_STRUCT_FIELD(u32_t src);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_MDNS_AUTH 4
PACK_STRUCT_BEGIN
/** MDNS service registration message structure */
struct mdns_service {
PACK_STRUCT_FIELD(u16_t prior);
PACK_STRUCT_FIELD(u16_t weight);
PACK_STRUCT_FIELD(u16_t port);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_MDNS_SERVICE 6
static os_timer_t mdns_timer;
/* forward declarations */
static void mdns_recv(void *s, struct udp_pcb *pcb, struct pbuf *p,
struct ip_addr *addr, u16_t port);
/*-----------------------------------------------------------------------------
* Globales
*----------------------------------------------------------------------------*/
/* MDNS variables */
//static char puck_datasheet[PUCK_DATASHEET_SIZE];
static struct udp_pcb *mdns_pcb = NULL;
static struct nodemcu_mdns_info * ms_info = NULL;
static struct ip_addr multicast_addr;
static uint8 register_flag = 0;
static uint8 mdns_flag = 0;
static u8_t *mdns_payload;
/**
* Compare the "dotted" name "query" with the encoded name "response"
* to make sure an answer from the DNS server matches the current mdns_table
* entry (otherwise, answers might arrive late for hostname not on the list
* any more).
*
* @param query hostname (not encoded) from the mdns_table
* @param response encoded hostname in the DNS response
* @return 0: names equal; 1: names differ
*/
static u8_t ICACHE_FLASH_ATTR
mdns_compare_name(unsigned char *query, unsigned char *response) {
unsigned char n;
do {
n = *response++;
/** @see RFC 1035 - 4.1.4. Message compression */
if ((n & 0xc0) == 0xc0) {
/* Compressed name */
break;
} else {
/* Not compressed name */
while (n > 0) {
if ((*query) != (*response)) {
return 1;
}
++response;
++query;
--n;
};
++query;
}
} while (*response != 0);
return 0;
}
static int
mdns_namelen(u8_t *p, unsigned int maxlen) {
u8_t *orig = p;
while (*p && *p <= 63) {
if (p - orig > maxlen) {
return -1;
}
p += *p + 1;
}
if (*p >= 0xc0) {
p += 2; // advance over the two byte pointer
} else {
p++; // advance over the final 0
}
if (p - orig > maxlen) {
return -1;
}
return p - orig;
}
static err_t send_packet(struct pbuf *p, struct ip_addr *dst_addr, u16_t dst_port, u8_t *addr_ptr) {
err_t err;
/* send dns packet */
struct netif *sta_netif = (struct netif *)eagle_lwip_getif(0x00);
struct netif *ap_netif = (struct netif *)eagle_lwip_getif(0x01);
if (addr_ptr) {
if (wifi_get_opmode() == 0x02) {
if (!ap_netif) {
return;
}
memcpy(addr_ptr, &ap_netif->ip_addr, sizeof(ap_netif->ip_addr));
} else {
if (!sta_netif) {
return;
}
memcpy(addr_ptr, &sta_netif->ip_addr, sizeof(sta_netif->ip_addr));
}
}
if (dst_addr) {
err = udp_sendto(mdns_pcb, p, dst_addr, dst_port);
} else {
err = udp_sendto(mdns_pcb, p, &multicast_addr, DNS_MDNS_PORT);
if(wifi_get_opmode() == 0x03 && wifi_get_broadcast_if() == 0x03 &&\
sta_netif != NULL && ap_netif != NULL) {
if(netif_is_up(sta_netif) && netif_is_up(ap_netif)) {
netif_set_default(sta_netif);
if (addr_ptr) {
memcpy(addr_ptr, &ap_netif->ip_addr, sizeof(ap_netif->ip_addr));
}
err = udp_sendto(mdns_pcb, p, &multicast_addr, DNS_MDNS_PORT);
netif_set_default(ap_netif);
}
}
}
/* free pbuf */
pbuf_free(p);
return err;
}
/**
* Send a mDNS packet for the service type
*
* @param id transaction ID in the DNS query packet
* @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
*/
static err_t ICACHE_FLASH_ATTR
mdns_send_service_type(u16_t id, struct ip_addr *dst_addr, u16_t dst_port) {
err_t err;
struct mdns_hdr *hdr;
struct mdns_answer ans;
struct mdns_auth auth;
struct mdns_service serv;
struct pbuf *p ,*p_sta;
char *query, *nptr;
const char *pHostname;
struct netif * sta_netif = NULL;
struct netif * ap_netif = NULL;
char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
u8_t n;
u16_t length = 0;
/* if here, we have either a new query or a retry on a previous query to process */
p = pbuf_alloc(PBUF_TRANSPORT,
SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
if (p != NULL) {
LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
/* fill dns header */
hdr = (struct mdns_hdr*) p->payload;
os_memset(hdr, 0, SIZEOF_DNS_HDR);
hdr->id = htons(id);
hdr->flags1 = DNS_FLAG1_RESPONSE;
pHostname = DNS_SD_SERVICE;
hdr->numanswers = htons(1);
query = (char*) hdr + SIZEOF_DNS_HDR;
--pHostname;
/* convert hostname into suitable query format. */
do {
++pHostname;
nptr = query;
++query;
for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++n;
}
*nptr = n;
} while (*pHostname != 0);
*query++ = '\0';
/* fill dns query */
ans.type = htons(DNS_RRTYPE_PTR);
ans.class = htons(DNS_RRCLASS_IN);
ans.ttl = htonl(3600);
ans.len = htons(os_strlen(service_name_with_suffix) + 1 +1 );
length = 0;
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
/* resize the query */
query = query + SIZEOF_DNS_ANSWER;
pHostname = service_name_with_suffix;
--pHostname;
/* convert hostname into suitable query format. */
do {
++pHostname;
nptr = query;
++query;
for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++n;
}
*nptr = n;
} while (*pHostname != 0);
*query++ = '\0';
/* resize pbuf to the exact dns query */
pbuf_realloc(p, (query + length) - ((char*) (p->payload)));
err = send_packet(p, dst_addr, dst_port, 0);
} else {
err = ERR_MEM;
}
return err;
}
/**
* Send a mDNS service answer packet.
*
* @param name service name to query
* @param id transaction ID in the DNS query packet
* @return ERR_OK if packet is sent; an err_t indicating the problem otherwise
*/
static err_t ICACHE_FLASH_ATTR
mdns_send_service(struct nodemcu_mdns_info *info, u16_t id, struct ip_addr *dst_addr, u16_t dst_port) {
err_t err;
struct mdns_hdr *hdr;
struct mdns_answer ans;
struct mdns_service serv;
struct mdns_auth auth;
struct pbuf *p ,*p_sta;
char *query, *nptr;
char *query_end;
const char *pHostname;
const char *name = info->host_name;
u8_t n;
u8_t i = 0;
u16_t length = 0;
u8_t addr1 = 12, addr2 = 12;
struct netif * sta_netif = NULL;
struct netif * ap_netif = NULL;
char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
u16_t dns_class = dst_addr ? DNS_RRCLASS_IN : DNS_RRCLASS_FLUSH_IN;
/* if here, we have either a new query or a retry on a previous query to process */
p = pbuf_alloc(PBUF_TRANSPORT,
SIZEOF_DNS_HDR + MDNS_MAX_NAME_LENGTH * 2 + SIZEOF_DNS_QUERY, PBUF_RAM);
if (p != NULL) {
LWIP_ASSERT("pbuf must be in one piece", p->next == NULL);
/* fill dns header */
hdr = (struct mdns_hdr*) p->payload;
os_memset(hdr, 0, SIZEOF_DNS_HDR);
hdr->id = htons(id);
hdr->flags1 = DNS_FLAG1_RESPONSE;
hdr->numanswers = htons(4);
query = (char*) hdr + SIZEOF_DNS_HDR;
query_end = (char *) p->payload + p->tot_len;
c_strlcpy(tmpBuf, service_name_with_suffix, sizeof(tmpBuf));
pHostname = tmpBuf;
--pHostname;
/* convert hostname into suitable query format. */
do {
++pHostname;
nptr = query;
++query;
++addr1;
++addr2;
for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++addr1;
++addr2;
++n;
}
*nptr = n;
} while (*pHostname != 0);
*query++ = '\0';
length = sizeof(MDNS_LOCAL);
addr1 -= length;
length = os_strlen(service_name_with_suffix) + 1;
addr2 -= length;
ans.type = htons(DNS_RRTYPE_PTR);
ans.class = htons(DNS_RRCLASS_IN);
ans.ttl = htonl(300);
length = os_strlen(ms_info->host_desc) + MDNS_LENGTH_ADD + 1;
ans.len = htons(length);
length = 0;
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
/* resize the query */
query = query + SIZEOF_DNS_ANSWER;
int name_offset = query - (const char *) hdr;
pHostname = ms_info->host_desc;
--pHostname;
/* convert hostname into suitable query format. */
do {
++pHostname;
nptr = query;
++query;
for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++n;
}
*nptr = n;
} while (*pHostname != 0);
*query++ = DNS_OFFSET_FLAG;
*query++ = DNS_DEFAULT_OFFSET;
*query++ = 0xc0 + (name_offset >> 8);
*query++ = name_offset & 0xff;
/* fill the answer */
ans.type = htons(DNS_RRTYPE_TXT);
ans.class = htons(dns_class);
ans.ttl = htonl(300);
// length = os_strlen(TXT_DATA) + MDNS_LENGTH_ADD + 1;
const char *attributes[12];
int attr_count = 0;
for(i = 0; i < 10 && (info->txt_data[i] != NULL); i++) {
length += os_strlen(info->txt_data[i]);
length++;
attributes[attr_count++] = info->txt_data[i];
}
//MDNS_DBG("Found %d user attributes\n", i);
static const char *defaults[] = { "platform=nodemcu", NULL };
for(i = 0; defaults[i] != NULL; i++) {
// See if this is a duplicate
int j;
int len = strchr(defaults[i], '=') + 1 - defaults[i];
for (j = 0; j < attr_count; j++) {
if (strncmp(attributes[j], defaults[i], len) == 0) {
break;
}
}
if (j == attr_count) {
length += os_strlen(defaults[i]);
length++;
attributes[attr_count++] = defaults[i];
}
}
//MDNS_DBG("Found %d total attributes\n", attr_count);
ans.len = htons(length);
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
query = query + SIZEOF_DNS_ANSWER;
// Check enough space in the packet
const char *end_of_packet = query + length + 2 + SIZEOF_DNS_ANSWER + SIZEOF_MDNS_SERVICE +
os_strlen(ms_info->host_name) + 7 + 1 + 2 + SIZEOF_DNS_ANSWER + SIZEOF_MDNS_AUTH;
if (query_end <= end_of_packet) {
MDNS_DBG("Too much data to send\n");
pbuf_free(p);
return ERR_MEM;
}
//MDNS_DBG("Query=%x, query_end=%x, end_ofpacket=%x, length=%x\n", query, query_end, end_of_packet, length);
i = 0;
while(attributes[i] != NULL && i < attr_count) {
pHostname = attributes[i];
--pHostname;
/* convert hostname into suitable query format. */
do {
++pHostname;
nptr = query;
++query;
for (n = 0; *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++n;
}
*nptr = n;
} while (*pHostname != 0);
i++;
}
// *query++ = '\0';
// Increment by length
*query++ = 0xc0 + (name_offset >> 8);
*query++ = name_offset & 0xff;
// Increment by 2
ans.type = htons(DNS_RRTYPE_SRV);
ans.class = htons(dns_class);
ans.ttl = htonl(300);
c_strlcpy(tmpBuf,ms_info->host_name, sizeof(tmpBuf));
c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
c_strlcat(tmpBuf, MDNS_LOCAL, sizeof(tmpBuf));
length = os_strlen(tmpBuf) + MDNS_LENGTH_ADD;
ans.len = htons(SIZEOF_MDNS_SERVICE + length);
length = 0;
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
/* resize the query */
query = query + SIZEOF_DNS_ANSWER;
serv.prior = htons(0);
serv.weight = htons(0);
serv.port = htons(ms_info->service_port);
MEMCPY( query, &serv, SIZEOF_MDNS_SERVICE);
/* resize the query */
query = query + SIZEOF_MDNS_SERVICE;
int hostname_offset = query - (const char *) hdr;
pHostname = tmpBuf;
--pHostname;
do {
++pHostname;
nptr = query;
++query;
for (n = 0; *pHostname != '.' && *pHostname != 0; ++pHostname) {
*query = *pHostname;
++query;
++n;
}
*nptr = n;
} while (*pHostname != 0);
*query++ = '\0';
// increment by strlen(service_name) + 1 + 7 + sizeof_dns_answer + sizeof_mdns_service
*query++ = 0xc0 + (hostname_offset >> 8);
*query++ = hostname_offset & 0xff;
ans.type = htons(DNS_RRTYPE_A);
ans.class = htons(dns_class);
ans.ttl = htonl(300);
ans.len = htons(DNS_IP_ADDR_LEN);
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
/* resize the query */
query = query + SIZEOF_DNS_ANSWER;
// increment by strlen(service_name) + 1 + 7 + sizeof_dns_answer
/* fill the payload of the mDNS message */
/* set the local IP address */
auth.src = 0;
MEMCPY( query, &auth, SIZEOF_MDNS_AUTH);
u8_t *addr_ptr = query + ((char *) &auth.src - (char *) &auth);
/* resize the query */
query = query + SIZEOF_MDNS_AUTH;
//MDNS_DBG("Final ptr=%x\n", query);
// increment by sizeof_mdns_auth
/* set the name of the authority field.
* The same name as the Query using the offset address*/
/* resize pbuf to the exact dns query */
pbuf_realloc(p, (query) - ((char*) (p->payload)));
err = send_packet(p, dst_addr, dst_port, addr_ptr);
} else {
MDNS_DBG("ERR_MEM \n");
err = ERR_MEM;
}
return err;
}
/**
* Receive input function for DNS response packets arriving for the dns UDP pcb.
*
* @params see udp.h
*/
static void ICACHE_FLASH_ATTR
mdns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr,
u16_t port) {
u16_t i;
struct mdns_hdr *hdr;
u8_t nquestions;
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(pcb);
struct nodemcu_mdns_info *info = (struct nodemcu_mdns_info *)arg;
/* is the dns message too big ? */
if (p->tot_len > DNS_MSG_SIZE) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too big\n"));
/* free pbuf and return */
goto memerr1;
}
/* is the dns message big enough ? */
if (p->tot_len < (SIZEOF_DNS_HDR + SIZEOF_DNS_QUERY + SIZEOF_DNS_ANSWER)) {
LWIP_DEBUGF(DNS_DEBUG, ("dns_recv: pbuf too small\n"));
/* free pbuf and return */
goto memerr1;
}
/* copy dns payload inside static buffer for processing */
if (pbuf_copy_partial(p, mdns_payload, p->tot_len, 0) == p->tot_len) {
/* The ID in the DNS header should be our entry into the name table. */
hdr = (struct mdns_hdr*) mdns_payload;
i = htons(hdr->id);
nquestions = htons(hdr->numquestions);
//nanswers = htons(hdr->numanswers);
/* if we have a question send an answer if necessary */
if (nquestions > 0) {
struct mdns_query qry;
int namelen = mdns_namelen((u8_t *) (hdr + 1), p->tot_len);
memcpy(&qry, namelen + (u8_t *) (hdr + 1), sizeof(qry));
u16_t qry_type = ntohs(qry.type);
/* MDNS_DS_DOES_NAME_CHECK */
/* Check if the name in the "question" part match with the name of the MDNS DS service. */
if (mdns_compare_name((unsigned char *) DNS_SD_SERVICE,
(unsigned char *) mdns_payload + SIZEOF_DNS_HDR) == 0) {
if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service_type(i, addr, port);
}
} else if (mdns_compare_name((unsigned char *) service_name_with_suffix,
(unsigned char *) mdns_payload + SIZEOF_DNS_HDR) == 0) {
if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service(info, i, addr, port);
}
} else {
char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
c_strlcpy(tmpBuf,ms_info->host_name, sizeof(tmpBuf));
c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
c_strlcat(tmpBuf, MDNS_LOCAL, sizeof(tmpBuf));
if (mdns_compare_name((unsigned char *) tmpBuf,
(unsigned char *) mdns_payload + SIZEOF_DNS_HDR) == 0) {
if (qry_type == DNS_RRTYPE_A || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service(info, i, addr, port);
}
} else {
c_strlcpy(tmpBuf,ms_info->host_desc, sizeof(tmpBuf));
c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
c_strlcat(tmpBuf, service_name_with_suffix, sizeof(tmpBuf));
if (mdns_compare_name((unsigned char *) tmpBuf,
(unsigned char *) mdns_payload + SIZEOF_DNS_HDR) == 0) {
if (qry_type == DNS_RRTYPE_TXT || qry_type == DNS_RRTYPE_SRV || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service(info, i, addr, port);
}
}
}
}
}
}
memerr1:
/* free pbuf */
pbuf_free(p);
return;
}
static void
mdns_free_info(struct nodemcu_mdns_info *info) {
os_free((void *) info);
}
/**
* close the UDP pcb .
*/
void ICACHE_FLASH_ATTR
nodemcu_mdns_close(void)
{
os_timer_disarm(&mdns_timer);
if (mdns_pcb != NULL) {
udp_remove(mdns_pcb);
}
if (mdns_payload) {
os_free(mdns_payload);
}
mdns_payload = NULL;
mdns_pcb = NULL;
mdns_free_info(ms_info);
ms_info = NULL;
}
static void ICACHE_FLASH_ATTR
mdns_set_servicename(const char *name) {
char tmpBuf[128];
os_sprintf(tmpBuf, "_%s._tcp.local", name);
if (service_name_with_suffix) {
os_free(service_name_with_suffix);
}
service_name_with_suffix = c_strdup(tmpBuf);
}
static u8_t reg_counter;
static void
mdns_reg_handler_restart(void) {
reg_counter = 99;
}
static void ICACHE_FLASH_ATTR
mdns_reg(struct nodemcu_mdns_info *info) {
mdns_send_service(info,0,0,0);
if (reg_counter++ > 10) {
mdns_send_service_type(0,0,0);
reg_counter = 0;
}
}
static struct nodemcu_mdns_info *
mdns_dup_info(const struct nodemcu_mdns_info *info) {
struct nodemcu_mdns_info *result;
// calculate length
int len = sizeof(struct nodemcu_mdns_info);
len += c_strlen(info->host_name) + 1;
len += c_strlen(info->host_desc) + 1;
len += c_strlen(info->service_name) + 1;
int i;
for (i = 0; i < sizeof(info->txt_data) / sizeof(info->txt_data[0]) && info->txt_data[i]; i++) {
len += c_strlen(info->txt_data[i]) + 1;
}
#define COPY_OVER(dest, src, p) len = c_strlen(src) + 1; memcpy(p, src, len); dest = p; p += len
result = (struct nodemcu_mdns_info *) os_zalloc(len);
if (result) {
char *p = (char *) (result + 1);
result->service_port = info->service_port;
COPY_OVER(result->host_name, info->host_name, p);
COPY_OVER(result->host_desc, info->host_desc, p);
COPY_OVER(result->service_name, info->service_name, p);
for (i = 0; i < sizeof(info->txt_data) / sizeof(info->txt_data[0]) && info->txt_data[i]; i++) {
COPY_OVER(result->txt_data[i], info->txt_data[i], p);
}
}
#undef COPY_OVER
return result;
}
/**
* Initialize the resolver: set up the UDP pcb and configure the default server
* (NEW IP).
*
* returns TRUE if it worked, FALSE if it failed.
*/
bool ICACHE_FLASH_ATTR
nodemcu_mdns_init(struct nodemcu_mdns_info *info) {
/* initialize default DNS server address */
multicast_addr.addr = DNS_MULTICAST_ADDRESS;
struct ip_info ipconfig;
mdns_free_info(ms_info);
ms_info = mdns_dup_info(info); // Save the passed block. We need all the data forever
if (!ms_info) {
return FALSE;
}
if (mdns_payload) {
os_free(mdns_payload);
}
mdns_payload = (u8_t *) os_malloc(DNS_MSG_SIZE);
if (!mdns_payload) {
MDNS_DBG("Alloc fail\n");
return FALSE;
}
LWIP_DEBUGF(DNS_DEBUG, ("dns_init: initializing\n"));
mdns_set_servicename(ms_info->service_name);
// get the host name as instrumentName_serialNumber for MDNS
// set the name of the service, the same as host name
MDNS_DBG("host_name = %s\n", ms_info->host_name);
MDNS_DBG("server_name = %s\n", service_name_with_suffix);
/* initialize mDNS */
mdns_pcb = udp_new();
if (!mdns_pcb) {
return FALSE;
}
/* join to the multicast address 224.0.0.251 */
if(wifi_get_opmode() & 0x01) {
struct netif *sta_netif = (struct netif *)eagle_lwip_getif(0x00);
if (sta_netif && sta_netif->ip_addr.addr && igmp_joingroup(&sta_netif->ip_addr, &multicast_addr) != ERR_OK) {
MDNS_DBG("sta udp_join_multigrup failed!\n");
return FALSE;
};
}
if(wifi_get_opmode() & 0x02) {
struct netif *ap_netif = (struct netif *)eagle_lwip_getif(0x01);
if (ap_netif && ap_netif->ip_addr.addr && igmp_joingroup(&ap_netif->ip_addr, &multicast_addr) != ERR_OK) {
MDNS_DBG("ap udp_join_multigrup failed!\n");
return FALSE;
};
}
register_flag = 1;
/* join to any IP address at the port 5353 */
if (udp_bind(mdns_pcb, IP_ADDR_ANY, DNS_MDNS_PORT) != ERR_OK) {
MDNS_DBG("udp_bind failed!\n");
return FALSE;
};
/*loopback function for the multicast(224.0.0.251) messages received at port 5353*/
udp_recv(mdns_pcb, mdns_recv, ms_info);
mdns_flag = 1;
/*
* Register the name of the instrument
*/
//MDNS_DBG("About to start timer\n");
os_timer_disarm(&mdns_timer);
os_timer_setfn(&mdns_timer, (os_timer_func_t *)mdns_reg,ms_info);
os_timer_arm(&mdns_timer, 1000 * 120, 1);
/* kick off the first one right away */
mdns_reg_handler_restart();
mdns_reg(ms_info);
return TRUE;
}
#endif /* LWIP_MDNS */

View File

@ -9,13 +9,21 @@
Register a hostname and start the mDNS service. If the service is already running, then it will be restarted with the new parameters.
#### Syntax
`mdns.register(hostname, servicename, port [, attributes])`
`mdns.register(hostname [, attributes])`
#### Parameters
- `hostname` The hostname for this device. Alphanumeric characters are best.
- `servicename` The service name for this device. Alphanumeric characters are best. This will get prefixed with `_` and suffixed with `._tcp`
- `port` The port number for the primary service.
- `attributes` A optional table of up to 10 attributes to be exposed. The keys must all be strings.
- `attributes` A optional table of options. The keys must all be strings.
The `attributes` contains two sorts of attributes -- those with specific names, and those that are service specific. [RFC 6763](https://tools.ietf.org/html/rfc6763#page-13}
defines how extra, service specific, attributes are encoded into the DNS. One example is that if the device supports printing, then the queue name can
be specified as an additional attribute. This module supports up to 10 such attributes.
The specific names are:
- `port` The port number for the service. Default value is 80.
- `service` The name of the service. Default value is 'http'.
- `dscription` A short phrase (under 63 characters) describing the service. Default is the hostname.
#### Returns
`nil`
@ -25,10 +33,12 @@ Various errors can be generated during argument validation. The NodeMCU must hav
#### Example
mdns.register("fishtank", "http", 80, { hardware='NodeMCU'})
mdns.register("fishtank", {hardware='NodeMCU'})
Using `dns-sd` on OS X, you can see `fishtank.local` as providing the `_http._tcp` service. You can also browse directly to `fishtank.local`. In Safari you can get all the mDNS web pages as part of your bookmarks menu.
mdns.register("fishtank", { description="Top Fishtank", service="http", port=80, location='Living Room' })
## mdns.close()
Shut down the mDNS service. This is not normally needed.