publish libraries to avoid users to have to download them one by one

This commit is contained in:
1technophile committed 2018-03-04 13:47:37 +01:00
1 parent 93c075e880
commit f0546fab8c
396 files changed
+536928

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) [2015] [Duncan Law]
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.
+91
View File
@@ -0,0 +1,91 @@
# esp8266_mdns
mDNS queries and responses on esp8266.
Or to describe it another way: An mDNS Client or Bonjour Client library for the esp8266.
This library aims to do the following:
1. Give access to incoming mDNS packets and decode Question and Answer Records for commonly used record types.
2. Allow Question and Answer Records for commonly used record types to be sent.
Future goals:
1. Dynamic buffer paging. Currently one page is read from the network. If the mDNS packet is larger than that page size, any responses in the remainder are lost. (See MAX_PACKET_SIZE in mdns.h.)
2. Automatic replies to incoming Questions.
3. Automatic retries when sending packets according to rfc6762.
Requirements
------------
- An Espressif [ESP8266](http://www.esp8266.com/) WiFi enabled SOC.
- The [ESP8266 Arduino](https://github.com/esp8266/Arduino) environment.
- ESP8266WiFi library.
- MDNS support in your operating system/client machines:
- For Mac OSX support is built in through Bonjour already.
- For Linux, install [Avahi](http://avahi.org/).
- For Windows, install [Bonjour](http://www.apple.com/support/bonjour/).
Usage
-----
Find information on how to add a library to your Arduino IDE [here](https://www.arduino.cc/en/Guide/Libraries).
The file [mdns.h](https://github.com/mrdunk/esp8266_mdns/blob/master/mdns.h) is well commented. Try looking there for information on specific methods.
To add a simple mDNS listener to an Aruino sketch which will display all mDNS packets over the serial console try the following:
```
// This sketch will display mDNS (multicast DNS) data seen on the network.
#include <ESP8266WiFi.h>
#include "mdns.h"
// When an mDNS packet gets parsed this optional callback gets called.
void packetCallback(const mdns::MDns* packet){
packet->Display();
packet->DisplayRawPacket();
}
// When an mDNS packet gets parsed this optional callback gets called once per Query.
// See mdns.h for definition of mdns::Query.
void queryCallback(const mdns::Query* query){
query->Display();
}
// When an mDNS packet gets parsed this optional callback gets called once per Query.
// See mdns.h for definition of mdns::Answer.
void answerCallback(const mdns::Answer* answer){
answer->Display();
}
// Initialise MDns.
// If you don't want the optional callbacks, just provide a NULL pointer as the callback.
mdns::MDns my_mdns(packetCallback, queryCallback, answerCallback);
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
// setting up Station AP
WiFi.begin("your_wifi_ssid", "your_wifi_password");
// Wait for connect to AP
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
tries++;
if (tries > 30) {
break;
}
}
Serial.println();
}
void loop() {
my_mdns.loop();
}
```
A more complete example which sends an mDNS Question and parses Answers is available in esp8266_mdns/examples/mdns_test/ .
Troubleshooting
---------------
Run [Wireshark](https://www.wireshark.org/) on a machine connected to your wireless network to confirm what is actually in flight.
The following filter will return only mDNS packets: ```udp.port == 5353``` .
Any mDNS packets seen by Wireshark should also appear on the ESP8266 Serial console.
@@ -0,0 +1,5 @@
# esp8266_mdns test program.
This is a sample program using the esp8266_mdns library.
It queries the network for hosts providing the service defined in QUESTION_SERVICE.
Any answers are parsed and stored, hopefully providing you with the port and ipv4 network address of all hosts on the network providing the service.
See https://github.com/mrdunk/esp8266_mdns for more information on the library and mDNS in general.
@@ -0,0 +1,226 @@
/*
* This sketch will query the network for hosts providing the service defined by
* QUESTION_SERVICE and will parse any replies with the aim of saving port and
* network address in the hosts array.
*/
#include <mdns.h>
#include "secrets.h" // Contains the following:
// const char* ssid = "Get off my wlan"; // your network SSID (name)
// const char* pass = "secretwlanpass"; // your network password
#define QUESTION_SERVICE "_mqtt._tcp.local"
// Make this value as large as available ram allows.
#define MAX_MDNS_PACKET_SIZE 512
#define MAX_HOSTS 4
#define HOSTS_SERVICE_NAME 0
#define HOSTS_PORT 1
#define HOSTS_HOST_NAME 2
#define HOSTS_ADDRESS 3
String hosts[MAX_HOSTS][4]; // Array containing information about hosts received over mDNS.
// When an mDNS packet gets parsed this callback gets called once per Query.
// See mdns.h for definition of mdns::Query.
void answerCallback(const mdns::Answer* answer) {
// A typical PTR record matches service to a human readable name.
// eg:
// service: _mqtt._tcp.local
// name: Mosquitto MQTT server on twinkle.local
if (answer->rrtype == MDNS_TYPE_PTR and strstr(answer->name_buffer, QUESTION_SERVICE) != 0) {
unsigned int i = 0;
for (; i < MAX_HOSTS; ++i) {
if (hosts[i][HOSTS_SERVICE_NAME] == answer->rdata_buffer) {
// Already in hosts[][].
break;
}
if (hosts[i][HOSTS_SERVICE_NAME] == "") {
// This hosts[][] entry is still empty.
hosts[i][HOSTS_SERVICE_NAME] = answer->rdata_buffer;
break;
}
}
if (i == MAX_HOSTS) {
Serial.print(" ** ERROR ** No space in buffer for ");
Serial.print('"');
Serial.print(answer->name_buffer);
Serial.print('"');
Serial.print(" : ");
Serial.print('"');
Serial.println(answer->rdata_buffer);
Serial.print('"');
}
}
// A typical SRV record matches a human readable name to port and FQDN info.
// eg:
// name: Mosquitto MQTT server on twinkle.local
// data: p=0;w=0;port=1883;host=twinkle.local
if (answer->rrtype == MDNS_TYPE_SRV) {
unsigned int i = 0;
for (; i < MAX_HOSTS; ++i) {
if (hosts[i][HOSTS_SERVICE_NAME] == answer->name_buffer) {
// This hosts entry matches the name of the host we are looking for
// so parse data for port and hostname.
char* port_start = strstr(answer->rdata_buffer, "port=");
if (port_start) {
port_start += 5;
char* port_end = strchr(port_start, ';');
char port[1 + port_end - port_start];
strncpy(port, port_start, port_end - port_start);
port[port_end - port_start] = '\0';
if (port_end) {
char* host_start = strstr(port_end, "host=");
if (host_start) {
host_start += 5;
hosts[i][HOSTS_PORT] = port;
hosts[i][HOSTS_HOST_NAME] = host_start;
}
}
}
break;
}
}
if (i == MAX_HOSTS) {
Serial.print(" Did not find ");
Serial.print('"');
Serial.print(answer->name_buffer);
Serial.print('"');
Serial.println(" in hosts buffer.");
}
}
// A typical A record matches an FQDN to network ipv4 address.
// eg:
// name: twinkle.local
// address: 192.168.192.9
if (answer->rrtype == MDNS_TYPE_A) {
int i = 0;
for (; i < MAX_HOSTS; ++i) {
if (hosts[i][HOSTS_HOST_NAME] == answer->name_buffer) {
hosts[i][HOSTS_ADDRESS] = answer->rdata_buffer;
break;
}
}
if (i == MAX_HOSTS) {
Serial.print(" Did not find ");
Serial.print('"');
Serial.print(answer->name_buffer);
Serial.print('"');
Serial.println(" in hosts buffer.");
}
}
Serial.println();
for (int i = 0; i < MAX_HOSTS; ++i) {
if (hosts[i][HOSTS_SERVICE_NAME] != "") {
Serial.print("> ");
Serial.print(hosts[i][HOSTS_SERVICE_NAME]);
Serial.print(" ");
Serial.print(hosts[i][HOSTS_PORT]);
Serial.print(" ");
Serial.print(hosts[i][HOSTS_HOST_NAME]);
Serial.print(" ");
Serial.println(hosts[i][HOSTS_ADDRESS]);
}
}
}
// buffer can be used bu other processes that need a large chunk of memory.
byte buffer[MAX_MDNS_PACKET_SIZE];
mdns::MDns my_mdns(NULL, NULL, answerCallback, buffer, MAX_MDNS_PACKET_SIZE);
void setup()
{
// Open serial communications and wait for port to open:
Serial.begin(115200);
// setting up Station AP
WiFi.begin(ssid, pass);
// Wait for connect to AP
Serial.print("[Connecting]");
Serial.print(ssid);
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
tries++;
if (tries > 30) {
break;
}
}
Serial.println();
printWifiStatus();
Serial.println("Connected to wifi");
// Query for all host information for a paticular service. ("_mqtt" in this case.)
my_mdns.Clear();
struct mdns::Query query_mqtt;
strncpy(query_mqtt.qname_buffer, QUESTION_SERVICE, MAX_MDNS_NAME_LEN);
query_mqtt.qtype = MDNS_TYPE_PTR;
query_mqtt.qclass = 1; // "INternet"
query_mqtt.unicast_response = 0;
my_mdns.AddQuery(query_mqtt);
my_mdns.Send();
/*
// Query for all service types on network.
my_mdns.Clear();
struct mdns::Query query_services;
strncpy(query_services.qname_buffer, "_services._dns-sd._udp.local", MAX_MDNS_NAME_LEN);
query_services.qtype = MDNS_TYPE_PTR;
query_services.qclass = 1; // "INternet"
query_services.unicast_response = 0;
my_mdns.AddQuery(query_services);
my_mdns.Send();*/
}
unsigned int last_packet_count = 0;
void loop()
{
my_mdns.loop();
#ifdef DEBUG_STATISTICS
// Give feedback on the percentage of incoming mDNS packets that fitted in buffer.
// Useful for tuning the buffer size to make best use of available memory.
if(last_packet_count != my_mdns.packet_count && my_mdns.packet_count != 0){
last_packet_count = my_mdns.packet_count;
Serial.print("mDNS decode success rate: ");
Serial.print(100 - (100 * my_mdns.buffer_size_fail / my_mdns.packet_count));
Serial.print("%\nLargest packet size: ");
Serial.println(my_mdns.largest_packet_seen);
}
#endif
// mDNS not using buffer outside my_mdns.loop() so it can be used for other tasks.
strncpy((char*)buffer,
"<html><head>Some webpage that needs a large buffer</head>"
"<body>big content...</body></html>",
MAX_MDNS_PACKET_SIZE);
// Display buffer here....
}
void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
@@ -0,0 +1,78 @@
/*
* This sketch will display mDNS (multicast DNS) data seen on the network.
*/
#include "mdns.h"
// When an mDNS packet gets parsed this callback gets called.
void packetCallback(const mdns::MDns* packet){
packet->Display();
packet->DisplayRawPacket();
}
// When an mDNS packet gets parsed this callback gets called once per Query.
// See mdns.h for definition of mdns::Query.
void queryCallback(const mdns::Query* query){
query->Display();
}
// When an mDNS packet gets parsed this callback gets called once per Query.
// See mdns.h for definition of mdns::Answer.
void answerCallback(const mdns::Answer* answer){
answer->Display();
}
// Initialise MDns. If you don't want the optioanl callbacks, just provide a NULL pointer.
mdns::MDns my_mdns(packetCallback, queryCallback, answerCallback);
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
// setting up Station AP
WiFi.begin("your_WiFI_SSID", "your_WiFi_password");
// Wait for connect to AP
int tries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
tries++;
if (tries > 30) {
break;
}
}
printWifiStatus();
Serial.println();
}
unsigned int last_packet_count = 0;
void loop() {
my_mdns.loop();
#ifdef DEBUG_STATISTICS
// Give feedback on the percentage of incoming mDNS packets that fitted in buffer.
// Useful for tuning the buffer size to make best use of available memory.
if(last_packet_count != my_mdns.packet_count && my_mdns.packet_count != 0){
last_packet_count = my_mdns.packet_count;
Serial.print("mDNS decode success rate: ");
Serial.print(100 - (100 * my_mdns.buffer_size_fail / my_mdns.packet_count));
Serial.print("%\nLargest packet size: ");
Serial.println(my_mdns.largest_packet_seen);
}
#endif
}
void printWifiStatus() {
// print the SSID of the network you're attached to:
Serial.println();
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "esp8266_mdns",
"keywords": "esp8266, mdns, Zeroconf, Bonjour, Avahi",
"description": "mDNS queries and responses on esp8266. Or to describe it another way: An mDNS Client or Bonjour Client library for the esp8266.",
"repository":
{
"type": "git",
"url": "https://github.com/mrdunk/esp8266_mdns.git"
},
"frameworks": "arduino",
"platforms": "espressif"
}
+9
View File
@@ -0,0 +1,9 @@
name=esp8266_mdns
version=1.1.7
author=mrdunk
maintainer=mrdunk
sentence=mDNS queries and responses on esp8266.
paragraph=Or to describe it another way: An mDNS Client or Bonjour Client library for the esp8266.
category=Communication
url=https://github.com/mrdunk/esp8266_mdns
architectures=esp8266
+627
View File
@@ -0,0 +1,627 @@
#include <Arduino.h>
#include "mdns.h"
namespace mdns {
// A UDP instance to let us send and receive packets over UDP.
WiFiUDP Udp;
// Helper function to display formatted data.
void PrintHex(const unsigned char data) {
char tmp[2];
sprintf(tmp, "%02X", data);
Serial.print(tmp);
Serial.print(" ");
}
void MDns::startUdpMulticast(){
Serial.println("Initializing Multicast.");
Udp.beginMulticast(WiFi.localIP(), IPAddress(224, 0, 0, 251), MDNS_TARGET_PORT);
}
bool MDns::loop() {
data_size = Udp.parsePacket();
if ( data_size > 12) {
if(data_size > largest_packet_seen){
largest_packet_seen = data_size;
}
#ifdef DEBUG_STATISTICS
if(data_size > max_packet_size) {
buffer_size_fail++;
data_size = max_packet_size;
}
packet_count++;
#endif
// We've received a packet which is long enough to contain useful data so
// read the data from it.
Udp.read(data_buffer, data_size); // read the packet into the buffer
// data_buffer[0] and data_buffer[1] contain the Query ID field which is unused in mDNS.
// data_buffer[2] and data_buffer[3] are DNS flags which are mostly unused in mDNS.
type = !(data_buffer[2] & 0b10000000); // If it's not a query, it's an answer.
truncated = data_buffer[2] & 0b00000010; // If it's truncated we can expect more data soon so we should wait for additional records before deciding whether to respond.
if (data_buffer[3] & 0b00001111) {
// Non zero Response code implies error.
return false;
}
// Number of incoming queries.
query_count = (data_buffer[4] << 8) + data_buffer[5];
// Number of incoming answers.
answer_count = (data_buffer[6] << 8) + data_buffer[7];
// Number of incoming Name Server resource records.
ns_count = (data_buffer[8] << 8) + data_buffer[9];
// Number of incoming Additional resource records.
ar_count = (data_buffer[10] << 8) + data_buffer[11];
if(p_packet_function_) {
// Since a callback function has been registered, execute it.
p_packet_function_(this);
}
#ifdef DEBUG_OUTPUT
Display();
#endif // DEBUG_OUTPUT
// Start of Data section.
buffer_pointer = 12;
for (unsigned int i_question = 0; i_question < query_count; i_question++) {
Query query;
Parse_Query(query);
if (query.valid) {
if (p_query_function_) {
// Since a callback function has been registered, execute it.
p_query_function_(&query);
}
}
if(buffer_pointer >= data_size){
return false;
}
#ifdef DEBUG_OUTPUT
query.Display();
#endif // DEBUG_OUTPUT
}
for (unsigned int i_answer = 0; i_answer < (answer_count + ns_count + ar_count); i_answer++) {
Answer answer;
Parse_Answer(answer);
if (answer.valid) {
if (p_answer_function_) {
// Since a callback function has been registered, execute it.
p_answer_function_(&answer);
}
}
if(buffer_pointer >= data_size){
return false;
}
#ifdef DEBUG_OUTPUT
answer.Display();
#endif // DEBUG_OUTPUT
}
#ifdef DEBUG_RAW
DisplayRawPacket();
#endif // DEBUG_RAW
return true;
}
return true; // Not enough data for a full packet to be waiting.
}
void MDns::Clear() {
data_buffer[0] = 0; // Query ID field which is unused in mDNS.
data_buffer[1] = 0; // Query ID field which is unused in mDNS.
data_buffer[2] = 0; // 0b00000000 for Query, 0b10000000 for Answer.
data_buffer[3] = 0; // DNS flags which are mostly unused in mDNS.
data_buffer[4] = 0; // Number of queries.
data_buffer[5] = 0; // Number of queries.
data_buffer[6] = 0; // Number of answers.
data_buffer[7] = 0; // Number of answers.
data_buffer[8] = 0; // Number of Server esource records.
data_buffer[9] = 0; // Number of Server esource records.
data_buffer[10] = 0; // Number of Additional resource records.
data_buffer[11] = 0; // Number of Additional resource records.
data_size = 12;
buffer_pointer = 12; // First byte of first Query/Record.
type = 0;
query_count = 0;
answer_count = 0;
ns_count = 0;
ar_count = 0;
}
unsigned int MDns::PopulateName(const char* name_buffer) {
// TODO: This section does not match the full mDNS spec
// as it does not re-use strings from previous queries.
unsigned int buffer_pointer_start = buffer_pointer;
int word_start = 0, word_end = 0;
while (true) {
if (name_buffer[word_end] == '.' or name_buffer[word_end] == '\0') {
const int word_length = word_end - word_start;
if(buffer_pointer >= data_size){
buffer_pointer = buffer_pointer_start;
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. MDns::PopulateName overran buffer.");
#endif
return 0;
}
data_buffer[buffer_pointer++] = (unsigned byte)word_length;
for (int i = word_start; i < word_end; ++i) {
if(buffer_pointer >= data_size){
buffer_pointer = buffer_pointer_start;
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. MDns::PopulateName overran buffer.");
#endif
return 0;
}
data_buffer[buffer_pointer++] = name_buffer[i];
}
if(name_buffer[word_end] == '\0'){
break;
}
word_end++; // Skip the '.' character.
word_start = word_end;
}
word_end++;
};
if(buffer_pointer >= data_size){
buffer_pointer = buffer_pointer_start;
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. MDns::PopulateName overran buffer while finishing.");
#endif
return 0;
}
data_buffer[buffer_pointer++] = '\0'; // End of qname.
return buffer_pointer - buffer_pointer_start;
}
bool MDns::AddQuery(const Query& query) {
if (answer_count || ns_count || ar_count) {
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. Resource records included before Queries.");
#endif
return false;
}
// Buffer increased by length of qname_buffer + a preceding length + zero termination
// + 4 bits of mDNS flags.
data_size += strlen(query.qname_buffer) +6;
// Create DNS name buffer from qname.
if(PopulateName(query.qname_buffer) == 0 || buffer_pointer +4 > data_size){
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. MDns::AddQuery overran expected buffer space.");
#endif
return false;
}
// The rest of the flags.
data_buffer[buffer_pointer++] = (query.qtype & 0xFF00) >> 8;
data_buffer[buffer_pointer++] = query.qtype & 0xFF;
unsigned int qclass = 0;
if (query.unicast_response) {
qclass = 0b1000000000000000;
}
qclass += query.qclass;
data_buffer[buffer_pointer++] = (qclass & 0xFF00) >> 8;
data_buffer[buffer_pointer++] = qclass & 0xFF;
data_size = buffer_pointer;
// Since the data fitted in the buffer, it's ok to update the header.
data_buffer[2] = 0; // 0b00000000 for Query, 0b10000000 for Answer.
type = 1;
++query_count;
data_buffer[4] = (query_count & 0xFF00) >> 8;
data_buffer[5] = query_count & 0xFF;
return true;
}
bool MDns::AddAnswer(const Answer& answer) {
if (ns_count || ar_count) {
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. NS or AR records added before Answer records");
#endif
return false;
}
data_size += strlen(answer.name_buffer) +12;
// Create DNS name buffer from name.
if(PopulateName(answer.name_buffer) == 0 || buffer_pointer +10 > data_size){
#ifdef DEBUG_OUTPUT
Serial.println(" ERROR. MDns::AddAnswer over-ran expected buffer space.");
#endif
return false;
}
data_buffer[buffer_pointer++] = (answer.rrtype & 0xFF00) >> 8;
data_buffer[buffer_pointer++] = answer.rrtype & 0xFF;
unsigned int rrclass = 0;
if (answer.rrset) {
rrclass = 0b1000000000000000;
}
rrclass += answer.rrclass;
data_buffer[buffer_pointer++] = (rrclass & 0xFF00) >> 8;
data_buffer[buffer_pointer++] = rrclass & 0xFF;
data_buffer[buffer_pointer++] = (answer.rrttl & 0xFF000000) >> 24;
data_buffer[buffer_pointer++] = (answer.rrttl & 0xFF0000) >> 16;
data_buffer[buffer_pointer++] = (answer.rrttl & 0xFF00) >> 8;
data_buffer[buffer_pointer++] = (answer.rrttl & 0xFF);
const unsigned int rdata_len_p0 = buffer_pointer++;
const unsigned int rdata_len_p1 = buffer_pointer++;
unsigned int rdata_len = 0;
switch (answer.rrtype) {
case MDNS_TYPE_A: // Returns a 32-bit IPv4 address
if(buffer_pointer +4 >= data_size){ return false; }
rdata_len = 4;
data_buffer[buffer_pointer++] = answer.rdata_buffer[0];
data_buffer[buffer_pointer++] = answer.rdata_buffer[1];
data_buffer[buffer_pointer++] = answer.rdata_buffer[2];
data_buffer[buffer_pointer++] = answer.rdata_buffer[3];
break;
case MDNS_TYPE_PTR: // Pointer to a canonical name.
rdata_len = PopulateName(answer.rdata_buffer);
if(buffer_pointer >= data_size){ return false; }
break;
default:
#ifdef DEBUG_OUTPUT
// TODO: Other record types.
Serial.println(" **ERROR** Sending this record type not implemented yet.");
#endif
return false;
}
data_buffer[rdata_len_p0] = (rdata_len & 0xFF00) >> 8;
data_buffer[rdata_len_p1] = rdata_len & 0xFF;
data_size = buffer_pointer;
// Since the data fitted in the buffer, it's ok to update the header.
answer_count++;
data_buffer[6] = (answer_count & 0xFF00) >> 8;
data_buffer[7] = answer_count & 0xFF;
return true;
}
void MDns::Send() const {
Serial.println("Sending UDP multicast packet");
Udp.begin(MDNS_SOURCE_PORT);
Udp.beginPacketMulticast(IPAddress(224, 0, 0, 251), MDNS_TARGET_PORT, WiFi.localIP(), MDNS_TTL);
Udp.write(data_buffer, data_size);
Udp.endPacket();
}
void MDns::Display() const {
Serial.println();
Serial.print("Packet size: ");
Serial.print(data_size);
Serial.print(" ");
Serial.println(data_size, HEX);
Serial.print(" TYPE: ");
Serial.print(type);
Serial.print(" QUERY_COUNT: ");
Serial.print(query_count);
Serial.print(" ANSWER_COUNT: ");
Serial.print(answer_count);
Serial.print(" NS_COUNT: ");
Serial.print(ns_count);
Serial.print(" AR_COUNT: ");
Serial.println(ar_count);
}
void MDns::Parse_Query(Query& query) {
#ifdef DEBUG_OUTPUT
query.buffer_pointer = buffer_pointer;
#endif
buffer_pointer = nameFromDnsPointer(query.qname_buffer, 0, MAX_MDNS_NAME_LEN,
data_buffer, buffer_pointer);
byte qtype_0 = data_buffer[buffer_pointer++];
byte qtype_1 = data_buffer[buffer_pointer++];
byte qclass_0 = data_buffer[buffer_pointer++];
byte qclass_1 = data_buffer[buffer_pointer++];
query.qtype = (qtype_0 << 8) + qtype_1;
query.unicast_response = (0b10000000 & qclass_0);
query.qclass = ((qclass_0 & 0b01111111) << 8) + qclass_1;
query.valid = true;
if (query.qclass != 0xFF && query.qclass != 0x01) {
// QCLASS is not ANY (0xFF) or INternet (0x01).
#ifdef DEBUG_OUTPUT
Serial.print(" **ERROR QCLASS** ");
Serial.println(query.qclass, HEX);
#endif
query.valid = false;
}
if (buffer_pointer >= data_size) {
// We've over-run the returned data.
// Something has gone wrong receiving or parsing the data.
#ifdef DEBUG_OUTPUT
Serial.print(" **ERROR size** ");
Serial.print(buffer_pointer, HEX);
Serial.print(" ");
Serial.println(data_size, HEX);
#endif
query.valid = false;
}
}
void MDns::Parse_Answer(Answer& answer) {
#ifdef DEBUG_OUTPUT
answer.buffer_pointer = buffer_pointer;
#endif
buffer_pointer = nameFromDnsPointer(answer.name_buffer, 0, MAX_MDNS_NAME_LEN,
data_buffer, buffer_pointer);
answer.rrtype = (data_buffer[buffer_pointer++] << 8);
answer.rrtype += data_buffer[buffer_pointer++];
byte rrclass_0 = data_buffer[buffer_pointer++];
byte rrclass_1 = data_buffer[buffer_pointer++];
answer.rrset = (0b10000000 & rrclass_0);
answer.rrclass = ((rrclass_0 & 0b01111111) << 8) + rrclass_1;
answer.rrttl = (data_buffer[buffer_pointer++] << 24);
answer.rrttl += (data_buffer[buffer_pointer++] << 16);
answer.rrttl += (data_buffer[buffer_pointer++] << 8);
answer.rrttl += data_buffer[buffer_pointer++];
if (buffer_pointer > data_size) {
// We've over-run the returned data.
// Something has gone wrong receiving or parsing the data.
#ifdef DEBUG_OUTPUT
Serial.print(" **ERROR size** ");
Serial.print(buffer_pointer, HEX);
Serial.print(" ");
Serial.println(data_size, HEX);
#endif
answer.valid = false;
return;
}
PopulateAnswerResult(&answer);
answer.valid = true;
}
// Display packet contents in HEX.
void MDns::DisplayRawPacket() const {
// display the packet contents in HEX
Serial.println("Raw packet");
unsigned int i, j;
for (i = 0; i <= data_size; i += 16) {
Serial.print("0x");
PrintHex(i >> 8); PrintHex(i);
Serial.print(" ");
for (j = 0; j < 16; j++) {
if (i + j >= data_size) {
break;
}
if (data_buffer[i + j] > 31 and data_buffer[i + j] < 128) {
Serial.print((char)data_buffer[i + j]);
} else {
Serial.print(".");
}
}
Serial.print(" ");
for (j = 0; j < 16; j++) {
if (i + j >= data_size) {
break;
}
PrintHex(data_buffer[i + j]);
Serial.print(' ');
}
Serial.println();
}
}
void MDns::PopulateAnswerResult(Answer* answer) {
int rdlength = (data_buffer[buffer_pointer++] << 8);
rdlength += data_buffer[buffer_pointer++];
switch (answer->rrtype) {
case MDNS_TYPE_A: // Returns a 32-bit IPv4 address
if (MAX_MDNS_NAME_LEN >= 16) {
sprintf(answer->rdata_buffer, "%u.%u.%u.%u",
data_buffer[buffer_pointer], data_buffer[buffer_pointer +1],
data_buffer[buffer_pointer +2], data_buffer[buffer_pointer +3]);
} else {
sprintf(answer->rdata_buffer, "ipv4");
}
buffer_pointer += 4;
break;
case MDNS_TYPE_PTR: // Pointer to a canonical name.
buffer_pointer = nameFromDnsPointer(answer->rdata_buffer, 0, MAX_MDNS_NAME_LEN,
data_buffer, buffer_pointer);
break;
case MDNS_TYPE_HINFO: // HINFO. host information
buffer_pointer = parseText(answer->rdata_buffer, MAX_MDNS_NAME_LEN, rdlength,
data_buffer, buffer_pointer);
break;
case MDNS_TYPE_TXT: // Originally for arbitrary human-readable text in a DNS record.
// We only return the first MAX_MDNS_NAME_LEN bytes of thir record type.
buffer_pointer = parseText(answer->rdata_buffer, MAX_MDNS_NAME_LEN, rdlength,
data_buffer, buffer_pointer);
break;
case MDNS_TYPE_AAAA: // Returns a 128-bit IPv6 address.
{
int buffer_pos = 0;
for (int i = 0; i < rdlength; i++) {
if (buffer_pos < MAX_MDNS_NAME_LEN - 3) {
sprintf(answer->rdata_buffer + buffer_pos, "%02X:", data_buffer[buffer_pointer++]);
} else {
buffer_pointer++;
}
buffer_pos += 3;
}
answer->rdata_buffer[--buffer_pos] = '\0'; // Remove trailing ':'
}
break;
case MDNS_TYPE_SRV: // Server Selection.
{
unsigned int priority = (data_buffer[buffer_pointer++] << 8);
priority += data_buffer[buffer_pointer++];
unsigned int weight = (data_buffer[buffer_pointer++] << 8);
weight += data_buffer[buffer_pointer++];
unsigned int port = (data_buffer[buffer_pointer++] << 8);
port += data_buffer[buffer_pointer++];
sprintf(answer->rdata_buffer, "p=%u;w=%u;port=%u;host=", priority, weight, port);
buffer_pointer = nameFromDnsPointer(answer->rdata_buffer, strlen(answer->rdata_buffer),
MAX_MDNS_NAME_LEN - strlen(answer->rdata_buffer) -1, data_buffer, buffer_pointer);
}
break;
default:
{
int buffer_pos = 0;
for (int i = 0; i < rdlength; i++) {
if (buffer_pos < MAX_MDNS_NAME_LEN - 3) {
sprintf(answer->rdata_buffer + buffer_pos, "%02X ", data_buffer[buffer_pointer++]);
} else {
buffer_pointer++;
}
buffer_pos += 3;
}
}
break;
}
}
MDns::~MDns(){
Udp.stop();
};
bool writeToBuffer(const byte value, char* p_name_buffer, int* p_name_buffer_pos,
const int name_buffer_len) {
if (*p_name_buffer_pos < name_buffer_len - 1) {
*(p_name_buffer + *p_name_buffer_pos) = value;
(*p_name_buffer_pos)++;
*(p_name_buffer + *p_name_buffer_pos) = '\0';
return true;
}
(*p_name_buffer_pos)++;
return false;
}
int parseText(char* data_buffer, const int data_buffer_len, const int data_len,
const byte* p_packet_buffer, int packet_buffer_pos) {
int i, data_buffer_pos = 0;
for (i = 0; i < data_len; i++) {
writeToBuffer(p_packet_buffer[packet_buffer_pos++], data_buffer, &data_buffer_pos, data_buffer_len);
}
data_buffer[data_buffer_pos] = '\0';
return packet_buffer_pos;
}
int nameFromDnsPointer(char* p_name_buffer, int name_buffer_pos, const int name_buffer_len,
const byte* p_packet_buffer, int packet_buffer_pos) {
return nameFromDnsPointer(p_name_buffer, name_buffer_pos, name_buffer_len,
p_packet_buffer, packet_buffer_pos, false);
}
int nameFromDnsPointer(char* p_name_buffer, int name_buffer_pos, const int name_buffer_len,
const byte* p_packet_buffer, int packet_buffer_pos, const bool recurse){
if (recurse) {
// Since we are adding more to an already populated buffer,
// replace the trailing EOL with the FQDN seperator.
name_buffer_pos--;
writeToBuffer('.', p_name_buffer, &name_buffer_pos, name_buffer_len);
}
if (p_packet_buffer[packet_buffer_pos] < 0xC0) {
// Since the first 2 bits are not set,
// this is the start of a name section.
// http://www.tcpipguide.com/free/t_DNSNameNotationandMessageCompressionTechnique.htm
const int word_len = p_packet_buffer[packet_buffer_pos++];
for (int l = 0; l < word_len; l++) {
writeToBuffer(*(p_packet_buffer + packet_buffer_pos++), p_name_buffer,
&name_buffer_pos, name_buffer_len);
}
writeToBuffer('\0', p_name_buffer, &name_buffer_pos, name_buffer_len);
if (p_packet_buffer[packet_buffer_pos] > 0) {
// Next word.
packet_buffer_pos = nameFromDnsPointer(p_name_buffer, name_buffer_pos,
name_buffer_len, p_packet_buffer,
packet_buffer_pos, true);
} else {
// End of string.
packet_buffer_pos++;
}
} else {
// Message Compression used. Next 2 bytes are a pointer to the actual name section.
int pointer = (p_packet_buffer[packet_buffer_pos++] - 0xC0) << 8;
pointer += p_packet_buffer[packet_buffer_pos++];
nameFromDnsPointer(p_name_buffer, name_buffer_pos, name_buffer_len,
p_packet_buffer, pointer, false);
}
return packet_buffer_pos;
}
void Query::Display() const {
#ifdef DEBUG_OUTPUT
Serial.print("question 0x");
Serial.println(buffer_pointer, HEX);
#endif
if (!valid) {
Serial.println(" **ERROR**");
}
Serial.print(" QNAME: ");
Serial.println(qname_buffer);
Serial.print(" QTYPE: 0x");
Serial.print(qtype, HEX);
Serial.print(" QCLASS: 0x");
Serial.print(qclass, HEX);
Serial.print(" Unicast Response: ");
Serial.println(unicast_response);
}
void Answer::Display() const {
#ifdef DEBUG_OUTPUT
Serial.print("answer 0x");
Serial.println(buffer_pointer, HEX);
#endif
if (!valid) {
Serial.println(" **ERROR**");
}
Serial.print(" RRNAME: ");
Serial.println(name_buffer);
Serial.print(" RRTYPE: 0x");
Serial.print(rrtype, HEX);
Serial.print(" RRCLASS: 0x");
Serial.print(rrclass, HEX);
Serial.print(" RRTTL: ");
Serial.print(rrttl);
Serial.print(" RRSET: ");
Serial.println(rrset);
Serial.print(" RRDATA: ");
Serial.println(rdata_buffer);
}
} // namespace mdns
+244
View File
@@ -0,0 +1,244 @@
#ifndef MDNS_H
#define MDNS_H
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define DEBUG_STATISTICS // Record how many incoming packets fitted into data_buffer.
//#define DEBUG_OUTPUT // Send packet summaries to Serial.
//#define DEBUG_RAW // Send HEX ans ASCII encoded raw packet to Serial.
#define MDNS_TYPE_A 0x0001
#define MDNS_TYPE_PTR 0x000C
#define MDNS_TYPE_HINFO 0x000D
#define MDNS_TYPE_TXT 0x0010
#define MDNS_TYPE_AAAA 0x001C
#define MDNS_TYPE_SRV 0x0021
#define MDNS_TARGET_PORT 5353
#define MDNS_SOURCE_PORT 5353
#define MDNS_TTL 255
// Make this as big as memory limitations allow.
// This default value can be overridden using the max_packet_size_ parameter of
// MDns().
#define MAX_PACKET_SIZE 1024
// The mDNS spec says this should never be more than 256 (including trailing '\0').
#define MAX_MDNS_NAME_LEN 256
namespace mdns{
// A single mDNS Query.
typedef struct Query{
#ifdef DEBUG_OUTPUT
unsigned int buffer_pointer; // Position of Answer in packet. (Used for debugging only.)
#endif
char qname_buffer[MAX_MDNS_NAME_LEN]; // Question Name: Contains the object, domain or zone name.
unsigned int qtype; // Question Type: Type of question being asked by client.
unsigned int qclass; // Question Class: Normally the value 1 for Internet (“IN”)
bool unicast_response; //
bool valid; // False if problems were encountered decoding packet.
void Display() const; // Display a summary of this Answer on Serial port.
} Query;
// A single mDNS Answer.
typedef struct Answer{
#ifdef DEBUG_OUTPUT
unsigned int buffer_pointer; // Position of Answer in packet. (Used for debugging only.)
#endif
char name_buffer[MAX_MDNS_NAME_LEN]; // object, domain or zone name.
char rdata_buffer[MAX_MDNS_NAME_LEN]; // The data portion of the resource record.
unsigned int rrtype; // ResourceRecord Type.
unsigned int rrclass; // ResourceRecord Class: Normally the value 1 for Internet (“IN”)
unsigned long int rrttl; // ResourceRecord Time To Live: Number of seconds ths should be remembered.
bool rrset; // Flush cache of records matching this name.
bool valid; // False if problems were encountered decoding packet.
void Display() const ; // Display a summary of this Answer on Serial port.
} Answer;
class MDns {
private:
public:
// Simple constructor does not fire any callbacks on incoming data.
// Default incoming data_buffer size is used.
MDns() : MDns(NULL, NULL, NULL, MAX_PACKET_SIZE) {}
// Simple constructor does not fire any callbacks on incoming data.
// Args:
// max_packet_size_ : Set the data_buffer size allocated to store incoming packets.
MDns(int max_packet_size_) : MDns(NULL, NULL, NULL, max_packet_size_) {}
// Constructor takes callbacks which fire when mDNS data arrives.
// Args:
// p_packet_function : Callback fires for every mDNS packet that arrives.
// p_query_function : Callback fires for every mDNS Query that arrives as part of a packet.
// p_answer_function : Callback fires for every mDNS Answer that arrives as part of a packet.
MDns(std::function<void(const MDns*)> p_packet_function,
std::function<void(const Query*)> p_query_function,
std::function<void(const Answer*)> p_answer_function) :
MDns(p_packet_function, p_query_function, p_answer_function, MAX_PACKET_SIZE) { }
// Constructor takes callbacks which fire when mDNS data arrives.
// Args:
// p_packet_function : Callback fires for every mDNS packet that arrives.
// p_query_function : Callback fires for every mDNS Query that arrives as part of a packet.
// p_answer_function : Callback fires for every mDNS Answer that arrives as part of a packet.
// max_packet_size_ : Set the data_buffer size allocated to store incoming packets.
MDns(std::function<void(const MDns*)> p_packet_function,
std::function<void(const Query*)> p_query_function,
std::function<void(const Answer*)> p_answer_function,
int max_packet_size_) :
#ifdef DEBUG_STATISTICS
buffer_size_fail(0),
largest_packet_seen(0),
packet_count(0),
#endif
p_packet_function_(p_packet_function),
p_query_function_(p_query_function),
p_answer_function_(p_answer_function),
buffer_pointer(0),
data_buffer(new byte[max_packet_size_]),
max_packet_size(max_packet_size_)
{
this->startUdpMulticast();
};
// Constructor can be passed the buffer to hold the mDNS data.
// This way the potentially large buffer can be shared with other processes.
// Args:
// p_packet_function : Callback fires for every mDNS packet that arrives.
// p_query_function : Callback fires for every mDNS Query that arrives as part of a packet.
// p_answer_function : Callback fires for every mDNS Answer that arrives as part of a packet.
// max_packet_size_ : Set the data_buffer size allocated to store incoming packets.
MDns(std::function<void(const MDns*)> p_packet_function,
std::function<void(const Query*)> p_query_function,
std::function<void(const Answer*)> p_answer_function,
byte* data_buffer_,
int max_packet_size_) :
#ifdef DEBUG_STATISTICS
buffer_size_fail(0),
largest_packet_seen(0),
packet_count(0),
#endif
p_packet_function_(p_packet_function),
p_query_function_(p_query_function),
p_answer_function_(p_answer_function),
buffer_pointer(0),
data_buffer(data_buffer_),
max_packet_size(max_packet_size_)
{
this->startUdpMulticast();
};
~MDns();
// Call this regularly to check for an incoming packet.
bool loop();
// Deprecated. Use loop() instead.
bool Check(){
return loop();
}
// Send this MDns packet.
void Send() const;
// Resets everything to represent an empty packet.
// Do this before building a packet for sending.
void Clear();
// Add a query to packet prior to sending.
// May only be done before any Answers have been added.
bool AddQuery(const Query& query);
// Add an answer to packet prior to sending.
bool AddAnswer(const Answer& answer);
// Display a summary of the packet on Serial port.
void Display() const;
// Display the raw packet in HEX and ASCII.
void DisplayRawPacket() const;
#ifdef DEBUG_STATISTICS
// Counter gets increased every time an incoming mDNS packet arrives that does
// not fit in the data_buffer.
unsigned int buffer_size_fail;
// Track the largest mDNS packet that has arrived.
// Useful for knowing what size to make data_buffer.
unsigned int largest_packet_seen;
// How many mDNS packets have arrived so far.
unsigned int packet_count;
#endif
private:
// Initializes udp multicast
void startUdpMulticast();
void Parse_Query(Query& query);
void Parse_Answer(Answer& answer);
unsigned int PopulateName(const char* name_buffer);
void PopulateAnswerResult(Answer* answer);
// Pointer to function that gets called for every incoming mDNS packet.
std::function<void(const MDns*)> p_packet_function_;
// Pointer to function that gets called for every incoming query.
std::function<void(const Query*)> p_query_function_;
// Pointer to function that gets called for every incoming answer.
std::function<void(const Answer*)> p_answer_function_;
// Position in data_buffer while processing packet.
unsigned int buffer_pointer;
// Buffer containing mDNS packet.
byte* data_buffer;
// Buffer size for incoming MDns packet.
unsigned int max_packet_size;
// Size of mDNS packet.
unsigned int data_size;
// Query or Answer
bool type;
// Whether more follows in another packet.
bool truncated;
// Number of Qeries in the packet.
unsigned int query_count;
// Number of Answers in the packet.
unsigned int answer_count;
unsigned int ns_count;
unsigned int ar_count;
};
// Display a byte on serial console in hexadecimal notation,
// padding with leading zero if necisary to provide evenly tabulated display data.
void PrintHex(unsigned char data);
// Extract Name from DNS data. Will follow pointers used by Message Compression.
// TODO Check for exceeding packet size.
int nameFromDnsPointer(char* p_name_buffer, int name_buffer_pos, const int name_buffer_len,
const byte* p_packet_buffer, int packet_buffer_pos);
int nameFromDnsPointer(char* p_name_buffer, int name_buffer_pos, const int name_buffer_len,
const byte* p_packet_buffer, int packet_buffer_pos, const bool recurse);
bool writeToBuffer(const byte value, char* p_name_buffer, int* p_name_buffer_pos, const int name_buffer_len);
int parseText(char* data_buffer, const int data_buffer_len, int const data_len,
const byte* p_packet_buffer, int packet_buffer_pos);
} // namespace mdns
#endif // MDNS_H