Merge branch 'feature/new-mdns' into 'master'

Feature/new mdns

This is a written from scratch mDNS implementation that supports compression, service and host lookups and service advertisement.

See merge request !365
This commit is contained in:
Ivan Grokhotkov 2017-01-16 00:15:59 +08:00
commit 5bd7e655b9
12 changed files with 2556 additions and 1 deletions

View file

View file

@ -0,0 +1,235 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ESP_MDNS_H_
#define ESP_MDNS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <tcpip_adapter.h>
struct mdns_server_s;
typedef struct mdns_server_s mdns_server_t;
/**
* @brief mDNS query result structure
*
*/
typedef struct mdns_result_s {
const char * host; /*!< hostname */
const char * instance; /*!< instance */
const char * txt; /*!< txt data */
uint16_t priority; /*!< service priority */
uint16_t weight; /*!< service weight */
uint16_t port; /*!< service port */
struct ip4_addr addr; /*!< ip4 address */
struct ip6_addr addrv6; /*!< ip6 address */
const struct mdns_result_s * next; /*!< next result, or NULL for the last result in the list */
} mdns_result_t;
/**
* @brief Initialize mDNS on given interface
*
* @param tcpip_if Interface that the server will listen on
* @param server Server pointer to populate on success
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG when bad tcpip_if is given
* - ESP_ERR_INVALID_STATE when the network returned error
* - ESP_ERR_NO_MEM on memory error
* - ESP_ERR_WIFI_NOT_INIT when WiFi is not initialized by eps_wifi_init
*/
esp_err_t mdns_init(tcpip_adapter_if_t tcpip_if, mdns_server_t ** server);
/**
* @brief Stop and free mDNS server
*
* @param server mDNS Server to free
*
*/
void mdns_free(mdns_server_t * server);
/**
* @brief Set the hostname for mDNS server
*
* @param server mDNS Server
* @param hostname Hostname to set
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_set_hostname(mdns_server_t * server, const char * hostname);
/**
* @brief Set the default instance name for mDNS server
*
* @param server mDNS Server
* @param instance Instance name to set
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_set_instance(mdns_server_t * server, const char * instance);
/**
* @brief Add service to mDNS server
*
* @param server mDNS Server
* @param service service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
* @param port service port
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_service_add(mdns_server_t * server, const char * service, const char * proto, uint16_t port);
/**
* @brief Remove service from mDNS server
*
* @param server mDNS Server
* @param service service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NOT_FOUND Service not found
* - ESP_FAIL unknown error
*/
esp_err_t mdns_service_remove(mdns_server_t * server, const char * service, const char * proto);
/**
* @brief Set instance name for service
*
* @param server mDNS Server
* @param service service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
* @param instance instance name to set
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NOT_FOUND Service not found
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_service_instance_set(mdns_server_t * server, const char * service, const char * proto, const char * instance);
/**
* @brief Set TXT data for service
*
* @param server mDNS Server
* @param service service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
* @param num_items number of items in TXT data
* @param txt string array of TXT data (eg. {"var=val","other=2"})
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NOT_FOUND Service not found
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_service_txt_set(mdns_server_t * server, const char * service, const char * proto, uint8_t num_items, const char ** txt);
/**
* @brief Set service port
*
* @param server mDNS Server
* @param service service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
* @param port service port
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NOT_FOUND Service not found
*/
esp_err_t mdns_service_port_set(mdns_server_t * server, const char * service, const char * proto, uint16_t port);
/**
* @brief Remove and free all services from mDNS server
*
* @param server mDNS Server
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t mdns_service_remove_all(mdns_server_t * server);
/**
* @brief Query mDNS for host or service
*
* @param server mDNS Server
* @param service service type or host name
* @param proto service protocol or NULL if searching for host
* @param timeout time to wait for answers. If 0, mdns_query_end MUST be called to end the search
*
* @return the number of results found
*/
size_t mdns_query(mdns_server_t * server, const char * service, const char * proto, uint32_t timeout);
/**
* @brief Stop mDNS Query started with timeout = 0
*
* @param server mDNS Server
*
* @return the number of results found
*/
size_t mdns_query_end(mdns_server_t * server);
/**
* @brief get the number of results currently in memoty
*
* @param server mDNS Server
*
* @return the number of results
*/
size_t mdns_result_get_count(mdns_server_t * server);
/**
* @brief Get mDNS Search result with given index
*
* @param server mDNS Server
* @param num the index of the result
*
* @return the result or NULL if error
*/
const mdns_result_t * mdns_result_get(mdns_server_t * server, size_t num);
/**
* @brief Remove and free all search results from mDNS server
*
* @param server mDNS Server
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
*/
esp_err_t mdns_result_free(mdns_server_t * server);
#ifdef __cplusplus
}
#endif
#endif /* ESP_MDNS_H_ */

