nodemcu-firmware/app/net/nodemcu_mdns.c

1142 lines
31 KiB
C
Raw Normal View History

/**
* 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
Next 1.5.4.1 master drop (#1627) * add u8g.fb_rle display * move comm drivers to u8g_glue.c * disable fb_rle per default * implement file.size for spiffs (#1516) Another bug squashed! * Fix start-up race between UART & start_lua. (#1522) Input during startup (especially while doing initial filesystem format) ran the risk of filling up the task queue, preventing the start_lua task from being queued, and hence NodeMCU would not start up that time. * Reimplemented esp_init_data_default. To work around the pesky "rf_cal[0] !=0x05" hang when booting on a chip which doesn't have esp_init_data written to it. It is no longer possible to do the writing of the esp_init_data_default from within nodemcu_init(), as the SDK now hangs long before it gets there. As such, I've had to reimplement this in our user_start_trampoline and get it all done before the SDK has a chance to look for the init data. It's unfortunate that we have to spend IRAM on this, but I see no better alternative at this point. * Replace hardcoded init data with generated data from SDK The esp_init_data_default.bin is now extracted from the SDK (and its patch file, if present), and the contents are automatically embedded into user_main.o. * Rework flashing instructions Clarifies issues around SDK init data and hopefully clears up some confusion, when paired with the esp_init_data_default changes in NodeMCU. * Fix typo * Fixes the gpio.serout problem from #1534 (#1535) * Fix some issues in gpio.serout * Minor cleanup * fix dereferencing NULL pointer in vfs_errno() (#1539) * add map ids for flash sizes 32m-c2, 64m, 128m in user_rf_cal_sector_set() (#1529) * Somfy/TELIS driver (#1521) * Reduced LUAL_BUFFERSIZE to 256. Should free up some stack (#1530) * avoid task queue overrun for serial input (#1540) Thank you. * Increase irom0_0_seg size for PR build * Improve reliability of FS detection. (#1528) * Version to make filesystem detection more reliable * Improve bad fs detection * Version of printf that doesn't suffer from buffer overflows (#1564) * Small improvement to http client (#1558) * Remove luaL_buffer from file_g_read() (#1541) * remove luaL_buffer from file_g_read() - avoid memory leak when function gets terminated by lua_error - skip scanning for end_char when reading until EOF * attempt to free memory in any case * Change HTTP failures from debug to error messages (#1568) * Change HTTP failures from debug to error messages * Add tag to HTTP error messages * Create macro for error msg and improve dbg msg * Add ssd1306_128x32 for U8G (#1571) * Update CONTRIBUTING.md * Add support to mix ws2812.buffer objects. (#1575) * Add load/dump/mix/power operations on the buffer object * Calculate the pixel value in mix and then clip to the range. * Fixed the two wrong userdata types * Added a couple more useful methods * Add support for shifting a piece of the buffer. * Fix a minor bug with offset shifts * Update to the wifi module (#1497) * Removed inline documentation for several functions and update comments Since documentation is now part of the repository, the inline documentation just adds to the already huge wifi.c * Wifi module: add new functionality, update documentation Functions Added: wifi.getdefaultmode(): returns default wifi opmode wifi.sta.apchange(): select alternate cached AP wifi.sta.apinfo(): get cached AP list wifi.sta.aplimit(): set cached AP limit wifi.sta.getapindex(): get index of currently configured AP wifi.sta.getdefaultconfig(): get default station configuration wifi.ap.getdefaultconfig(): get default AP configuration functions modified: wifi.setmode: saving mode to flash is now optional wifi.sta.config: now accepts table as an argument and save config to flash is now optional wifi.sta.getconfig: added option to return table wifi.ap.config: save config to flash is now optional wifi.ap.getconfig: added option to return table Documentation changes: - Modified documentation to reflect above changes - Removed unnecessary inline documentation from `wifi.c` - Updated documentation for `wifi.sta.disconnect`to address issue #1480 - Fixed inaccurate documentation for function `wifi.sleeptype` - Added more details to `wifi.nullmodesleep()` * Move function `wifi.sleeptype()` to `wifi.sta.sleeptype()` * Fixed problem where wifi.x.getconfig() returned invalid strings when ssid or password were set to maximum length. * fix error in documentation for `wifi.sta.getapindex` * Renamed some wifi functions wifi.sta.apinfo -> getapinfo wifi.sta.aplimit -> setaplimit wifi.sta.apchange -> changeap also organized the wifi_station_map array * Make the MQTT PING functionality work better. (#1557) Deal with flow control stopped case * Implement object model for files (#1532) * Eus channelfix (#1583) Squashed commits included: Bug fixes and final implementation - Added Content-Length: 0 to all headers - Endpoint name checks not using trailing space so cache-busting techniques can be used (i.e., append a nonce to the URL) - Track when connecting so APList scan doesn't take place during (which changes the channel) - More debugging output added to assist in tracking down some issues Added /status.json endpoint for phone apps/XHR to get JSON response Station Status caching for wifi channel workaround + AJAX/CORS - During checkstation poll, cache the last station status - Shut down the station if status = 2,3,4 and channel is different than SoftAP - Add Access-Control-Allow-Origin: * to endpoint responses used by a service - Add a /setwifi GET endpoint for phone apps/XHR to use (same parameters as /update endpoint). Returns a JSON response containing chip id and status code. - Add handler for OPTIONS verb (needed for CORS support) Wi-Fi Channel Issue Workaround - Do a site survey upon startup, set SoftAP channel to the strongest rssi's channel - Compare successful station connect channel to SoftAP's. If different, then defer the Lua success callback to the end. Shut down Station and start the SoftAP back up with original channel. - After the 10 second shutdown timer fires, check to see if success callback was already called. If not, then call it while starting the Station back up. HTTP Response and DNS enhancements - If DNS's UDP buffer fills up, keep going as non-fatal. It's UDP and not guaranteed anyways. I've seen this occur when connecting a PC to the SoftAP and every open program tries to phone home at the same time, overwhelming the EUS DNS server. - Support for detecting/handling pre-gzipped `enduser_setup.html` (and `http_html_backup`) payload. Nice for keeping the size of the `state->http_payload_data` as small as possible (also makes minimization not as critical) - Corrected misuse of HTTP 401 response status (changed one occurrence to 400/Bad Request, and changed another to 405/Method Not Allowed) * Normalized formatting (tabs-to-spaces) * Added documentation * Corrected misuse of strlen for binary (gzip) data. * Added NULL check after malloc * fix vfs_lseek() result checking in enduser_setup and clarify SPIFFS_lseek() return value (#1570) * Fix link * Overhaul flashing docs once again (#1587) * Add chapter about determine flash size plus small fixes * Rewrite esptool.py chapter, move flash size chapter to end * i2c - allow slave stretching SCL (just loop and check) (#1589) * Add note on dev board usage of SPI bus 0 (#1591) * Turn SPI busses note to admonition note * support for custom websocket headers (#1573) Looks good to me. Thank you. Also: - allow for '\0's in received messages * add client:config for setting websocket headers Also: - headers are case-insensitive now * fix docs * fix typo * remove unnecessary luaL_argcheck calls * replace os_sprintf with simple string copy * Handle error condition in file.read() (#1599) * handle error condition in file.read() * simplify loop initialization * Fix macro as suggested in #1548 * Extract and hoist net receive callbacks This is done to avoid the accidental upval binding * Fix typo at rtctime.md rtctime.dsleep -> rtctime.dsleep_aligned
2016-12-01 21:37:24 +01:00
#define MDNS_DBG(...) dbg_printf(...)
#else
#define MDNS_DBG(...) do {} while (0)
#endif
#define min(a,b) ((a) < (b) ? (a) : (b))
#define MDNS_NAME_LENGTH 68 //68
#define DNS_RRTYPE_NSEC 47
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_a_rr {
PACK_STRUCT_FIELD(u32_t src);
}PACK_STRUCT_STRUCT;
PACK_STRUCT_END
#define SIZEOF_MDNS_A_RR 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 *pktbase) {
unsigned char n;
do {
n = *response++;
/** @see RFC 1035 - 4.1.4. Message compression */
if ((n & 0xc0) == 0xc0) {
n = ((n << 8) + *response) & 0x3fff;
if (n < response - pktbase) {
response = pktbase + n;
} else {
return 1;
}
} else {
/* Not compressed name */
while (n > 0) {
char q = *query;
if (q >= 'A' && q <= 'Z') {
q = q + 'a' - 'A';
}
char r = *response;
if (r >= 'A' && r <= 'Z') {
r = r + 'a' - 'A';
}
if (q != r) {
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;
}
/* Copy an unencoded name into an encoded name */
static unsigned char *copy_and_encode_name(unsigned char *ptr, const char *name) {
while (*name) {
const char *p = name;
while (*p != '.' && *p) {
p++;
}
*ptr++ = p - name;
memcpy(ptr, name, p - name);
ptr += p - name;
if (!*p) {
break;
}
name = p + 1;
}
*ptr++ = 0;
return ptr;
}
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_a_rr a_rr;
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;
int max_ttl = dst_addr ? 10 : 7200;
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(min(max_ttl, 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_a_rr a_rr;
struct pbuf *p ,*p_sta;
char *query, *nptr;
char *query_end;
const char *pHostname;
const char *name = info->host_name;
int max_ttl = dst_addr ? 10 : 7200;
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);
hdr->numextrarr = htons(1);
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(min(max_ttl, 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(min(max_ttl, 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_A_RR +
2 + SIZEOF_DNS_ANSWER + 5;
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(min(max_ttl, 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(min(max_ttl, 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 */
a_rr.src = 0;
MEMCPY( query, &a_rr, SIZEOF_MDNS_A_RR);
u8_t *addr_ptr = query + ((char *) &a_rr.src - (char *) &a_rr);
/* resize the query */
query = query + SIZEOF_MDNS_A_RR;
// Append the NSEC record that says we only have an A record
*query++ = 0xc0 + (hostname_offset >> 8);
*query++ = hostname_offset & 0xff;
ans.type = htons(DNS_RRTYPE_NSEC);
ans.class = htons(dns_class);
ans.ttl = htonl(min(max_ttl, 300));
ans.len = htons(5);
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
query = query + SIZEOF_DNS_ANSWER;
*query++ = 0xc0 + (hostname_offset >> 8);
*query++ = hostname_offset & 0xff;
*query++ = 0;
*query++ = 1;
*query++ = 0x40;
//MDNS_DBG("Final ptr=%x\n", query);
// increment by sizeof_mdns_a_rr
/* 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);
if (!dst_addr) {
// this is being sent multicast...
// so reset the timer
os_timer_disarm(&mdns_timer);
os_timer_arm(&mdns_timer, 1000 * 280, 1);
}
} else {
MDNS_DBG("ERR_MEM \n");
err = ERR_MEM;
}
return err;
}
static char *append_nsec_record(char *query, u32_t actual_rr, int max_ttl) {
struct mdns_answer ans;
ans.type = htons(DNS_RRTYPE_NSEC);
ans.class = htons(DNS_RRCLASS_IN);
ans.ttl = htonl(min(max_ttl, 300));
ans.len = htons(9);
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
char *rr_len = query + ((char *) &ans.len - (char *) &ans) + 1;
query = query + SIZEOF_DNS_ANSWER;
*query++ = 0xc0;
*query++ = sizeof(struct mdns_hdr);
*query++ = 0;
char *bm_len = query;
*query++ = 5;
char *abase = query;
*query++ = 0;
*query++ = 0;
*query++ = 0;
*query++ = 0;
*query++ = 0;
while (actual_rr > 0) {
int v = actual_rr & 255;
if (v < 5 * 8) {
abase[v >> 3] |= 0x80 >> (v & 7);
actual_rr = actual_rr >> 8;
}
}
while (query[-1] == 0) {
query--;
(*bm_len)--;
(*rr_len)--;
}
return query;
}
/**
* This sends an empty response -- this is used when we doin't have an RR to send
* but the name exists
*/
static void
mdns_send_no_rr(struct mdns_hdr *req, const char *name, u32_t actual_rr, struct ip_addr *dst_addr, u16_t dst_port) {
int max_ttl = dst_addr ? 10 : 7200;
struct pbuf *p;
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 */
struct mdns_hdr *hdr = (struct mdns_hdr*) p->payload;
os_memset(hdr, 0, SIZEOF_DNS_HDR);
hdr->id = req->id;
hdr->flags1 = DNS_FLAG1_RESPONSE;
hdr->numextrarr = htons(1);
char *query = (char*) hdr + SIZEOF_DNS_HDR;
char *query_end = (char *) p->payload + p->tot_len;
// Now copy over the dns name
int len = strlen(name);
if (query_end - query >= len + SIZEOF_DNS_QUERY + 15) {
query = copy_and_encode_name((char *) (hdr + 1), name);
query = append_nsec_record(query, actual_rr, max_ttl);
// Set the length code correctly
pbuf_realloc(p, query - ((char*) (p->payload)));
send_packet(p, dst_addr, dst_port, NULL);
}
}
}
/**
* This sends a single A record and the NSEC record as additional
*/
static void
mdns_send_a_rr(struct mdns_hdr *req, const char *name, struct ip_addr *dst_addr, u16_t dst_port) {
int max_ttl = dst_addr ? 10 : 7200;
struct pbuf *p;
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 */
struct mdns_hdr *hdr = (struct mdns_hdr*) p->payload;
os_memset(hdr, 0, SIZEOF_DNS_HDR);
hdr->id = req->id;
hdr->flags1 = DNS_FLAG1_RESPONSE;
hdr->numanswers = htons(1);
hdr->numextrarr = htons(1);
char *query = (char*) hdr + SIZEOF_DNS_HDR;
char *query_end = (char *) p->payload + p->tot_len;
// Now copy over the dns name
int len = strlen(name) + 1;
if (query_end - query >= len + SIZEOF_DNS_QUERY + 4 + 2 + 4 + 15) {
query = copy_and_encode_name((char *) (hdr + 1), name);
struct mdns_answer ans;
ans.type = htons(DNS_RRTYPE_A);
ans.class = htons(DNS_RRCLASS_IN);
ans.ttl = htonl(min(max_ttl, 300));
ans.len = htons(4);
MEMCPY( query, &ans, SIZEOF_DNS_ANSWER);
query = query + SIZEOF_DNS_ANSWER;
char *addr_ptr = query;
query += 4;
// Now add the NSEC record
*query++ = 0xc0;
*query++ = sizeof(*hdr);
query = append_nsec_record(query, DNS_RRTYPE_A, max_ttl);
// Set the length code correctly
pbuf_realloc(p, query - ((char*) (p->payload)));
send_packet(p, dst_addr, dst_port, addr_ptr);
}
}
}
/**
* 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 */
u8_t qno;
u8_t *qptr = (u8_t *) (hdr + 1);
u8_t *qend = mdns_payload + p->tot_len;
for (qno = 0; qno < nquestions && qptr < qend; qno++) {
char tmpBuf[PUCK_DATASHEET_SIZE + PUCK_SERVICE_LENGTH];
struct mdns_query qry;
int namelen = mdns_namelen(qptr, qend - qptr);
memcpy(&qry, namelen + qptr, sizeof(qry));
u16_t qry_type = ntohs(qry.type);
if (port == 5353 && (ntohs(qry.class) & 0x8000) == 0) {
addr = NULL;
}
u32_t actual_rr = 0;
const char *no_rr_name = NULL;
/* 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 *) qptr, (unsigned char *) hdr) == 0) {
if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service_type(i, addr, port);
} else {
no_rr_name = DNS_SD_SERVICE;
actual_rr = DNS_RRTYPE_PTR;
}
} else if (mdns_compare_name((unsigned char *) service_name_with_suffix,
(unsigned char *) qptr, (unsigned char *) hdr) == 0) {
if (qry_type == DNS_RRTYPE_PTR || qry_type == DNS_RRTYPE_ANY) {
mdns_send_service(info, i, addr, port);
} else {
no_rr_name = service_name_with_suffix;
actual_rr = DNS_RRTYPE_PTR;
}
} else {
c_strlcpy(tmpBuf,ms_info->host_name, sizeof(tmpBuf));
c_strlcat(tmpBuf, ".", sizeof(tmpBuf));
c_strlcat(tmpBuf, MDNS_LOCAL, sizeof(tmpBuf));
no_rr_name = tmpBuf;
if (mdns_compare_name((unsigned char *) tmpBuf,
(unsigned char *) qptr, (unsigned char *) hdr) == 0) {
if (qry_type == DNS_RRTYPE_A || qry_type == DNS_RRTYPE_ANY) {
mdns_send_a_rr(hdr, tmpBuf, addr, port);
} else {
actual_rr = DNS_RRTYPE_A;
}
} 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 *) qptr, (unsigned char *) 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);
} else {
actual_rr = (DNS_RRTYPE_TXT << 8) + DNS_RRTYPE_SRV;
}
}
}
}
if (actual_rr) {
mdns_send_no_rr(hdr, no_rr_name, actual_rr, addr, port);
}
qptr += namelen + sizeof(qry); // Now points to next question
}
}
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 * 280, 1);
/* kick off the first one right away */
mdns_reg_handler_restart();
mdns_reg(ms_info);
return TRUE;
}
#endif /* LWIP_MDNS */