diff --git a/libraries/ESP8266LLMNR/ESP8266LLMNR.cpp b/libraries/ESP8266LLMNR/ESP8266LLMNR.cpp new file mode 100644 index 0000000000..1b855f0094 --- /dev/null +++ b/libraries/ESP8266LLMNR/ESP8266LLMNR.cpp @@ -0,0 +1,281 @@ +/* + * ESP8266 LLMNR responder + * Copyright (C) 2017 Stephen Warren + * + * Based on: + * ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) + * Version 1.1 + * Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) + * ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) + * MDNS-SD Suport 2015 Hristo Gochkov + * Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Reference: + * https://tools.ietf.org/html/rfc4795 (LLMNR) + * https://tools.ietf.org/html/rfc1035 (DNS) + */ + +#include +#include +#include +#include + +extern "C" { +#include +} + +#include +#include +#include + +//#define LLMNR_DEBUG + +#define BIT(x) (1 << (x)) + +#define FLAGS_QR BIT(15) +#define FLAGS_OP_SHIFT 11 +#define FLAGS_OP_MASK 0xf +#define FLAGS_C BIT(10) +#define FLAGS_TC BIT(9) +#define FLAGS_T BIT(8) +#define FLAGS_RCODE_SHIFT 0 +#define FLAGS_RCODE_MASK 0xf + +#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read()) +#define _conn_read8() _conn->read() +#define _conn_readS(b, l) _conn->read((b), (l)); + +static const IPAddress LLMNR_MULTICAST_ADDR(224, 0, 0, 252); +static const int LLMNR_MULTICAST_TTL = 1; +static const int LLMNR_PORT = 5355; + +LLMNRResponder::LLMNRResponder() : + _conn(0) { +} + +LLMNRResponder::~LLMNRResponder() { + if (_conn) + _conn->unref(); +} + +bool LLMNRResponder::begin(const char* hostname) { + // Max length for a single label in DNS + if (strlen(hostname) > 63) + return false; + + _hostname = hostname; + _hostname.toLowerCase(); + + _sta_got_ip_handler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP& event){ + _restart(); + }); + + _sta_disconnected_handler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected& event) { + _restart(); + }); + + return _restart(); +} + +void LLMNRResponder::notify_ap_change() { + _restart(); +} + +bool LLMNRResponder::_restart() { + if (_conn) { + _conn->unref(); + _conn = 0; + } + + ip_addr_t multicast_addr; + multicast_addr.addr = (uint32_t)LLMNR_MULTICAST_ADDR; + + if (igmp_joingroup(IP_ADDR_ANY, &multicast_addr) != ERR_OK) + return false; + + _conn = new UdpContext; + _conn->ref(); + + if (!_conn->listen(*IP_ADDR_ANY, LLMNR_PORT)) + return false; + + _conn->setMulticastTTL(LLMNR_MULTICAST_TTL); + _conn->onRx(std::bind(&LLMNRResponder::_process_packet, this)); + _conn->connect(multicast_addr, LLMNR_PORT); +} + +void LLMNRResponder::_process_packet() { + if (!_conn || !_conn->next()) + return; + +#ifdef LLMNR_DEBUG + Serial.println("LLMNR: RX'd packet"); +#endif + + uint16_t id = _conn_read16(); + uint16_t flags = _conn_read16(); + uint16_t qdcount = _conn_read16(); + uint16_t ancount = _conn_read16(); + uint16_t nscount = _conn_read16(); + uint16_t arcount = _conn_read16(); + +#ifdef LLMNR_DEBUG + Serial.print("LLMNR: ID="); + Serial.println(id, HEX); + Serial.print("LLMNR: FLAGS="); + Serial.println(flags, HEX); + Serial.print("LLMNR: QDCOUNT="); + Serial.println(qdcount); + Serial.print("LLMNR: ANCOUNT="); + Serial.println(ancount); + Serial.print("LLMNR: NSCOUNT="); + Serial.println(nscount); + Serial.print("LLMNR: ARCOUNT="); + Serial.println(arcount); +#endif + +#define BAD_FLAGS (FLAGS_QR | (FLAGS_OP_MASK << FLAGS_OP_SHIFT) | FLAGS_C) + if (flags & BAD_FLAGS) { +#ifdef LLMNR_DEBUG + Serial.println("Bad flags"); +#endif + return; + } + + if (qdcount != 1) { +#ifdef LLMNR_DEBUG + Serial.println("QDCOUNT != 1"); +#endif + return; + } + + if (ancount || nscount || arcount) { +#ifdef LLMNR_DEBUG + Serial.println("AN/NS/AR-COUNT != 0"); +#endif + return; + } + + uint8_t namelen = _conn_read8(); +#ifdef LLMNR_DEBUG + Serial.print("QNAME len "); + Serial.println(namelen); +#endif + if (namelen != _hostname.length()) { +#ifdef LLMNR_DEBUG + Serial.println("QNAME len mismatch"); +#endif + return; + } + + char qname[64]; + _conn_readS(qname, namelen); + _conn_read8(); + qname[namelen] = '\0'; +#ifdef LLMNR_DEBUG + Serial.print("QNAME "); + Serial.println(qname); +#endif + + if (strcmp(_hostname.c_str(), qname)) { +#ifdef LLMNR_DEBUG + Serial.println("QNAME mismatch"); +#endif + return; + } + + uint16_t qtype = _conn_read16(); + uint16_t qclass = _conn_read16(); + +#ifdef LLMNR_DEBUG + Serial.print("QTYPE "); + Serial.print(qtype); + Serial.print(" QCLASS "); + Serial.println(qclass); +#endif + + bool have_rr = + (qtype == 1) && /* A */ + (qclass == 1); /* IN */ + + _conn->flush(); + +#ifdef LLMNR_DEBUG + Serial.println("Match; responding"); + if (!have_rr) + Serial.println("(no matching RRs)"); +#endif + + struct ip_info remote_ip_info; + remote_ip_info.ip.addr = _conn->getRemoteAddress(); + struct ip_info ip_info; + bool match_ap = false; + if (wifi_get_opmode() & SOFTAP_MODE) { + wifi_get_ip_info(SOFTAP_IF, &ip_info); + if (ip_info.ip.addr && ip_addr_netcmp(&remote_ip_info.ip, &ip_info.ip, &ip_info.netmask)) + match_ap = true; + } + if (!match_ap) + wifi_get_ip_info(STATION_IF, &ip_info); + uint32_t ip = ip_info.ip.addr; + + // Header + uint8_t header[] = { + id >> 8, id & 0xff, // ID + FLAGS_QR >> 8, 0, // FLAGS + 0, 1, // QDCOUNT + 0, !!have_rr, // ANCOUNT + 0, 0, // NSCOUNT + 0, 0, // ARCOUNT + }; + _conn->append(reinterpret_cast(header), sizeof(header)); + // Question + _conn->append(reinterpret_cast(&namelen), 1); + _conn->append(qname, namelen); + uint8_t q[] = { + 0, // Name terminator + 0, 1, // TYPE (A) + 0, 1, // CLASS (IN) + }; + _conn->append(reinterpret_cast(q), sizeof(q)); + // Answer, if we have one + if (have_rr) { + _conn->append(reinterpret_cast(&namelen), 1); + _conn->append(qname, namelen); + uint8_t rr[] = { + 0, // Name terminator + 0, 1, // TYPE (A) + 0, 1, // CLASS (IN) + 0, 0, 0, 30, // TTL (30 seconds) + 0, 4, // RDLENGTH + ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff, // RDATA + }; + _conn->append(reinterpret_cast(rr), sizeof(rr)); + } + _conn->setMulticastInterface(remote_ip_info.ip); + _conn->send(&remote_ip_info.ip, _conn->getRemotePort()); +} + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LLMNR) +LLMNRResponder LLMNR; +#endif diff --git a/libraries/ESP8266LLMNR/ESP8266LLMNR.h b/libraries/ESP8266LLMNR/ESP8266LLMNR.h new file mode 100644 index 0000000000..4ad1d4a74d --- /dev/null +++ b/libraries/ESP8266LLMNR/ESP8266LLMNR.h @@ -0,0 +1,66 @@ +/* + * ESP8266 LLMNR responder + * Copyright (C) 2017 Stephen Warren + * + * Based on: + * ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) + * Version 1.1 + * Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) + * ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) + * MDNS-SD Suport 2015 Hristo Gochkov + * Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef ESP8266LLMNR_H +#define ESP8266LLMNR_H + +#include + +class UdpContext; + +class LLMNRResponder { +public: + LLMNRResponder(); + ~LLMNRResponder(); + + /* Initialize and start responding to LLMNR requests on all interfaces */ + bool begin(const char* hostname); + + /* Application should call this whenever AP is configured/disabled */ + void notify_ap_change(); + +private: + String _hostname; + UdpContext *_conn; + WiFiEventHandler _sta_got_ip_handler; + WiFiEventHandler _sta_disconnected_handler; + + bool _restart(); + void _process_packet(); +}; + +#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LLMNR) +extern LLMNRResponder LLMNR; +#endif + +#endif diff --git a/libraries/ESP8266LLMNR/README.md b/libraries/ESP8266LLMNR/README.md new file mode 100644 index 0000000000..2ecdf973ef --- /dev/null +++ b/libraries/ESP8266LLMNR/README.md @@ -0,0 +1,92 @@ +ESP8266 LLMNR (Link-Local Multicast Name Resolution) +==================================================== + +This is a simple implementation of an LLMNR responder the ESP8266 Arduino +package. Only support for advertizing a single hostname is currently +implemented. + +LLMNR is a very similar protocol to MDNS. The primary practical difference is +that Windows systems (at least Windows 7 and later; perhaps earlier) support +the protocol out-of-the-box, whereas additional software is required to support +MDNS. However, Linux support is currently more complex, and MacOS X support +appears non-existent. + +Requirements +------------ +- ESP8266WiFi library +- LLMNR support in your operating system/client machines: + - For Windows, support is already built in (in Windows 7 at least). + - For Linux, the systemd-resolve application supports LLMNR. + - For Mac OSX: Unknown; likely not supported. + +Usage +----- +1. Install this repository using the instructions in the top-levle README.md + file. +2. Include the ESP8266LLMNR library in the sketch. +3. Call the LLMNR.begin() method in the sketch's setup() function, and provide + the hostname to advertize. This should not include any ".local" prefix. +4. If ESP8266 AP mode is enabled, disabled, or the WiFi or AP configuration is + changed, call LLMNR.notify_ap_change() after the change is made. + +See the included LLMNR + HTTP server sketch for a full example. + +References +---------- +1. https://tools.ietf.org/html/rfc4795 (LLMNR) +2. https://tools.ietf.org/html/rfc1035 (DNS) + +Caveats +------- +1. LLMNR implementations MUST support EDNS0 [RFC2671] and extended RCODE + values. It is likely that this implementation does not; I have not read + that RFC. +2. LLMNR responders MUST support listening for TCP queries. This implementation + does not. +3. On receiving an LLMNR query, the responder MUST check whether it was sent to + an LLMNR multicast addresses defined in Section 2. If it was sent to another + multicast address, then the query MUST be silently discarded. This + implementation makes no such check; it is hoped that the ESP8266 network + stack filters out such packets since the code only joins the relevant + multicast group and does not listen for unicast packets. This assumption may + be invalid. +4. Prior to sending an LLMNR response with the 'T' bit clear, a responder + configured with a UNIQUE name MUST verify that there is no other host within + the scope of LLMNR query propagation that is authoritative for the same name + on that interface. This implementation performs no such verification. +5. Prior to verifying that its name is UNIQUE, a responder MUST set the 'T' bit + in responses. This implementation does not; it assumes that name is unique + and responds with the 'T' bit clear in all cases. +6. To verify uniqueness, a responder MUST send an LLMNR query with the 'C' bit + clear, over all protocols on which it responds to LLMNR queries (IPv4 and/or + IPv6). This implementation does not. + +License +------- +Copyright (C) 2017 Stephen Warren + +Based on: +ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) +Version 1.1 +Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) +ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) +MDNS-SD Suport 2015 Hristo Gochkov +Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libraries/ESP8266LLMNR/examples/LLMNR_Web_Server/LLMNR_Web_Server.ino b/libraries/ESP8266LLMNR/examples/LLMNR_Web_Server/LLMNR_Web_Server.ino new file mode 100644 index 0000000000..960b26e9f8 --- /dev/null +++ b/libraries/ESP8266LLMNR/examples/LLMNR_Web_Server/LLMNR_Web_Server.ino @@ -0,0 +1,106 @@ +/* + * ESP8266 LLMNR responder sample + * Copyright (C) 2017 Stephen Warren + * + * Based on: + * ESP8266 Multicast DNS (port of CC3000 Multicast DNS library) + * Version 1.1 + * Copyright (c) 2013 Tony DiCola (tony@tonydicola.com) + * ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com) + * MDNS-SD Suport 2015 Hristo Gochkov + * Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com) + * + * License (MIT license): + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +/* + * This is an example of an HTTP server that is accessible via http://esp8266/ + * (or perhaps http://esp8266.local/) thanks to the LLMNR responder. + * + * Instructions: + * - Update WiFi SSID and password as necessary. + * - Flash the sketch to the ESP8266 board. + * - Windows: + * - No additional software is necessary. + * - Point your browser to http://esp8266/, you should see a response. In most + * cases, it is important that you manually type the "http://" to force the + * browser to search for a hostname to connect to, rather than perform a web + * search. + * - Alternatively, run the following command from the command prompt: + * ping esp8266 + * - Linux: + * - To validate LLMNR, install the systemd-resolve utility. + * - Execute the following command: + * systemd-resolve -4 -p llmnr esp8266 + * - It may be possible to configure your system to use LLMNR for all name + * lookups. However, that is beyond the scope of this description. + * + */ + +#include +#include +#include +#include + +const char* ssid = "replace_me"; +const char* password = "replace_me"; + +ESP8266WebServer web_server(80); + +void handle_http_not_found() { + web_server.send(404, "text/plain", "Not Found"); +} + +void handle_http_root() { + web_server.send(200, "text/plain", "It works!"); +} + +void setup(void) { + Serial.begin(115200); + + // Connect to WiFi network + WiFi.begin(ssid, password); + Serial.println(""); + + // Wait for connection + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.print("Connected to "); + Serial.println(ssid); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Start LLMNR responder + LLMNR.begin("esp8266"); + Serial.println("LLMNR responder started"); + // Start HTTP server + web_server.onNotFound(handle_http_not_found); + web_server.on("/", handle_http_root); + web_server.begin(); + Serial.println("HTTP server started"); +} + +void loop(void) { + web_server.handleClient(); +} diff --git a/libraries/ESP8266LLMNR/keywords.txt b/libraries/ESP8266LLMNR/keywords.txt new file mode 100644 index 0000000000..b20f341b6f --- /dev/null +++ b/libraries/ESP8266LLMNR/keywords.txt @@ -0,0 +1,23 @@ +####################################### +# Syntax Coloring Map For Ultrasound +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ESP8266LLMNR KEYWORD1 +LLMNRResponder KEYWORD1 +LLMNR KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +notify_ap_change KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +