diff --git a/components/mdns/include/mdns.h b/components/mdns/include/mdns.h index 35c8d538a..8c58687b5 100644 --- a/components/mdns/include/mdns.h +++ b/components/mdns/include/mdns.h @@ -1,16 +1,8 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef ESP_MDNS_H_ #define ESP_MDNS_H_ @@ -500,6 +492,24 @@ esp_err_t mdns_service_txt_item_remove(const char * service_type, const char * p esp_err_t mdns_service_txt_item_remove_for_host(const char * service_type, const char * proto, const char * hostname, const char * key); +/** + * @brief Add subtype for service. + * + * @param instance_name instance name. If NULL, will find the first service with the same service type and protocol. + * @param service_type service type (_http, _ftp, etc) + * @param proto service protocol (_tcp, _udp) + * @param hostname service hostname. If NULL, local hostname will be used. + * @param subtype The subtype to add. + * + * @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_subtype_add_for_host(const char *instance_name, const char *service_type, const char *proto, + const char *hostname, const char *subtype); + /** * @brief Remove and free all services from mDNS server * diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index 8e8a86436..0ce3af28a 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -15,6 +15,10 @@ void mdns_debug_packet(const uint8_t * data, size_t len); #endif +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) +#endif + // Internal size of IPv6 address is defined here as size of AAAA record in mdns packet // since the ip6_addr_t is defined in lwip and depends on using IPv6 zones #define _MDNS_SIZEOF_IP6_ADDR (MDNS_ANSWER_AAAA_SIZE) @@ -174,6 +178,24 @@ static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char return NULL; } +static mdns_srv_item_t * _mdns_get_service_item_subtype(const char *subtype, const char * service, const char * proto) +{ + mdns_srv_item_t * s = _mdns_server->services; + while (s) { + if (_mdns_service_match(s->service, service, proto, NULL)) { + mdns_subtype_t *subtype_item = s->service->subtype; + while(subtype_item) { + if (!strcasecmp(subtype_item->subtype, subtype)) { + return s; + } + subtype_item = subtype_item->next; + } + } + s = s->next; + } + return NULL; +} + static mdns_host_item_t * mdns_get_host_item(const char * hostname) { if (hostname == NULL || strcasecmp(hostname, _mdns_server->hostname) == 0) { @@ -605,6 +627,54 @@ static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, cons return record_length; } +/** + * @brief appends PTR record for a subtype to a packet, incrementing the index + * + * @param packet MDNS packet + * @param index offset in the packet + * @param instance the service instance name + * @param subtype the service subtype + * @param proto the service protocol + * @param flush whether to set the flush flag + * @param bye whether to set the bye flag + * + * @return length of added data: 0 on error or length on success + */ +static uint16_t _mdns_append_subtype_ptr_record(uint8_t *packet, uint16_t *index, const char *instance, + const char *subtype, const char *service, const char *proto, bool flush, + bool bye) +{ + const char *subtype_str[5] = {subtype, MDNS_SUB_STR, service, proto, MDNS_DEFAULT_DOMAIN}; + const char *instance_str[4] = {instance, service, proto, MDNS_DEFAULT_DOMAIN}; + uint16_t record_length = 0; + uint8_t part_length; + + if (service == NULL) { + return 0; + } + + part_length = _mdns_append_fqdn(packet, index, subtype_str, ARRAY_SIZE(subtype_str)); + if (!part_length) { + return 0; + } + record_length += part_length; + + part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, false, bye ? 0 : MDNS_ANSWER_PTR_TTL); + if (!part_length) { + return 0; + } + record_length += part_length; + + uint16_t data_len_location = *index - 2; + part_length = _mdns_append_fqdn(packet, index, instance_str, ARRAY_SIZE(instance_str)); + if (!part_length) { + return 0; + } + _mdns_set_u16(packet, data_len_location, part_length); + record_length += part_length; + return record_length; +} + /** * @brief appends DNS-SD PTR record for service to a packet, incrementing the index * @@ -1010,6 +1080,33 @@ static uint8_t _mdns_append_host_answer(uint8_t * packet, uint16_t * index, mdns return num_records; } +/** + * @brief Append PTR answers to packet + * + * @return number of answers added to the packet + */ +static uint8_t _mdns_append_service_ptr_answers(uint8_t *packet, uint16_t *index, mdns_service_t *service, bool flush, + bool bye) +{ + uint8_t appended_answers = 0; + + if (_mdns_append_ptr_record(packet, index, _mdns_get_service_instance_name(service), service->service, + service->proto, flush, bye) <= 0) { + return appended_answers; + } + + mdns_subtype_t *subtype = service->subtype; + while (subtype) { + appended_answers += + (_mdns_append_subtype_ptr_record(packet, index, _mdns_get_service_instance_name(service), subtype->subtype, + service->service, service->proto, flush, bye) > 0); + subtype = subtype->next; + } + + return appended_answers; +} + + /** * @brief Append answer to packet * @@ -1018,12 +1115,8 @@ static uint8_t _mdns_append_host_answer(uint8_t * packet, uint16_t * index, mdns static uint8_t _mdns_append_answer(uint8_t * packet, uint16_t * index, mdns_out_answer_t * answer, mdns_if_t tcpip_if) { if (answer->type == MDNS_TYPE_PTR) { - if (answer->service) { - return _mdns_append_ptr_record(packet, index, - _mdns_get_service_instance_name(answer->service), - answer->service->service, answer->service->proto, - answer->flush, answer->bye) > 0; + return _mdns_append_service_ptr_answers(packet, index, answer->service, answer->flush, answer->bye); } else { return _mdns_append_ptr_record(packet, index, answer->custom_instance, answer->custom_service, answer->custom_proto, @@ -1435,6 +1528,24 @@ static bool _mdns_create_answer_from_hostname(mdns_tx_packet_t * packet, const c return true; } +static bool _mdns_service_match_ptr_question(const mdns_service_t *service, const mdns_parsed_question_t *question) +{ + if (!_mdns_service_match(service, question->service, question->proto, NULL)) { + return false; + } + if (question->sub) { + mdns_subtype_t *subtype = service->subtype; + while (subtype) { + if (!strcasecmp(subtype->subtype, question->host)) { + return true; + } + subtype = subtype->next; + } + return false; + } + return true; +} + /** * @brief Create answer packet to questions from parsed packet */ @@ -1466,7 +1577,7 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t *parsed_ } else if (q->service && q->proto) { mdns_srv_item_t *service = _mdns_server->services; while (service) { - if (_mdns_service_match(service->service, q->service, q->proto, NULL)) { + if (_mdns_service_match_ptr_question(service->service, q)) { if (!_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) { _mdns_free_tx_packet(packet); return; @@ -2179,6 +2290,7 @@ static mdns_service_t * _mdns_create_service(const char * service, const char * s->instance = instance?strndup(instance, MDNS_NAME_BUF_LEN - 1):NULL; s->txt = new_txt; s->port = port; + s->subtype = NULL; if (hostname) { s->hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1); @@ -2338,7 +2450,12 @@ static void _mdns_free_service(mdns_service_t * service) free((char *)s->value); free(s); } - free(service->txt); + while (service->subtype) { + mdns_subtype_t * next = service->subtype->next; + free((char *)service->subtype->subtype); + free(service->subtype); + service->subtype = next; + } free(service); } @@ -2701,9 +2818,12 @@ static bool _mdns_name_is_ours(mdns_name_t * name) return false; } + //find the service mdns_srv_item_t * service; - if (_str_null_or_empty(name->host)) { + if (name->sub) { + service = _mdns_get_service_item_subtype(name->host, name->service, name->proto); + } else if (_str_null_or_empty(name->host)) { service = _mdns_get_service_item(name->service, name->proto, NULL); } else { service = _mdns_get_service_item_instance(name->host, name->service, name->proto, NULL); @@ -2712,8 +2832,8 @@ static bool _mdns_name_is_ours(mdns_name_t * name) return false; } - //if host is empty and we have service, we have success - if (_str_null_or_empty(name->host)) { + //if query is PTR query and we have service, we have success + if (name->sub || _str_null_or_empty(name->host)) { return true; } @@ -3122,7 +3242,8 @@ void mdns_parse_packet(mdns_rx_packet_t * packet) a = a->next; } continue; - } else if (name->sub || !_mdns_name_is_ours(name)) { + } + if (!_mdns_name_is_ours(name)) { continue; } @@ -3140,6 +3261,7 @@ void mdns_parse_packet(mdns_rx_packet_t * packet) question->unicast = unicast; question->type = type; + question->sub = name->sub; if (_mdns_strdup_check(&(question->host), name->host) || _mdns_strdup_check(&(question->service), name->service) || _mdns_strdup_check(&(question->proto), name->proto) @@ -4247,6 +4369,9 @@ static void _mdns_free_action(mdns_action_t * action) case ACTION_SERVICE_TXT_DEL: free(action->data.srv_txt_del.key); break; + case ACTION_SERVICE_SUBTYPE_ADD: + free(action->data.srv_subtype_add.subtype); + break; case ACTION_SEARCH_ADD: //fallthrough case ACTION_SEARCH_SEND: @@ -4282,6 +4407,8 @@ static void _mdns_execute_action(mdns_action_t * action) mdns_service_t * service; char * key; char * value; + char *subtype; + mdns_subtype_t *subtype_item; mdns_txt_linked_item_t * txt, * t; switch(action->type) { @@ -4395,6 +4522,19 @@ static void _mdns_execute_action(mdns_action_t * action) _mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false); + break; + case ACTION_SERVICE_SUBTYPE_ADD: + service = action->data.srv_subtype_add.service->service; + subtype = action->data.srv_subtype_add.subtype; + subtype_item = (mdns_subtype_t *)malloc(sizeof(mdns_subtype_t)); + if (!subtype_item) { + HOOK_MALLOC_FAILED; + _mdns_free_action(action); + return; + } + subtype_item->subtype = subtype; + subtype_item->next = service->subtype; + service->subtype = subtype_item; break; case ACTION_SERVICE_DEL: a = _mdns_server->services; @@ -5249,6 +5389,40 @@ esp_err_t mdns_service_txt_item_remove(const char * service, const char * proto, return mdns_service_txt_item_remove_for_host(service, proto, _mdns_server->hostname, key); } +esp_err_t mdns_service_subtype_add_for_host(const char *instance_name, const char *service, const char *proto, + const char *hostname, const char *subtype) +{ + if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) || + _str_null_or_empty(subtype)) { + return ESP_ERR_INVALID_ARG; + } + mdns_srv_item_t * s = _mdns_get_service_item_instance(instance_name, service, proto, hostname); + + if (!s) { + return ESP_ERR_NOT_FOUND; + } + mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t)); + if (!action) { + HOOK_MALLOC_FAILED; + return ESP_ERR_NO_MEM; + } + + action->type = ACTION_SERVICE_SUBTYPE_ADD; + action->data.srv_subtype_add.service = s; + action->data.srv_subtype_add.subtype = strdup(subtype); + + if (!action->data.srv_subtype_add.subtype) { + free(action); + return ESP_ERR_NO_MEM; + } + if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) { + free(action->data.srv_subtype_add.subtype); + free(action); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + esp_err_t mdns_service_instance_name_set_for_host(const char * service, const char * proto, const char * hostname, const char * instance) { diff --git a/components/mdns/private_include/mdns_private.h b/components/mdns/private_include/mdns_private.h index 059d1ad25..a92fb8dd2 100644 --- a/components/mdns/private_include/mdns_private.h +++ b/components/mdns/private_include/mdns_private.h @@ -1,16 +1,8 @@ -// Copyright 2015-2017 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. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef MDNS_PRIVATE_H_ #define MDNS_PRIVATE_H_ @@ -176,6 +168,7 @@ typedef enum { ACTION_SERVICE_TXT_REPLACE, ACTION_SERVICE_TXT_SET, ACTION_SERVICE_TXT_DEL, + ACTION_SERVICE_SUBTYPE_ADD, ACTION_SERVICES_CLEAR, ACTION_SEARCH_ADD, ACTION_SEARCH_SEND, @@ -225,6 +218,7 @@ typedef struct { typedef struct mdns_parsed_question_s { struct mdns_parsed_question_s * next; uint16_t type; + bool sub; bool unicast; char * host; char * service; @@ -279,6 +273,11 @@ typedef struct mdns_txt_linked_item_s { struct mdns_txt_linked_item_s * next; /*!< next result, or NULL for the last result in the list */ } mdns_txt_linked_item_t; +typedef struct mdns_subtype_s { + const char *subtype; /*!< subtype */ + struct mdns_subtype_s * next; /*!< next result, or NULL for the last result in the list */ +} mdns_subtype_t; + typedef struct { const char * instance; const char * service; @@ -288,6 +287,7 @@ typedef struct { uint16_t weight; uint16_t port; mdns_txt_linked_item_t * txt; + mdns_subtype_t *subtype; } mdns_service_t; typedef struct mdns_srv_item_s { @@ -431,6 +431,10 @@ typedef struct { mdns_srv_item_t * service; char * key; } srv_txt_del; + struct { + mdns_srv_item_t * service; + char * subtype; + } srv_subtype_add; struct { mdns_search_once_t * search; } search_add; diff --git a/examples/protocols/mdns/main/mdns_example_main.c b/examples/protocols/mdns/main/mdns_example_main.c index ad347ddb2..f53e06031 100644 --- a/examples/protocols/mdns/main/mdns_example_main.c +++ b/examples/protocols/mdns/main/mdns_example_main.c @@ -53,6 +53,7 @@ static void initialise_mdns(void) //initialize service ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, 3) ); + ESP_ERROR_CHECK( mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server") ); #if CONFIG_MDNS_MULTIPLE_INSTANCE ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer1", "_http", "_tcp", 80, NULL, 0) ); #endif