1861
components/mdns/mdns.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -58,6 +58,17 @@ extern "C" {
#define IPSTR "%d.%d.%d.%d" #define IPSTR "%d.%d.%d.%d"
#define IPV62STR(ipaddr) IP6_ADDR_BLOCK1(&(ipaddr)), \
IP6_ADDR_BLOCK2(&(ipaddr)), \
IP6_ADDR_BLOCK3(&(ipaddr)), \
IP6_ADDR_BLOCK4(&(ipaddr)), \
IP6_ADDR_BLOCK5(&(ipaddr)), \
IP6_ADDR_BLOCK6(&(ipaddr)), \
IP6_ADDR_BLOCK7(&(ipaddr)), \
IP6_ADDR_BLOCK8(&(ipaddr))
#define IPV6STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
typedef struct { typedef struct {
ip4_addr_t ip; ip4_addr_t ip;
ip4_addr_t netmask; ip4_addr_t netmask;

View file

@ -36,7 +36,8 @@ INPUT = ../components/esp32/include/esp_wifi.h \
../components/sdmmc/include/sdmmc_cmd.h \ ../components/sdmmc/include/sdmmc_cmd.h \
../components/fatfs/src/esp_vfs_fat.h \ ../components/fatfs/src/esp_vfs_fat.h \
../components/fatfs/src/diskio.h \ ../components/fatfs/src/diskio.h \
../components/esp32/include/esp_core_dump.h ../components/esp32/include/esp_core_dump.h \
../components/mdns/include/mdns.h
## Get warnings for functions that have no documentation for their parameters or return value ## Get warnings for functions that have no documentation for their parameters or return value
## ##

215
docs/api/mdns.rst Normal file
View file

@ -0,0 +1,215 @@
mDNS Service
============
Overview
--------
mDNS is a multicast UDP service that is used to provide local network service and host discovery.
mDNS is installed by default on most operating systems or is available as separate package. On ``Mac OS`` it is installed by default and is called ``Bonjour``. Apple releases an installer for ``Windows`` that can be found `on Apple's support page <https://support.apple.com/downloads/bonjour%2520for%2520windows>`_. On ``Linux``, mDNS is provided by `avahi <https://github.com/lathiat/avahi>`_ and is usually installed by default.
mDNS Properties
^^^^^^^^^^^^^^^
* ``hostname``: the hostname that the device will respond to. If not set, the ``hostname`` will be read from the interface. Example: ``my-esp32`` will resolve to ``my-esp32.local``
* ``default_instance``: friendly name for your device, like ``Jhon's ESP32 Thing``. If not set, ``hostname`` will be used.
Example method to start mDNS for the STA interface and set ``hostname`` and ``default_instance``:
::
mdns_server_t * mdns = NULL;
void start_mdns_service()
{
//initialize mDNS service on STA interface
esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns);
if (err) {
printf("MDNS Init failed: %d\n", err);
return;
}
//set hostname
mdns_set_hostname(mdns, "my-esp32");
//set default instance
mdns_set_instance(mdns, "Jhon's ESP32 Thing");
}
mDNS Services
^^^^^^^^^^^^^
mDNS can advertise information about network services that your device offers. Each service is defined by a few properties.
* ``service``: (required) service type, prepended with underscore. Some common types can be found `here <http://www.dns-sd.org/serviceTypes.html>`_.
* ``proto``: (required) protocol that the service runs on, prepended with underscore. Example: ``_tcp`` or ``_udp``
* ``port``: (required) network port that the service runs on
* ``instance``: friendly name for your service, like ``Jhon's ESP32 Web Server``. If not defined, ``default_instance`` will be used.
* ``txt``: ``var=val`` array of strings, used to define properties for your service
Example method to add a few services and different properties:
::
void add_mdns_services()
{
//add our services
mdns_service_add(mdns, "_http", "_tcp", 80);
mdns_service_add(mdns, "_arduino", "_tcp", 3232);
mdns_service_add(mdns, "_myservice", "_udp", 1234);
//NOTE: services must be added before their properties can be set
//use custom instance for the web server
mdns_service_instance_set(mdns, "_http", "_tcp", "Jhon's ESP32 Web Server");
const char * arduTxtData[4] = {
"board=esp32",
"tcp_check=no",
"ssh_upload=no",
"auth_upload=no"
};
//set txt data for service (will free and replace current data)
mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData);
//change service port
mdns_service_port_set(mdns, "_myservice", "_udp", 4321);
}
mDNS Query
^^^^^^^^^^
mDNS provides methods for browsing for services and resolving host's IP/IPv6 addresses.
Results are returned as a linked list of ``mdns_result_t`` objects. If the result is from host query,
it will contain only ``addr`` and ``addrv6`` if found. Service queries will populate all fields
in a result that were found.
Example method to resolve host IPs:
::
void resolve_mdns_host(const char * hostname)
{
printf("mDNS Host Lookup: %s.local\n", hostname);
//run search for 1000 ms
if (mdns_query(mdns, hostname, NULL, 1000)) {
//results were found
const mdns_result_t * results = mdns_result_get(mdns, 0);
//itterate through all results
size_t i = 1;
while(results) {
//print result information
printf(" %u: IP:" IPSTR ", IPv6:" IPV6STR "\n", i++
IP2STR(&results->addr), IPV62STR(results->addrv6));
//load next result. Will be NULL if this was the last one
results = results->next;
}
//free the results from memory
mdns_result_free(mdns);
} else {
//host was not found
printf(" Host Not Found\n");
}
}
Example method to resolve local services:
::
void find_mdns_service(const char * service, const char * proto)
{
printf("mDNS Service Lookup: %s.%s\n", service, proto);
//run search for 1000 ms
if (mdns_query(mdns, service, proto, 1000)) {
//results were found
const mdns_result_t * results = mdns_result_get(mdns, 0);
//itterate through all results
size_t i = 1;
while(results) {
//print result information
printf(" %u: hostname:%s, instance:\"%s\", IP:" IPSTR ", IPv6:" IPV6STR ", port:%u, txt:%s\n", i++,
(results->host)?results->host:"NULL", (results->instance)?results->instance:"NULL",
IP2STR(&results->addr), IPV62STR(results->addrv6),
results->port, (results->txt)?results->txt:"\r");
//load next result. Will be NULL if this was the last one
results = results->next;
}
//free the results from memory
mdns_result_free(mdns);
} else {
//service was not found
printf(" Service Not Found\n");
}
}
Example of using the methods above:
::
void my_app_some_method(){
//search for esp32-mdns.local
resolve_mdns_host("esp32-mdns");
//search for HTTP servers
find_mdns_service("_http", "_tcp");
//or file servers
find_mdns_service("_smb", "_tcp"); //windows sharing
find_mdns_service("_afpovertcp", "_tcp"); //apple sharing
find_mdns_service("_nfs", "_tcp"); //NFS server
find_mdns_service("_ftp", "_tcp"); //FTP server
//or networked printer
find_mdns_service("_printer", "_tcp");
find_mdns_service("_ipp", "_tcp");
}
Application Example
-------------------
mDNS server/scanner example: `examples/30_mdns_example <https://github.com/espressif/esp-idf/tree/master/examples/30_mdns_example>`_.
API Reference
-------------
Header Files
^^^^^^^^^^^^
* `components/mdns/include/mdns.h <https://github.com/espressif/esp-idf/blob/master/components/mdns/include/mdns.h>`_
Macros
^^^^^^
Type Definitions
^^^^^^^^^^^^^^^^
.. doxygentypedef:: mdns_server_t
.. doxygentypedef:: mdns_result_t
Enumerations
^^^^^^^^^^^^
Structures
^^^^^^^^^^
.. doxygenstruct:: mdns_result_s
:members:
Functions
^^^^^^^^^
.. doxygenfunction:: mdns_init
.. doxygenfunction:: mdns_free
.. doxygenfunction:: mdns_set_hostname
.. doxygenfunction:: mdns_set_instance
.. doxygenfunction:: mdns_service_add
.. doxygenfunction:: mdns_service_remove
.. doxygenfunction:: mdns_service_instance_set
.. doxygenfunction:: mdns_service_txt_set
.. doxygenfunction:: mdns_service_port_set
.. doxygenfunction:: mdns_service_remove_all
.. doxygenfunction:: mdns_query
.. doxygenfunction:: mdns_query_end
.. doxygenfunction:: mdns_result_get_count
.. doxygenfunction:: mdns_result_get
.. doxygenfunction:: mdns_result_free

View file

@ -124,6 +124,7 @@ Contents:
Deep Sleep <api/deep_sleep> Deep Sleep <api/deep_sleep>
deep-sleep-stub deep-sleep-stub
mDNS <api/mdns>
Template <api/template> Template <api/template>
.. toctree:: .. toctree::

View file

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := mdns-test
include $(IDF_PATH)/make/project.mk

View file

@ -0,0 +1,5 @@
# 30_mdns example
Shows how to use mDNS to advertise lookup services and hosts
See the README.md file in the upper level 'examples' directory for more information about examples.

View file

@ -0,0 +1,29 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "myssid"
help
WiFi password (WPA or WPA2) for the example to use.
Can be left blank if the network has no security set.
config MDNS_HOSTNAME
string "mDNS Hostname"
default "esp32-mdns"
help
mDNS Hostname for example to use
config MDNS_INSTANCE
string "mDNS Instance Name"
default "ESP32 with mDNS"
help
mDNS Instance Name for example to use
endmenu

View file

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View file

@ -0,0 +1,184 @@
/* MDNS-SD Query and advertise Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "mdns.h"
/* The examples use simple WiFi configuration that you can set via
'make menuconfig'.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD
#define EXAMPLE_MDNS_HOSTNAME CONFIG_MDNS_HOSTNAME
#define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP? */
const int CONNECTED_BIT = BIT0;
static const char *TAG = "mdns-test";
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_CONNECTED:
/* enable ipv6 */
tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA);
break;
case SYSTEM_EVENT_STA_GOT_IP:
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
/* This is a workaround as ESP32 WiFi libs don't currently
auto-reassociate. */
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASS,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
static void query_mdns_service(mdns_server_t * mdns, const char * service, const char * proto)
{
if(!mdns) {
return;
}
uint32_t res;
if (!proto) {
ESP_LOGI(TAG, "Host Lookup: %s", service);
res = mdns_query(mdns, service, 0, 1000);
if (res) {
size_t i;
for(i=0; i<res; i++) {
const mdns_result_t * r = mdns_result_get(mdns, i);
if (r) {
ESP_LOGI(TAG, " %u: " IPSTR " " IPV6STR, i+1,
IP2STR(&r->addr), IPV62STR(r->addrv6));
}
}
mdns_result_free(mdns);
} else {
ESP_LOGI(TAG, " Not Found");
}
} else {
ESP_LOGI(TAG, "Service Lookup: %s.%s ", service, proto);
res = mdns_query(mdns, service, proto, 1000);
if (res) {
size_t i;
for(i=0; i<res; i++) {
const mdns_result_t * r = mdns_result_get(mdns, i);
if (r) {
ESP_LOGI(TAG, " %u: %s \"%s\" " IPSTR " " IPV6STR " %u %s", i+1,
(r->host)?r->host:"", (r->instance)?r->instance:"",
IP2STR(&r->addr), IPV62STR(r->addrv6),
r->port, (r->txt)?r->txt:"");
}
}
mdns_result_free(mdns);
}
}
}
static void mdns_task(void *pvParameters)
{
mdns_server_t * mdns = NULL;
while(1) {
/* Wait for the callback to set the CONNECTED_BIT in the
event group.
*/
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);
ESP_LOGI(TAG, "Connected to AP");
if (!mdns) {
esp_err_t err = mdns_init(TCPIP_ADAPTER_IF_STA, &mdns);
if (err) {
ESP_LOGE(TAG, "Failed starting MDNS: %u", err);
continue;
}
ESP_ERROR_CHECK( mdns_set_hostname(mdns, EXAMPLE_MDNS_HOSTNAME) );
ESP_ERROR_CHECK( mdns_set_instance(mdns, EXAMPLE_MDNS_INSTANCE) );
const char * arduTxtData[4] = {
"board=esp32",
"tcp_check=no",
"ssh_upload=no",
"auth_upload=no"
};
ESP_ERROR_CHECK( mdns_service_add(mdns, "_arduino", "_tcp", 3232) );
ESP_ERROR_CHECK( mdns_service_txt_set(mdns, "_arduino", "_tcp", 4, arduTxtData) );
ESP_ERROR_CHECK( mdns_service_add(mdns, "_http", "_tcp", 80) );
ESP_ERROR_CHECK( mdns_service_instance_set(mdns, "_http", "_tcp", "ESP32 WebServer") );
ESP_ERROR_CHECK( mdns_service_add(mdns, "_smb", "_tcp", 445) );
} else {
query_mdns_service(mdns, "esp32", NULL);
query_mdns_service(mdns, "_arduino", "_tcp");
query_mdns_service(mdns, "_http", "_tcp");
query_mdns_service(mdns, "_printer", "_tcp");
query_mdns_service(mdns, "_ipp", "_tcp");
query_mdns_service(mdns, "_afpovertcp", "_tcp");
query_mdns_service(mdns, "_smb", "_tcp");
query_mdns_service(mdns, "_ftp", "_tcp");
query_mdns_service(mdns, "_nfs", "_tcp");
}
ESP_LOGI(TAG, "Restarting in 10 seconds!");
vTaskDelay(10000 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Starting again!");
}
}
void app_main()
{
nvs_flash_init();
initialise_wifi();
xTaskCreate(&mdns_task, "mdns_task", 2048, NULL, 5, NULL);
}