diff --git a/app/Makefile b/app/Makefile index 94aa221c..426ec33f 100644 --- a/app/Makefile +++ b/app/Makefile @@ -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. diff --git a/app/include/nodemcu_mdns.h b/app/include/nodemcu_mdns.h new file mode 100644 index 00000000..8f9161ed --- /dev/null +++ b/app/include/nodemcu_mdns.h @@ -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 diff --git a/app/libc/c_string.c b/app/libc/c_string.c index 69ce067c..011a1899 100644 --- a/app/libc/c_string.c +++ b/app/libc/c_string.c @@ -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 + * + * 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 + * + * 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 */ +} + diff --git a/app/libc/c_string.h b/app/libc/c_string.h index eae05f73..22d44b71 100644 --- a/app/libc/c_string.h +++ b/app/libc/c_string.h @@ -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_ */ diff --git a/app/modules/mdns.c b/app/modules/mdns.c index dc186864..8d9a077a 100644 --- a/app/modules/mdns.c +++ b/app/modules/mdns.c @@ -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; } diff --git a/app/net/Makefile b/app/net/Makefile new file mode 100644 index 00000000..de251109 --- /dev/null +++ b/app/net/Makefile @@ -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 + diff --git a/app/net/nodemcu_mdns.c b/app/net/nodemcu_mdns.c new file mode 100644 index 00000000..72a2345a --- /dev/null +++ b/app/net/nodemcu_mdns.c @@ -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 */ diff --git a/docs/en/modules/mdns.md b/docs/en/modules/mdns.md index ef6c1285..758de49d 100644 --- a/docs/en/modules/mdns.md +++ b/docs/en/modules/mdns.md @@ -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.