/* * 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); 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 }