mbed-os/connectivity/nanostack/sal-stack-nanostack/source/6LoWPAN/Thread/thread_bbr_api.c

1236 lines
45 KiB
C

/*
* Copyright (c) 2017-2020, Pelion 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"
#ifdef HAVE_THREAD_ROUTER
#include <string.h>
#include "ns_types.h"
#include <nsdynmemLIB.h>
#include <ns_list.h>
#include "ns_trace.h"
#include "eventOS_event_timer.h"
#include "randLIB.h"
#include "common_functions.h"
#include "thread_border_router_api.h"
#include "thread_bbr_api.h"
#include "net_ipv6_api.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "Common_Protocols/ipv6_constants.h"
#include "DHCPv6_Server/DHCPv6_server_service.h"
#include "6LoWPAN/Thread/thread_dhcpv6_server.h"
#include "thread_management_if.h"
#include "6LoWPAN/Thread/thread_config.h"
#include "6LoWPAN/Thread/thread_constants.h"
#include "6LoWPAN/Thread/thread_common.h"
#include "6LoWPAN/Thread/thread_bootstrap.h"
#include "6LoWPAN/Thread/thread_joiner_application.h"
#include "6LoWPAN/Thread/thread_bbr_commercial.h"
#include "6LoWPAN/Thread/thread_tmfcop_lib.h"
#include "6LoWPAN/Thread/thread_management_internal.h"
#include "6LoWPAN/Thread/thread_network_data_lib.h"
#include "6LoWPAN/Thread/thread_router_bootstrap.h"
#include "6LoWPAN/Thread/thread_border_router_api_internal.h"
#include "6LoWPAN/Thread/thread_mdns.h"
#include "6LoWPAN/MAC/mac_helper.h"
#include "coap_service_api.h"
#include "thread_management_server.h"
#include "socket_api.h"
#include "coap_service_api.h"
#include "Common_Protocols/icmpv6.h"
#define TRACE_GROUP "tBBR"
/*
* Border router instance data.
*/
typedef struct {
uint8_t commissioner_address[16];
uint8_t bbr_prefix[8];
uint16_t commissioner_pet_request_msg_id;
uint32_t br_delay_timer;
uint32_t br_delete_timer;
uint32_t router_upgrade_delay_timer;
uint16_t commissioner_timer;/* Commissioner parameter */
uint16_t commissioner_port; /* source port of commissioner border router */
uint16_t joiner_router_rloc;
uint8_t br_count;
int8_t interface_id;
int8_t coap_service_id;
int8_t coap_extension_virtual_service_id;
int8_t br_service_id;
int8_t backbone_interface_id;
int8_t udp_proxy_socket; /* socket to relay messages between BA and nodes */
bool br_info_published: 1;
bool br_hosted: 1;
bool routing_enabled: 1;
bool commissioner_connected: 1;
ns_list_link_t link;
} thread_bbr_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(bbr_instance_list, thread_bbr_t, link);
static thread_bbr_t *thread_bbr_find_by_interface(int8_t interface_id)
{
thread_bbr_t *this = NULL;
ns_list_foreach(thread_bbr_t, cur_br, &bbr_instance_list) {
if (cur_br->interface_id == interface_id) {
this = cur_br;
break;
}
}
return this;
}
static thread_bbr_t *thread_border_router_find_by_service(int8_t service_id)
{
thread_bbr_t *this = NULL;
ns_list_foreach(thread_bbr_t, cur_br, &bbr_instance_list) {
if (cur_br->coap_service_id == service_id || cur_br->br_service_id == service_id) {
this = cur_br;
break;
}
}
return this;
}
static thread_bbr_t *thread_border_router_find_by_udp_proxy_recv_socket_id(int8_t socket_id)
{
thread_bbr_t *this = NULL;
ns_list_foreach(thread_bbr_t, cur_br, &bbr_instance_list) {
if (cur_br->udp_proxy_socket == socket_id) {
this = cur_br;
break;
}
}
return this;
}
static void thread_border_router_commissioner_info_clear(thread_bbr_t *this)
{
this->commissioner_timer = 0;
coap_service_close_secure_connection(this->br_service_id, this->commissioner_address, this->commissioner_port);
coap_service_unregister_uri(this->coap_service_id, THREAD_URI_RELAY_RECEIVE);
this->commissioner_connected = false;
}
static int thread_border_agent_tmf_get_request_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
(void)source_address;
(void)source_port;
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
if (!this) {
return -1;
}
return thread_management_server_tmf_get_request_handler(this->interface_id, service_id, request_ptr);
}
static int thread_border_router_relay_transmit_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
uint8_t destination_address[16];
uint16_t shortAddress;
(void)source_address;
(void)source_port;
tr_debug("border router relay transmit");
thci_trace("brCommissionerDataRelayedInbound");
if (!this) {
return -1;
}
;
if (thread_management_get_ml_prefix_112(this->interface_id, destination_address) != 0 ||
2 > thread_meshcop_tlv_data_get_uint16(request_ptr->payload_ptr, request_ptr->payload_len, MESHCOP_TLV_JOINER_ROUTER_LOCATOR, &shortAddress)) {
tr_warn("No joiner router address");
return -1;
}
common_write_16_bit(shortAddress, &destination_address[14]);
coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, destination_address, THREAD_MANAGEMENT_PORT,
COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, THREAD_URI_RELAY_TRANSMIT, COAP_CT_OCTET_STREAM, request_ptr->payload_ptr, request_ptr->payload_len, NULL);
return -1;
}
static int br_commissioner_security_start_cb(int8_t service_id, uint8_t address[static 16], uint16_t port, uint8_t *pw, uint8_t *pw_len)
{
int ret = -1;
(void)address;
(void)port;
tr_info("brCommissionerDtlsSessionStarted");
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
if (this) {
link_configuration_s *linkConfiguration = thread_joiner_application_get_config(this->interface_id);
if (linkConfiguration) {
memcpy(pw, linkConfiguration->PSKc, 16);
*pw_len = 16;
ret = 0;
} else {
*pw_len = 0;
}
// ret = coap_service_security_key_set( service_id, address, port, this->PSKc_ptr, this->PSKc_len );
}
return ret;
}
static int br_commissioner_security_done_cb(int8_t service_id, uint8_t address[16], uint8_t keyblock[static 40])
{
(void)service_id;
(void)address;
(void)keyblock;
thci_trace("brCommissionerAccepted");
return 0;
}
static int thread_border_router_relay_receive_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
(void) source_address;
(void) source_port;
tr_debug("border router relay receive");
thci_trace("brCommissionerDataRelayedOutbound");
if (!this) {
return -1;
}
coap_service_request_send(this->br_service_id, COAP_REQUEST_OPTIONS_NONE, this->commissioner_address, this->commissioner_port,
COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, THREAD_URI_RELAY_RECEIVE, COAP_CT_OCTET_STREAM, request_ptr->payload_ptr, request_ptr->payload_len, NULL);
return -1;// no response for relay
}
/**
* Thread border router petition
* uri = tn/mc/la
*/
static int thread_border_router_leader_petition_resp_cb(int8_t service_id, uint8_t source_address[static 16], uint16_t source_port, sn_coap_hdr_s *response_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
uint8_t *ptr;
(void)source_address;
(void)source_port;
if (!response_ptr) {
tr_warn("invalid params");
return -1;
}
thci_trace("BR recv petition Resp data: %s", trace_array(response_ptr->payload_ptr, response_ptr->payload_len));
//tr_debug("border router leader response");
if (!this) {
tr_warn("commissioner service missing!");
return -1;
}
if (1 <= thread_meshcop_tlv_find(response_ptr->payload_ptr, response_ptr->payload_len, MESHCOP_TLV_STATE, &ptr) && *ptr == 1) {
// commissioning petition successfull
if (this->commissioner_connected == false) {
tr_debug("enabling native commissioner");
coap_service_register_uri(this->coap_service_id, THREAD_URI_RELAY_RECEIVE, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_border_router_relay_receive_cb);
}
this->commissioner_connected = true;
} else {
tr_debug("disabling native commissioner");
this->commissioner_connected = false;
}
coap_service_response_send_by_msg_id(this->br_service_id, COAP_REQUEST_OPTIONS_SECURE_BYPASS, this->commissioner_pet_request_msg_id, COAP_MSG_CODE_RESPONSE_CHANGED, COAP_CT_OCTET_STREAM, response_ptr->payload_ptr, response_ptr->payload_len);
this->commissioner_pet_request_msg_id = 0;
if (!this->commissioner_connected) {
// Commissioner rejected by leader
thread_border_router_commissioner_info_clear(this);
}
return 0;
}
static int thread_border_router_leader_message_resp_cb(int8_t service_id, uint8_t source_address[static 16], uint16_t source_port, sn_coap_hdr_s *response_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
(void)source_address;
(void)source_port;
if (!response_ptr || !this) {
tr_warn("invalid params");
return -1;
}
thci_trace("BR recv Resp data: %s", trace_array(response_ptr->payload_ptr, response_ptr->payload_len));
coap_service_response_send_by_msg_id(this->br_service_id, COAP_REQUEST_OPTIONS_SECURE_BYPASS, this->commissioner_pet_request_msg_id, COAP_MSG_CODE_RESPONSE_CHANGED, COAP_CT_OCTET_STREAM, response_ptr->payload_ptr, response_ptr->payload_len);
this->commissioner_pet_request_msg_id = 0;
return 0;
}
/* UDP_TX.ntf c/tx
* Handle message originating from Commissioner.
* */
static int thread_border_router_udp_proxy_transmit_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
int ret_val = 0;
uint8_t *udp_data_ptr, *ipv6_addr_ptr;
uint16_t udp_data_len, ipv6_addr_len, encapsulation_payload_len;
uint16_t dest_port;
uint8_t *encapsulation_payload;
ns_address_t ns_source_addr, ns_dest_addr;
int16_t sock_status;
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
(void) source_address;
(void) source_port;
tr_debug("Recv UDP_TX.ntf: %s", trace_array(request_ptr->payload_ptr, request_ptr->payload_len));
if (!this) {
return -1;
}
udp_data_len = thread_meshcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, MESHCOP_TLV_UDP_ENCAPSULATION, &udp_data_ptr);
ipv6_addr_len = thread_meshcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, MESHCOP_TLV_IPV6_ADDRESS, &ipv6_addr_ptr);
if (udp_data_len == 0 || ipv6_addr_len != 16) {
tr_err("UDP_TX.ntf invalid message");
return -1;
}
// Find source and destination ports from the encapsulation message
// source port is not used as we are already using ephemeral port in udp_proxy socket.
dest_port = common_read_16_bit(udp_data_ptr + 2);
// Get UDP payload
encapsulation_payload = udp_data_ptr + 4;
encapsulation_payload_len = udp_data_len - 4;
// Set source parameters
if (thread_management_get_commissioner_address(this->interface_id, ns_source_addr.address, 0) < 0) {
tr_error("Failed to get commissioner ALOC");
return -1;
}
// tr_debug("commissioner ALOC: %s", trace_ipv6(ns_source_addr.address));
ns_source_addr.identifier = 0; // Use ephemeral port instead of src_port
ns_source_addr.type = ADDRESS_IPV6;
if (this->udp_proxy_socket < 0) {
tr_error("UDP proxy socket not open!");
return -1;
}
/* Bind source to Commissioner ALOC */
ret_val = socket_bind(this->udp_proxy_socket, &ns_source_addr);
if (ret_val < 0) {
tr_error("UDP_TX socket bind2 failed %d", ret_val);
ret_val = -1;
}
// Set destination parameters
ns_dest_addr.identifier = dest_port;
ns_dest_addr.type = ADDRESS_IPV6;
memcpy(ns_dest_addr.address, ipv6_addr_ptr, 16);
tr_debug("push TMF msg to: %s, sock=%d", trace_ipv6(ns_dest_addr.address), this->udp_proxy_socket);
sock_status = socket_sendto(this->udp_proxy_socket, &ns_dest_addr, encapsulation_payload, encapsulation_payload_len);
if (sock_status < 0) {
tr_error("UDP Proxy socket write failed %d", sock_status);
ret_val = -1;
}
return -1;
}
/*
* Handle messages to commissioner.
* Create UDP_RX.ntf TMF message and send it to commissioner.
*/
static void thread_border_router_udp_proxy_tmf_message_receive(int8_t socket_id, ns_address_t *ns_address, uint8_t *tmf_data, int16_t tmf_data_len)
{
uint8_t *payload_ptr, *ptr;
uint16_t payload_len;
uint16_t dest_port = THREAD_MANAGEMENT_PORT;
tr_debug("UDP_RX tmf from %s, port=%d", trace_ipv6(ns_address->address), ns_address->identifier);
thread_bbr_t *this = thread_border_router_find_by_udp_proxy_recv_socket_id(socket_id);
if (!this) {
tr_error("BA instance not found!");
return;
}
payload_len = (2 + 2 + 2 + 2 + tmf_data_len) + (2 + THREAD_IPV6_ADDRESS_TLV_LENGTH);
payload_ptr = ptr = ns_dyn_mem_alloc(payload_len);
if (!payload_ptr) {
tr_error("UDP_RX.ntf alloc failed!");
return;
}
/* IPv6 Address TLV */
ptr = thread_meshcop_tlv_data_write(ptr, MESHCOP_TLV_IPV6_ADDRESS, THREAD_IPV6_ADDRESS_TLV_LENGTH, ns_address->address);
/* UDP Encapsulation TLV */
*ptr++ = MESHCOP_TLV_UDP_ENCAPSULATION;
*ptr++ = 0xff;
ptr = common_write_16_bit(2 + 2 + tmf_data_len, ptr); // Length = source port + dest port + TMF message
ptr = common_write_16_bit(ns_address->identifier, ptr); //source port
ptr = common_write_16_bit(dest_port, ptr); // destination port
memcpy(ptr, tmf_data, tmf_data_len);
tr_debug("send to: %s, port=%d", trace_ipv6(this->commissioner_address), this->commissioner_port);
tr_debug("UDP_RX.ntf: %s", trace_array(payload_ptr, payload_len));
coap_service_request_send(this->br_service_id, COAP_REQUEST_OPTIONS_NONE, this->commissioner_address, this->commissioner_port,
COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, THREAD_URI_UDP_RECVEIVE_NOTIFICATION, COAP_CT_OCTET_STREAM, payload_ptr, payload_len, NULL);
ns_dyn_mem_free(payload_ptr);
return;
}
/*
* UDP_Proxy receive socket callback.
* This method receives TMF messages from devices in Thread network and sends them to commissioner
*/
static void thread_border_router_udp_proxy_socket_recv_callback(void *socket_cb)
{
socket_callback_t *socket_callback = (socket_callback_t *)socket_cb;
ns_address_t ns_addr;
int16_t length;
if (socket_callback->event_type == SOCKET_DATA) {
if (socket_callback->d_len > 0) {
uint8_t *payload = (uint8_t *) ns_dyn_mem_alloc(socket_callback->d_len);
if (!payload) {
tr_error("buffer allocation failed");
return;
}
length = socket_read(socket_callback->socket_id, &ns_addr, payload, socket_callback->d_len);
if (length > 0) {
thread_border_router_udp_proxy_tmf_message_receive(socket_callback->socket_id, &ns_addr, payload, length);
}
ns_dyn_mem_free(payload);
}
}
}
static int thread_border_petition_to_leader_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
uint8_t destination_address[16];
char *uri_ptr;
tr_debug("border router petition to leader");
if (!this) {
return -1;
}
if (0 != thread_management_get_leader_address(this->interface_id, destination_address)) {
tr_debug("No leader");
return -1;
}
if (strncmp(THREAD_URI_COMMISSIONER_PETITION, (const char *)request_ptr->uri_path_ptr, request_ptr->uri_path_len) == 0) {
uri_ptr = THREAD_URI_LEADER_PETITION;
} else if (strncmp(THREAD_URI_COMMISSIONER_KEEP_ALIVE, (const char *)request_ptr->uri_path_ptr, request_ptr->uri_path_len) == 0) {
uri_ptr = THREAD_URI_LEADER_KEEP_ALIVE;
} else {
return -1;
}
// Update commissioner timeout for deleting the commissioner session if there is no messages.
this->commissioner_timer = THREAD_COMMISSIONER_KEEP_ALIVE_INTERVAL / 1000 + 10;
//TODO simple relaying is enough
memcpy(this->commissioner_address, source_address, 16);
this->commissioner_port = source_port;
this->commissioner_pet_request_msg_id = request_ptr->msg_id;//TODO one message at a time causes problems
thci_trace("BR recv uri:%.*s data: %s", request_ptr->uri_path_len, request_ptr->uri_path_ptr, trace_array(request_ptr->payload_ptr, request_ptr->payload_len));
//tr_debug("relay data %s",trace_array(request_ptr->payload_ptr, request_ptr->payload_len));
coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, destination_address, THREAD_MANAGEMENT_PORT,
COAP_MSG_TYPE_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, uri_ptr, COAP_CT_OCTET_STREAM, request_ptr->payload_ptr, request_ptr->payload_len, thread_border_router_leader_petition_resp_cb);
return 0;
}
static int thread_border_relay_to_leader_cb(int8_t service_id, uint8_t source_address[16], uint16_t source_port, sn_coap_hdr_s *request_ptr)
{
thread_bbr_t *this = thread_border_router_find_by_service(service_id);
uint8_t destination_address[16];
char uri_ptr[10];
tr_error("border router relay to leader using OLD specification");
if (!this || request_ptr->uri_path_len > 10) {
return -1;
}
if (0 != thread_management_get_leader_address(this->interface_id, destination_address)) {
tr_debug("No leader");
return -1;
}
//buffer length is limited to 10 characters
memset(uri_ptr, 0, 10);
memcpy(uri_ptr, (const char *)request_ptr->uri_path_ptr, request_ptr->uri_path_len);
// Update commissioner timeout for deleting the commissioner session if there is no messages.
this->commissioner_timer = THREAD_COMMISSIONER_KEEP_ALIVE_INTERVAL / 1000 + 10;
//TODO simple relaying is enough
memcpy(this->commissioner_address, source_address, 16);
this->commissioner_port = source_port;
this->commissioner_pet_request_msg_id = request_ptr->msg_id;//TODO one message at a time causes problems
thci_trace("BR recv uri:%.*s data: %s", request_ptr->uri_path_len, request_ptr->uri_path_ptr, trace_array(request_ptr->payload_ptr, request_ptr->payload_len));
coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_NONE, destination_address, THREAD_MANAGEMENT_PORT,
COAP_MSG_TYPE_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, uri_ptr, COAP_CT_OCTET_STREAM, request_ptr->payload_ptr, request_ptr->payload_len, thread_border_router_leader_message_resp_cb);
return -1;
}
#ifdef HAVE_THREAD_BORDER_ROUTER
static bool thread_bbr_default_route_exists(struct protocol_interface_info_entry *cur, uint8_t prefix_ptr[8])
{
uint16_t rloc16 = mac_helper_mac16_address_get(cur);
ns_list_foreach(thread_network_data_prefix_cache_entry_t, prefix, &cur->thread_info->networkDataStorage.localPrefixList) {
if (prefix_ptr &&
(prefix->servicesPrefixLen != 64 ||
memcmp(prefix_ptr, prefix->servicesPrefix, 8) != 0)) {
// Only matching prefixes are counted
continue;
}
ns_list_foreach(thread_network_server_data_entry_t, br, &prefix->borderRouterList) {
if (br->routerID == 0xfffe) {
continue;
}
if (!br->P_default_route) {
continue;
}
if (rloc16 != br->routerID) {
// different default route exists
return true;
}
}
}
return false;
}
static bool thread_bbr_i_host_prefix(struct protocol_interface_info_entry *cur, uint8_t prefix_ptr[8], uint8_t *br_count, bool *i_am_lowest)
{
bool i_host_this_prefix = false;
uint16_t rloc16 = mac_helper_mac16_address_get(cur);
uint16_t lowest_rloc16 = 0xfffe;
if (br_count) {
*br_count = 0;
}
if (i_am_lowest) {
*i_am_lowest = false;
}
if (!prefix_ptr) {
return false;
}
ns_list_foreach(thread_network_data_prefix_cache_entry_t, prefix, &cur->thread_info->networkDataStorage.localPrefixList) {
if (prefix->servicesPrefixLen != 64 ||
memcmp(prefix_ptr, prefix->servicesPrefix, 8) != 0) {
continue;
}
ns_list_foreach(thread_network_server_data_entry_t, br, &prefix->borderRouterList) {
if (br->routerID == 0xfffe) {
continue;
}
if (!br->P_default_route) {
continue;
}
if (lowest_rloc16 > br->routerID) {
lowest_rloc16 = br->routerID;
}
if (br_count) {
(*br_count)++;
}
if (rloc16 == br->routerID) {
i_host_this_prefix = true;
}
}
}
//tr_info("br: prefix %s, brs:%d %s",trace_array(prefix_ptr,8), *br_count, i_host_this_prefix?"I host":"");
if (i_am_lowest) {
*i_am_lowest = (lowest_rloc16 == rloc16);
}
return i_host_this_prefix;
}
static void thread_bbr_network_data_remove(thread_bbr_t *this)
{
tr_info("br: remove default route from network");
thread_border_router_prefix_delete(this->interface_id, this->bbr_prefix, 64);
DHCPv6_server_service_delete(this->interface_id, this->bbr_prefix, true);
memset(this->bbr_prefix, 0, 8);
this->br_info_published = false;
}
static void thread_bbr_network_data_send(thread_bbr_t *this, uint8_t prefix[8], uint8_t eui64[8])
{
thread_border_router_info_t br_info = { 0 };
tr_info("br: publish default route to network");
// delete old prefix
memset(this->bbr_prefix, 0, 8);
// create new prefix
if (thread_dhcp6_server_init(this->interface_id, prefix, eui64, THREAD_MIN_PREFIX_LIFETIME) != 0) {
tr_warn("DHCP server alloc fail");
// set 20 seconds delay before next process
this->br_delay_timer = 20;
return;
}
memcpy(this->bbr_prefix, prefix, 8);
br_info.P_default_route = true;
br_info.P_dhcp = true;
br_info.P_on_mesh = true;
br_info.stableData = true;
thread_border_router_prefix_add(this->interface_id, this->bbr_prefix, 64, &br_info);
thread_border_router_publish(this->interface_id);
this->br_info_published = true;
}
static void thread_bbr_routing_enable(thread_bbr_t *this, bool multicast_routing_enabled)
{
// Start multicast proxying
// We do not enable multicast forwarding as there is other default router present in network
multicast_fwd_set_forwarding(this->interface_id, multicast_routing_enabled);
if (this->routing_enabled) {
return;
}
tr_info("br: enable routing");
this->routing_enabled = true;
}
static void thread_bbr_routing_disable(thread_bbr_t *this)
{
if (!this->routing_enabled) {
return;
}
tr_info("br: disable routing");
// Stop multicast proxying
//multicast_fwd_set_proxy_upstream(-1);
// By default allow sitelocal multicast to enter mesh
multicast_fwd_set_forwarding(this->interface_id, false);
this->routing_enabled = false;
}
static void thread_bbr_status_check(thread_bbr_t *this, uint32_t seconds)
{
uint8_t global_address[16];
uint8_t *bbr_prefix_ptr = NULL;
bool br_lowest_host;
if (this->br_delay_timer) {
if (this->br_delay_timer > seconds) {
this->br_delay_timer -= seconds;
return;
}
this->br_delay_timer = 0;
}
if (arm_net_address_get(this->backbone_interface_id, ADDR_IPV6_GP, global_address) == 0) {
bbr_prefix_ptr = global_address;
}
if (this->br_info_published &&
(!bbr_prefix_ptr || memcmp(this->bbr_prefix, bbr_prefix_ptr, 8) != 0)) {
// Address is changed or address removed
// remove the old prefix and read the status of the new prefix
tr_info("br: Address changed or not valid stop routing");
thread_bbr_network_data_remove(this);
thread_bbr_routing_disable(this);
thread_border_router_publish(this->interface_id);
}
// Check if network data as border router is possible or modified
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(this->interface_id);
if (!cur) {
return;
}
this->br_hosted = thread_bbr_i_host_prefix(cur, bbr_prefix_ptr, &this->br_count, &br_lowest_host);
if (!this->br_info_published && bbr_prefix_ptr && this->br_count == 0) {
// publish global route either no border routers or our info has changed
tr_info("br: start and publish br info");
thread_bbr_network_data_send(this, bbr_prefix_ptr, &global_address[8]);
}
// Check from network data are we currently BR or not and change routing state
if (this->br_hosted) {
//If there is a default router present in any prefix other than us we do not forward multicast
//This prevents multicasts to different interfaces where Thread Mesh is forwarder
bool forward_multicast = !thread_bbr_default_route_exists(cur, NULL);
thread_bbr_commercial_mcast_fwd_check(cur->id, &forward_multicast);
thread_bbr_routing_enable(this, forward_multicast);
} else {
thread_bbr_routing_disable(this);
}
// Check if we can abort the deletion
if (this->br_count == 1) {
if (bbr_prefix_ptr) {
// Everything is ok cancel clearing if it was running as other BR is removed
this->br_delete_timer = 0;
}
}
if (this->br_delete_timer) {
if (this->br_delete_timer > seconds) {
this->br_delete_timer -= seconds;
} else {
// Delete timer was set and we need to remove our subscription.
thread_bbr_network_data_remove(this);
thread_border_router_publish(this->interface_id);
this->br_delete_timer = 0;
}
} else {
// Check states when we need to remove our BR from network
if (this->br_hosted && this->br_count > 1) {
// Race condition More border routers than there should trigger random delay to remove BR
// our implementation prefers lowest RLOC to to stay to reduce problem time
if (br_lowest_host) {
this->br_delete_timer = randLIB_get_random_in_range(20, 60);
} else {
this->br_delete_timer = randLIB_get_random_in_range(5, 10);
}
tr_info("br: too many BRs start remove jitter:%"PRIu32, this->br_delete_timer);
return;
}
if (this->br_info_published && !bbr_prefix_ptr) {
// Need to disable ND proxy will give a 20 second delay for it We could also disable routing immediately
this->br_delete_timer = 20;
tr_info("br: can not be border router need to remove after: %"PRIu32, this->br_delete_timer);
return;
}
}
}
static bool thread_bbr_activated(thread_bbr_t *this, uint32_t seconds)
{
protocol_interface_info_entry_t *cur;
if (this->backbone_interface_id < 0) {
return false;
}
cur = protocol_stack_interface_info_get_by_id(this->interface_id);
if (!cur || !cur->thread_info) {
return false;
}
// Check if we are router If we are not router upgrade to router.
// If upgrade fails then start as child router because network is full
if (thread_am_router(cur)) {
return true;
}
if (cur->thread_info->routerIdRequested) {
// Router id reguest pending we need to wait for response
return false;
}
if (this->router_upgrade_delay_timer) {
if (this->router_upgrade_delay_timer > seconds) {
this->router_upgrade_delay_timer -= seconds;
} else {
this->router_upgrade_delay_timer = 0;
}
// Delay timer running no actions can be made, so we will become router as child
return true;
}
// We will try to become router. This is done only in 120 seconds intervals if failed
thread_router_bootstrap_router_id_request(cur, THREAD_BBR_ROUTER_ID_REQUEST_STATUS);
this->router_upgrade_delay_timer = 120;
return false;
}
bool thread_bbr_routing_enabled(protocol_interface_info_entry_t *cur)
{
thread_bbr_t *this = thread_bbr_find_by_interface(cur->thread_info->interface_id);
if (!this) {
return false;
}
return this->routing_enabled;
}
void thread_bbr_network_data_update_notify(protocol_interface_info_entry_t *cur)
{
(void)cur;
thread_mdns_network_data_update_notify();
thread_bbr_commercial_route_update(cur);
}
#endif /* HAVE_THREAD_BORDER_ROUTER*/
static void thread_bbr_udp_proxy_service_stop(int8_t interface_id)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this) {
tr_error("Failed to find BA instance");
return;
}
socket_close(this->udp_proxy_socket);
this->udp_proxy_socket = -1;
}
int thread_bbr_commissioner_proxy_service_update(int8_t interface_id)
{
protocol_interface_info_entry_t *cur;
ns_address_t ns_source_addr;
int ret_val;
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!this || !cur) {
tr_error("Failed to find BBR instance");
ret_val = -1;
goto return_fail;
}
if (!cur->thread_info->registered_commissioner.commissioner_valid) {
// commissioner not enabled
if (this->udp_proxy_socket != -1) {
thread_bbr_udp_proxy_service_stop(interface_id);
}
return 0;
}
if (this->udp_proxy_socket != -1) {
// commissioner is valid and UDP service is already running
return 0;
}
// Set source parameters, if commissioner is available
ret_val = thread_management_get_commissioner_address(this->interface_id, &ns_source_addr.address[0], 0);
if (ret_val < 0) {
tr_error("Failed to get commissioner ALOC %d", ret_val);
ret_val = -1;
goto return_fail;
}
this->udp_proxy_socket = socket_open(SOCKET_UDP, 0, thread_border_router_udp_proxy_socket_recv_callback);
if (this->udp_proxy_socket < 0) {
tr_error("socket allocation failed!");
ret_val = -2;
goto return_fail;
}
/* register to handle UDP_TX.nft */
coap_service_register_uri(this->br_service_id, THREAD_URI_UDP_TRANSMIT_NOTIFICATION, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_router_udp_proxy_transmit_cb);
return 0;
return_fail:
thread_bbr_udp_proxy_service_stop(interface_id);
return ret_val;
}
int8_t thread_bbr_init(int8_t interface_id, uint16_t external_commisssioner_port)
{
thread_bbr_t *this = thread_bbr_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_bbr_t));
if (!this) {
return -2;
}
this->commissioner_pet_request_msg_id = 0;
this->commissioner_connected = false;
this->commissioner_port = 0;
this->interface_id = interface_id;
this->udp_proxy_socket = -1;
this->commissioner_timer = 0;
this->backbone_interface_id = -1;
this->br_delay_timer = 0;
this->router_upgrade_delay_timer = 0;
this->br_delete_timer = 0;
this->br_info_published = false;
this->routing_enabled = false;
memset(this->bbr_prefix, 0, 8);
this->joiner_router_rloc = 0xffff;
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;
}
this->br_service_id = coap_service_initialize(this->interface_id, external_commisssioner_port, COAP_SERVICE_OPTIONS_SECURE | COAP_SERVICE_OPTIONS_SECURE_BYPASS, br_commissioner_security_start_cb, br_commissioner_security_done_cb);
//TODO this needs secure bypass option HACK made
if (this->br_service_id < 0) {
tr_warn("Thread border router br-service init failed");
coap_service_delete(this->coap_service_id);
ns_dyn_mem_free(this);
return -4;
}
// Border Agent handles MGMT_GET, MGMT_ACTIVE_GET, and MGMT_PENDING_GET directly
coap_service_register_uri(this->br_service_id, THREAD_URI_MANAGEMENT_GET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_agent_tmf_get_request_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_ACTIVE_GET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_agent_tmf_get_request_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_PENDING_GET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_agent_tmf_get_request_cb);
// TODO, these URI's should be available in BA, if they are asked from BA by Native/External commissioner */
/*
coap_service_register_uri(this->br_service_id, THREAD_URI_ACTIVE_SET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_relay_to_leader_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_PENDING_SET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_relay_to_leader_cb);
*/
// Border router URIs for native and external commissioner
coap_service_register_uri(this->br_service_id, THREAD_URI_RELAY_TRANSMIT, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_border_router_relay_transmit_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_COMMISSIONER_PETITION, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_petition_to_leader_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_COMMISSIONER_KEEP_ALIVE, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_petition_to_leader_cb);
// These messages should not be forwarded according to new specification
coap_service_register_uri(this->br_service_id, THREAD_URI_COMMISSIONER_SET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_relay_to_leader_cb);
coap_service_register_uri(this->br_service_id, THREAD_URI_COMMISSIONER_GET, COAP_SERVICE_ACCESS_GET_ALLOWED, thread_border_relay_to_leader_cb);
ns_list_add_to_start(&bbr_instance_list, this);
return 0;
}
int8_t thread_bbr_get_commissioner_service(int8_t interface_id)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this) {
return 0;
}
return this->br_service_id;
}
void thread_bbr_delete(int8_t interface_id)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this) {
return;
}
thread_bbr_stop(interface_id);
coap_service_delete(this->coap_service_id);
coap_service_delete(this->br_service_id);
ns_list_remove(&bbr_instance_list, this);
ns_dyn_mem_free(this);
}
void thread_bbr_seconds_timer(int8_t interface_id, uint32_t seconds)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this) {
return;
}
if (this->commissioner_timer) {
if (this->commissioner_timer > seconds) {
this->commissioner_timer -= seconds;
} else {
//Clear commissioner session from the border router
tr_info("Timing out the commissioner");
thread_border_router_commissioner_info_clear(this);
}
}
#ifdef HAVE_THREAD_BORDER_ROUTER
// check if Border router can be active
if (thread_bbr_activated(this, seconds)) {
// Run the BBR SM
thread_bbr_status_check(this, seconds);
}
thread_bbr_commercial_seconds_timer(interface_id, seconds);
#endif
}
#endif // HAVE_THREAD_ROUTER
#ifdef HAVE_THREAD_BORDER_ROUTER
int thread_bbr_na_send(int8_t interface_id, const uint8_t target[static 16])
{
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(interface_id);
if (!cur) {
return -1;
}
// Send NA only if it is enabled for the backhaul
if (!cur->send_na) {
return -1;
}
buffer_t *buffer = icmpv6_build_na(cur, false, true, true, target, NULL, ADDR_UNSPECIFIED);
protocol_push(buffer);
return 0;
}
int thread_bbr_nd_entry_add(int8_t interface_id, const uint8_t *addr_data_ptr, uint32_t lifetime, void *info)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this || this->backbone_interface_id < 0) {
return -1;
}
ipv6_route_t *route = ipv6_route_add_with_info(addr_data_ptr, 128, interface_id, NULL, ROUTE_THREAD_PROXIED_HOST, info, 0, lifetime, 0);
// We are using route info field to store sequence number
if (!route) {
// Direct route to host allows ND proxying to work
tr_err("bbr out of resources");
return -2;
}
// send NA
thread_bbr_na_send(this->backbone_interface_id, addr_data_ptr);
return 0;
}
int thread_bbr_dua_entry_add(int8_t interface_id, const uint8_t *addr_data_ptr, uint32_t lifetime, const uint8_t *mleid_ptr)
{
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
thread_pbbr_dua_info_t *map;
if (!this || this->backbone_interface_id < 0) {
return -1;
}
ipv6_route_t *route = ipv6_route_lookup_with_info(addr_data_ptr, 128, interface_id, NULL, ROUTE_THREAD_PROXIED_DUA_HOST, NULL, 0);
if (!route) {
map = ns_dyn_mem_alloc(sizeof(thread_pbbr_dua_info_t));
if (!map) {
goto error;
}
// We are using route info field to store BBR MLEID map
route = ipv6_route_add_with_info(addr_data_ptr, 128, interface_id, NULL, ROUTE_THREAD_PROXIED_DUA_HOST, map, 0, lifetime, 0);
if (!route) {
// Direct route to host allows ND proxying to work
ns_dyn_mem_free(map);
goto error;
}
// Route info autofreed
route->info_autofree = true;
}
route->lifetime = lifetime; // update lifetime also from old route
map = route->info.info;
memcpy(map->mleid_ptr, mleid_ptr, 8);
map->last_contact_time = protocol_core_monotonic_time;
route->info.info = map;
// send NA
thread_bbr_na_send(this->backbone_interface_id, addr_data_ptr);
return 0;
error:
tr_err("out of resources");
return -2;
}
int thread_bbr_proxy_state_update(int8_t caller_interface_id, int8_t handler_interface_id, bool status)
{
protocol_interface_info_entry_t *cur = protocol_stack_interface_info_get_by_id(handler_interface_id);
(void) caller_interface_id;
if (!cur) {
tr_error("No Interface");
return -1;
}
// Route prefix is variable-length, so need to zero pad for ip6tos
bool weHostServiceAlso = false;
bool validToLearOnMeshRoute;
uint16_t routerId;
routerId = cur->mac_parameters->mac_short_address;
thread_network_data_cache_entry_t *networkData;
networkData = &cur->thread_info->networkDataStorage;
validToLearOnMeshRoute = thread_on_mesh_route_possible_add(cur->thread_info->thread_device_mode);
tr_debug("Proxy update");
ns_list_foreach(thread_network_data_prefix_cache_entry_t, curPrefix, &networkData->localPrefixList) {
weHostServiceAlso = thread_nd_hosted_by_this_routerid(routerId, &curPrefix->routeList);
if (weHostServiceAlso) {
ipv6_route_add(curPrefix->servicesPrefix, curPrefix->servicesPrefixLen, cur->id, NULL, ROUTE_THREAD, 0xffffffff, 0);
}
weHostServiceAlso = thread_nd_hosted_by_this_routerid(routerId, &curPrefix->borderRouterList);
ns_list_foreach(thread_network_server_data_entry_t, curRoute, &curPrefix->borderRouterList) {
if (thread_nd_on_mesh_address_valid(curRoute)) {
if (validToLearOnMeshRoute) {
if (curRoute->P_dhcp && weHostServiceAlso) {
if (status) {
ipv6_route_delete(curPrefix->servicesPrefix, curPrefix->servicesPrefixLen, cur->id, NULL, ROUTE_THREAD);
} else {
ipv6_route_add(curPrefix->servicesPrefix, curPrefix->servicesPrefixLen, cur->id, NULL, ROUTE_THREAD, 0xffffffff, 0);
}
}
}
}
}
}
return 0;
}
#endif
/*Public API control*/
int thread_bbr_start(int8_t interface_id, int8_t backbone_interface_id)
{
(void) interface_id;
(void) backbone_interface_id;
#ifdef HAVE_THREAD_BORDER_ROUTER
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
link_configuration_s *link_configuration_ptr = thread_joiner_application_get_config(interface_id);
uint8_t *extended_random_mac = thread_joiner_application_random_mac_get(interface_id);
char service_name[30] = {0};
char *ptr;
if (!this || !link_configuration_ptr || backbone_interface_id < 0) {
return -1;
}
tr_info("Thread BBR start if:%d, bb_if:%d", interface_id, backbone_interface_id);
this->backbone_interface_id = backbone_interface_id;
ptr = service_name;
*ptr++ = 'a' + extended_random_mac[0] % 26;
*ptr++ = 'a' + extended_random_mac[1] % 26;
*ptr++ = 'a' + extended_random_mac[2] % 26;
*ptr++ = 'a' + extended_random_mac[3] % 26;
memcpy(ptr, "-ARM-", 5);
ptr += 5;
memcpy(ptr, link_configuration_ptr->name, 16);
// Start mdns service
thread_mdns_start(this->interface_id, this->backbone_interface_id, service_name);
multicast_fwd_set_proxy_upstream(this->backbone_interface_id);
multicast_fwd_full_for_scope(this->interface_id, 0);
multicast_fwd_full_for_scope(this->backbone_interface_id, 0);
// By default multicast forwarding is not enabled as it causes multicast loops
multicast_fwd_set_forwarding(this->interface_id, false);
// Configure BBR neighbour cache parameters
arm_nwk_ipv6_neighbour_cache_configure(THREAD_BBR_IPV6_NEIGHBOUR_CACHE_SIZE,
THREAD_BBR_IPV6_NEIGHBOUR_CACHE_SHORT_TERM,
THREAD_BBR_IPV6_NEIGHBOUR_CACHE_LONG_TERM,
THREAD_BBR_IPV6_NEIGHBOUR_CACHE_LIFETIME);
thread_bbr_commercial_init(interface_id, backbone_interface_id);
return 0;
#else
return -1;
#endif // HAVE_THREAD_BORDER_ROUTER
}
int thread_bbr_timeout_set(int8_t interface_id, uint32_t timeout_a, uint32_t timeout_b, uint32_t delay)
{
(void) interface_id;
(void) timeout_a;
(void) timeout_b;
(void) delay;
#ifdef HAVE_THREAD_BORDER_ROUTER
thread_bbr_commercial_timeout_set(interface_id, timeout_a, timeout_b, delay);
return 0;
#else
return -1;
#endif // HAVE_THREAD_BORDER_ROUTER
}
int thread_bbr_prefix_set(int8_t interface_id, uint8_t *prefix)
{
(void) interface_id;
(void) prefix;
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_bbr_commercial_prefix_set(interface_id, prefix);
#else
return -1;
#endif // HAVE_THREAD_BORDER_ROUTER
}
int thread_bbr_sequence_number_set(int8_t interface_id, uint8_t sequence_number)
{
(void) interface_id;
(void) sequence_number;
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_bbr_commercial_sequence_number_set(interface_id, sequence_number);
#else
return -1;
#endif // HAVE_THREAD_BORDER_ROUTER
}
int thread_bbr_validation_interface_address_set(int8_t interface_id, const uint8_t *addr_ptr, uint16_t port)
{
(void) interface_id;
(void) addr_ptr;
(void) port;
#ifdef HAVE_THREAD_BORDER_ROUTER
return thread_bbr_commercial_address_set(interface_id, addr_ptr, port);
#else
return -1;
#endif // HAVE_THREAD_BORDER_ROUTER
}
void thread_bbr_stop(int8_t interface_id)
{
(void) interface_id;
#ifdef HAVE_THREAD_BORDER_ROUTER
thread_bbr_t *this = thread_bbr_find_by_interface(interface_id);
if (!this) {
return;
}
thread_bbr_commercial_delete(interface_id);
thread_bbr_network_data_remove(this);
thread_bbr_routing_disable(this);
thread_border_router_publish(interface_id);
thread_mdns_stop();
this->backbone_interface_id = -1;
#else
return;
#endif // HAVE_THREAD_BORDER_ROUTER
}