Skip to content

Commit 04df3ad

Browse files
swarrenigrr
authored andcommitted
Add an LLMNR responder implementation (#2880)
This is a simple implementation of an LLMNR responder for 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.
1 parent d2b370b commit 04df3ad

File tree

5 files changed

+568
-0
lines changed

5 files changed

+568
-0
lines changed
+281
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/*
2+
* ESP8266 LLMNR responder
3+
* Copyright (C) 2017 Stephen Warren <[email protected]>
4+
*
5+
* Based on:
6+
* ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
7+
* Version 1.1
8+
* Copyright (c) 2013 Tony DiCola ([email protected])
9+
* ESP8266 port (c) 2015 Ivan Grokhotkov ([email protected])
10+
* MDNS-SD Suport 2015 Hristo Gochkov
11+
* Extended MDNS-SD support 2016 Lars Englund ([email protected])
12+
*
13+
* License (MIT license):
14+
*
15+
* Permission is hereby granted, free of charge, to any person obtaining a copy
16+
* of this software and associated documentation files (the "Software"), to deal
17+
* in the Software without restriction, including without limitation the rights
18+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19+
* copies of the Software, and to permit persons to whom the Software is
20+
* furnished to do so, subject to the following conditions:
21+
*
22+
* The above copyright notice and this permission notice shall be included in
23+
* all copies or substantial portions of the Software.
24+
*
25+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31+
* THE SOFTWARE.
32+
*
33+
* Reference:
34+
* https://tools.ietf.org/html/rfc4795 (LLMNR)
35+
* https://tools.ietf.org/html/rfc1035 (DNS)
36+
*/
37+
38+
#include <debug.h>
39+
#include <functional>
40+
#include <ESP8266LLMNR.h>
41+
#include <WiFiUdp.h>
42+
43+
extern "C" {
44+
#include <user_interface.h>
45+
}
46+
47+
#include <lwip/udp.h>
48+
#include <lwip/igmp.h>
49+
#include <include/UdpContext.h>
50+
51+
//#define LLMNR_DEBUG
52+
53+
#define BIT(x) (1 << (x))
54+
55+
#define FLAGS_QR BIT(15)
56+
#define FLAGS_OP_SHIFT 11
57+
#define FLAGS_OP_MASK 0xf
58+
#define FLAGS_C BIT(10)
59+
#define FLAGS_TC BIT(9)
60+
#define FLAGS_T BIT(8)
61+
#define FLAGS_RCODE_SHIFT 0
62+
#define FLAGS_RCODE_MASK 0xf
63+
64+
#define _conn_read16() (((uint16_t)_conn->read() << 8) | _conn->read())
65+
#define _conn_read8() _conn->read()
66+
#define _conn_readS(b, l) _conn->read((b), (l));
67+
68+
static const IPAddress LLMNR_MULTICAST_ADDR(224, 0, 0, 252);
69+
static const int LLMNR_MULTICAST_TTL = 1;
70+
static const int LLMNR_PORT = 5355;
71+
72+
LLMNRResponder::LLMNRResponder() :
73+
_conn(0) {
74+
}
75+
76+
LLMNRResponder::~LLMNRResponder() {
77+
if (_conn)
78+
_conn->unref();
79+
}
80+
81+
bool LLMNRResponder::begin(const char* hostname) {
82+
// Max length for a single label in DNS
83+
if (strlen(hostname) > 63)
84+
return false;
85+
86+
_hostname = hostname;
87+
_hostname.toLowerCase();
88+
89+
_sta_got_ip_handler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP& event){
90+
_restart();
91+
});
92+
93+
_sta_disconnected_handler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected& event) {
94+
_restart();
95+
});
96+
97+
return _restart();
98+
}
99+
100+
void LLMNRResponder::notify_ap_change() {
101+
_restart();
102+
}
103+
104+
bool LLMNRResponder::_restart() {
105+
if (_conn) {
106+
_conn->unref();
107+
_conn = 0;
108+
}
109+
110+
ip_addr_t multicast_addr;
111+
multicast_addr.addr = (uint32_t)LLMNR_MULTICAST_ADDR;
112+
113+
if (igmp_joingroup(IP_ADDR_ANY, &multicast_addr) != ERR_OK)
114+
return false;
115+
116+
_conn = new UdpContext;
117+
_conn->ref();
118+
119+
if (!_conn->listen(*IP_ADDR_ANY, LLMNR_PORT))
120+
return false;
121+
122+
_conn->setMulticastTTL(LLMNR_MULTICAST_TTL);
123+
_conn->onRx(std::bind(&LLMNRResponder::_process_packet, this));
124+
_conn->connect(multicast_addr, LLMNR_PORT);
125+
}
126+
127+
void LLMNRResponder::_process_packet() {
128+
if (!_conn || !_conn->next())
129+
return;
130+
131+
#ifdef LLMNR_DEBUG
132+
Serial.println("LLMNR: RX'd packet");
133+
#endif
134+
135+
uint16_t id = _conn_read16();
136+
uint16_t flags = _conn_read16();
137+
uint16_t qdcount = _conn_read16();
138+
uint16_t ancount = _conn_read16();
139+
uint16_t nscount = _conn_read16();
140+
uint16_t arcount = _conn_read16();
141+
142+
#ifdef LLMNR_DEBUG
143+
Serial.print("LLMNR: ID=");
144+
Serial.println(id, HEX);
145+
Serial.print("LLMNR: FLAGS=");
146+
Serial.println(flags, HEX);
147+
Serial.print("LLMNR: QDCOUNT=");
148+
Serial.println(qdcount);
149+
Serial.print("LLMNR: ANCOUNT=");
150+
Serial.println(ancount);
151+
Serial.print("LLMNR: NSCOUNT=");
152+
Serial.println(nscount);
153+
Serial.print("LLMNR: ARCOUNT=");
154+
Serial.println(arcount);
155+
#endif
156+
157+
#define BAD_FLAGS (FLAGS_QR | (FLAGS_OP_MASK << FLAGS_OP_SHIFT) | FLAGS_C)
158+
if (flags & BAD_FLAGS) {
159+
#ifdef LLMNR_DEBUG
160+
Serial.println("Bad flags");
161+
#endif
162+
return;
163+
}
164+
165+
if (qdcount != 1) {
166+
#ifdef LLMNR_DEBUG
167+
Serial.println("QDCOUNT != 1");
168+
#endif
169+
return;
170+
}
171+
172+
if (ancount || nscount || arcount) {
173+
#ifdef LLMNR_DEBUG
174+
Serial.println("AN/NS/AR-COUNT != 0");
175+
#endif
176+
return;
177+
}
178+
179+
uint8_t namelen = _conn_read8();
180+
#ifdef LLMNR_DEBUG
181+
Serial.print("QNAME len ");
182+
Serial.println(namelen);
183+
#endif
184+
if (namelen != _hostname.length()) {
185+
#ifdef LLMNR_DEBUG
186+
Serial.println("QNAME len mismatch");
187+
#endif
188+
return;
189+
}
190+
191+
char qname[64];
192+
_conn_readS(qname, namelen);
193+
_conn_read8();
194+
qname[namelen] = '\0';
195+
#ifdef LLMNR_DEBUG
196+
Serial.print("QNAME ");
197+
Serial.println(qname);
198+
#endif
199+
200+
if (strcmp(_hostname.c_str(), qname)) {
201+
#ifdef LLMNR_DEBUG
202+
Serial.println("QNAME mismatch");
203+
#endif
204+
return;
205+
}
206+
207+
uint16_t qtype = _conn_read16();
208+
uint16_t qclass = _conn_read16();
209+
210+
#ifdef LLMNR_DEBUG
211+
Serial.print("QTYPE ");
212+
Serial.print(qtype);
213+
Serial.print(" QCLASS ");
214+
Serial.println(qclass);
215+
#endif
216+
217+
bool have_rr =
218+
(qtype == 1) && /* A */
219+
(qclass == 1); /* IN */
220+
221+
_conn->flush();
222+
223+
#ifdef LLMNR_DEBUG
224+
Serial.println("Match; responding");
225+
if (!have_rr)
226+
Serial.println("(no matching RRs)");
227+
#endif
228+
229+
struct ip_info remote_ip_info;
230+
remote_ip_info.ip.addr = _conn->getRemoteAddress();
231+
struct ip_info ip_info;
232+
bool match_ap = false;
233+
if (wifi_get_opmode() & SOFTAP_MODE) {
234+
wifi_get_ip_info(SOFTAP_IF, &ip_info);
235+
if (ip_info.ip.addr && ip_addr_netcmp(&remote_ip_info.ip, &ip_info.ip, &ip_info.netmask))
236+
match_ap = true;
237+
}
238+
if (!match_ap)
239+
wifi_get_ip_info(STATION_IF, &ip_info);
240+
uint32_t ip = ip_info.ip.addr;
241+
242+
// Header
243+
uint8_t header[] = {
244+
id >> 8, id & 0xff, // ID
245+
FLAGS_QR >> 8, 0, // FLAGS
246+
0, 1, // QDCOUNT
247+
0, !!have_rr, // ANCOUNT
248+
0, 0, // NSCOUNT
249+
0, 0, // ARCOUNT
250+
};
251+
_conn->append(reinterpret_cast<const char*>(header), sizeof(header));
252+
// Question
253+
_conn->append(reinterpret_cast<const char*>(&namelen), 1);
254+
_conn->append(qname, namelen);
255+
uint8_t q[] = {
256+
0, // Name terminator
257+
0, 1, // TYPE (A)
258+
0, 1, // CLASS (IN)
259+
};
260+
_conn->append(reinterpret_cast<const char*>(q), sizeof(q));
261+
// Answer, if we have one
262+
if (have_rr) {
263+
_conn->append(reinterpret_cast<const char*>(&namelen), 1);
264+
_conn->append(qname, namelen);
265+
uint8_t rr[] = {
266+
0, // Name terminator
267+
0, 1, // TYPE (A)
268+
0, 1, // CLASS (IN)
269+
0, 0, 0, 30, // TTL (30 seconds)
270+
0, 4, // RDLENGTH
271+
ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff, // RDATA
272+
};
273+
_conn->append(reinterpret_cast<const char*>(rr), sizeof(rr));
274+
}
275+
_conn->setMulticastInterface(remote_ip_info.ip);
276+
_conn->send(&remote_ip_info.ip, _conn->getRemotePort());
277+
}
278+
279+
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LLMNR)
280+
LLMNRResponder LLMNR;
281+
#endif

