mbed-os/source/6LoWPAN/Thread/thread_border_router_api.c

1170 lines
39 KiB
C

/*
* Copyright (c) 2014-2018, Arm Limited and affiliates.
* SPDX-License-Identifier: BSD-3-Clause
*
* 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. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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 "nsconfig.h"
#include <string.h>
#include "ns_types.h"
#include <nsdynmemLIB.h>
#include "eventOS_event.h"
#include <ns_list.h>
#include "ns_trace.h"
#include "Core/include/ns_buffer.h"
#include "common_functions.h"
#include "randLIB.h"
#include "thread_border_router_api.h"
#include "thread_management_if.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "6LoWPAN/Thread/thread_config.h"
#include "6LoWPAN/Thread/thread_common.h"
#include "6LoWPAN/Thread/thread_extension_bbr.h"
#include "6LoWPAN/Thread/thread_network_data_lib.h"
#include "6LoWPAN/Thread/thread_network_data_storage.h"
#include "6LoWPAN/Thread/thread_management_client.h"
#include "6LoWPAN/Thread/thread_joiner_application.h"
#include "6LoWPAN/Thread/thread_tmfcop_lib.h"
#include "6LoWPAN/Thread/thread_border_router_api_internal.h"
#include "6LoWPAN/Thread/thread_mdns.h"
#include "6LoWPAN/Bootstraps/protocol_6lowpan.h"
#include "6LoWPAN/MAC/mac_helper.h"
#include "MLE/mle.h"
#include "thread_meshcop_lib.h"
#include "thread_network_data_lib.h"
#include "coap_service_api.h"
#define TRACE_GROUP "tBRa"
#ifdef HAVE_THREAD_ROUTER
/*
* Structure containing IPv6 Router advertisement options for DNS configuration.
*/
typedef struct {
uint16_t option_length;
uint8_t *option_data;
} dns_option_t;
/*
* Border router instance data.
*/
typedef struct {
dns_option_t *dns_search_list_option; /* option_data encoded according to RFC6106, DNSSL */
dns_option_t *recursive_dns_server_option; /* option_data encoded according to RFC6106, RDNSS */
uint16_t nwk_data_resubmit_timer; /* network data resubmit timeout */
int8_t interface_id;
int8_t coap_service_id;
ns_list_link_t link;
} thread_border_router_t;
/* Neighbor discovery options according to RFC6106 (rfc4861) */
#define RFC6106_RECURSIVE_DNS_SERVER_OPTION 25
#define RFC6106_DNS_SEARCH_LIST_OPTION 31
static NS_LIST_DEFINE(border_router_instance_list, thread_border_router_t, link);
static thread_border_router_t *thread_border_router_find_by_interface(int8_t interface_id)
{
thread_border_router_t *this = NULL;
ns_list_foreach(thread_border_router_t, cur_br, &border_router_instance_list) {
if (cur_br->interface_id == interface_id) {
this = cur_br;
break;
}
}
return this;
}
static thread_border_router_t *thread_border_router_find_by_service(int8_t service_id)
{
thread_border_router_t *this = NULL;
ns_list_foreach(thread_border_router_t, cur_br, &border_router_instance_list) {
if (cur_br->coap_service_id == service_id) {
this = cur_br;
break;
}
}
return this;
}
/*
* Read Border Router TLV: DNS search list option.
* DNSSL option data will be fetched from:
* 1. User has provided static configuration by using method thread_border_router_dns_search_list_option_set.
* 2. ICMP RA messages (if service is enabled and running in background).
*/
static uint8_t *thread_management_server_border_router_nd_dnssl_option_read(thread_border_router_t *this, uint16_t *resp_len)
{
if (this->dns_search_list_option) {
*resp_len = this->dns_search_list_option->option_length;
return (uint8_t *)&this->dns_search_list_option->option_data;
} else {
// TODO: Read DNSSL from stored ICMP RA messages.
*resp_len = 0;
return NULL;
}
}
/*
* Read Border Router TLV: Recursive DNS Server option.
* RDNSS option data will be fetched from:
* 1. User has provided static configuration by using method thread_border_router_recursive_dns_server_option_set.
* 2. ICMP RA messages (if service is enabled and running in background).
*/
static uint8_t *thread_management_server_border_router_nd_rdnss_option_read(thread_border_router_t *this, uint16_t *resp_len)
{
if (this->recursive_dns_server_option) {
*resp_len = this->recursive_dns_server_option->option_length;
return (uint8_t *)&this->recursive_dns_server_option->option_data;
} else {
// TODO: Read RDNSS from stored ICMP RA messages.
*resp_len = 0;
return NULL;
}
}
/**
* Thread Neighbor discovery data request handler
*/
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)
{
thread_border_router_t *this = thread_border_router_find_by_service(service_id);
uint8_t *request_tlv_ptr;
uint8_t *resp_payload_ptr;
uint16_t options_len, rdnss_option_len, dnssl_option_len, payload_len;
uint8_t options_data[4];
uint8_t *dns_server_tlv_ptr = NULL;
uint8_t *dns_search_list_tlv_ptr = NULL;
uint8_t *ptr;
sn_coap_msg_code_e return_code = COAP_MSG_CODE_RESPONSE_CHANGED;
(void) source_port;
(void) source_address;
tr_debug("neighbor data request");
if (!this) {
return -1;
}
request_tlv_ptr = options_data;
rdnss_option_len = dnssl_option_len = 0;
options_len = thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ND_OPTION, &request_tlv_ptr);
while (options_len > 0) {
switch (*request_tlv_ptr) {
case RFC6106_RECURSIVE_DNS_SERVER_OPTION:
dns_server_tlv_ptr = thread_management_server_border_router_nd_rdnss_option_read(this, &rdnss_option_len);
break;
case RFC6106_DNS_SEARCH_LIST_OPTION:
dns_search_list_tlv_ptr = thread_management_server_border_router_nd_dnssl_option_read(this, &dnssl_option_len);
break;
}
request_tlv_ptr++;
options_len--;
}
payload_len = rdnss_option_len + dnssl_option_len;
resp_payload_ptr = ptr = ns_dyn_mem_alloc(payload_len + 4); //reserve space also for type/length
if (!resp_payload_ptr) {
return_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR;
goto send_response;
}
*ptr++ = TMFCOP_TLV_ND_DATA;
if (payload_len > 255) {
*ptr++ = 0xff;
ptr = common_write_16_bit(payload_len, ptr);
} else {
*ptr++ = payload_len;
}
if (dns_server_tlv_ptr) {
memcpy(ptr, dns_server_tlv_ptr, rdnss_option_len);
ptr += rdnss_option_len;
}
if (dns_search_list_tlv_ptr) {
memcpy(ptr, dns_search_list_tlv_ptr, dnssl_option_len);
ptr += dnssl_option_len;
}
send_response:
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);
ns_dyn_mem_free(resp_payload_ptr);
return 0;
}
/*
* Check that local prefix is available in received network data
*/
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)
{
ns_list_foreach(thread_network_data_prefix_cache_entry_t, nwk_prefix, &network_data_ptr->localPrefixList) {
if (nwk_prefix->servicesPrefixLen != local_prefix->servicesPrefixLen) {
continue;
}
if (!bitsequal(nwk_prefix->servicesPrefix, local_prefix->servicesPrefix, local_prefix->servicesPrefixLen)) {
continue;
}
// check that prefix is hosted by this router
if (!thread_nd_hosted_by_this_routerid(router_id, &nwk_prefix->routeList) &&
!thread_nd_hosted_by_this_routerid(router_id, &nwk_prefix->borderRouterList)) {
return false;
}
return true;
}
return false;
}
/*
* Check that local service is available in network data
*/
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)
{
thread_network_data_service_cache_entry_t *service_cache_entry = thread_network_data_service_entry_find(&network_data_ptr->service_list, local_service);
if (service_cache_entry) {
return thread_network_data_service_hosted_by_this_router_id(service_cache_entry, router_id);
}
return false;
}
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)
{
bool instance_found = false;
if (thread_nd_hosted_by_this_routerid(router_id, &prefix->routeList)) {
instance_found = true;
}
if (thread_nd_hosted_by_this_routerid(router_id, &prefix->borderRouterList)) {
instance_found = true;
}
if (!instance_found) {
/* Router ID not in propagated network data; skip */
return true;
}
instance_found = false;
ns_list_foreach(thread_network_local_data_entry_t, localPrefix, &local_data->prefix_list) {
if (prefix->servicesPrefixLen != localPrefix->servicesPrefixLen) {
continue;
}
if (bitsequal(prefix->servicesPrefix, localPrefix->servicesPrefix, localPrefix->servicesPrefixLen)) {
/* Prefix exists in local data; return true */
instance_found = true;
break;
}
}
if (!instance_found) {
tr_debug("Found missing prefix: %s", trace_array(prefix->servicesPrefix, 8));
return false;
}
return true;
}
static void thread_border_router_child_network_data_clean(protocol_interface_info_entry_t *cur, uint16_t child_id)
{
uint8_t addr16_buf[2];
common_write_16_bit(child_id, addr16_buf);
if (mac_neighbor_table_address_discover(mac_neighbor_info(cur), addr16_buf, ADDR_802_15_4_SHORT)) {
/* Child is available in mle, do nothing */
return;
}
// Child is not our child => network data contains data from lost children, remove it
tr_debug("Remove nwk data from lost child: %04x", child_id);
thread_management_client_network_data_unregister(cur->id, child_id);
}
static void thread_border_router_lost_children_nwk_data_validate(protocol_interface_info_entry_t *cur, uint16_t router_short_addr)
{
if (!thread_is_router_addr(router_short_addr)) {
// not validating children nwk data
return;
}
thread_network_data_cache_entry_t *network_data = &cur->thread_info->networkDataStorage;
ns_list_foreach(thread_network_data_prefix_cache_entry_t, curLP, &network_data->localPrefixList) {
/* Go throgh all routes */
ns_list_foreach(thread_network_server_data_entry_t, curRoute, &curLP->routeList) {
if (thread_addr_is_child(router_short_addr, curRoute->routerID)) {
// Router children found
thread_border_router_child_network_data_clean(cur, curRoute->routerID);
}
}
/* Go through all BR's */
ns_list_foreach(thread_network_server_data_entry_t, curBR, &curLP->borderRouterList) {
if (thread_addr_is_child(router_short_addr, curBR->routerID)) {
// Router children found
thread_border_router_child_network_data_clean(cur, curBR->routerID);
}
}
}
/* Go throgh all services */
ns_list_foreach(thread_network_data_service_cache_entry_t, service, &network_data->service_list) {
ns_list_foreach(thread_network_data_service_server_entry_t, server, &service->server_list) {
if (thread_addr_is_child(router_short_addr, server->router_id)) {
// Router children found
thread_border_router_child_network_data_clean(cur, server->router_id);
}
}
}
}
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)
{
bool instance_found = false;
ns_list_foreach(thread_network_data_service_server_entry_t, server, &service->server_list) {
if (server->router_id == router_id) {
instance_found = true;
break;
}
}
if (!instance_found) {
/* Router ID not in propagated network data; skip */
return true;
}
ns_list_foreach(thread_network_data_service_entry_t, local_service, &local_data->service_list) {
if (local_service->S_service_data_length != service->S_service_data_length) {
continue;
}
if (memcmp(local_service->S_service_data, service->S_service_data, local_service->S_service_data_length) != 0) {
continue;
}
if (local_service->S_enterprise_number != service->S_enterprise_number) {
continue;
}
/* Service exists in local data; return true */
return true;
}
tr_debug("Found missing service: %s", trace_array(service->S_service_data, service->S_service_data_length));
return false;
}
/*
* Check that local server data is available in network data
*/
static bool thread_border_router_local_srv_data_in_network_data_check(protocol_interface_info_entry_t *cur)
{
thread_network_data_cache_entry_t *network_data;
thread_network_local_data_cache_entry_t *local_data;
uint16_t router_id = cur->mac_parameters->mac_short_address;
network_data = &cur->thread_info->networkDataStorage;
local_data = &cur->thread_info->localServerDataBase;
ns_list_foreach(thread_network_local_data_entry_t, localPrefix, &local_data->prefix_list) {
// find matching prefix from network data
if (thread_border_router_network_data_prefix_match(network_data, localPrefix, router_id) == false) {
return false;
}
}
ns_list_foreach(thread_network_data_service_entry_t, localService, &local_data->service_list) {
// find matching service from network data
if (thread_border_router_network_data_service_match(network_data, localService, router_id) == false) {
return false;
}
}
ns_list_foreach(thread_network_data_prefix_cache_entry_t, prefix, &network_data->localPrefixList) {
if (thread_border_router_local_network_data_prefix_match(local_data, prefix, router_id) == false) {
return false;
}
}
ns_list_foreach(thread_network_data_service_cache_entry_t, service, &network_data->service_list) {
if (thread_border_router_local_network_data_service_match(local_data, service, router_id) == false) {
return false;
}
}
thread_border_router_lost_children_nwk_data_validate(cur, router_id);
return true;
}
#ifdef HAVE_THREAD_BORDER_ROUTER
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)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (!this) {
return -1;
}
ns_dyn_mem_free(this->recursive_dns_server_option);
if (recursive_dns_server_option) {
this->recursive_dns_server_option = ns_dyn_mem_alloc(sizeof(dns_option_t) + recursive_dns_server_option_len);
if (!this->recursive_dns_server_option) {
return -2;
}
memcpy(&this->recursive_dns_server_option->option_data, recursive_dns_server_option, recursive_dns_server_option_len);
this->recursive_dns_server_option->option_length = recursive_dns_server_option_len;
}
return 0;
}
#endif
#ifdef HAVE_THREAD_BORDER_ROUTER
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)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (!this) {
return -1;
}
ns_dyn_mem_free(this->dns_search_list_option);
if (dns_search_list_option) {
this->dns_search_list_option = ns_dyn_mem_alloc(sizeof(dns_option_t) + search_list_option_len);
if (!this->dns_search_list_option) {
return -2;
}
memcpy(&this->dns_search_list_option->option_data, dns_search_list_option, search_list_option_len);
this->dns_search_list_option->option_length = search_list_option_len;
}
return 0;
}
#endif
int8_t thread_border_router_init(int8_t interface_id)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (this) {
return 0;
}
tr_debug("thread_border_router_init if=%d", interface_id);
this = ns_dyn_mem_alloc(sizeof(thread_border_router_t));
if (!this) {
return -2;
}
this->dns_search_list_option = NULL;
this->recursive_dns_server_option = NULL;
this->interface_id = interface_id;
this->nwk_data_resubmit_timer = 0;
this->coap_service_id = coap_service_initialize(this->interface_id, THREAD_MANAGEMENT_PORT, COAP_SERVICE_OPTIONS_NONE, NULL, NULL);
if (this->coap_service_id < 0) {
tr_warn("Thread border router coap init failed");
ns_dyn_mem_free(this);
return -3;
}
// Register to Mesh CoAP URIs
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);
ns_list_add_to_start(&border_router_instance_list, this);
return 0;
}
void thread_border_router_delete(int8_t interface_id)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (!this) {
return;
}
coap_service_delete(this->coap_service_id);
ns_list_remove(&border_router_instance_list, this);
ns_dyn_mem_free(this->dns_search_list_option);
ns_dyn_mem_free(this->recursive_dns_server_option);
ns_dyn_mem_free(this);
}
void thread_border_router_seconds_timer(int8_t interface_id, uint32_t seconds)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (!this) {
return;
}
if (this->nwk_data_resubmit_timer) {
if (this->nwk_data_resubmit_timer > seconds) {
this->nwk_data_resubmit_timer -= seconds;
} else {
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id);
this->nwk_data_resubmit_timer = 0;
if (cur) {
if (!thread_border_router_local_srv_data_in_network_data_check(cur)) {
tr_info("nwk data mismatch - resubmit");
thread_border_router_publish(cur->id);
}
}
}
}
}
void thread_border_router_resubmit_timer_set(int8_t interface_id, int16_t seconds)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (!this) {
return;
}
if (seconds >= 0) {
this->nwk_data_resubmit_timer = seconds;
} else {
// re-init network data resubmit timer to default value
this->nwk_data_resubmit_timer = THREAD_DATA_RESUBMIT_DELAY + randLIB_get_random_in_range(0, THREAD_DATA_RESUBMIT_DELAY / 10);
}
}
void thread_border_router_network_data_appl_callback(protocol_interface_info_entry_t *cur)
{
if (cur->thread_info->network_data_tlv_cb) {
uint16_t payload_len = thread_network_data_tlv_size(cur, true);
uint8_t *payload_ptr = ns_dyn_mem_alloc(payload_len + 3); /* 3 => room is also needed for TLV ID and length */
if (payload_ptr) {
thread_network_data_tlv_write(cur, payload_ptr, true);
// Send Network data TLV to application without TLV ID and length
cur->thread_info->network_data_tlv_cb(cur->thread_info->interface_id, payload_ptr + 2, payload_len);
ns_dyn_mem_free(payload_ptr);
}
}
}
void thread_border_router_network_data_update_notify(protocol_interface_info_entry_t *cur)
{
thread_border_router_t *this = thread_border_router_find_by_interface(cur->thread_info->interface_id);
if (!this) {
return;
}
if (!thread_border_router_local_srv_data_in_network_data_check(cur)) {
if (this->nwk_data_resubmit_timer == 0) {
this->nwk_data_resubmit_timer = 5;
tr_debug("Resubmitted timer trig");
}
} else {
tr_debug("All data registered");
this->nwk_data_resubmit_timer = 0;
}
thread_border_router_network_data_appl_callback(cur);
}
void thread_border_router_old_partition_data_clean(int8_t interface_id)
{
thread_border_router_t *this = thread_border_router_find_by_interface(interface_id);
if (this) {
coap_service_request_delete_by_service_id(this->coap_service_id);
}
thread_extension_bbr_old_partition_data_clean(interface_id);
}
#endif // HAVE_THREAD_ROUTER
/*External APIs*/
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)
{
#ifdef HAVE_THREAD_ROUTER
protocol_interface_info_entry_t *cur;
thread_prefix_tlv_t prefixTlv;
thread_border_router_tlv_entry_t service;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) {
return -1;
}
if (!prefix_info_ptr || !prefix_ptr) {
return -2;
}
if (prefix_info_ptr->P_dhcp == true && prefix_info_ptr->P_slaac == true) {
return -3;// Can not configure both services on
}
prefixTlv.domainId = 0;
prefixTlv.Prefix = prefix_ptr;
prefixTlv.PrefixLen = prefix_len;
service.P_configure = prefix_info_ptr->P_configure;
service.P_default_route = prefix_info_ptr->P_default_route;
service.P_dhcp = prefix_info_ptr->P_dhcp;
service.P_preferred = prefix_info_ptr->P_preferred;
service.P_slaac = prefix_info_ptr->P_slaac;
service.Prf = prefix_info_ptr->Prf;
service.stableData = prefix_info_ptr->stableData;
service.P_on_mesh = prefix_info_ptr->P_on_mesh;
service.P_nd_dns = prefix_info_ptr->P_nd_dns;
service.P_res1 = prefix_info_ptr->P_res1;
return thread_local_server_list_add_on_mesh_server(&cur->thread_info->localServerDataBase, &prefixTlv, &service);
#else
(void) interface_id;
(void) prefix_ptr;
(void) prefix_len;
(void) prefix_info_ptr;
return -1;
#endif
}
int thread_border_router_prefix_delete(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len)
{
#ifdef HAVE_THREAD_ROUTER
protocol_interface_info_entry_t *cur;
thread_prefix_tlv_t prefixTlv;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) {
return -1;
}
if (!prefix_ptr) {
return -2;
}
prefixTlv.domainId = 0;
prefixTlv.Prefix = prefix_ptr;
prefixTlv.PrefixLen = prefix_len;
return thread_local_server_list_del_on_mesh_server(&cur->thread_info->localServerDataBase, &prefixTlv);
#else
(void) interface_id;
(void) prefix_ptr;
(void) prefix_len;
return -1;
#endif
}
int thread_border_router_route_add(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len, bool stable, int8_t prf)
{
#ifdef HAVE_THREAD_ROUTER
thread_prefix_tlv_t prefixTlv;
thread_border_router_tlv_entry_t route;
protocol_interface_info_entry_t *cur;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
if (!cur->thread_info || thread_attach_ready(cur) != 0) {
return -2;
}
prefixTlv.domainId = 0;
prefixTlv.Prefix = prefix_ptr;
prefixTlv.PrefixLen = prefix_len;
memset(&route, 0, sizeof(thread_border_router_tlv_entry_t));
route.Prf = prf;
route.stableData = stable;
return thread_local_server_add_route(&cur->thread_info->localServerDataBase, &prefixTlv, &route);
#else
(void) interface_id;
(void) prefix_ptr;
(void) prefix_len;
(void) stable;
(void) prf;
return -1;
#endif
}
int thread_border_router_route_delete(int8_t interface_id, uint8_t *prefix_ptr, uint8_t prefix_len)
{
#ifdef HAVE_THREAD_ROUTER
thread_prefix_tlv_t prefixTlv;
protocol_interface_info_entry_t *cur;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
if (!cur->thread_info || thread_attach_ready(cur) != 0) {
return -1;
}
prefixTlv.domainId = 0;
prefixTlv.Prefix = prefix_ptr;
prefixTlv.PrefixLen = prefix_len;
return thread_local_server_del_route(&cur->thread_info->localServerDataBase, &prefixTlv);
#else
(void) interface_id;
(void) prefix_ptr;
(void) prefix_len;
return -1;
#endif
}
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)
{
#ifdef HAVE_THREAD_ROUTER
protocol_interface_info_entry_t *cur;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) {
return -1;
}
if (!service_data || !service_len) {
return -2;
}
thread_network_data_service_entry_t service = {
.T = false,
.S_id = sid,
.S_enterprise_number = enterprise_number,
.S_service_data = service_data,
.S_service_data_length = service_len,
.S_server_data = server_data,
.S_server_data_length = server_data_len,
.S_stable = stable,
};
if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
service.T = true;
}
return thread_local_service_list_add(&cur->thread_info->localServerDataBase, &service);
#else
(void)interface_id;
(void)service_data;
(void)service_len;
(void)sid;
(void)enterprise_number;
(void)server_data;
(void)server_data_len;
(void)stable;
return -1;
#endif
}
int thread_border_router_service_delete(int8_t interface_id, uint8_t *service_data, uint8_t service_len, uint32_t enterprise_number)
{
#ifdef HAVE_THREAD_ROUTER
protocol_interface_info_entry_t *cur;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur || !cur->thread_info || thread_attach_ready(cur) != 0) {
return -1;
}
if (!service_data || !service_len) {
return -2;
}
thread_network_data_service_entry_t service = {
.S_enterprise_number = enterprise_number,
.S_service_data = service_data,
.S_service_data_length = service_len,
};
return thread_local_service_list_del(&cur->thread_info->localServerDataBase, &service);
#else
(void) interface_id;
(void) service_data;
(void) service_len;
(void) enterprise_number;
return -1;
#endif
}
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)
{
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_border_router_recursive_dns_server_option_store(interface_id, recursive_dns_server_option, recursive_dns_server_option_len);
#else
(void)interface_id;
(void)recursive_dns_server_option;
(void)recursive_dns_server_option_len;
return -1;
#endif
}
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)
{
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_border_router_dns_search_list_option_store(interface_id, dns_search_list_option, search_list_option_len);
#else
(void)interface_id;
(void)dns_search_list_option;
(void)search_list_option_len;
return -1;
#endif
}
/** Network data set response callback.
*
* callback to inform if network data was set to leader.
*
* /param status status of operation 0 success, -1 failure from leader received
* /param data_ptr pointer to network data TLV that leader accepted.
* /param data_len length of network data.
*
*/
#ifdef HAVE_THREAD_ROUTER
static void thread_tmf_client_network_data_set_cb(int8_t interface_id, int8_t status, uint8_t *data_ptr, uint16_t data_len)
{
protocol_interface_info_entry_t *cur;
(void) data_len;
(void) data_ptr;
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return;
}
cur->thread_info->localServerDataBase.publish_coap_req_id = 0;
tr_debug("BR a/sd response status: %s, addr: %x", status ? "Fail" : "OK", cur->thread_info->localServerDataBase.registered_rloc16);
if (cur->thread_info->localServerDataBase.publish_pending) {
cur->thread_info->localServerDataBase.publish_pending = false;
thread_border_router_publish(cur->id);
}
if (status == 0) {
// If request was successful, then update RLOC to new one.
cur->thread_info->localServerDataBase.registered_rloc16 = mac_helper_mac16_address_get(cur);
cur->thread_info->localServerDataBase.release_old_address = false;
}
}
#endif
int thread_border_router_publish(int8_t interface_id)
{
#ifdef HAVE_THREAD_ROUTER
uint16_t network_data_len;
uint8_t *payload_ptr;
uint8_t *ptr;
uint16_t rloc16;
int ret_val;
protocol_interface_info_entry_t *cur;
tr_debug("Border router Publish Local Services");
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
if (!cur->thread_info || thread_attach_ready(cur) != 0) {
return -2;
}
rloc16 = mac_helper_mac16_address_get(cur);
tr_debug("Border router old: %x, new: %x", cur->thread_info->localServerDataBase.registered_rloc16, rloc16);
if (cur->thread_info->localServerDataBase.publish_coap_req_id) {
if (rloc16 != cur->thread_info->localServerDataBase.registered_rloc16) {
/*
* Device short address has changed, cancel previous a/sd requests
* and start resubmit timer
* */
tr_debug("RLOC changed, kill pending a/sd request");
thread_management_client_coap_message_delete(cur->id, cur->thread_info->localServerDataBase.publish_coap_req_id);
thread_border_router_resubmit_timer_set(interface_id, 5);
} else {
cur->thread_info->localServerDataBase.publish_pending = true;
tr_debug("Activate pending status for publish");
}
return 0;
}
//Allocate Memory for Data
network_data_len = thread_nd_own_service_list_data_size(&cur->thread_info->localServerDataBase);
// Room for RLOC16 Room for Network data TLV
ptr = payload_ptr = ns_dyn_mem_temporary_alloc(network_data_len + 4 + 5);
if (!ptr) {
return -3;
}
ptr = thread_tmfcop_tlv_data_write_header(ptr, TMFCOP_TLV_NETWORK_DATA, network_data_len);
ptr = thread_nd_own_service_list_data_write(&cur->thread_info->localServerDataBase, ptr, rloc16);
if (cur->thread_info->localServerDataBase.registered_rloc16 != 0xffff &&
cur->thread_info->localServerDataBase.release_old_address &&
cur->thread_info->localServerDataBase.registered_rloc16 != rloc16) {
// Our address has changed so we must register our network with new address and remove the old address
tr_debug("BR address changed - remove old %x", cur->thread_info->localServerDataBase.registered_rloc16);
ptr = thread_tmfcop_tlv_data_write_uint16(ptr, TMFCOP_TLV_RLOC16, cur->thread_info->localServerDataBase.registered_rloc16);
}
cur->thread_info->localServerDataBase.registered_rloc16 = rloc16;
ret_val = thread_management_client_network_data_register(cur->id, payload_ptr, ptr - payload_ptr, thread_tmf_client_network_data_set_cb);
if (payload_ptr) {
ns_dyn_mem_free(payload_ptr);
}
if (ret_val > 0) {
// a/sd request successful, save coap request id
cur->thread_info->localServerDataBase.publish_coap_req_id = (uint16_t)ret_val;
ret_val = 0 ;
}
thread_border_router_resubmit_timer_set(interface_id, -1);
return ret_val;
#else
(void) interface_id;
return -1;
#endif
}
int thread_border_router_delete_all(int8_t interface_id)
{
#ifdef HAVE_THREAD_ROUTER
protocol_interface_info_entry_t *cur;
tr_debug("Border router Delete Local Service");
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
if (!cur->thread_info || thread_attach_ready(cur) != 0) {
return -2;
}
//Delete first Local List
thread_network_local_data_free_and_clean(&cur->thread_info->localServerDataBase, interface_id);
return 0;
#else
(void) interface_id;
return -1;
#endif
}
int thread_border_router_network_data_callback_register(int8_t interface_id, thread_network_data_tlv_cb *nwk_data_cb)
{
#ifdef HAVE_THREAD
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
if (!cur->thread_info || thread_attach_ready(cur) != 0) {
return -2;
}
cur->thread_info->network_data_tlv_cb = nwk_data_cb;
thread_border_router_network_data_appl_callback(cur);
return 0;
#else
(void)interface_id;
(void)nwk_data_cb;
return -1;
#endif
}
int thread_border_router_prefix_tlv_find(uint8_t *network_data_tlv, uint16_t network_data_tlv_length, uint8_t **prefix_tlv, bool *stable)
{
#ifdef HAVE_THREAD
uint16_t tlv_length;
if (!network_data_tlv || !network_data_tlv_length || !prefix_tlv) {
return -1;
}
//tr_debug("thread_tlv_lib_prefix_find() len=%d, tlv=%s", network_data_tlv_length, trace_array(network_data_tlv, network_data_tlv_length));
*stable = true;
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);
if (tlv_length == 0) {
tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_PREFIX, prefix_tlv);
*stable = false;
}
return tlv_length;
#else
(void)network_data_tlv;
(void)network_data_tlv_length;
(void)prefix_tlv;
(void)stable;
return -1;
#endif
}
int thread_border_router_tlv_find(uint8_t *prefix_tlv, uint16_t prefix_tlv_length, uint8_t **border_router_tlv, bool *stable)
{
#ifdef HAVE_THREAD
uint16_t tlv_length;
if (!prefix_tlv || !prefix_tlv_length || !border_router_tlv) {
return -1;
}
//tr_debug("thread_tlv_lib_border_router_find() len=%d, tlv=%s", prefix_tlv_length, trace_array(prefix_tlv, prefix_tlv_length));
uint8_t prefix_length = prefix_tlv[1];
uint8_t prefix_byte_len = prefixBits_to_bytes(prefix_length);
prefix_tlv = prefix_tlv + 2 + prefix_byte_len; //2 = domain ID + prefix length
prefix_tlv_length = prefix_tlv_length - prefix_byte_len - 2;
// find stable prefix first and if not found return unstable data
*stable = true;
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);
if (tlv_length == 0) {
tlv_length = thread_meshcop_tlv_find_next(prefix_tlv, prefix_tlv_length, THREAD_NWK_DATA_TYPE_BORDER_ROUTER, border_router_tlv);
*stable = false;
}
return tlv_length;
#else
(void)prefix_tlv;
(void)prefix_tlv_length;
(void)border_router_tlv;
(void)stable;
return -1;
#endif
}
int thread_border_router_prefix_context_id(uint8_t *prefix_tlv, uint16_t prefix_tlv_length)
{
#ifdef HAVE_THREAD
if (!prefix_tlv || !prefix_tlv_length) {
return -1;
}
uint16_t data_length = prefix_tlv_length;
while (data_length) {
uint8_t type = *prefix_tlv++;
uint16_t len = *prefix_tlv++;
data_length -= 2;
type &= THREAD_NWK_DATA_TYPE_MASK;
if (type == THREAD_NWK_DATA_TYPE_6LOWPAN_ID) {
return (*prefix_tlv & 0x0f);
}
data_length -= len;
prefix_tlv += len;
}
return -2;
#else
(void)prefix_tlv;
(void)prefix_tlv_length;
return -1;
#endif
}
int thread_border_router_service_tlv_find(uint8_t *network_data_tlv, uint16_t network_data_tlv_length, uint8_t **service_tlv, bool *stable)
{
#ifdef HAVE_THREAD
uint16_t tlv_length;
if (!network_data_tlv || !network_data_tlv_length || !service_tlv) {
return -1;
}
*stable = true;
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);
if (tlv_length == 0) {
tlv_length = thread_meshcop_tlv_find_next(network_data_tlv, network_data_tlv_length, THREAD_NWK_DATA_TYPE_SERVICE_DATA, service_tlv);
*stable = false;
}
return tlv_length;
#else
(void)network_data_tlv;
(void)network_data_tlv_length;
(void)service_tlv;
(void)stable;
return -1;
#endif
}
int thread_border_router_server_tlv_find(uint8_t *service_tlv, uint16_t service_tlv_length, uint8_t **server_tlv, bool *stable)
{
#ifdef HAVE_THREAD
uint16_t tlv_length;
if (!service_tlv || !service_tlv_length || !server_tlv) {
return -1;
}
uint8_t t_flag = service_tlv[0] >> 7;
service_tlv += 1;
if (!t_flag) {
service_tlv_length -= 4;
service_tlv += 4;
}
uint8_t service_data_len = *service_tlv;
service_tlv += 1 + service_data_len;
service_tlv_length = service_tlv_length - service_data_len - 2;
*stable = true;
tlv_length = thread_meshcop_tlv_find_next(service_tlv, service_tlv_length, THREAD_NWK_DATA_TYPE_SERVER_DATA | THREAD_NWK_STABLE_DATA, server_tlv);
if (tlv_length == 0) {
tlv_length = thread_meshcop_tlv_find_next(service_tlv, service_tlv_length, THREAD_NWK_DATA_TYPE_SERVER_DATA, server_tlv);
*stable = false;
}
return tlv_length;
#else
(void)service_tlv;
(void)service_tlv_length;
(void)server_tlv;
(void)stable;
return -1;
#endif
}
int thread_border_router_mdns_responder_start(int8_t interface_id, int8_t interface_id_mdns, const char *service_name)
{
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_mdns_start(interface_id, interface_id_mdns, service_name);
#else
(void)interface_id;
(void)interface_id_mdns;
(void)service_name;
return -1;
#endif
}
int thread_border_router_mdns_responder_stop(void)
{
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_mdns_stop();
#else
return -1;
#endif
}