/* * Copyright (c) 2014-2019, 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" #ifdef HAVE_THREAD_NEIGHBOR_DISCOVERY #include #include "ns_list.h" #include "ns_trace.h" #include "nsdynmemLIB.h" #include "Core/include/ns_address_internal.h" #include "thread_tmfcop_lib.h" #include "coap_service_api.h" #include "thread_config.h" #include "thread_management_server.h" #include "thread_resolution_client.h" #define TRACE_GROUP TRACE_GROUP_THREAD_RESOLUTION_CLIENT #define ADDRESS_QUERY_TIMEOUT 3 #define ADDRESS_QUERY_INITIAL_RETRY_DELAY 15 #define ADDRESS_QUERY_MAX_RETRY_DELAY (8*60*60) // 8 hours = 28800 seconds #define ADDRESS_QUERY_SET_SIZE 8 /* Thread says queries go to this "all-routers" address - routers respond for * end nodes. */ static const uint8_t ADDR_MESH_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x03, [15] = 0x02 }; // ff03::2 typedef struct address_query { /* Outgoing query information */ uint8_t eid[16]; // IPv6 address uint16_t aq_timeout; uint16_t aq_retry_timeout; uint8_t aq_failures; /* Incoming notification information */ uint8_t an_ml_eid[8]; bool an_received; bool an_duplicate; uint16_t an_rloc; uint32_t an_last_transaction_time; ns_list_link_t link; } address_query_t; typedef struct thread_resolution_client { thread_resolution_client_response_cb *response_cb_ptr; thread_resolution_client_notification_cb *notification_cb_ptr; thread_resolution_client_error_cb *error_cb_ptr; int8_t interface_id; int8_t coap_service_id; NS_LIST_HEAD(address_query_t, link) queries; ns_list_link_t link; } thread_resolution_client_t; static NS_LIST_DEFINE(instance_list, thread_resolution_client_t, link); static thread_resolution_client_t *thread_resolution_client_find(int8_t interface_id) { ns_list_foreach(thread_resolution_client_t, cur_ptr, &instance_list) { if (cur_ptr->interface_id == interface_id) { return cur_ptr; } } return NULL; } static thread_resolution_client_t *thread_resolution_client_find_by_service(int8_t service_id) { ns_list_foreach(thread_resolution_client_t, cur_ptr, &instance_list) { if (cur_ptr->coap_service_id == service_id) { return cur_ptr; } } return NULL; } static address_query_t *address_query_find(thread_resolution_client_t *this, const uint8_t eid[static 16]) { ns_list_foreach(address_query_t, query, &this->queries) { if (addr_ipv6_equal(query->eid, eid)) { return query; } } return NULL; } static void address_query_delete(thread_resolution_client_t *this, address_query_t *query) { ns_list_remove(&this->queries, query); ns_dyn_mem_free(query); } static void address_query_timeout(thread_resolution_client_t *this, address_query_t *query) { if (!query->an_received) { query->aq_retry_timeout = ADDRESS_QUERY_INITIAL_RETRY_DELAY; /* Spec says initial * 2^Failures, _after_ increment, but that's silly */ for (uint8_t i = query->aq_failures; i; i--) { if (query->aq_retry_timeout < ADDRESS_QUERY_MAX_RETRY_DELAY / 2) { query->aq_retry_timeout *= 2; } else { query->aq_retry_timeout = ADDRESS_QUERY_MAX_RETRY_DELAY; break; } } query->aq_retry_timeout++; // +1 for timer vagaries if (query->aq_failures < UINT8_MAX) { query->aq_failures++; } tr_info("AQ failed (%d)", query->aq_failures); this->response_cb_ptr(this->interface_id, -1, query->eid, 0xFFFE, 0, NULL); return; } if (query->an_duplicate) { /* Note that error message contains ML-EID that we are *accepting*. All * *other* routers should invalidate, meaning it still makes sense * to process the result. */ tr_warn("Duplicate address during resolution"); thread_resolution_client_address_error(this->interface_id, ADDR_MESH_LOCAL_ALL_ROUTERS, query->eid, query->an_ml_eid); } /* No need to make the callback here - we did it immediately */ //tr_info("Resolved %s %s -> %04x", trace_ipv6(query->eid), trace_array(query->an_ml_eid, 8), query->an_rloc); //this->response_cb_ptr(this->interface_id, 0, query->eid, query->an_rloc, query->an_last_transaction_time, query->an_ml_eid); address_query_delete(this, query); } /* Handle incoming "notification" POSTs */ static int thread_resolution_client_notification_post_cb(int8_t service_id, uint8_t source_address[static 16], uint16_t source_port, sn_coap_hdr_s *request_ptr) { thread_resolution_client_t *this = thread_resolution_client_find_by_service(service_id); uint8_t *target_ip_addr = NULL; uint8_t *mleid = NULL; uint16_t target_rloc = 0xffff; uint32_t last_transaction_time = 0; sn_coap_msg_code_e coap_code; (void) source_address; (void) source_port; tr_debug("Thread resolution notification cb"); if (!this) { coap_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR; goto done; } /* Target EID, Locator and MLE-ID are compulsory */ if (16 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_TARGET_EID, &target_ip_addr) || 2 > thread_tmfcop_tlv_data_get_uint16(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_RLOC16, &target_rloc) || 8 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ML_EID, &mleid)) { coap_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; goto done; } /* Last transaction is optional - I think defaulting to 0 will give the desired result when it's absent */ thread_tmfcop_tlv_data_get_uint32(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_LAST_TRANSACTION_TIME, &last_transaction_time); address_query_t *query = address_query_find(this, target_ip_addr); if (query && query->aq_timeout > 0) { tr_info("a/an(solicited); target=%s mleid=%s rloc=%04x, ltt=%"PRIu32, trace_ipv6(target_ip_addr), trace_array(mleid, 8), target_rloc, last_transaction_time); /* A notification for an ongoing query */ if (query->an_received && memcmp(query->an_ml_eid, mleid, 8)) { query->an_duplicate = true; tr_warn("DUP!"); } if (!query->an_received || last_transaction_time <= query->an_last_transaction_time) { query->an_received = true; memcpy(query->an_ml_eid, mleid, 8); query->an_rloc = target_rloc; query->an_last_transaction_time = last_transaction_time; /* If we get "old then new" responses, we call the response twice. This * will likely mean the first transmission goes to the old address, then subsequent * will go to the new address. But better than waiting the full 3 seconds * to make the call. */ tr_info("Resolved %s %s -> %04x", trace_ipv6(query->eid), trace_array(query->an_ml_eid, 8), query->an_rloc); this->response_cb_ptr(this->interface_id, 0, query->eid, query->an_rloc, query->an_last_transaction_time, query->an_ml_eid); tr_info("Accepted"); } else { tr_info("Rejected"); } } else { /* Not for an non-ongoing query, but delete any stale ones - no longer "failed" */ if (query) { ns_list_remove(&this->queries, query); ns_dyn_mem_free(query); } tr_info("a/an(unsolicited); target=%s mleid=%s rloc=%04x", trace_ipv6(target_ip_addr), trace_array(mleid, 8), target_rloc); /* And tell the core */ if (this->notification_cb_ptr) { this->notification_cb_ptr(this->interface_id, target_ip_addr, target_rloc, mleid); } else { coap_code = COAP_MSG_CODE_RESPONSE_FORBIDDEN; goto done; } } coap_code = COAP_MSG_CODE_RESPONSE_CHANGED; done: coap_service_response_send(service_id, COAP_REQUEST_OPTIONS_ADDRESS_SHORT, request_ptr, coap_code, COAP_CT_NONE, NULL, 0); return 0; } /* Handle incoming "error" POSTs */ static int thread_resolution_client_error_post_cb(int8_t service_id, uint8_t source_address[static 16], uint16_t source_port, sn_coap_hdr_s *request_ptr) { thread_resolution_client_t *this = thread_resolution_client_find_by_service(service_id); uint8_t *target_ip_addr = NULL; uint8_t *mleid = NULL; sn_coap_msg_code_e coap_code; (void) source_address; (void) source_port; tr_debug("Thread resolution error cb"); if (!this) { coap_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR; goto done; } /* Target EID and ML-EID are compulsory */ if (16 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_TARGET_EID, &target_ip_addr) || 8 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ML_EID, &mleid)) { coap_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST; goto done; } if (this->error_cb_ptr) { this->error_cb_ptr(this->interface_id, target_ip_addr, mleid); } else { coap_code = COAP_MSG_CODE_RESPONSE_FORBIDDEN; goto done; } coap_code = COAP_MSG_CODE_RESPONSE_CHANGED; done: /* If the CoAP message was sent as confirmable, send response */ if (request_ptr->msg_type == COAP_MSG_TYPE_CONFIRMABLE) { coap_service_response_send(service_id, COAP_REQUEST_OPTIONS_ADDRESS_SHORT, request_ptr, coap_code, COAP_CT_NONE, NULL, 0); return 0; } return -1; } /** * Public Api functions */ void thread_resolution_client_init(int8_t interface_id) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); if (this) { return; } this = ns_dyn_mem_alloc(sizeof(thread_resolution_client_t)); if (!this) { return; } this->interface_id = interface_id; this->notification_cb_ptr = NULL; this->error_cb_ptr = NULL; //TODO: Check if to use ephemeral port here this->coap_service_id = thread_management_server_service_id_get(interface_id); if (this->coap_service_id < 0) { tr_err("Thread resolution client init failed"); ns_dyn_mem_free(this); return; } ns_list_init(&this->queries); ns_list_add_to_start(&instance_list, this); coap_service_register_uri(this->coap_service_id, THREAD_URI_ADDRESS_NOTIFICATION, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_resolution_client_notification_post_cb); coap_service_register_uri(this->coap_service_id, THREAD_URI_ADDRESS_ERROR, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_resolution_client_error_post_cb); } void thread_resolution_client_delete(int8_t interface_id) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); if (!this) { return; } coap_service_unregister_uri(this->coap_service_id, THREAD_URI_ADDRESS_NOTIFICATION); coap_service_unregister_uri(this->coap_service_id, THREAD_URI_ADDRESS_ERROR); ns_list_foreach_safe(address_query_t, query, &this->queries) { ns_list_remove(&this->queries, query); ns_dyn_mem_free(query); } ns_list_remove(&instance_list, this); ns_dyn_mem_free(this); } void thread_resolution_client_set_notification_cb(int8_t interface_id, thread_resolution_client_notification_cb notification_cb) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); if (!this) { return; } this->notification_cb_ptr = notification_cb; } void thread_resolution_client_set_error_cb(int8_t interface_id, thread_resolution_client_error_cb error_cb) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); if (!this) { return; } this->error_cb_ptr = error_cb; } int thread_resolution_client_address_error(int8_t interface_id, const uint8_t dest_ip_addr[16], const uint8_t target_ip_addr[16], const uint8_t ml_eid[8]) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); sn_coap_msg_type_e msg_type = COAP_MSG_TYPE_CONFIRMABLE; uint8_t payload[2 + 16 + 2 + 8]; uint8_t *ptr; uint8_t options; if (!this || !target_ip_addr || !dest_ip_addr || !ml_eid) { return -1; } ptr = payload; ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_TARGET_EID, 16, target_ip_addr); ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_ML_EID, 8, ml_eid); options = COAP_REQUEST_OPTIONS_ADDRESS_SHORT; if (addr_is_ipv6_multicast(dest_ip_addr)) { options |= COAP_REQUEST_OPTIONS_MULTICAST; msg_type = COAP_MSG_TYPE_NON_CONFIRMABLE; } tr_debug("TX thread address error: target %s, mle %s, dest %s", trace_ipv6(target_ip_addr), trace_array(ml_eid, 8), trace_ipv6(dest_ip_addr)); /* We don't expect a response to this POST, so we don't specify a callback. */ coap_service_request_send(this->coap_service_id, options, dest_ip_addr, THREAD_MANAGEMENT_PORT, msg_type, COAP_MSG_CODE_REQUEST_POST, THREAD_URI_ADDRESS_ERROR, COAP_CT_OCTET_STREAM, payload, ptr - payload, NULL); return 0; } int thread_resolution_client_resolve(int8_t interface_id, uint8_t ip_addr[16], thread_resolution_client_response_cb *resp_cb) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); uint8_t payload[2 + 16]; uint8_t *ptr; if (!this || !ip_addr || !resp_cb) { return -1; } address_query_t *query = address_query_find(this, ip_addr); if (query) { if (query->aq_timeout != 0) { /* Already in progress */ return 0; } if (query->aq_retry_timeout != 0) { /* Will not query - this will make the packet get dropped (with Address Unreachable) */ return -1; } /* Otherwise both timeouts zero - fine to proceed */ /* Remove so it can be readded to start of list */ ns_list_remove(&this->queries, query); } else { /* Get a new set entry - periodic timer will clear up if we go above limit */ query = ns_dyn_mem_alloc(sizeof * query); if (!query) { return -1; } memset(query, 0, sizeof * query); memcpy(query->eid, ip_addr, 16); } ns_list_add_to_start(&this->queries, query); query->aq_timeout = ADDRESS_QUERY_TIMEOUT + 1; // +1 to allow for timer vagaries this->response_cb_ptr = resp_cb; ptr = payload; ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_TARGET_EID, 16, ip_addr); tr_debug("thread address query: target %s", trace_ipv6(ip_addr)); /* We don't expect a response to this POST, so we don't specify a callback. * Instead, this POST may trigger an inbound POST, which will go to our * ADDRESS_QUERY_RESPONSE POST handler. */ coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_MULTICAST | COAP_REQUEST_OPTIONS_ADDRESS_SHORT, ADDR_MESH_LOCAL_ALL_ROUTERS, THREAD_MANAGEMENT_PORT, COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST, THREAD_URI_ADDRESS_QUERY_REQUEST, COAP_CT_OCTET_STREAM, payload, ptr - payload, NULL); return 0; } void thread_resolution_client_timer(int8_t interface_id, uint16_t seconds) { thread_resolution_client_t *this = thread_resolution_client_find(interface_id); if (!this) { return; } unsigned count = 0; /* Run timers on set entries */ ns_list_foreach_safe(address_query_t, query, &this->queries) { ++count; if (query->aq_retry_timeout != 0) { if (query->aq_retry_timeout <= seconds) { query->aq_retry_timeout = 0; } else { query->aq_retry_timeout -= seconds; } } if (query->aq_timeout != 0) { if (query->aq_timeout <= seconds) { query->aq_timeout = 0; address_query_timeout(this, query); } else { query->aq_timeout -= seconds; } } } /* Remove oldest excess entries that are not ongoing */ if (count > ADDRESS_QUERY_SET_SIZE) { ns_list_foreach_reverse_safe(address_query_t, query, &this->queries) { if (query->aq_timeout == 0) { address_query_delete(this, query); if (--count <= ADDRESS_QUERY_SET_SIZE) { break; } } } } } #endif // HAVE_THREAD_NEIGHBOR_DISCOVERY