diff --git a/examples/MDNS_WiFiWebServer/MDNS_WiFiWebServer.ino b/examples/MDNS_WiFiWebServer/MDNS_WiFiWebServer.ino new file mode 100644 index 00000000..5bf9912d --- /dev/null +++ b/examples/MDNS_WiFiWebServer/MDNS_WiFiWebServer.ino @@ -0,0 +1,168 @@ +/* + MDNS WiFi Web Server + + A simple web server that shows the value of the analog input pins, + and exposes itself on the MDNS name 'wifi101.local'. + + On Linux (like Ubuntu 15.04) or OSX you can access the web page + on the device in a browser at 'http://wifi101.local/'. + + On Windows you'll first need to install the Bonjour Printer Services + from: + https://support.apple.com/kb/dl999?locale=en_US + Then you can access the device in a browser at 'http://wifi101.local/'. + + This example is written for a network using WPA encryption. For + WEP or WPA, change the Wifi.begin() call accordingly. + + Circuit: + * WiFi shield attached + * Analog inputs attached to pins A0 through A5 (optional) + + created 13 July 2010 + by dlf (Metodo2 srl) + modified 31 May 2012 + by Tom Igoe + modified 27 January 2016 + by Tony DiCola + +*/ + +#include +#include +#include + +char ssid[] = "yourNetwork"; // your network SSID (name) +char pass[] = "secretPassword"; // your network password +int keyIndex = 0; // your network key Index number (needed only for WEP) + +char mdnsName[] = "wifi101"; // the MDNS name that the board will respond to +// Note that the actual MDNS name will have '.local' after +// the name above, so "wifi101" will be accessible on +// the MDNS name "wifi101.local". + +int status = WL_IDLE_STATUS; + +// Create a MDNS responder to listen and respond to MDNS name requests. +MDNSResponder mdns; + +WiFiServer server(80); + +void setup() { + //Initialize serial and wait for port to open: + Serial.begin(9600); + while (!Serial) { + ; // wait for serial port to connect. Needed for native USB port only + } + + // check for the presence of the shield: + if (WiFi.status() == WL_NO_SHIELD) { + Serial.println("WiFi shield not present"); + // don't continue: + while (true); + } + + // attempt to connect to Wifi network: + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + // Connect to WPA/WPA2 network. Change this line if using open or WEP network: + status = WiFi.begin(ssid, pass); + + // wait 10 seconds for connection: + delay(10000); + } + // you're connected now, so print out the status: + printWifiStatus(); + + server.begin(); + + // Setup the MDNS responder to listen to the configured name. + // NOTE: You _must_ call this _after_ connecting to the WiFi network and + // being assigned an IP address. + if (!mdns.begin(mdnsName)) { + Serial.println("Failed to start MDNS responder!"); + while(1); + } + + Serial.print("Server listening at http://"); + Serial.print(mdnsName); + Serial.println(".local/"); +} + + +void loop() { + // Call the update() function on the MDNS responder every loop iteration to + // make sure it can detect and respond to name requests. + mdns.poll(); + + // listen for incoming clients + WiFiClient client = server.available(); + if (client) { + Serial.println("new client"); + // an http request ends with a blank line + boolean currentLineIsBlank = true; + while (client.connected()) { + if (client.available()) { + char c = client.read(); + Serial.write(c); + // if you've gotten to the end of the line (received a newline + // character) and the line is blank, the http request has ended, + // so you can send a reply + if (c == '\n' && currentLineIsBlank) { + // send a standard http response header + client.println("HTTP/1.1 200 OK"); + client.println("Content-Type: text/html"); + client.println("Connection: close"); // the connection will be closed after completion of the response + client.println("Refresh: 5"); // refresh the page automatically every 5 sec + client.println(); + client.println(""); + client.println(""); + // output the value of each analog input pin + for (int analogChannel = 0; analogChannel < 6; analogChannel++) { + int sensorReading = analogRead(analogChannel); + client.print("analog input "); + client.print(analogChannel); + client.print(" is "); + client.print(sensorReading); + client.println("
"); + } + client.println(""); + break; + } + if (c == '\n') { + // you're starting a new line + currentLineIsBlank = true; + } + else if (c != '\r') { + // you've gotten a character on the current line + currentLineIsBlank = false; + } + } + } + // give the web browser time to receive the data + delay(1); + + // close the connection: + client.stop(); + Serial.println("client disconnected"); + } +} + + +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); + + // print the received signal strength: + long rssi = WiFi.RSSI(); + Serial.print("signal strength (RSSI):"); + Serial.print(rssi); + Serial.println(" dBm"); +} diff --git a/keywords.txt b/keywords.txt index 6ee1d3e6..c281b294 100755 --- a/keywords.txt +++ b/keywords.txt @@ -25,6 +25,7 @@ flush KEYWORD2 stop KEYWORD2 connected KEYWORD2 begin KEYWORD2 +beginMulti KEYWORD2 disconnect KEYWORD2 macAddress KEYWORD2 localIP KEYWORD2 @@ -36,9 +37,11 @@ RSSI KEYWORD2 encryptionType KEYWORD2 getResult KEYWORD2 getSocket KEYWORD2 +poll KEYWORD2 WiFiClient KEYWORD2 WiFiServer KEYWORD2 WiFiSSLClient KEYWORD2 +WifiMdns KEYWORD2 ####################################### # Constants (LITERAL1) diff --git a/src/WiFiMdns.cpp b/src/WiFiMdns.cpp new file mode 100644 index 00000000..ceca86d8 --- /dev/null +++ b/src/WiFiMdns.cpp @@ -0,0 +1,209 @@ +// Port of CC3000 MDNS Responder to WINC1500. +// Author: Tony DiCola +// +// This MDNSResponder class implements just enough MDNS functionality to respond +// to name requests, for example 'foo.local'. This does not implement any other +// MDNS or Bonjour functionality like services, etc. +// +// Copyright (c) 2016 Adafruit Industries. All right reserved. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include + +#include "Arduino.h" +#include "WiFiMdns.h" + +// Important RFC's for reference: +// - DNS request and response: http://www.ietf.org/rfc/rfc1035.txt +// - Multicast DNS: http://www.ietf.org/rfc/rfc6762.txt + +#define HEADER_SIZE 12 +#define TTL_OFFSET 4 +#define IP_OFFSET 10 + +const uint8_t expectedRequestHeader[HEADER_SIZE] PROGMEM = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x01, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00 +}; + +const uint8_t responseHeader[] PROGMEM = { + 0x00, 0x00, // ID = 0 + 0x84, 0x00, // Flags = response + authoritative answer + 0x00, 0x00, // Question count = 0 + 0x00, 0x01, // Answer count = 1 + 0x00, 0x00, // Name server records = 0 + 0x00, 0x01 // Additional records = 1 +}; + +// Generate positive response for IPV4 address +const uint8_t aRecord[] PROGMEM = { + 0x00, 0x01, // Type = 1, A record/IPV4 address + 0x80, 0x01, // Class = Internet, with cache flush bit + 0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later + 0x00, 0x04, // Length of record + 0x00, 0x00, 0x00, 0x00 // IP address, to be filled in later +}; + +// Generate negative response for IPV6 address (CC3000 doesn't support IPV6) +const uint8_t nsecRecord[] PROGMEM = { + 0xC0, 0x0C, // Name offset + 0x00, 0x2F, // Type = 47, NSEC (overloaded by MDNS) + 0x80, 0x01, // Class = Internet, with cache flush bit + 0x00, 0x00, 0x00, 0x00, // TTL in seconds, to be filled in later + 0x00, 0x08, // Length of record + 0xC0, 0x0C, // Next domain = offset to FQDN + 0x00, // Block number = 0 + 0x04, // Length of bitmap = 4 bytes + 0x40, 0x00, 0x00, 0x00 // Bitmap value = Only first bit (A record/IPV4) is set +}; + +const uint8_t domain[] PROGMEM = { 'l', 'o', 'c', 'a', 'l' }; + +MDNSResponder::MDNSResponder() : + expectedRequestLength(0) +{ +} + +MDNSResponder::~MDNSResponder() +{ +} + +bool MDNSResponder::begin(const char* _name, uint32_t _ttlSeconds) +{ + int nameLength = strlen(_name); + + if (nameLength > 255) { + // Can only handle domains that are upto 255 chars in length. + expectedRequestLength = 0; + return false; + } + + name = _name; + ttlSeconds = _ttlSeconds; + + name.toLowerCase(); + expectedRequestLength = HEADER_SIZE + 1 + nameLength + 1 + sizeof(domain) + 5; + + // Open the MDNS UDP listening socket on port 5353 with multicast address + // 224.0.0.251 (0xE00000FB) + if (!udpSocket.beginMulti(IPAddress(224, 0, 0, 251), 5353)) { + return false; + } + + return true; +} + +void MDNSResponder::poll() +{ + if (parseRequest()) { + replyToRequest(); + } +} + +bool MDNSResponder::parseRequest() +{ + if (udpSocket.parsePacket()) { + // check if parsed packet matches expected request length + if (udpSocket.available() != expectedRequestLength) { + // it does not, read the full packet in and drop data + while(udpSocket.available()) { + udpSocket.read(); + } + + return false; + } + + // read packet + uint8_t request[expectedRequestLength]; + udpSocket.read(request, expectedRequestLength); + + // parse request + uint8_t requestNameLength = request[HEADER_SIZE]; + uint8_t* requestName = &request[HEADER_SIZE + 1]; + uint8_t requestDomainLength = request[HEADER_SIZE + 1 + requestNameLength]; + uint8_t* requestDomain = &request[HEADER_SIZE + 1 + requestNameLength + 1]; + uint16_t requestQtype; + uint16_t requestQclass; + + memcpy(&requestQtype, &request[expectedRequestLength - 4], sizeof(requestQtype)); + memcpy(&requestQclass, &request[expectedRequestLength - 2], sizeof(requestQclass)); + + requestQtype = _ntohs(requestQtype); + requestQclass = _ntohs(requestQclass); + + // compare request + if (memcmp_P(request, expectedRequestHeader, HEADER_SIZE) == 0 && // request header match + requestNameLength == name.length() && // name length match + strncasecmp(name.c_str(), (char*)requestName, requestNameLength) == 0 && // name match + requestDomainLength == sizeof(domain) && // domain length match + memcmp_P(requestDomain, domain, requestDomainLength) == 0 && // suffix match + requestQtype == 0x0001 && // request QType match + requestQclass == 0x0001) { // request QClass match + + return true; + } + } + + return false; +} + +void MDNSResponder::replyToRequest() +{ + int nameLength = name.length(); + int domainLength = sizeof(domain); + uint32_t ipAddress = WiFi.localIP(); + uint32_t ttl = _htonl(ttlSeconds); + + int responseSize = sizeof(responseHeader) + 1 + nameLength + 1 + domainLength + 1 + sizeof(aRecord) + sizeof(nsecRecord); + uint8_t response[responseSize]; + uint8_t* r = response; + + // copy header + memcpy_P(r, responseHeader, sizeof(responseHeader)); + r += sizeof(responseHeader); + + // copy name + *r = nameLength; + memcpy(r + 1, name.c_str(), nameLength); + r += (1 + nameLength); + + // copy domain + *r = domainLength; + memcpy_P(r + 1, domain, domainLength); + r += (1 + domainLength); + + // terminator + *r = 0x00; + r++; + + // copy A record + memcpy_P(r, aRecord, sizeof(aRecord)); + memcpy(r + TTL_OFFSET, &ttl, sizeof(ttl)); // replace TTL value + memcpy(r + IP_OFFSET, &ipAddress, sizeof(ipAddress)); // replace IP address value + r += sizeof(aRecord); + + // copy NSEC record + memcpy_P(r, nsecRecord, sizeof(nsecRecord)); + r += sizeof(nsecRecord); + + udpSocket.beginPacket(IPAddress(224, 0, 0, 251), 5353); + udpSocket.write(response, responseSize); + udpSocket.endPacket(); +} diff --git a/src/WiFiMdns.h b/src/WiFiMdns.h new file mode 100644 index 00000000..4f069aa5 --- /dev/null +++ b/src/WiFiMdns.h @@ -0,0 +1,51 @@ +// Port of CC3000 MDNS Responder to WINC1500. +// Author: Tony DiCola +// +// This MDNSResponder class implements just enough MDNS functionality to respond +// to name requests, for example 'foo.local'. This does not implement any other +// MDNS or Bonjour functionality like services, etc. +// +// Copyright (c) 2016 Adafruit Industries. All right reserved. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef WIFIMDNS_H +#define WIFIMDNS_H + +#include "WiFi101.h" +#include "WiFiUdp.h" + +class MDNSResponder { +public: + MDNSResponder(); + ~MDNSResponder(); + bool begin(const char* _name, uint32_t _ttlSeconds = 3600); + void poll(); + +private: + bool parseRequest(); + void replyToRequest(); + +private: + String name; + uint32_t ttlSeconds; + + int expectedRequestLength; + + // UDP socket for receiving/sending MDNS data. + WiFiUDP udpSocket; +}; + +#endif diff --git a/src/WiFiUdp.cpp b/src/WiFiUdp.cpp index edb7d928..0d6e8cd8 100644 --- a/src/WiFiUdp.cpp +++ b/src/WiFiUdp.cpp @@ -92,12 +92,25 @@ uint8_t WiFiUDP::begin(uint16_t port) return 1; } +uint8_t WiFiUDP::beginMulti(IPAddress ip, uint16_t port) +{ + uint32_t multiIp = ip; + + if (!begin(port)) { + return 0; + } + + setsockopt(_socket, SOL_SOCKET, IP_ADD_MEMBERSHIP, &multiIp, sizeof(multiIp)); + + return 1; +} + /* return number of bytes available in the current packet, will return zero if parsePacket hasn't been called yet */ int WiFiUDP::available() { m2m_wifi_handle_events(NULL); - + if (_socket != -1) { return _rcvSize; } @@ -109,7 +122,7 @@ void WiFiUDP::stop() { if (_socket < 0) return; - + socketBufferUnregister(_socket); close(_socket); _socket = -1; @@ -130,7 +143,7 @@ int WiFiUDP::beginPacket(IPAddress ip, uint16_t port) { _sndIP = ip; _sndPort = port; - + return 1; } @@ -174,7 +187,7 @@ size_t WiFiUDP::write(const uint8_t *buffer, size_t size) int WiFiUDP::parsePacket() { m2m_wifi_handle_events(NULL); - + if (_socket != -1) { if (_rcvSize != 0) { return _rcvSize; @@ -207,11 +220,11 @@ int WiFiUDP::read(unsigned char* buf, size_t size) // sizeof(size_t) is architecture dependent // but we need a 16 bit data type here uint16_t size_tmp = available(); - + if (size_tmp == 0) { return -1; } - + if (size < size_tmp) { size_tmp = size; } diff --git a/src/WiFiUdp.h b/src/WiFiUdp.h index d089fcb6..5f4f12fd 100644 --- a/src/WiFiUdp.h +++ b/src/WiFiUdp.h @@ -38,10 +38,11 @@ class WiFiUDP : public UDP { public: WiFiUDP(); // Constructor virtual uint8_t begin(uint16_t); // initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use + virtual uint8_t beginMulti(IPAddress, uint16_t); // initialize, start listening on specified multicast IP address and port. Returns 1 if successful, 0 if there are no sockets available to use virtual void stop(); // Finish with the UDP socket // Sending UDP packets - + // Start building up a packet to send to the remote host specific in ip and port // Returns 1 if successful, 0 if there was a problem with the supplied IP address or port virtual int beginPacket(IPAddress ip, uint16_t port); @@ -55,7 +56,7 @@ class WiFiUDP : public UDP { virtual size_t write(uint8_t); // Write size bytes from buffer into the packet virtual size_t write(const uint8_t *buffer, size_t size); - + using Print::write; // Start processing the next available incoming packet