Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
thread_border_router_api.c
00001 /* 00002 * Copyright (c) 2014-2018, Arm Limited and affiliates. 00003 * SPDX-License-Identifier: BSD-3-Clause 00004 * 00005 * Redistribution and use in source and binary forms, with or without 00006 * modification, are permitted provided that the following conditions are met: 00007 * 00008 * 1. Redistributions of source code must retain the above copyright 00009 * notice, this list of conditions and the following disclaimer. 00010 * 2. Redistributions in binary form must reproduce the above copyright 00011 * notice, this list of conditions and the following disclaimer in the 00012 * documentation and/or other materials provided with the distribution. 00013 * 3. Neither the name of the copyright holder nor the 00014 * names of its contributors may be used to endorse or promote products 00015 * derived from this software without specific prior written permission. 00016 * 00017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 00018 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 00019 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 00020 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 00021 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00022 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 00023 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 00024 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 00025 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 00026 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00027 * POSSIBILITY OF SUCH DAMAGE. 00028 */ 00029 #include "nsconfig.h" 00030 00031 #include <string.h> 00032 #include "ns_types.h" 00033 #include <nsdynmemLIB.h> 00034 #include "eventOS_event.h" 00035 #include <ns_list.h> 00036 #include "ns_trace.h" 00037 #include "Core/include/ns_buffer.h" 00038 #include "common_functions.h" 00039 #include "randLIB.h" 00040 #include "thread_border_router_api.h" 00041 #include "thread_management_if.h" 00042 #include "NWK_INTERFACE/Include/protocol.h" 00043 #include "6LoWPAN/Thread/thread_config.h" 00044 #include "6LoWPAN/Thread/thread_common.h" 00045 #include "6LoWPAN/Thread/thread_network_data_lib.h" 00046 #include "6LoWPAN/Thread/thread_network_data_storage.h" 00047 #include "6LoWPAN/Thread/thread_management_client.h" 00048 #include "6LoWPAN/Thread/thread_joiner_application.h" 00049 #include "6LoWPAN/Thread/thread_tmfcop_lib.h" 00050 #include "6LoWPAN/Thread/thread_border_router_api_internal.h" 00051 #include "6LoWPAN/Thread/thread_bbr_commercial.h" 00052 #include "6LoWPAN/Thread/thread_mdns.h" 00053 #include "6LoWPAN/Bootstraps/protocol_6lowpan.h" 00054 #include "6LoWPAN/MAC/mac_helper.h" 00055 #include "MLE/mle.h" 00056 #include "thread_meshcop_lib.h" 00057 #include "thread_network_data_lib.h" 00058 #include "coap_service_api.h" 00059 00060 #define TRACE_GROUP "tBRa" 00061 00062 #ifdef HAVE_THREAD_ROUTER 00063 /* 00064 * Structure containing IPv6 Router advertisement options for DNS configuration. 00065 */ 00066 typedef struct { 00067 uint16_t option_length; 00068 uint8_t *option_data; 00069 } dns_option_t; 00070 00071 /* 00072 * Border router instance data. 00073 */ 00074 typedef struct { 00075 dns_option_t *dns_search_list_option; /* option_data encoded according to RFC6106, DNSSL */ 00076 dns_option_t *recursive_dns_server_option; /* option_data encoded according to RFC6106, RDNSS */ 00077 uint16_t nwk_data_resubmit_timer; /* network data resubmit timeout */ 00078 int8_t interface_id; 00079 int8_t coap_service_id; 00080 ns_list_link_t link; 00081 } thread_border_router_t; 00082 00083 /* Neighbor discovery options according to RFC6106 (rfc4861) */ 00084 #define RFC6106_RECURSIVE_DNS_SERVER_OPTION 25 00085 #define RFC6106_DNS_SEARCH_LIST_OPTION 31 00086 00087 static NS_LIST_DEFINE(border_router_instance_list, thread_border_router_t, link); 00088 00089 00090 static thread_border_router_t *thread_border_router_find_by_interface(int8_t interface_id) 00091 { 00092 thread_border_router_t *this = NULL; 00093 ns_list_foreach(thread_border_router_t, cur_br, &border_router_instance_list) { 00094 if (cur_br->interface_id == interface_id) { 00095 this = cur_br; 00096 break; 00097 } 00098 } 00099 return this; 00100 } 00101 00102 static thread_border_router_t *thread_border_router_find_by_service(int8_t service_id) 00103 { 00104 thread_border_router_t *this = NULL; 00105 ns_list_foreach(thread_border_router_t, cur_br, &border_router_instance_list) { 00106 if (cur_br->coap_service_id == service_id) { 00107 this = cur_br; 00108 break; 00109 } 00110 } 00111 return this; 00112 } 00113 00114 /* 00115 * Read Border Router TLV: DNS search list option. 00116 * DNSSL option data will be fetched from: 00117 * 1. User has provided static configuration by using method thread_border_router_dns_search_list_option_set. 00118 * 2. ICMP RA messages (if service is enabled and running in background). 00119 */ 00120 static uint8_t *thread_management_server_border_router_nd_dnssl_option_read(thread_border_router_t *this, uint16_t *resp_len) 00121 { 00122 if (this->dns_search_list_option) { 00123 *resp_len = this->dns_search_list_option->option_length; 00124 return (uint8_t *)&this->dns_search_list_option->option_data; 00125 } else { 00126 // TODO: Read DNSSL from stored ICMP RA messages. 00127 *resp_len = 0; 00128 return NULL; 00129 } 00130 } 00131 00132 /* 00133 * Read Border Router TLV: Recursive DNS Server option. 00134 * RDNSS option data will be fetched from: 00135 * 1. User has provided static configuration by using method thread_border_router_recursive_dns_server_option_set. 00136 * 2. ICMP RA messages (if service is enabled and running in background). 00137 */ 00138 static uint8_t *thread_management_server_border_router_nd_rdnss_option_read(thread_border_router_t *this, uint16_t *resp_len) 00139 { 00140 if (this->recursive_dns_server_option) { 00141 *resp_len = this->recursive_dns_server_option->option_length; 00142 return (uint8_t *)&this->recursive_dns_server_option->option_data; 00143 } else { 00144 // TODO: Read RDNSS from stored ICMP RA messages. 00145 *resp_len = 0; 00146 return NULL; 00147 } 00148 } 00149 00150 /** 00151 * Thread Neighbor discovery data request handler 00152 */ 00153 static int thread_border_router_neighbor_discovery_data_req_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr) 00154 { 00155 thread_border_router_t *this = thread_border_router_find_by_service(service_id); 00156 uint8_t *request_tlv_ptr; 00157 uint8_t *resp_payload_ptr; 00158 uint16_t options_len, rdnss_option_len, dnssl_option_len, payload_len; 00159 uint8_t options_data[4]; 00160 uint8_t *dns_server_tlv_ptr = NULL; 00161 uint8_t *dns_search_list_tlv_ptr = NULL; 00162 uint8_t *ptr; 00163 sn_coap_msg_code_e return_code = COAP_MSG_CODE_RESPONSE_CHANGED; 00164 (void) source_port; 00165 (void) source_address; 00166 00167 tr_debug("neighbor data request"); 00168 00169 if (!this) { 00170 return -1; 00171 } 00172 00173 request_tlv_ptr = options_data; 00174 rdnss_option_len = dnssl_option_len = 0; 00175 00176 options_len = thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ND_OPTION, &request_tlv_ptr); 00177 00178 while (options_len > 0) { 00179 switch (*request_tlv_ptr) { 00180 case RFC6106_RECURSIVE_DNS_SERVER_OPTION: 00181 dns_server_tlv_ptr = thread_management_server_border_router_nd_rdnss_option_read(this, &rdnss_option_len); 00182 break; 00183 case RFC6106_DNS_SEARCH_LIST_OPTION: 00184 dns_search_list_tlv_ptr = thread_management_server_border_router_nd_dnssl_option_read(this, &dnssl_option_len); 00185 break; 00186 } 00187 request_tlv_ptr++; 00188 options_len--; 00189 } 00190 00191 payload_len = rdnss_option_len + dnssl_option_len; 00192 00193 resp_payload_ptr = ptr = ns_dyn_mem_alloc(payload_len + 4); //reserve space also for type/length 00194 if (!resp_payload_ptr) { 00195 return_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR; 00196 goto send_response; 00197 } 00198 00199 *ptr++ = TMFCOP_TLV_ND_DATA; 00200 if (payload_len > 255) { 00201 *ptr++ = 0xff; 00202 ptr = common_write_16_bit(payload_len, ptr); 00203 } else { 00204 *ptr++ = payload_len; 00205 } 00206 00207 if (dns_server_tlv_ptr) { 00208 memcpy(ptr, dns_server_tlv_ptr, rdnss_option_len); 00209 ptr += rdnss_option_len; 00210 } 00211 00212 if (dns_search_list_tlv_ptr) { 00213 memcpy(ptr, dns_search_list_tlv_ptr, dnssl_option_len); 00214 ptr += dnssl_option_len; 00215 } 00216 00217 send_response: 00218 coap_service_response_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, request_ptr, return_code, COAP_CT_OCTET_STREAM, resp_payload_ptr, ptr - resp_payload_ptr); 00219 ns_dyn_mem_free(resp_payload_ptr); 00220 return 0; 00221 } 00222 /* 00223 * Check that local prefix is available in received network data 00224 */ 00225 static bool thread_border_router_network_data_prefix_match(thread_network_data_cache_entry_t *network_data_ptr, thread_network_local_data_entry_t *local_prefix, uint16_t router_id) 00226 { 00227 ns_list_foreach(thread_network_data_prefix_cache_entry_t, nwk_prefix, &network_data_ptr->localPrefixList ) { 00228 if (nwk_prefix->servicesPrefixLen != local_prefix->servicesPrefixLen) { 00229 continue; 00230 } 00231 00232 if (!bitsequal(nwk_prefix->servicesPrefix, local_prefix->servicesPrefix, local_prefix->servicesPrefixLen)) { 00233 continue; 00234 } 00235 00236 // check that prefix is hosted by this router 00237 if (!thread_nd_hosted_by_this_routerid(router_id, &nwk_prefix->routeList) && 00238 !thread_nd_hosted_by_this_routerid(router_id, &nwk_prefix->borderRouterList)) { 00239 return false; 00240 } 00241 00242 return true; 00243 } 00244 00245 return false; 00246 } 00247 00248 /* 00249 * Check that local service is available in network data 00250 */ 00251 static bool thread_border_router_network_data_service_match(thread_network_data_cache_entry_t *network_data_ptr, thread_network_data_service_entry_t *local_service, uint16_t router_id) 00252 { 00253 thread_network_data_service_cache_entry_t *service_cache_entry = thread_network_data_service_entry_find(&network_data_ptr->service_list, local_service); 00254 00255 if (service_cache_entry) { 00256 return thread_network_data_service_hosted_by_this_router_id(service_cache_entry, router_id); 00257 } 00258 00259 return false; 00260 } 00261 00262 static bool thread_border_router_local_network_data_prefix_match(thread_network_local_data_cache_entry_t *local_data, thread_network_data_prefix_cache_entry_t *prefix, uint16_t router_id) 00263 { 00264 bool instance_found = false; 00265 00266 if (thread_nd_hosted_by_this_routerid(router_id, &prefix->routeList)) { 00267 instance_found = true; 00268 } 00269 00270 if (thread_nd_hosted_by_this_routerid(router_id, &prefix->borderRouterList)) { 00271 instance_found = true; 00272 } 00273 00274 if (!instance_found) { 00275 /* Router ID not in propagated network data; skip */ 00276 return true; 00277 } 00278 00279 instance_found = false; 00280 00281 ns_list_foreach(thread_network_local_data_entry_t, localPrefix, &local_data->prefix_list) { 00282 if (prefix->servicesPrefixLen != localPrefix->servicesPrefixLen) { 00283 continue; 00284 } 00285 00286 if (bitsequal(prefix->servicesPrefix , localPrefix->servicesPrefix, localPrefix->servicesPrefixLen)) { 00287 /* Prefix exists in local data; return true */ 00288 instance_found = true; 00289 break; 00290 } 00291 } 00292 00293 if (!instance_found) { 00294 tr_debug("Found missing prefix: %s", trace_array(prefix->servicesPrefix , 8)); 00295 return false; 00296 } 00297 00298 return true; 00299 } 00300 00301 static void thread_border_router_child_network_data_clean(protocol_interface_info_entry_t *cur, uint16_t child_id) 00302 { 00303 uint8_t addr16_buf[2]; 00304 00305 common_write_16_bit(child_id, addr16_buf); 00306 if (mac_neighbor_table_address_discover(mac_neighbor_info(cur), addr16_buf, ADDR_802_15_4_SHORT )) { 00307 /* Child is available in mle, do nothing */ 00308 return; 00309 } 00310 00311 // Child is not our child => network data contains data from lost children, remove it 00312 tr_debug("Remove nwk data from lost child: %04x", child_id); 00313 thread_management_client_network_data_unregister(cur->id, child_id); 00314 } 00315 00316 static void thread_border_router_lost_children_nwk_data_validate(protocol_interface_info_entry_t *cur, uint16_t router_short_addr) 00317 { 00318 if (!thread_is_router_addr(router_short_addr)) { 00319 // not validating children nwk data 00320 return; 00321 } 00322 00323 thread_network_data_cache_entry_t *network_data = &cur->thread_info->networkDataStorage; 00324 00325 ns_list_foreach(thread_network_data_prefix_cache_entry_t, curLP, &network_data->localPrefixList ) { 00326 /* Go throgh all routes */ 00327 ns_list_foreach(thread_network_server_data_entry_t, curRoute, &curLP->routeList) { 00328 if (thread_addr_is_child(router_short_addr, curRoute->routerID)) { 00329 // Router children found 00330 thread_border_router_child_network_data_clean(cur, curRoute->routerID); 00331 } 00332 } 00333 00334 /* Go through all BR's */ 00335 ns_list_foreach(thread_network_server_data_entry_t, curBR, &curLP->borderRouterList) { 00336 if (thread_addr_is_child(router_short_addr, curBR->routerID)) { 00337 // Router children found 00338 thread_border_router_child_network_data_clean(cur, curBR->routerID); 00339 } 00340 } 00341 } 00342 00343 /* Go throgh all services */ 00344 ns_list_foreach(thread_network_data_service_cache_entry_t, service, &network_data->service_list) { 00345 ns_list_foreach(thread_network_data_service_server_entry_t, server, &service->server_list) { 00346 if (thread_addr_is_child(router_short_addr, server->router_id)) { 00347 // Router children found 00348 thread_border_router_child_network_data_clean(cur, server->router_id); 00349 } 00350 } 00351 } 00352 } 00353 00354 static bool thread_border_router_local_network_data_service_match(thread_network_local_data_cache_entry_t *local_data, thread_network_data_service_cache_entry_t *service, uint16_t router_id) 00355 { 00356 bool instance_found = false; 00357 00358 ns_list_foreach(thread_network_data_service_server_entry_t, server, &service->server_list) { 00359 if (server->router_id == router_id) { 00360 instance_found = true; 00361 break; 00362 } 00363 } 00364 00365 if (!instance_found) { 00366 /* Router ID not in propagated network data; skip */ 00367 return true; 00368 } 00369 00370 ns_list_foreach(thread_network_data_service_entry_t, local_service, &local_data->service_list) { 00371 if (local_service->S_service_data_length != service->S_service_data_length) { 00372 continue; 00373 } 00374 00375 if (memcmp(local_service->S_service_data, service->S_service_data, local_service->S_service_data_length) != 0) { 00376 continue; 00377 } 00378 00379 if (local_service->S_enterprise_number != service->S_enterprise_number) { 00380 continue; 00381 } 00382 00383 /* Service exists in local data; return true */ 00384 return true; 00385 } 00386 00387 tr_debug("Found missing service: %s", trace_array(service->S_service_data, service->S_service_data_length)); 00388 return false; 00389 } 00390 00391 /* 00392 * Check that local server data is available in network data 00393 */ 00394 static bool thread_border_router_local_srv_data_in_network_data_check(protocol_interface_info_entry_t *cur) 00395 { 00396 thread_network_data_cache_entry_t *network_data; 00397 thread_network_local_data_cache_entry_t *local_data; 00398 uint16_t router_id = cur->mac_parameters->mac_short_address; 00399 00400 network_data = &cur->thread_info->networkDataStorage; 00401 local_data = &cur->thread_info->localServerDataBase; 00402 00403 ns_list_foreach(thread_network_local_data_entry_t, localPrefix, &local_data->prefix_list) { 00404 // find matching prefix from network data 00405 if (thread_border_router_network_data_prefix_match(network_data, localPrefix, router_id) == false) { 00406 return false; 00407 } 00408 } 00409 00410 ns_list_foreach(thread_network_data_service_entry_t, localService, &local_data->service_list) { 00411 // find matching service from network data 00412 if (thread_border_router_network_data_service_match(network_data, localService, router_id) == false) { 00413 return false; 00414 } 00415 } 00416 00417 ns_list_foreach(thread_network_data_prefix_cache_entry_t, prefix, &network_data->localPrefixList ) { 00418 if (thread_border_router_local_network_data_prefix_match(local_data, prefix, router_id) == false) { 00419 return false; 00420 } 00421 } 00422 00423 ns_list_foreach(thread_network_data_service_cache_entry_t, service, &network_data->service_list) { 00424 if (thread_border_router_local_network_data_service_match(local_data, service, router_id) == false) { 00425 return false; 00426 } 00427 } 00428 00429 thread_border_router_lost_children_nwk_data_validate(cur, router_id); 00430 00431 return true; 00432 } 00433 00434 #ifdef HAVE_THREAD_BORDER_ROUTER 00435 static int thread_border_router_recursive_dns_server_option_store(int8_t interface_id, uint8_t *recursive_dns_server_option, uint16_t recursive_dns_server_option_len) 00436 { 00437 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00438 if (!this) { 00439 return -1; 00440 } 00441 00442 ns_dyn_mem_free(this->recursive_dns_server_option); 00443 00444 if (recursive_dns_server_option) { 00445 this->recursive_dns_server_option = ns_dyn_mem_alloc(sizeof(dns_option_t) + recursive_dns_server_option_len); 00446 if (!this->recursive_dns_server_option) { 00447 return -2; 00448 } 00449 memcpy(&this->recursive_dns_server_option->option_data, recursive_dns_server_option, recursive_dns_server_option_len); 00450 this->recursive_dns_server_option->option_length = recursive_dns_server_option_len; 00451 } 00452 return 0; 00453 } 00454 #endif 00455 00456 #ifdef HAVE_THREAD_BORDER_ROUTER 00457 static int thread_border_router_dns_search_list_option_store(int8_t interface_id, uint8_t *dns_search_list_option, uint16_t search_list_option_len) 00458 { 00459 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00460 if (!this) { 00461 return -1; 00462 } 00463 00464 ns_dyn_mem_free(this->dns_search_list_option); 00465 if (dns_search_list_option) { 00466 this->dns_search_list_option = ns_dyn_mem_alloc(sizeof(dns_option_t) + search_list_option_len); 00467 if (!this->dns_search_list_option) { 00468 return -2; 00469 } 00470 memcpy(&this->dns_search_list_option->option_data, dns_search_list_option, search_list_option_len); 00471 this->dns_search_list_option->option_length = search_list_option_len; 00472 } 00473 return 0; 00474 } 00475 #endif 00476 00477 int8_t thread_border_router_init(int8_t interface_id) 00478 { 00479 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00480 if (this) { 00481 return 0; 00482 } 00483 00484 tr_debug("thread_border_router_init if=%d", interface_id); 00485 00486 this = ns_dyn_mem_alloc(sizeof(thread_border_router_t)); 00487 if (!this) { 00488 return -2; 00489 } 00490 this->dns_search_list_option = NULL; 00491 this->recursive_dns_server_option = NULL; 00492 this->interface_id = interface_id; 00493 this->nwk_data_resubmit_timer = 0; 00494 this->coap_service_id = coap_service_initialize(this->interface_id, THREAD_MANAGEMENT_PORT, COAP_SERVICE_OPTIONS_NONE, NULL, NULL); 00495 if (this->coap_service_id < 0) { 00496 tr_warn("Thread border router coap init failed"); 00497 ns_dyn_mem_free(this); 00498 return -3; 00499 } 00500 // Register to Mesh CoAP URIs 00501 coap_service_register_uri(this->coap_service_id, THREAD_URI_NEIGHBOR_DISCOVERY_DATA_REQ, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_router_neighbor_discovery_data_req_cb); 00502 00503 ns_list_add_to_start(&border_router_instance_list, this); 00504 return 0; 00505 } 00506 00507 void thread_border_router_delete(int8_t interface_id) 00508 { 00509 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00510 if (!this) { 00511 return; 00512 } 00513 00514 coap_service_delete(this->coap_service_id); 00515 00516 ns_list_remove(&border_router_instance_list, this); 00517 ns_dyn_mem_free(this->dns_search_list_option); 00518 ns_dyn_mem_free(this->recursive_dns_server_option); 00519 ns_dyn_mem_free(this); 00520 } 00521 00522 void thread_border_router_seconds_timer(int8_t interface_id, uint32_t seconds) 00523 { 00524 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00525 if (!this) { 00526 return; 00527 } 00528 00529 if (this->nwk_data_resubmit_timer) { 00530 if (this->nwk_data_resubmit_timer > seconds) { 00531 this->nwk_data_resubmit_timer -= seconds; 00532 } else { 00533 protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id); 00534 this->nwk_data_resubmit_timer = 0; 00535 if (cur) { 00536 if (!thread_border_router_local_srv_data_in_network_data_check(cur)) { 00537 tr_info("nwk data mismatch - resubmit"); 00538 thread_border_router_publish(cur->id); 00539 } 00540 } 00541 } 00542 } 00543 } 00544 00545 void thread_border_router_resubmit_timer_set(int8_t interface_id, int16_t seconds) 00546 { 00547 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00548 00549 if (!this) { 00550 return; 00551 } 00552 00553 if (seconds >= 0) { 00554 this->nwk_data_resubmit_timer = seconds; 00555 } else { 00556 // re-init network data resubmit timer to default value 00557 this->nwk_data_resubmit_timer = THREAD_DATA_RESUBMIT_DELAY + randLIB_get_random_in_range(0, THREAD_DATA_RESUBMIT_DELAY / 10); 00558 } 00559 } 00560 00561 void thread_border_router_network_data_appl_callback(protocol_interface_info_entry_t *cur) 00562 { 00563 if (cur->thread_info->network_data_tlv_cb) { 00564 uint16_t payload_len = thread_network_data_tlv_size(cur, true); 00565 uint8_t *payload_ptr = ns_dyn_mem_alloc(payload_len + 3); /* 3 => room is also needed for TLV ID and length */ 00566 if (payload_ptr) { 00567 thread_network_data_tlv_write(cur, payload_ptr, true); 00568 // Send Network data TLV to application without TLV ID and length 00569 cur->thread_info->network_data_tlv_cb(cur->thread_info->interface_id, payload_ptr + 2, payload_len); 00570 ns_dyn_mem_free(payload_ptr); 00571 } 00572 } 00573 } 00574 00575 void thread_border_router_network_data_update_notify(protocol_interface_info_entry_t *cur) 00576 { 00577 thread_border_router_t *this = thread_border_router_find_by_interface(cur->thread_info->interface_id); 00578 00579 if (!this) { 00580 return; 00581 } 00582 if (!thread_border_router_local_srv_data_in_network_data_check(cur)) { 00583 if (this->nwk_data_resubmit_timer == 0) { 00584 this->nwk_data_resubmit_timer = 5; 00585 tr_debug("Resubmitted timer trig"); 00586 } 00587 00588 } else { 00589 tr_debug("All data registered"); 00590 this->nwk_data_resubmit_timer = 0; 00591 } 00592 00593 thread_border_router_network_data_appl_callback(cur); 00594 } 00595 00596 void thread_border_router_old_partition_data_clean(int8_t interface_id) 00597 { 00598 thread_border_router_t *this = thread_border_router_find_by_interface(interface_id); 00599 if (this) { 00600 coap_service_request_delete_by_service_id(this->coap_service_id); 00601 } 00602 thread_bbr_commercial_old_partition_data_clean(interface_id); 00603 } 00604 #endif // HAVE_THREAD_ROUTER 00605 00606 /*External APIs*/ 00607 00608 int thread_border_router_prefix_add(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len, thread_border_router_info_t *prefix_info_ptr) 00609 { 00610 #ifdef HAVE_THREAD_ROUTER 00611 protocol_interface_info_entry_t *cur; 00612 thread_prefix_tlv_t prefixTlv; 00613 thread_border_router_tlv_entry_t service; 00614 cur = protocol_stack_interface_info_get_by_id(interface_id); 00615 if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) { 00616 return -1; 00617 } 00618 00619 if (!prefix_info_ptr || !prefix_ptr) { 00620 return -2; 00621 } 00622 if (prefix_info_ptr->P_dhcp == true && prefix_info_ptr->P_slaac == true) { 00623 return -3;// Can not configure both services on 00624 } 00625 00626 prefixTlv.domainId = 0; 00627 prefixTlv.Prefix = prefix_ptr; 00628 prefixTlv.PrefixLen = prefix_len; 00629 00630 service.P_configure = prefix_info_ptr->P_configure; 00631 service.P_default_route = prefix_info_ptr->P_default_route; 00632 service.P_dhcp = prefix_info_ptr->P_dhcp; 00633 service.P_preferred = prefix_info_ptr->P_preferred; 00634 service.P_slaac = prefix_info_ptr->P_slaac; 00635 service.Prf = prefix_info_ptr->Prf; 00636 service.stableData = prefix_info_ptr->stableData; 00637 service.P_on_mesh = prefix_info_ptr->P_on_mesh; 00638 service.P_nd_dns = prefix_info_ptr->P_nd_dns; 00639 service.P_res1 = prefix_info_ptr->P_res1; 00640 00641 return thread_local_server_list_add_on_mesh_server(&cur->thread_info->localServerDataBase, &prefixTlv, &service); 00642 #else 00643 (void) interface_id; 00644 (void) prefix_ptr; 00645 (void) prefix_len; 00646 (void) prefix_info_ptr; 00647 return -1; 00648 #endif 00649 } 00650 00651 int thread_border_router_prefix_delete(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len) 00652 { 00653 #ifdef HAVE_THREAD_ROUTER 00654 protocol_interface_info_entry_t *cur; 00655 thread_prefix_tlv_t prefixTlv; 00656 cur = protocol_stack_interface_info_get_by_id(interface_id); 00657 if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) { 00658 return -1; 00659 } 00660 00661 if (!prefix_ptr) { 00662 return -2; 00663 } 00664 prefixTlv.domainId = 0; 00665 prefixTlv.Prefix = prefix_ptr; 00666 prefixTlv.PrefixLen = prefix_len; 00667 00668 return thread_local_server_list_del_on_mesh_server(&cur->thread_info->localServerDataBase, &prefixTlv); 00669 #else 00670 (void) interface_id; 00671 (void) prefix_ptr; 00672 (void) prefix_len; 00673 return -1; 00674 #endif 00675 } 00676 00677 int thread_border_router_route_add(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len, bool stable, int8_t prf) 00678 { 00679 #ifdef HAVE_THREAD_ROUTER 00680 thread_prefix_tlv_t prefixTlv; 00681 thread_border_router_tlv_entry_t route; 00682 protocol_interface_info_entry_t *cur; 00683 00684 cur = protocol_stack_interface_info_get_by_id(interface_id); 00685 if (!cur) { 00686 return -1; 00687 } 00688 00689 if (!cur->thread_info || thread_attach_ready(cur) != 0) { 00690 return -2; 00691 } 00692 prefixTlv.domainId = 0; 00693 prefixTlv.Prefix = prefix_ptr; 00694 prefixTlv.PrefixLen = prefix_len; 00695 00696 memset(&route, 0, sizeof(thread_border_router_tlv_entry_t)); 00697 route.Prf = prf; 00698 route.stableData = stable; 00699 00700 return thread_local_server_add_route(&cur->thread_info->localServerDataBase, &prefixTlv, &route); 00701 #else 00702 (void) interface_id; 00703 (void) prefix_ptr; 00704 (void) prefix_len; 00705 (void) stable; 00706 (void) prf; 00707 return -1; 00708 #endif 00709 } 00710 00711 int thread_border_router_route_delete(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len) 00712 { 00713 #ifdef HAVE_THREAD_ROUTER 00714 thread_prefix_tlv_t prefixTlv; 00715 protocol_interface_info_entry_t *cur; 00716 00717 cur = protocol_stack_interface_info_get_by_id(interface_id); 00718 if (!cur) { 00719 return -1; 00720 } 00721 00722 if (!cur->thread_info || thread_attach_ready(cur) != 0) { 00723 return -1; 00724 } 00725 00726 prefixTlv.domainId = 0; 00727 prefixTlv.Prefix = prefix_ptr; 00728 prefixTlv.PrefixLen = prefix_len; 00729 00730 return thread_local_server_del_route(&cur->thread_info->localServerDataBase, &prefixTlv); 00731 #else 00732 (void) interface_id; 00733 (void) prefix_ptr; 00734 (void) prefix_len; 00735 return -1; 00736 #endif 00737 00738 } 00739 00740 int thread_border_router_service_add(int8_t interface_id, uint8_t *service_data, uint8_t service_len, uint8_t sid, uint32_t enterprise_number, uint8_t *server_data, uint8_t server_data_len, bool stable) 00741 { 00742 #ifdef HAVE_THREAD_ROUTER 00743 protocol_interface_info_entry_t *cur; 00744 cur = protocol_stack_interface_info_get_by_id(interface_id); 00745 00746 if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) { 00747 return -1; 00748 } 00749 00750 if (!service_data || !service_len) { 00751 return -2; 00752 } 00753 00754 thread_network_data_service_entry_t service = { 00755 .T = false, 00756 .S_id = sid, 00757 .S_enterprise_number = enterprise_number, 00758 .S_service_data = service_data, 00759 .S_service_data_length = service_len, 00760 .S_server_data = server_data, 00761 .S_server_data_length = server_data_len, 00762 .S_stable = stable, 00763 }; 00764 00765 if (enterprise_number == THREAD_ENTERPRISE_NUMBER) { 00766 service.T = true; 00767 } 00768 00769 return thread_local_service_list_add(&cur->thread_info->localServerDataBase, &service); 00770 #else 00771 (void)interface_id; 00772 (void)service_data; 00773 (void)service_len; 00774 (void)sid; 00775 (void)enterprise_number; 00776 (void)server_data; 00777 (void)server_data_len; 00778 (void)stable; 00779 return -1; 00780 #endif 00781 } 00782 00783 int thread_border_router_service_delete(int8_t interface_id, uint8_t *service_data, uint8_t service_len, uint32_t enterprise_number) 00784 { 00785 #ifdef HAVE_THREAD_ROUTER 00786 protocol_interface_info_entry_t *cur; 00787 cur = protocol_stack_interface_info_get_by_id(interface_id); 00788 00789 if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) { 00790 return -1; 00791 } 00792 00793 if (!service_data || !service_len) { 00794 return -2; 00795 } 00796 00797 thread_network_data_service_entry_t service = { 00798 .S_enterprise_number = enterprise_number, 00799 .S_service_data = service_data, 00800 .S_service_data_length = service_len, 00801 }; 00802 00803 return thread_local_service_list_del(&cur->thread_info->localServerDataBase, &service); 00804 #else 00805 (void) interface_id; 00806 (void) service_data; 00807 (void) service_len; 00808 (void) enterprise_number; 00809 return -1; 00810 #endif 00811 } 00812 00813 int thread_border_router_recursive_dns_server_option_set(int8_t interface_id, uint8_t *recursive_dns_server_option, uint16_t recursive_dns_server_option_len) 00814 { 00815 #ifdef HAVE_THREAD_BORDER_ROUTER 00816 return thread_border_router_recursive_dns_server_option_store(interface_id, recursive_dns_server_option, recursive_dns_server_option_len); 00817 #else 00818 (void)interface_id; 00819 (void)recursive_dns_server_option; 00820 (void)recursive_dns_server_option_len; 00821 return -1; 00822 #endif 00823 } 00824 00825 int thread_border_router_dns_search_list_option_set(int8_t interface_id, uint8_t *dns_search_list_option, uint16_t search_list_option_len) 00826 { 00827 #ifdef HAVE_THREAD_BORDER_ROUTER 00828 return thread_border_router_dns_search_list_option_store(interface_id, dns_search_list_option, search_list_option_len); 00829 #else 00830 (void)interface_id; 00831 (void)dns_search_list_option; 00832 (void)search_list_option_len; 00833 return -1; 00834 #endif 00835 } 00836 00837 /** Network data set response callback. 00838 * 00839 * callback to inform if network data was set to leader. 00840 * 00841 * /param status status of operation 0 success, -1 failure from leader received 00842 * /param data_ptr pointer to network data TLV that leader accepted. 00843 * /param data_len length of network data. 00844 * 00845 */ 00846 #ifdef HAVE_THREAD_ROUTER 00847 static void thread_tmf_client_network_data_set_cb(int8_t interface_id, int8_t status, uint8_t *data_ptr, uint16_t data_len) 00848 { 00849 protocol_interface_info_entry_t *cur; 00850 (void) data_len; 00851 (void) data_ptr; 00852 00853 cur = protocol_stack_interface_info_get_by_id(interface_id); 00854 if (!cur) { 00855 return; 00856 } 00857 00858 cur->thread_info->localServerDataBase.publish_coap_req_id = 0; 00859 00860 tr_debug("BR a/sd response status: %s, addr: %x", status ? "Fail" : "OK", cur->thread_info->localServerDataBase.registered_rloc16); 00861 00862 if (cur->thread_info->localServerDataBase.publish_pending) { 00863 cur->thread_info->localServerDataBase.publish_pending = false; 00864 thread_border_router_publish(cur->id); 00865 } 00866 00867 if (status == 0) { 00868 // If request was successful, then update RLOC to new one. 00869 cur->thread_info->localServerDataBase.registered_rloc16 = mac_helper_mac16_address_get(cur); 00870 cur->thread_info->localServerDataBase.release_old_address = false; 00871 } 00872 } 00873 #endif 00874 00875 int thread_border_router_publish(int8_t interface_id) 00876 { 00877 #ifdef HAVE_THREAD_ROUTER 00878 uint16_t network_data_len; 00879 uint8_t *payload_ptr; 00880 uint8_t *ptr; 00881 uint16_t rloc16; 00882 int ret_val; 00883 protocol_interface_info_entry_t *cur; 00884 00885 tr_debug("Border router Publish Local Services"); 00886 cur = protocol_stack_interface_info_get_by_id(interface_id); 00887 if (!cur) { 00888 return -1; 00889 } 00890 00891 if (!cur->thread_info || thread_attach_ready(cur) != 0) { 00892 return -2; 00893 } 00894 00895 rloc16 = mac_helper_mac16_address_get(cur); 00896 tr_debug("Border router old: %x, new: %x", cur->thread_info->localServerDataBase.registered_rloc16, rloc16); 00897 00898 if (cur->thread_info->localServerDataBase.publish_coap_req_id) { 00899 if (rloc16 != cur->thread_info->localServerDataBase.registered_rloc16) { 00900 /* 00901 * Device short address has changed, cancel previous a/sd requests 00902 * and start resubmit timer 00903 * */ 00904 tr_debug("RLOC changed, kill pending a/sd request"); 00905 thread_management_client_coap_message_delete(cur->id, cur->thread_info->localServerDataBase.publish_coap_req_id); 00906 thread_border_router_resubmit_timer_set(interface_id, 5); 00907 } else { 00908 cur->thread_info->localServerDataBase.publish_pending = true; 00909 tr_debug("Activate pending status for publish"); 00910 } 00911 return 0; 00912 } 00913 00914 //Allocate Memory for Data 00915 network_data_len = thread_nd_own_service_list_data_size(&cur->thread_info->localServerDataBase); 00916 00917 // Room for RLOC16 Room for Network data TLV 00918 ptr = payload_ptr = ns_dyn_mem_temporary_alloc(network_data_len + 4 + 5); 00919 if (!ptr) { 00920 return -3; 00921 } 00922 00923 ptr = thread_tmfcop_tlv_data_write_header(ptr, TMFCOP_TLV_NETWORK_DATA, network_data_len); 00924 ptr = thread_nd_own_service_list_data_write(&cur->thread_info->localServerDataBase, ptr, rloc16); 00925 00926 if (cur->thread_info->localServerDataBase.registered_rloc16 != 0xffff && 00927 cur->thread_info->localServerDataBase.release_old_address && 00928 cur->thread_info->localServerDataBase.registered_rloc16 != rloc16) { 00929 // Our address has changed so we must register our network with new address and remove the old address 00930 tr_debug("BR address changed - remove old %x", cur->thread_info->localServerDataBase.registered_rloc16); 00931 ptr = thread_tmfcop_tlv_data_write_uint16(ptr, TMFCOP_TLV_RLOC16, cur->thread_info->localServerDataBase.registered_rloc16); 00932 } 00933 00934 cur->thread_info->localServerDataBase.registered_rloc16 = rloc16; 00935 ret_val = thread_management_client_network_data_register(cur->id, payload_ptr, ptr - payload_ptr, thread_tmf_client_network_data_set_cb); 00936 if (payload_ptr) { 00937 ns_dyn_mem_free(payload_ptr); 00938 } 00939 if (ret_val > 0) { 00940 // a/sd request successful, save coap request id 00941 cur->thread_info->localServerDataBase.publish_coap_req_id = (uint16_t)ret_val; 00942 ret_val = 0 ; 00943 } 00944 00945 thread_border_router_resubmit_timer_set(interface_id, -1); 00946 00947 return ret_val; 00948 #else 00949 (void) interface_id; 00950 return -1; 00951 #endif 00952 } 00953 00954 int thread_border_router_delete_all(int8_t interface_id) 00955 { 00956 #ifdef HAVE_THREAD_ROUTER 00957 protocol_interface_info_entry_t *cur; 00958 tr_debug("Border router Delete Local Service"); 00959 cur = protocol_stack_interface_info_get_by_id(interface_id); 00960 if (!cur) { 00961 return -1; 00962 } 00963 00964 if (!cur->thread_info || thread_attach_ready(cur) != 0) { 00965 return -2; 00966 } 00967 00968 //Delete first Local List 00969 thread_network_local_data_free_and_clean(&cur->thread_info->localServerDataBase, interface_id); 00970 00971 return 0; 00972 #else 00973 (void) interface_id; 00974 return -1; 00975 #endif 00976 } 00977 00978 int thread_border_router_network_data_callback_register(int8_t interface_id, thread_network_data_tlv_cb *nwk_data_cb) 00979 { 00980 #ifdef HAVE_THREAD 00981 protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id); 00982 if (!cur) { 00983 return -1; 00984 } 00985 00986 if (!cur->thread_info || thread_attach_ready(cur) != 0) { 00987 return -2; 00988 } 00989 00990 cur->thread_info->network_data_tlv_cb = nwk_data_cb; 00991 00992 thread_border_router_network_data_appl_callback(cur); 00993 00994 return 0; 00995 #else 00996 (void)interface_id; 00997 (void)nwk_data_cb; 00998 return -1; 00999 #endif 01000 } 01001 01002 int thread_border_router_prefix_tlv_find(uint8_t *network_data_tlv, uint16_t network_data_tlv_length, uint8_t **prefix_tlv, bool *stable) 01003 { 01004 #ifdef HAVE_THREAD 01005 uint16_t tlv_length; 01006 if (!network_data_tlv || !network_data_tlv_length || !prefix_tlv) { 01007 return -1; 01008 } 01009 //tr_debug("thread_tlv_lib_prefix_find() len=%d, tlv=%s", network_data_tlv_length, trace_array(network_data_tlv, network_data_tlv_length)); 01010 *stable = true; 01011 tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_PREFIX | THREAD_NWK_STABLE_DATA, prefix_tlv); 01012 if (tlv_length == 0) { 01013 tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_PREFIX, prefix_tlv); 01014 *stable = false; 01015 } 01016 return tlv_length; 01017 #else 01018 (void)network_data_tlv; 01019 (void)network_data_tlv_length; 01020 (void)prefix_tlv; 01021 (void)stable; 01022 return -1; 01023 #endif 01024 } 01025 01026 int thread_border_router_tlv_find(uint8_t *prefix_tlv, uint16_t prefix_tlv_length, uint8_t **border_router_tlv, bool *stable) 01027 { 01028 #ifdef HAVE_THREAD 01029 uint16_t tlv_length; 01030 if (!prefix_tlv || !prefix_tlv_length || !border_router_tlv) { 01031 return -1; 01032 } 01033 01034 //tr_debug("thread_tlv_lib_border_router_find() len=%d, tlv=%s", prefix_tlv_length, trace_array(prefix_tlv, prefix_tlv_length)); 01035 uint8_t prefix_length = prefix_tlv[1]; 01036 uint8_t prefix_byte_len = prefixBits_to_bytes(prefix_length); 01037 prefix_tlv = prefix_tlv + 2 + prefix_byte_len; //2 = domain ID + prefix length 01038 prefix_tlv_length = prefix_tlv_length - prefix_byte_len - 2; 01039 01040 // find stable prefix first and if not found return unstable data 01041 *stable = true; 01042 tlv_length = thread_meshcop_tlv_find_next(prefix_tlv, prefix_tlv_length, THREAD_NWK_DATA_TYPE_BORDER_ROUTER | THREAD_NWK_STABLE_DATA, border_router_tlv); 01043 if (tlv_length == 0) { 01044 tlv_length = thread_meshcop_tlv_find_next(prefix_tlv, prefix_tlv_length, THREAD_NWK_DATA_TYPE_BORDER_ROUTER, border_router_tlv); 01045 *stable = false; 01046 } 01047 return tlv_length; 01048 #else 01049 (void)prefix_tlv; 01050 (void)prefix_tlv_length; 01051 (void)border_router_tlv; 01052 (void)stable; 01053 return -1; 01054 #endif 01055 } 01056 01057 int thread_border_router_prefix_context_id(uint8_t *prefix_tlv, uint16_t prefix_tlv_length) 01058 { 01059 #ifdef HAVE_THREAD 01060 if (!prefix_tlv || !prefix_tlv_length) { 01061 return -1; 01062 } 01063 01064 uint16_t data_length = prefix_tlv_length; 01065 01066 while (data_length) { 01067 uint8_t type = *prefix_tlv++; 01068 uint16_t len = *prefix_tlv++; 01069 data_length -= 2; 01070 01071 type &= THREAD_NWK_DATA_TYPE_MASK; 01072 01073 if (type == THREAD_NWK_DATA_TYPE_6LOWPAN_ID) { 01074 return (*prefix_tlv & 0x0f); 01075 } 01076 01077 data_length -= len; 01078 prefix_tlv += len; 01079 } 01080 01081 return -2; 01082 #else 01083 (void)prefix_tlv; 01084 (void)prefix_tlv_length; 01085 return -1; 01086 #endif 01087 } 01088 01089 int thread_border_router_service_tlv_find(uint8_t *network_data_tlv, uint16_t network_data_tlv_length, uint8_t **service_tlv, bool *stable) 01090 { 01091 #ifdef HAVE_THREAD 01092 uint16_t tlv_length; 01093 if (!network_data_tlv || !network_data_tlv_length || !service_tlv) { 01094 return -1; 01095 } 01096 01097 *stable = true; 01098 tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_SERVICE_DATA | THREAD_NWK_STABLE_DATA, service_tlv); 01099 if (tlv_length == 0) { 01100 tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_SERVICE_DATA, service_tlv); 01101 *stable = false; 01102 } 01103 return tlv_length; 01104 #else 01105 (void)network_data_tlv; 01106 (void)network_data_tlv_length; 01107 (void)service_tlv; 01108 (void)stable; 01109 return -1; 01110 #endif 01111 } 01112 01113 int thread_border_router_server_tlv_find(uint8_t *service_tlv, uint16_t service_tlv_length, uint8_t **server_tlv, bool *stable) 01114 { 01115 #ifdef HAVE_THREAD 01116 uint16_t tlv_length; 01117 if (!service_tlv || !service_tlv_length || !server_tlv) { 01118 return -1; 01119 } 01120 01121 uint8_t t_flag = service_tlv[0] >> 7; 01122 service_tlv += 1; 01123 01124 if (!t_flag) { 01125 service_tlv_length -= 4; 01126 service_tlv += 4; 01127 } 01128 01129 uint8_t service_data_len = *service_tlv; 01130 service_tlv += 1 + service_data_len; 01131 service_tlv_length = service_tlv_length - service_data_len - 2; 01132 01133 *stable = true; 01134 tlv_length = thread_meshcop_tlv_find_next(service_tlv, service_tlv_length, THREAD_NWK_DATA_TYPE_SERVER_DATA | THREAD_NWK_STABLE_DATA, server_tlv); 01135 if (tlv_length == 0) { 01136 tlv_length = thread_meshcop_tlv_find_next(service_tlv, service_tlv_length, THREAD_NWK_DATA_TYPE_SERVER_DATA, server_tlv); 01137 *stable = false; 01138 } 01139 return tlv_length; 01140 #else 01141 (void)service_tlv; 01142 (void)service_tlv_length; 01143 (void)server_tlv; 01144 (void)stable; 01145 return -1; 01146 #endif 01147 } 01148 01149 int thread_border_router_mdns_responder_start(int8_t interface_id, int8_t interface_id_mdns, const char *service_name) 01150 { 01151 #ifdef HAVE_THREAD_BORDER_ROUTER 01152 return thread_mdns_start(interface_id, interface_id_mdns, service_name); 01153 #else 01154 (void)interface_id; 01155 (void)interface_id_mdns; 01156 (void)service_name; 01157 return -1; 01158 #endif 01159 } 01160 01161 int thread_border_router_mdns_responder_stop(void) 01162 { 01163 #ifdef HAVE_THREAD_BORDER_ROUTER 01164 return thread_mdns_stop(); 01165 #else 01166 return -1; 01167 #endif 01168 } 01169
Generated on Tue Jul 12 2022 13:54:58 by
1.7.2