diff --git a/components/esp_netif/CMakeLists.txt b/components/esp_netif/CMakeLists.txt index e3f4871e8a..a78a3116ce 100644 --- a/components/esp_netif/CMakeLists.txt +++ b/components/esp_netif/CMakeLists.txt @@ -1,3 +1,11 @@ +idf_build_get_property(target IDF_TARGET) + +if(${target} STREQUAL "linux") + # Header only library for linux + idf_component_register(INCLUDE_DIRS include) + return() +endif() + set(srcs "esp_netif_handlers.c" "esp_netif_objects.c" diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt index a8d2cedb3d..cb3ed49960 100644 --- a/components/mdns/CMakeLists.txt +++ b/components/mdns/CMakeLists.txt @@ -1,7 +1,22 @@ -idf_component_register(SRCS "mdns.c" - "mdns_console.c" - "mdns_networking.c" - INCLUDE_DIRS "include" - PRIV_INCLUDE_DIRS "private_include" - REQUIRES lwip console esp_netif - PRIV_REQUIRES esp_timer) +if(CONFIG_MDNS_NETWORKING_SOCKET) + set(MDNS_NETWORKING "mdns_networking_socket.c") +else() + set(MDNS_NETWORKING "mdns_networking_lwip.c") +endif() + +idf_build_get_property(target IDF_TARGET) +if(${target} STREQUAL "linux") + set(dependencies esp_system_protocols_linux) + set(srcs "mdns.c" ${MDNS_NETWORKING}) +else() + set(dependencies lwip console esp_netif) + set(private_dependencies esp_timer) + set(srcs "mdns.c" ${MDNS_NETWORKING} "mdns_console.c") +endif() + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "private_include" + REQUIRES ${dependencies} + PRIV_REQUIRES ${private_dependencies}) diff --git a/components/mdns/Kconfig b/components/mdns/Kconfig index 38dec76287..b49ae703c7 100644 --- a/components/mdns/Kconfig +++ b/components/mdns/Kconfig @@ -75,4 +75,13 @@ menu "mDNS" Configures period of mDNS timer, which periodically transmits packets and schedules mDNS searches. + config MDNS_NETWORKING_SOCKET + bool "Use BSD sockets for mdns networking" + default n + help + Enables optional mdns networking implementation using BSD sockets + in UDP multicast mode. + This option creates a new thread to serve receiving packets (TODO). + This option uses additional N sockets, where N is number of interfaces. + endmenu diff --git a/components/mdns/component.mk b/components/mdns/component.mk index 064cc0608e..f22f83094a 100644 --- a/components/mdns/component.mk +++ b/components/mdns/component.mk @@ -1,2 +1,7 @@ +ifdef CONFIG_MDNS_NETWORKING_SOCKET +COMPONENT_OBJEXCLUDE := mdns_networking_lwip.o +else +COMPONENT_OBJEXCLUDE := mdns_networking_socket.o +endif COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_PRIV_INCLUDEDIRS := private_include diff --git a/components/mdns/host_test/CMakeLists.txt b/components/mdns/host_test/CMakeLists.txt new file mode 100644 index 0000000000..132e436eaf --- /dev/null +++ b/components/mdns/host_test/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(mdns_host) diff --git a/components/mdns/host_test/README.md b/components/mdns/host_test/README.md new file mode 100644 index 0000000000..ab916520dd --- /dev/null +++ b/components/mdns/host_test/README.md @@ -0,0 +1,25 @@ +# Setup dummy network interfaces +``` +sudo ip link add eth2 type dummy +sudo ip addr add 192.168.1.200/24 dev eth2 +sudo ip link set eth2 up +sudo ifconfig eth2 multicast +``` + +# Dig on a specified interface + +``` +dig +short -b 192.168.1.200 -p 5353 @224.0.0.251 myesp.local +``` + +# Run avahi to browse services + +Avahi needs the netif to have the "multicast" flag set + +```bash +david@david-comp:~/esp/idf (feature/mdns_networking_socket)$ avahi-browse -a -r -p ++;eth2;IPv6;myesp-service2;Web Site;local ++;eth2;IPv4;myesp-service2;Web Site;local +=;eth2;IPv6;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password" +=;eth2;IPv4;myesp-service2;Web Site;local;myesp.local;192.168.1.200;80;"board=esp32" "u=user" "p=password" +``` diff --git a/components/mdns/host_test/components/esp_event_mock/CMakeLists.txt b/components/mdns/host_test/components/esp_event_mock/CMakeLists.txt new file mode 100644 index 0000000000..606c16f808 --- /dev/null +++ b/components/mdns/host_test/components/esp_event_mock/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS esp_event_mock.c + INCLUDE_DIRS include + REQUIRES esp_system_protocols_linux) diff --git a/components/mdns/host_test/components/esp_event_mock/esp_event_mock.c b/components/mdns/host_test/components/esp_event_mock/esp_event_mock.c new file mode 100644 index 0000000000..cd6b079b35 --- /dev/null +++ b/components/mdns/host_test/components/esp_event_mock/esp_event_mock.c @@ -0,0 +1,29 @@ +// Copyright 2021 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. + +#include "esp_err.h" +#include "esp_event.h" + +const char * WIFI_EVENT = "WIFI_EVENT"; +const char * IP_EVENT = "IP_EVENT"; + +esp_err_t esp_event_handler_register(const char * event_base, int32_t event_id, void* event_handler, void* event_handler_arg) +{ + return ESP_OK; +} + +esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler) +{ + return ESP_OK; +} diff --git a/components/mdns/host_test/components/esp_event_mock/include/esp_event.h b/components/mdns/host_test/components/esp_event_mock/include/esp_event.h new file mode 100644 index 0000000000..18801e4b9f --- /dev/null +++ b/components/mdns/host_test/components/esp_event_mock/include/esp_event.h @@ -0,0 +1,32 @@ +// Copyright 2021 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. +#pragma once + +#include "stdbool.h" +#include "esp_err.h" +#include "esp_event_base.h" +#include "bsd_strings.h" + +#define ESP_EVENT_DECLARE_BASE(x) +#define ESP_EVENT_ANY_ID (-1) + +typedef void * esp_event_base_t; +typedef void * system_event_t; + +const char* WIFI_EVENT; +const char* IP_EVENT; + +esp_err_t esp_event_handler_register(const char * event_base, int32_t event_id, void* event_handler, void* event_handler_arg); + +esp_err_t esp_event_handler_unregister(const char * event_base, int32_t event_id, void* event_handler); diff --git a/components/mdns/host_test/components/esp_event_mock/include/esp_event_base.h b/components/mdns/host_test/components/esp_event_mock/include/esp_event_base.h new file mode 100644 index 0000000000..740b712702 --- /dev/null +++ b/components/mdns/host_test/components/esp_event_mock/include/esp_event_base.h @@ -0,0 +1,21 @@ +// Copyright 2021 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. +#pragma once + +typedef enum { + WIFI_EVENT_STA_CONNECTED, /**< ESP32 station connected to AP */ + WIFI_EVENT_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */ + WIFI_EVENT_AP_START, /**< ESP32 soft-AP start */ + WIFI_EVENT_AP_STOP, /**< ESP32 soft-AP stop */ +} mdns_used_event_t; diff --git a/components/mdns/host_test/components/esp_netif_linux/CMakeLists.txt b/components/mdns/host_test/components/esp_netif_linux/CMakeLists.txt new file mode 100644 index 0000000000..086034b089 --- /dev/null +++ b/components/mdns/host_test/components/esp_netif_linux/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS esp_netif_linux.c + INCLUDE_DIRS include + REQUIRES esp_system_protocols_linux) diff --git a/components/mdns/host_test/components/esp_netif_linux/Kconfig b/components/mdns/host_test/components/esp_netif_linux/Kconfig new file mode 100644 index 0000000000..c903f7ca5f --- /dev/null +++ b/components/mdns/host_test/components/esp_netif_linux/Kconfig @@ -0,0 +1,9 @@ +menu "LWIP-MOCK-CONFIG" + + config LWIP_IPV6 + bool "Enable IPv6" + default true + help + Enable/disable IPv6 + +endmenu diff --git a/components/mdns/host_test/components/esp_netif_linux/esp_netif_linux.c b/components/mdns/host_test/components/esp_netif_linux/esp_netif_linux.c new file mode 100644 index 0000000000..bbde590737 --- /dev/null +++ b/components/mdns/host_test/components/esp_netif_linux/esp_netif_linux.c @@ -0,0 +1,163 @@ +// Copyright 2021 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. +#include +#include + +#include "esp_netif.h" +#include "esp_err.h" +#include //strlen +#include +#include //inet_addr +#include +#include +#include +#include "esp_netif_types.h" + +#define MAX_NETIFS 4 + +static esp_netif_t* s_netif_list[MAX_NETIFS] = { 0 }; + +struct esp_netif_obj +{ + const char *if_key; + const char *if_desc; +}; + +esp_netif_t *esp_netif_get_handle_from_ifkey(const char *if_key) +{ + for (int i=0; iif_key, if_key) == 0) { + return s_netif_list[i]; + } + } + return NULL; +} + +esp_err_t esp_netif_get_ip_info(esp_netif_t *esp_netif, esp_netif_ip_info_t *ip_info) +{ + if (esp_netif == NULL) { + return ESP_ERR_INVALID_STATE; + } + struct ifaddrs *addrs, *tmp; + getifaddrs(&addrs); + tmp = addrs; + + while (tmp) { + if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) { + char addr[20]; + struct sockaddr_in *pAddr = (struct sockaddr_in *) tmp->ifa_addr; + inet_ntop(AF_INET, &pAddr->sin_addr, addr, sizeof(addr) ); + if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) { + printf("AF_INET: %s: %s\n", tmp->ifa_name, addr); + memcpy(&ip_info->ip.addr, &pAddr->sin_addr, 4); + break; + } + } + tmp = tmp->ifa_next; + } + return ESP_OK; +} + +esp_err_t esp_netif_dhcpc_get_status(esp_netif_t *esp_netif, esp_netif_dhcp_status_t *status) +{ + return ESP_OK; +} + + +esp_err_t esp_netif_get_ip6_linklocal(esp_netif_t *esp_netif, esp_ip6_addr_t *if_ip6) +{ + if (esp_netif == NULL) { + return ESP_ERR_INVALID_STATE; + } + struct ifaddrs *addrs, *tmp; + getifaddrs(&addrs); + tmp = addrs; + + while (tmp) + { + if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET6) { + char addr[64]; + struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)tmp->ifa_addr; + inet_ntop(AF_INET6, &pAddr->sin6_addr, addr, sizeof(addr) ); + if (strcmp(esp_netif->if_desc, tmp->ifa_name) == 0) { + printf("AF_INET6: %s: %s\n", tmp->ifa_name, addr); + memcpy(if_ip6->addr, &pAddr->sin6_addr, 4*4); + break; + } + } + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); + return ESP_OK; +} + + +int esp_netif_get_netif_impl_index(esp_netif_t *esp_netif) +{ + if (esp_netif == NULL) { + return -1; + } + uint32_t interfaceIndex = if_nametoindex(esp_netif->if_desc); + return interfaceIndex; +} + +esp_err_t esp_netif_get_netif_impl_name(esp_netif_t *esp_netif, char* name) +{ + if (esp_netif == NULL) { + return ESP_ERR_INVALID_STATE; + } + strcpy(name, esp_netif->if_desc); + return ESP_OK; +} + +const char *esp_netif_get_desc(esp_netif_t *esp_netif) +{ + if (esp_netif == NULL) { + return NULL; + } + return esp_netif->if_desc; +} + +esp_netif_t *esp_netif_new(const esp_netif_config_t *config) +{ + if (esp_netif_get_handle_from_ifkey(config->base->if_key)) { + return NULL; + } + esp_netif_t* netif = calloc(1, sizeof(struct esp_netif_obj)); + if (netif) { + netif->if_desc = config->base->if_desc; + netif->if_key = config->base->if_key; + } + + for (int i=0; i + +void _esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) +{ + ESP_LOGE("ESP_ERROR_CHECK", "Failed with esp_err_t: 0x%x", rc); + ESP_LOGE("ESP_ERROR_CHECK", "Expression: %s", expression); + ESP_LOGE("ESP_ERROR_CHECK", "Functions: %s %s(%d)", function, file, line); + abort(); +} + +void esp_log_buffer_hexdump_internal(const char *tag, const void *buffer, uint16_t buff_len, esp_log_level_t log_level) +{ + if ( LOG_LOCAL_LEVEL >= log_level ) { + ESP_LOG_LEVEL(log_level, tag, "Buffer:%p length:%d", buffer, buff_len); + for (int i=0; i + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "string.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/components/mdns/host_test/components/esp_timer_linux/CMakeLists.txt b/components/mdns/host_test/components/esp_timer_linux/CMakeLists.txt new file mode 100644 index 0000000000..0c598a2e61 --- /dev/null +++ b/components/mdns/host_test/components/esp_timer_linux/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS esp_timer_linux.c timer_task.cpp + INCLUDE_DIRS include + REQUIRES esp_system_protocols_linux freertos_linux) + +target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17) diff --git a/components/mdns/host_test/components/esp_timer_linux/esp_timer_linux.c b/components/mdns/host_test/components/esp_timer_linux/esp_timer_linux.c new file mode 100644 index 0000000000..bb2326985f --- /dev/null +++ b/components/mdns/host_test/components/esp_timer_linux/esp_timer_linux.c @@ -0,0 +1,47 @@ +// Copyright 2021 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. +#include "esp_err.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +void * create_tt(esp_timer_cb_t cb); + +void destroy_tt(void* tt); + +void set_tout(void* tt, uint32_t ms); + +esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, + esp_timer_handle_t* out_handle) +{ + *out_handle = (esp_timer_handle_t)create_tt(create_args->callback); + return ESP_OK; +} + +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period) +{ + set_tout(timer, period/1000); + return ESP_OK; +} + +esp_err_t esp_timer_stop(esp_timer_handle_t timer) +{ + return ESP_OK; +} + +esp_err_t esp_timer_delete(esp_timer_handle_t timer) +{ + destroy_tt(timer); + return ESP_OK; +} diff --git a/components/mdns/host_test/components/esp_timer_linux/include/esp_timer.h b/components/mdns/host_test/components/esp_timer_linux/include/esp_timer.h new file mode 100644 index 0000000000..6691630490 --- /dev/null +++ b/components/mdns/host_test/components/esp_timer_linux/include/esp_timer.h @@ -0,0 +1,41 @@ +// Copyright 2021 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. +#pragma once + +#include +#include + +typedef struct esp_timer* esp_timer_handle_t; + +typedef void (*esp_timer_cb_t)(void* arg); + +typedef enum { + ESP_TIMER_TASK, +} esp_timer_dispatch_t; + +typedef struct { + esp_timer_cb_t callback; //!< Function to call when timer expires + void* arg; //!< Argument to pass to the callback + esp_timer_dispatch_t dispatch_method; //!< Call the callback from task or from ISR + const char* name; //!< Timer name, used in esp_timer_dump function + bool skip_unhandled_events; //!< Skip unhandled events for periodic timers +} esp_timer_create_args_t; + +esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, + esp_timer_handle_t* out_handle); +esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period); + +esp_err_t esp_timer_stop(esp_timer_handle_t timer); + +esp_err_t esp_timer_delete(esp_timer_handle_t timer); diff --git a/components/mdns/host_test/components/esp_timer_linux/timer_task.cpp b/components/mdns/host_test/components/esp_timer_linux/timer_task.cpp new file mode 100644 index 0000000000..bd971b3f0c --- /dev/null +++ b/components/mdns/host_test/components/esp_timer_linux/timer_task.cpp @@ -0,0 +1,38 @@ +// Copyright 2021 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. + +#include "timer_task.hpp" +#include +#include +#include +#include + +extern "C" void * create_tt(cb_t cb) +{ + auto * tt = new TimerTaskMock(cb); + return tt; +} + +extern "C" void destroy_tt(void* tt) +{ + auto * timer_task = static_cast(tt); + delete(timer_task); +} + + +extern "C" void set_tout(void* tt, uint32_t ms) +{ + auto * timer_task = static_cast(tt); + timer_task->SetTimeout(ms); +} diff --git a/components/mdns/host_test/components/esp_timer_linux/timer_task.hpp b/components/mdns/host_test/components/esp_timer_linux/timer_task.hpp new file mode 100644 index 0000000000..8d32a6bd0b --- /dev/null +++ b/components/mdns/host_test/components/esp_timer_linux/timer_task.hpp @@ -0,0 +1,61 @@ +// Copyright 2021 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. +#pragma once + +#include +#include +#include +#include +#include +#include + +typedef void (*cb_t)(void* arg); + +class TimerTaskMock +{ +public: + TimerTaskMock(cb_t cb): cb(cb), t(run_static, this), active(false), ms(INT32_MAX) {} + ~TimerTaskMock(void) { active = false; t.join(); } + + void SetTimeout(uint32_t m) + { + ms = m; + active = true; + } + +private: + + static void run_static(TimerTaskMock* timer) + { + timer->run(); + } + + void run(void) + { + while (!active.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + while (active.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + cb(nullptr); + } + } + + cb_t cb; + std::thread t; + std::atomic active; + uint32_t ms; + +}; diff --git a/components/mdns/host_test/components/freertos_linux/CMakeLists.txt b/components/mdns/host_test/components/freertos_linux/CMakeLists.txt new file mode 100644 index 0000000000..7a84af21a7 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register(SRCS freertos_linux.c queue_unique_ptr.cpp + INCLUDE_DIRS include + REQUIRES esp_system_protocols_linux) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(${COMPONENT_LIB} PRIVATE Threads::Threads) + +target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17) diff --git a/components/mdns/host_test/components/freertos_linux/Kconfig b/components/mdns/host_test/components/freertos_linux/Kconfig new file mode 100644 index 0000000000..fa57dab7e2 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/Kconfig @@ -0,0 +1,7 @@ +menu "FreeRTOS" + + config FREERTOS_NO_AFFINITY + hex + default 0x7FFFFFFF + +endmenu diff --git a/components/mdns/host_test/components/freertos_linux/freertos_linux.c b/components/mdns/host_test/components/freertos_linux/freertos_linux.c new file mode 100644 index 0000000000..7633645045 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/freertos_linux.c @@ -0,0 +1,192 @@ +// Copyright 2021 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. + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include +#include +#include +#include + +void * create_q(void); + +void destroy_q(void* q); + +bool send_q(void* q, uint8_t *data, size_t len); + +bool recv_q(void* q, uint8_t *data, size_t len, uint32_t ms); + +static uint64_t s_semaphore_data = 0; + +struct queue_handle { + size_t item_size; + void * q; +}; + +QueueHandle_t xQueueCreate( uint32_t uxQueueLength, uint32_t uxItemSize ) +{ + struct queue_handle * h = calloc(1, sizeof(struct queue_handle)); + h->item_size = uxItemSize; + h->q = create_q(); + return (QueueHandle_t)h; +} + +uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait) +{ + struct queue_handle * h = xQueue; + return send_q(h->q, (uint8_t*)pvItemToQueue, h->item_size) ? pdTRUE : pdFAIL; +} + +uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait) +{ + struct queue_handle * h = xQueue; + return recv_q(h->q, (uint8_t*)pvBuffer, h->item_size, xTicksToWait) ? pdTRUE : pdFAIL; +} + +BaseType_t xSemaphoreGive( QueueHandle_t xQueue) +{ + return xQueueSend(xQueue, &s_semaphore_data, portMAX_DELAY); +} + +BaseType_t xSemaphoreTake( QueueHandle_t xQueue, TickType_t pvTask ) +{ + return xQueueReceive(xQueue, &s_semaphore_data, portMAX_DELAY); +} + +void vQueueDelete( QueueHandle_t xQueue ) +{ + struct queue_handle * h = xQueue; + if (h->q) { + destroy_q(h->q); + } + free(xQueue); +} + +QueueHandle_t xSemaphoreCreateBinary(void) +{ + QueueHandle_t sempaphore = xQueueCreate(1, 1); + return sempaphore; +} + +QueueHandle_t xSemaphoreCreateMutex(void) +{ + QueueHandle_t sempaphore = xQueueCreate(1, 1); + if (sempaphore) { + xSemaphoreGive(sempaphore); + } + return sempaphore; +} + +void vTaskDelete(TaskHandle_t *task) +{ + if (task == NULL) { + pthread_exit(0); + } + void *thread_rval = NULL; + pthread_join((pthread_t)task, &thread_rval); +} + +TickType_t xTaskGetTickCount( void ) +{ + struct timespec spec; + clock_gettime(CLOCK_REALTIME, &spec); + return spec.tv_nsec / 1000000 + spec.tv_sec * 1000; +} + +void vTaskDelay( const TickType_t xTicksToDelay ) +{ + usleep(xTicksToDelay*1000); +} + +void * pthread_task(void * params) +{ + struct { + void * const param; + TaskFunction_t task; + bool started; + } *pthread_params = params; + + void * const param = pthread_params->param; + TaskFunction_t task = pthread_params->task; + pthread_params->started = true; + + task(param); + + return NULL; +} + +BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, + const char * const pcName, + const uint32_t usStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pvCreatedTask, + const BaseType_t xCoreID) +{ + xTaskCreate(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask); + return pdTRUE; +} + + +void xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask) +{ + pthread_t new_thread = (pthread_t)NULL; + pthread_attr_t attr; + struct { + void * const param; + TaskFunction_t task; + bool started; + } pthread_params = { .param = pvParameters, .task = pvTaskCode}; + int res = pthread_attr_init(&attr); + assert(res == 0); + res = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + assert(res == 0); + res = pthread_create(&new_thread, &attr, pthread_task, &pthread_params); + assert(res == 0); + + if (pvCreatedTask) { + *pvCreatedTask = (void*)new_thread; + } + + // just wait till the task started so we can unwind params from the stack + while (pthread_params.started == false) { + usleep(1000); + } +} + +uint32_t esp_get_free_heap_size(void) +{ + return 0; +} + +uint32_t esp_random(void) +{ + return rand(); +} + +void xTaskNotifyGive(TaskHandle_t task) +{ + +} + +BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time ) +{ + return true; +} + +TaskHandle_t xTaskGetCurrentTaskHandle(void) +{ + return NULL; +} diff --git a/components/mdns/host_test/components/freertos_linux/include/esp_task.h b/components/mdns/host_test/components/freertos_linux/include/esp_task.h new file mode 100644 index 0000000000..797b416b6e --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/include/esp_task.h @@ -0,0 +1,20 @@ +// Copyright 2021 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. +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define ESP_TASK_PRIO_MAX 25 +#define ESP_TASKD_EVENT_PRIO 5 diff --git a/components/mdns/host_test/components/freertos_linux/include/freertos/FreeRTOS.h b/components/mdns/host_test/components/freertos_linux/include/freertos/FreeRTOS.h new file mode 100644 index 0000000000..ca13d0463f --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/include/freertos/FreeRTOS.h @@ -0,0 +1,46 @@ +// Copyright 2021 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. +#pragma once + +#include +#include +#include +#include + +#define portTICK_PERIOD_MS 1 +#define portMAX_DELAY ( TickType_t ) 0xffffffffUL + +typedef void * xSemaphoreHandle; +typedef void * SemaphoreHandle_t; +typedef void * xQueueHandle; +typedef void * QueueHandle_t; +typedef void * TaskHandle_t; +typedef uint32_t TickType_t; +typedef uint32_t portTickType; + +typedef void (*TaskFunction_t)( void * ); +typedef unsigned int UBaseType_t; +typedef int BaseType_t; + +#define pdFALSE ( ( BaseType_t ) 0 ) +#define pdTRUE ( ( BaseType_t ) 1 ) + +#define pdPASS ( pdTRUE ) +#define pdFAIL ( pdFALSE ) + +#define portTICK_RATE_MS portTICK_PERIOD_MS +#define pdMS_TO_TICKS(tick) (tick) + +uint32_t esp_get_free_heap_size(void); +uint32_t esp_random(void); diff --git a/components/mdns/host_test/components/freertos_linux/include/freertos/task.h b/components/mdns/host_test/components/freertos_linux/include/freertos/task.h new file mode 100644 index 0000000000..f6c213ed42 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/include/freertos/task.h @@ -0,0 +1,58 @@ +// Copyright 2021 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. +#pragma once + +#include "freertos/FreeRTOS.h" + +#define xTaskHandle TaskHandle_t +#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) ) + +void vTaskDelay( const TickType_t xTicksToDelay ); + +void xTaskNotifyGive(TaskHandle_t task); + +TaskHandle_t xTaskGetCurrentTaskHandle(void); + +BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time ); + +BaseType_t xTaskCreatePinnedToCore( TaskFunction_t pvTaskCode, + const char * const pcName, + const uint32_t usStackDepth, + void * const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t * const pvCreatedTask, + const BaseType_t xCoreID); + +void xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask); + +TickType_t xTaskGetTickCount( void ); + +void vQueueDelete( QueueHandle_t xQueue ); + +QueueHandle_t xSemaphoreCreateBinary(void); + +QueueHandle_t xSemaphoreCreateMutex(void); + +BaseType_t xSemaphoreGive( QueueHandle_t xQueue); + +BaseType_t xSemaphoreTake( QueueHandle_t xQueue, TickType_t pvTask ); + +void vTaskDelete(TaskHandle_t *task); + +QueueHandle_t xQueueCreate( uint32_t uxQueueLength, + uint32_t uxItemSize ); + +uint32_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait); + +uint32_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait); diff --git a/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.cpp b/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.cpp new file mode 100644 index 0000000000..60c4a8a285 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.cpp @@ -0,0 +1,51 @@ +// Copyright 2021 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. + +#include "queue_unique_ptr.hpp" +#include +#include +#include +#include + +extern "C" void * create_q(void) +{ + auto * q = new QueueMock>(); + return q; +} + +extern "C" void destroy_q(void* q) +{ + auto * queue = static_cast> *>(q); + delete(queue); +} + +extern "C" bool send_q(void* q, uint8_t *data, size_t len) +{ + auto v = std::make_unique>(len); + v->assign(data, data+len); + auto queue = static_cast> *>(q); + queue->send(std::move(v)); + return true; +} + +extern "C" bool recv_q(void* q, uint8_t *data, size_t len, uint32_t ms) +{ + auto queue = static_cast> *>(q); + auto v = queue->receive(ms); + if (v == nullptr) { + return false; + } + memcpy(data, (void *)v->data(), len); + return true; +} diff --git a/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.hpp b/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.hpp new file mode 100644 index 0000000000..fe722b97f0 --- /dev/null +++ b/components/mdns/host_test/components/freertos_linux/queue_unique_ptr.hpp @@ -0,0 +1,55 @@ +// Copyright 2021 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. + +#pragma once + +#include +#include +#include +#include +#include +#include + +template +class QueueMock +{ +public: + QueueMock(void): q(), m(), c() {} + ~QueueMock(void) {} + +void send(std::unique_ptr t) +{ + std::lock_guard lock(m); + q.push(std::move(t)); + c.notify_one(); +} + +std::unique_ptr receive(uint32_t ms) +{ + std::unique_lock lock(m); + while(q.empty()) { + if (c.wait_for(lock, std::chrono::milliseconds(ms)) == std::cv_status::timeout) { + return nullptr; + } + } + std::unique_ptr val = std::move(q.front()); + q.pop(); + return val; +} + +private: + std::queue> q; + mutable std::mutex m; + std::condition_variable c; +}; diff --git a/components/mdns/host_test/main/CMakeLists.txt b/components/mdns/host_test/main/CMakeLists.txt new file mode 100644 index 0000000000..8d5202d647 --- /dev/null +++ b/components/mdns/host_test/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS + "." + REQUIRES mdns) diff --git a/components/mdns/host_test/main/main.c b/components/mdns/host_test/main/main.c new file mode 100644 index 0000000000..f3883b8c35 --- /dev/null +++ b/components/mdns/host_test/main/main.c @@ -0,0 +1,59 @@ +#include +#include "mdns.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static const char *TAG = "mdns-test"; + +static void query_mdns_host(const char * host_name) +{ + ESP_LOGI(TAG, "Query A: %s.local", host_name); + + struct esp_ip4_addr addr; + addr.addr = 0; + + esp_err_t err = mdns_query_a(host_name, 2000, &addr); + if(err){ + if(err == ESP_ERR_NOT_FOUND){ + ESP_LOGW(TAG, "%x: Host was not found!", (err)); + return; + } + ESP_LOGE(TAG, "Query Failed: %x", (err)); + return; + } + + ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr)); +} + +int main(int argc , char *argv[]) +{ + + setvbuf(stdout, NULL, _IONBF, 0); + const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = "eth2" }; + esp_netif_config_t cfg = { .base = &base_cg }; + esp_netif_t *sta = esp_netif_new(&cfg); + + mdns_init(); + + mdns_hostname_set("myesp"); + ESP_LOGI(TAG, "mdns hostname set to: [%s]", "myesp"); + //set default mDNS instance name + mdns_instance_name_set("myesp-inst"); + //structure with TXT records + mdns_txt_item_t serviceTxtData[3] = { + {"board","esp32"}, + {"u","user"}, + {"p","password"} + }; + vTaskDelay(1000); + ESP_ERROR_CHECK(mdns_service_add("myesp-service2", "_http", "_tcp", 80, serviceTxtData, 3)); + vTaskDelay(2000); + + query_mdns_host("david-comp"); + vTaskDelay(2000); + esp_netif_destroy(sta); + mdns_free(); + ESP_LOGI(TAG, "Exit"); + return 0; +} diff --git a/components/mdns/mdns_networking.c b/components/mdns/mdns_networking_lwip.c similarity index 100% rename from components/mdns/mdns_networking.c rename to components/mdns/mdns_networking_lwip.c diff --git a/components/mdns/mdns_networking_socket.c b/components/mdns/mdns_networking_socket.c new file mode 100644 index 0000000000..55533a9f7f --- /dev/null +++ b/components/mdns/mdns_networking_socket.c @@ -0,0 +1,504 @@ +// Copyright 2021 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. + +/** + * @brief MDNS Server Networking module implemented using BSD sockets + */ + +#include +#include "esp_event.h" +#include "mdns_networking.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "esp_log.h" + +#if defined(CONFIG_IDF_TARGET_LINUX) +#include +#include +#endif + +extern mdns_server_t * _mdns_server; + +static const char *TAG = "MDNS_Networking"; +static bool s_run_sock_recv_task = false; +static int create_socket(esp_netif_t *netif); +static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol); + +#if defined(CONFIG_IDF_TARGET_LINUX) +// Need to define packet buffer struct on linux +struct pbuf { + struct pbuf * next; + void * payload; + size_t tot_len; + size_t len; +}; +#else +// Compatibility define to access sock-addr struct the same way for lwip and linux +#define s6_addr32 un.u32_addr +#endif // CONFIG_IDF_TARGET_LINUX + +static void delete_socket(int sock) +{ + close(sock); +} + +static struct udp_pcb* sock_to_pcb(int sock) +{ + if (sock < 0) { + return NULL; + } + // Note: sock=0 is a valid descriptor, so save it as +1 ("1" is a valid pointer) + intptr_t sock_plus_one = sock + 1; + return (struct udp_pcb*)sock_plus_one; +} + +static int pcb_to_sock(struct udp_pcb* pcb) +{ + if (pcb == NULL) { + return -1; + } + intptr_t sock_plus_one = (intptr_t)pcb; + return sock_plus_one - 1; +} + +void* _mdns_get_packet_data(mdns_rx_packet_t *packet) +{ + return packet->pb->payload; +} + +size_t _mdns_get_packet_len(mdns_rx_packet_t *packet) +{ + return packet->pb->len; +} + +void _mdns_packet_free(mdns_rx_packet_t *packet) +{ + free(packet->pb->payload); + free(packet->pb); + free(packet); +} + +esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + struct udp_pcb * pcb = _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb; + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb = NULL; + if (_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb == NULL && + _mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb == NULL) { + // if the interface for both protocol uninitialized, close the interface socket + int sock = pcb_to_sock(pcb); + if (sock >= 0) { + delete_socket(sock); + } + } + + for (int i=0; iinterfaces[i].pcbs[j].pcb) + // If any of the interfaces/protocol initialized + return ESP_OK; + } + } + + // no interface alive, stop the rx task + s_run_sock_recv_task = false; + vTaskDelay(pdMS_TO_TICKS(500)); + return ESP_OK; +} + +#if defined(CONFIG_IDF_TARGET_LINUX) +#ifdef CONFIG_LWIP_IPV6 +static char* inet6_ntoa_r(struct in6_addr addr, char* ptr, size_t size) +{ + inet_ntop(AF_INET6, &(addr.s6_addr32[0]), ptr, size); + return ptr; +} +#endif // CONFIG_LWIP_IPV6 +static char* inet_ntoa_r(struct in_addr addr, char* ptr, size_t size) +{ + char * res = inet_ntoa(addr); + if (res && strlen(res) < size) { + strcpy(ptr, res); + } + return res; +} +#endif // CONFIG_IDF_TARGET_LINUX + +static inline char* get_string_address(struct sockaddr_storage *source_addr) +{ + static char address_str[40]; // 40=(8*4+7+term) is the max size of ascii IPv6 addr "XXXX:XX...XX:XXXX" + char *res = NULL; + // Convert ip address to string + if (source_addr->ss_family == PF_INET) { + res = inet_ntoa_r(((struct sockaddr_in *)source_addr)->sin_addr, address_str, sizeof(address_str)); + } +#ifdef CONFIG_LWIP_IPV6 + else if (source_addr->ss_family == PF_INET6) { + res = inet6_ntoa_r(((struct sockaddr_in6 *)source_addr)->sin6_addr, address_str, sizeof(address_str)); + } +#endif + if (!res) { + address_str[0] = '\0'; // Returns empty string if conversion didn't succeed + } + return address_str; +} + + +static inline size_t espaddr_to_inet(const esp_ip_addr_t *addr, const uint16_t port, const mdns_ip_protocol_t ip_protocol, struct sockaddr_storage *in_addr) +{ + size_t ss_addr_len = 0; + memset(in_addr, 0, sizeof(struct sockaddr_storage)); + if (ip_protocol == MDNS_IP_PROTOCOL_V4 && addr->type == ESP_IPADDR_TYPE_V4) { + in_addr->ss_family = PF_INET; +#if !defined(CONFIG_IDF_TARGET_LINUX) + in_addr->s2_len = sizeof(struct sockaddr_in); +#endif + ss_addr_len = sizeof(struct sockaddr_in); + struct sockaddr_in *in_addr_ip4 = (struct sockaddr_in *) in_addr; + in_addr_ip4->sin_port = port; + in_addr_ip4->sin_addr.s_addr = addr->u_addr.ip4.addr; + } +#if CONFIG_LWIP_IPV6 + else if (ip_protocol == MDNS_IP_PROTOCOL_V6 && addr->type == ESP_IPADDR_TYPE_V6) { + memset(in_addr, 0, sizeof(struct sockaddr_storage)); + in_addr->ss_family = PF_INET6; +#if !defined(CONFIG_IDF_TARGET_LINUX) + in_addr->s2_len = sizeof(struct sockaddr_in6); +#endif + ss_addr_len = sizeof(struct sockaddr_in6); + struct sockaddr_in6 * in_addr_ip6 = (struct sockaddr_in6 *)in_addr; + uint32_t *u32_addr = in_addr_ip6->sin6_addr.s6_addr32; + in_addr_ip6->sin6_port = port; + u32_addr[0] = addr->u_addr.ip6.addr[0]; + u32_addr[1] = addr->u_addr.ip6.addr[1]; + u32_addr[2] = addr->u_addr.ip6.addr[2]; + u32_addr[3] = addr->u_addr.ip6.addr[3]; + } +#endif // CONFIG_LWIP_IPV6 + return ss_addr_len; +} + +size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t * data, size_t len) +{ + int sock = pcb_to_sock(_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb); + if (sock < 0) { + return 0; + } + struct sockaddr_storage in_addr; + size_t ss_size = espaddr_to_inet(ip, htons(port), ip_protocol, &in_addr); + if (!ss_size) { + ESP_LOGE(TAG, "espaddr_to_inet() failed: Mismatch of IP protocols"); + return 0; + } + ESP_LOGD(TAG, "[sock=%d]: Sending to IP %s port %d", sock, get_string_address(&in_addr), port); + ssize_t actual_len = sendto(sock, data, len, 0, (struct sockaddr *)&in_addr, ss_size); + if (actual_len < 0) { + ESP_LOGE(TAG, "[sock=%d]: _mdns_udp_pcb_write sendto() has failed\n error=%d: %s", sock, errno, strerror(errno)); + } + return actual_len; +} + +static inline void inet_to_espaddr(const struct sockaddr_storage *in_addr, esp_ip_addr_t *addr, uint16_t *port) +{ + if (in_addr->ss_family == PF_INET) { + struct sockaddr_in * in_addr_ip4 = (struct sockaddr_in *)in_addr; + memset(addr, 0, sizeof(esp_ip_addr_t)); + *port = in_addr_ip4->sin_port; + addr->u_addr.ip4.addr = in_addr_ip4->sin_addr.s_addr; + addr->type = ESP_IPADDR_TYPE_V4; + } +#if CONFIG_LWIP_IPV6 + else if (in_addr->ss_family == PF_INET6) { + struct sockaddr_in6 * in_addr_ip6 = (struct sockaddr_in6 *)in_addr; + memset(addr, 0, sizeof(esp_ip_addr_t)); + *port = in_addr_ip6->sin6_port; + uint32_t *u32_addr = in_addr_ip6->sin6_addr.s6_addr32; + if (u32_addr[0] == 0 && u32_addr[1] == 0 && u32_addr[2] == esp_netif_htonl(0x0000FFFFUL)) { + // Mapped IPv4 address, convert directly to IPv4 + addr->type = ESP_IPADDR_TYPE_V4; + addr->u_addr.ip4.addr = u32_addr[3]; + } else { + addr->type = ESP_IPADDR_TYPE_V6; + addr->u_addr.ip6.addr[0] = u32_addr[0]; + addr->u_addr.ip6.addr[1] = u32_addr[1]; + addr->u_addr.ip6.addr[2] = u32_addr[2]; + addr->u_addr.ip6.addr[3] = u32_addr[3]; + } + } +#endif // CONFIG_LWIP_IPV6 +} + +void sock_recv_task(void* arg) +{ + while (s_run_sock_recv_task) { + struct timeval tv = { + .tv_sec = 1, + .tv_usec = 0, + }; + fd_set rfds; + FD_ZERO(&rfds); + int max_sock = -1; + for (int i=0; iinterfaces[i].pcbs[j].pcb); + if (sock >= 0) { + FD_SET(sock, &rfds); + max_sock = MAX(max_sock, sock); + } + } + } + if (max_sock < 0) { + vTaskDelay(pdMS_TO_TICKS(1000)); + ESP_LOGI(TAG, "No sock!"); + continue; + } + + int s = select(max_sock + 1, &rfds, NULL, NULL, &tv); + if (s < 0) { + ESP_LOGE(TAG, "Select failed: errno %d", errno); + break; + } else if (s > 0) { + for (int tcpip_if=0; tcpip_ifinterfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V4].pcb); + if (sock < 0) { + sock = pcb_to_sock(_mdns_server->interfaces[tcpip_if].pcbs[MDNS_IP_PROTOCOL_V6].pcb); + } + if (sock < 0) { + continue; + } + if (FD_ISSET(sock, &rfds)) { + static char recvbuf[MDNS_MAX_PACKET_SIZE]; + uint16_t port = 0; + + struct sockaddr_storage raddr; // Large enough for both IPv4 or IPv6 + socklen_t socklen = sizeof(struct sockaddr_storage); + esp_ip_addr_t addr = {0}; + int len = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, + (struct sockaddr *) &raddr, &socklen); + if (len < 0) { + ESP_LOGE(TAG, "multicast recvfrom failed: errno %d", errno); + break; + } + ESP_LOGD(TAG, "[sock=%d]: Received from IP:%s", sock, get_string_address(&raddr)); + ESP_LOG_BUFFER_HEXDUMP(TAG, recvbuf, len, ESP_LOG_VERBOSE); + inet_to_espaddr(&raddr, &addr, &port); + + // Allocate the packet structure and pass it to the mdns main engine + mdns_rx_packet_t *packet = (mdns_rx_packet_t *) calloc(1, sizeof(mdns_rx_packet_t)); + struct pbuf *packet_pbuf = calloc(1, sizeof(struct pbuf)); + uint8_t *buf = malloc(len); + if (packet == NULL || packet_pbuf == NULL || buf == NULL ) { + free(buf); + free(packet_pbuf); + free(packet); + HOOK_MALLOC_FAILED; + ESP_LOGE(TAG, "Failed to allocate the mdns packet"); + continue; + } + memcpy(buf, recvbuf, len); + packet_pbuf->next = NULL; + packet_pbuf->payload = buf; + packet_pbuf->tot_len = len; + packet_pbuf->len = len; + packet->tcpip_if = tcpip_if; + packet->pb = packet_pbuf; + packet->src_port = ntohs(port); + memcpy(&packet->src, &addr, sizeof(esp_ip_addr_t)); + // TODO(IDF-3651): Add the correct dest addr -- for mdns to decide multicast/unicast + // Currently it's enough to assume the packet is multicast and mdns to check the source port of the packet + memset(&packet->dest, 0, sizeof(esp_ip_addr_t)); + packet->multicast = 1; + packet->dest.type = packet->src.type; + packet->ip_protocol = + packet->src.type == ESP_IPADDR_TYPE_V4 ? MDNS_IP_PROTOCOL_V4 : MDNS_IP_PROTOCOL_V6; + if (!_mdns_server || !_mdns_server->action_queue || _mdns_send_rx_action(packet) != ESP_OK) { + ESP_LOGE(TAG, "_mdns_send_rx_action failed!"); + free(packet->pb->payload); + free(packet->pb); + free(packet); + } + } + } + } + } + vTaskDelete(NULL); +} + +static void mdns_networking_init(void) +{ + if (s_run_sock_recv_task == false) { + s_run_sock_recv_task = true; + xTaskCreate( sock_recv_task, "mdns recv task", 3*1024, NULL, 5, NULL ); + } +} + +static struct udp_pcb* create_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + if (_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb) { + return _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb; + } + mdns_ip_protocol_t other_ip_proto = ip_protocol==MDNS_IP_PROTOCOL_V4?MDNS_IP_PROTOCOL_V6:MDNS_IP_PROTOCOL_V4; + esp_netif_t *netif = _mdns_get_esp_netif(tcpip_if); + if (_mdns_server->interfaces[tcpip_if].pcbs[other_ip_proto].pcb) { + struct udp_pcb* other_pcb = _mdns_server->interfaces[tcpip_if].pcbs[other_ip_proto].pcb; + int err = join_mdns_multicast_group(pcb_to_sock(other_pcb), netif, ip_protocol); + if (err < 0) { + ESP_LOGE(TAG, "Failed to add ipv6 multicast group for protocol %d", ip_protocol); + return NULL; + } + return other_pcb; + } + int sock = create_socket(netif); + if (sock < 0) { + ESP_LOGE(TAG, "Failed to create the socket!"); + return NULL; + } + int err = join_mdns_multicast_group(sock, netif, ip_protocol); + if (err < 0) { + ESP_LOGE(TAG, "Failed to add ipv6 multicast group for protocol %d", ip_protocol); + } + return sock_to_pcb(sock); +} + +esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol) +{ + ESP_LOGI(TAG, "_mdns_pcb_init(tcpip_if=%d, ip_protocol=%d)", tcpip_if, ip_protocol); + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].pcb = create_pcb(tcpip_if, ip_protocol); + _mdns_server->interfaces[tcpip_if].pcbs[ip_protocol].failed_probes = 0; + + mdns_networking_init(); + return ESP_OK; +} + +static int create_socket(esp_netif_t *netif) +{ +#if CONFIG_LWIP_IPV6 + int sock = socket(PF_INET6, SOCK_DGRAM, 0); +#else + int sock = socket(PF_INET, SOCK_DGRAM, 0); +#endif + if (sock < 0) { + ESP_LOGE(TAG, "Failed to create socket. Error %d", errno); + return -1; + } + + int on = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0) { + ESP_LOGE(TAG, "setsockopt SO_REUSEADDR: %s\n", strerror(errno)); + } + // Bind the socket to any address +#if CONFIG_LWIP_IPV6 + struct sockaddr_in6 saddr = { INADDR_ANY }; + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(5353); + bzero(&saddr.sin6_addr.s6_addr, sizeof(saddr.sin6_addr.s6_addr)); + int err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)); + if (err < 0) { + ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno); + goto err; + } +#else + struct sockaddr_in saddr = { 0 }; + saddr.sin_family = AF_INET; + saddr.sin_port = htons(5353); + bzero(&saddr.sin_addr.s_addr, sizeof(saddr.sin_addr.s_addr)); + int err = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); + if (err < 0) { + ESP_LOGE(TAG, "Failed to bind socket. Error %d", errno); + goto err; + } +#endif // CONFIG_LWIP_IPV6 + struct ifreq ifr; + esp_netif_get_netif_impl_name(netif, ifr.ifr_name); + int ret = setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(struct ifreq)); + if (ret < 0) { + ESP_LOGE(TAG, "\"%s\" Unable to bind socket to specified interface: errno %d", esp_netif_get_desc(netif), errno); + goto err; + } + + return sock; + +err: + close(sock); + return -1; +} + +#if CONFIG_LWIP_IPV6 +static int socket_add_ipv6_multicast_group(int sock, esp_netif_t *netif) +{ + int ifindex = esp_netif_get_netif_impl_index(netif); + int err = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)); + if (err < 0) { + ESP_LOGE(TAG, "Failed to set IPV6_MULTICAST_IF. Error %d", errno); + return err; + } + + struct ipv6_mreq v6imreq = { 0 }; + esp_ip_addr_t multi_addr = ESP_IP6ADDR_INIT(0x000002ff, 0, 0, 0xfb000000); + memcpy(&v6imreq.ipv6mr_multiaddr, &multi_addr.u_addr.ip6.addr, sizeof(v6imreq.ipv6mr_multiaddr)); + v6imreq.ipv6mr_interface = ifindex; + err = setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &v6imreq, sizeof(struct ipv6_mreq)); + if (err < 0) { + ESP_LOGE(TAG, "Failed to set IPV6_ADD_MEMBERSHIP. Error %d", errno); + return err; + } + return err; +} +#endif // CONFIG_LWIP_IPV6 + +static int socket_add_ipv4_multicast_group(int sock, esp_netif_t *netif) +{ + struct ip_mreq imreq = { 0 }; + int err = 0; + esp_netif_ip_info_t ip_info = { 0 }; + + if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) { + ESP_LOGE(TAG, "Failed to esp_netif_get_ip_info()"); + goto err; + } + imreq.imr_interface.s_addr = ip_info.ip.addr; + + esp_ip_addr_t multicast_addr = ESP_IP4ADDR_INIT(224, 0, 0, 251); + imreq.imr_multiaddr.s_addr = multicast_addr.u_addr.ip4.addr; + + err = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq)); + if (err < 0) { + ESP_LOGE(TAG, "%d %s", sock, strerror(errno)); + ESP_LOGE(TAG, "Failed to set IP_ADD_MEMBERSHIP. Error %d", errno); + goto err; + } + + err: + return err; +} + +static int join_mdns_multicast_group(int sock, esp_netif_t *netif, mdns_ip_protocol_t ip_protocol) +{ + if (ip_protocol == MDNS_IP_PROTOCOL_V4) { + return socket_add_ipv4_multicast_group(sock, netif); + } +#if CONFIG_LWIP_IPV6 + if (ip_protocol == MDNS_IP_PROTOCOL_V6) { + return socket_add_ipv6_multicast_group(sock, netif); + } +#endif // CONFIG_LWIP_IPV6 + return -1; +} diff --git a/components/mdns/private_include/mdns_private.h b/components/mdns/private_include/mdns_private.h index 6931d4f5a7..5e6deeab5a 100644 --- a/components/mdns/private_include/mdns_private.h +++ b/components/mdns/private_include/mdns_private.h @@ -14,8 +14,10 @@ #ifndef MDNS_PRIVATE_H_ #define MDNS_PRIVATE_H_ +#include "sdkconfig.h" #include "mdns.h" #include "esp_task.h" +#include "esp_timer.h" //#define MDNS_ENABLE_DEBUG @@ -248,7 +250,6 @@ typedef struct mdns_parsed_record_s { typedef struct { mdns_if_t tcpip_if; mdns_ip_protocol_t ip_protocol; - //struct udp_pcb *pcb; esp_ip_addr_t src; uint16_t src_port; uint8_t multicast;