libraries/ESP8266LLMNR/ESP8266LLMNR.h

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* ESP8266 LLMNR responder
3+
* Copyright (C) 2017 Stephen Warren <[email protected]>
4+
*
5+
* Based on:
6+
* ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
7+
* Version 1.1
8+
* Copyright (c) 2013 Tony DiCola ([email protected])
9+
* ESP8266 port (c) 2015 Ivan Grokhotkov ([email protected])
10+
* MDNS-SD Suport 2015 Hristo Gochkov
11+
* Extended MDNS-SD support 2016 Lars Englund ([email protected])
12+
*
13+
* License (MIT license):
14+
*
15+
* Permission is hereby granted, free of charge, to any person obtaining a copy
16+
* of this software and associated documentation files (the "Software"), to deal
17+
* in the Software without restriction, including without limitation the rights
18+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19+
* copies of the Software, and to permit persons to whom the Software is
20+
* furnished to do so, subject to the following conditions:
21+
*
22+
* The above copyright notice and this permission notice shall be included in
23+
* all copies or substantial portions of the Software.
24+
*
25+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31+
* THE SOFTWARE.
32+
*/
33+
34+
#ifndef ESP8266LLMNR_H
35+
#define ESP8266LLMNR_H
36+
37+
#include <ESP8266WiFi.h>
38+
39+
class UdpContext;
40+
41+
class LLMNRResponder {
42+
public:
43+
LLMNRResponder();
44+
~LLMNRResponder();
45+
46+
/* Initialize and start responding to LLMNR requests on all interfaces */
47+
bool begin(const char* hostname);
48+
49+
/* Application should call this whenever AP is configured/disabled */
50+
void notify_ap_change();
51+
52+
private:
53+
String _hostname;
54+
UdpContext *_conn;
55+
WiFiEventHandler _sta_got_ip_handler;
56+
WiFiEventHandler _sta_disconnected_handler;
57+
58+
bool _restart();
59+
void _process_packet();
60+
};
61+
62+
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_LLMNR)
63+
extern LLMNRResponder LLMNR;
64+
#endif
65+
66+
#endif

0 commit comments

Comments
 (0)