mirror of https://github.com/ARMmbed/mbed-os.git
473 lines
18 KiB
C
473 lines
18 KiB
C
/*
|
|
* Copyright (c) 2014-2017, 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 <ns_types.h>
|
|
#include "ns_list.h"
|
|
#include "ns_trace.h"
|
|
#include "nsdynmemLIB.h"
|
|
#include "Core/include/address.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;
|
|
ns_list_init(&this->queries);
|
|
//TODO: Check if to use ephemeral port here
|
|
this->coap_service_id = thread_management_server_service_id_get(interface_id);
|
|
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
|
|
|