Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers thread_resolution_client.c Source File

thread_resolution_client.c

00001 /*
00002  * Copyright (c) 2014-2017, Arm Limited and affiliates.
00003  * SPDX-License-Identifier: BSD-3-Clause
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions are met:
00007  *
00008  * 1. Redistributions of source code must retain the above copyright
00009  *    notice, this list of conditions and the following disclaimer.
00010  * 2. Redistributions in binary form must reproduce the above copyright
00011  *    notice, this list of conditions and the following disclaimer in the
00012  *    documentation and/or other materials provided with the distribution.
00013  * 3. Neither the name of the copyright holder nor the
00014  *    names of its contributors may be used to endorse or promote products
00015  *    derived from this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
00021  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027  * POSSIBILITY OF SUCH DAMAGE.
00028  */
00029 
00030 #include "nsconfig.h"
00031 #ifdef HAVE_THREAD_NEIGHBOR_DISCOVERY
00032 #include <ns_types.h>
00033 #include "ns_list.h"
00034 #include "ns_trace.h"
00035 #include "nsdynmemLIB.h"
00036 #include "Core/include/address.h"
00037 #include "thread_tmfcop_lib.h"
00038 
00039 #include "coap_service_api.h"
00040 
00041 #include "thread_config.h"
00042 #include "thread_resolution_client.h"
00043 
00044 #define TRACE_GROUP TRACE_GROUP_THREAD_RESOLUTION_CLIENT
00045 
00046 #define ADDRESS_QUERY_TIMEOUT               3
00047 #define ADDRESS_QUERY_INITIAL_RETRY_DELAY   15
00048 #define ADDRESS_QUERY_MAX_RETRY_DELAY       (8*60*60) // 8 hours = 28800 seconds
00049 
00050 #define ADDRESS_QUERY_SET_SIZE 8
00051 
00052 /* Thread says queries go to this "all-routers" address - routers respond for
00053  * end nodes.
00054  */
00055 static const uint8_t ADDR_MESH_LOCAL_ALL_ROUTERS[16] = { 0xff, 0x03, [15] = 0x02 }; // ff03::2
00056 
00057 typedef struct address_query {
00058     /* Outgoing query information */
00059     uint8_t eid[16]; // IPv6 address
00060     uint16_t aq_timeout;
00061     uint16_t aq_retry_timeout;
00062     uint8_t aq_failures;
00063     /* Incoming notification information */
00064     uint8_t an_ml_eid[8];
00065     bool an_received;
00066     bool an_duplicate;
00067     uint16_t an_rloc;
00068     uint32_t an_last_transaction_time;
00069     ns_list_link_t link;
00070 } address_query_t;
00071 
00072 typedef struct thread_resolution_client {
00073     thread_resolution_client_response_cb *response_cb_ptr;
00074     thread_resolution_client_notification_cb *notification_cb_ptr;
00075     thread_resolution_client_error_cb *error_cb_ptr;
00076     int8_t interface_id;
00077     int8_t coap_service_id;
00078     NS_LIST_HEAD (address_query_t, link) queries;
00079     ns_list_link_t link;
00080 } thread_resolution_client_t;
00081 
00082 static NS_LIST_DEFINE(instance_list, thread_resolution_client_t, link);
00083 
00084 static thread_resolution_client_t *thread_resolution_client_find(int8_t interface_id)
00085 {
00086     ns_list_foreach(thread_resolution_client_t , cur_ptr, &instance_list) {
00087         if (cur_ptr->interface_id == interface_id) {
00088             return cur_ptr;
00089         }
00090     }
00091     return NULL;
00092 }
00093 
00094 static thread_resolution_client_t *thread_resolution_client_find_by_service(int8_t service_id)
00095 {
00096     ns_list_foreach(thread_resolution_client_t, cur_ptr, &instance_list) {
00097         if (cur_ptr->coap_service_id == service_id) {
00098             return cur_ptr;
00099         }
00100     }
00101     return NULL;
00102 }
00103 
00104 static address_query_t *address_query_find(thread_resolution_client_t *this, const uint8_t eid[static 16])
00105 {
00106     ns_list_foreach(address_query_t, query, &this->queries) {
00107         if (addr_ipv6_equal(query->eid, eid)) {
00108             return query;
00109         }
00110     }
00111     return NULL;
00112 }
00113 
00114 static void address_query_delete(thread_resolution_client_t *this, address_query_t *query)
00115 {
00116     ns_list_remove(&this->queries, query);
00117     ns_dyn_mem_free(query);
00118 }
00119 
00120 static void address_query_timeout(thread_resolution_client_t *this, address_query_t *query)
00121 {
00122     if (!query->an_received) {
00123         query->aq_retry_timeout = ADDRESS_QUERY_INITIAL_RETRY_DELAY;
00124         /* Spec says initial * 2^Failures, _after_ increment, but that's silly */
00125         for (uint8_t i = query->aq_failures; i; i--) {
00126             if (query->aq_retry_timeout < ADDRESS_QUERY_MAX_RETRY_DELAY / 2) {
00127                 query->aq_retry_timeout *= 2;
00128             } else {
00129                 query->aq_retry_timeout = ADDRESS_QUERY_MAX_RETRY_DELAY;
00130                 break;
00131             }
00132         }
00133         query->aq_retry_timeout++; // +1 for timer vagaries
00134 
00135         if (query->aq_failures < UINT8_MAX) {
00136             query->aq_failures++;
00137         }
00138         tr_info("AQ failed (%d)", query->aq_failures);
00139         this->response_cb_ptr(this->interface_id, -1, query->eid, 0xFFFE, 0, NULL);
00140         return;
00141     }
00142 
00143     if (query->an_duplicate) {
00144         /* Note that error message contains ML-EID that we are *accepting*. All
00145          * *other* routers should invalidate, meaning it still makes sense
00146          * to process the result.
00147          */
00148         tr_warn("Duplicate address during resolution");
00149         thread_resolution_client_address_error(this->interface_id, ADDR_MESH_LOCAL_ALL_ROUTERS, query->eid, query->an_ml_eid);
00150     }
00151 
00152     /* No need to make the callback here - we did it immediately */
00153     //tr_info("Resolved %s %s -> %04x", trace_ipv6(query->eid), trace_array(query->an_ml_eid, 8), query->an_rloc);
00154     //this->response_cb_ptr(this->interface_id, 0, query->eid, query->an_rloc, query->an_last_transaction_time, query->an_ml_eid);
00155     address_query_delete(this, query);
00156 }
00157 
00158 /* Handle incoming "notification" POSTs */
00159 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)
00160 {
00161     thread_resolution_client_t *this = thread_resolution_client_find_by_service(service_id);
00162     uint8_t *target_ip_addr = NULL;
00163     uint8_t *mleid = NULL;
00164     uint16_t target_rloc = 0xffff;
00165     uint32_t last_transaction_time = 0;
00166     sn_coap_msg_code_e coap_code;
00167     (void) source_address;
00168     (void) source_port;
00169 
00170     tr_debug("Thread resolution notification cb");
00171     if (!this) {
00172         coap_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR;
00173         goto done;
00174     }
00175 
00176     /* Target EID, Locator and MLE-ID are compulsory */
00177     if (16 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_TARGET_EID, &target_ip_addr) ||
00178             2 > thread_tmfcop_tlv_data_get_uint16(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_RLOC16, &target_rloc) ||
00179             8 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ML_EID, &mleid)) {
00180         coap_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST;
00181         goto done;
00182     }
00183 
00184     /* Last transaction is optional - I think defaulting to 0 will give the desired result when it's absent */
00185     thread_tmfcop_tlv_data_get_uint32(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_LAST_TRANSACTION_TIME, &last_transaction_time);
00186 
00187     address_query_t *query = address_query_find(this, target_ip_addr);
00188     if (query && query->aq_timeout > 0) {
00189         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);
00190         /* A notification for an ongoing query */
00191         if (query->an_received && memcmp(query->an_ml_eid, mleid, 8)) {
00192             query->an_duplicate = true;
00193             tr_warn("DUP!");
00194         }
00195 
00196         if (!query->an_received || last_transaction_time <= query->an_last_transaction_time) {
00197             query->an_received = true;
00198             memcpy(query->an_ml_eid, mleid, 8);
00199             query->an_rloc = target_rloc;
00200             query->an_last_transaction_time = last_transaction_time;
00201             /* If we get "old then new" responses, we call the response twice. This
00202              * will likely mean the first transmission goes to the old address, then subsequent
00203              * will go to the new address. But better than waiting the full 3 seconds
00204              * to make the call.
00205              */
00206             tr_info("Resolved %s %s -> %04x", trace_ipv6(query->eid), trace_array(query->an_ml_eid, 8), query->an_rloc);
00207             this->response_cb_ptr(this->interface_id, 0, query->eid, query->an_rloc, query->an_last_transaction_time, query->an_ml_eid);
00208             tr_info("Accepted");
00209         } else {
00210             tr_info("Rejected");
00211         }
00212     } else {
00213         /* Not for an non-ongoing query, but delete any stale ones - no longer "failed" */
00214         if (query) {
00215             ns_list_remove(&this->queries, query);
00216             ns_dyn_mem_free(query);
00217         }
00218         tr_info("a/an(unsolicited); target=%s mleid=%s rloc=%04x", trace_ipv6(target_ip_addr), trace_array(mleid, 8), target_rloc);
00219         /* And tell the core */
00220         if (this->notification_cb_ptr) {
00221             this->notification_cb_ptr(this->interface_id, target_ip_addr, target_rloc, mleid);
00222         } else {
00223             coap_code = COAP_MSG_CODE_RESPONSE_FORBIDDEN;
00224             goto done;
00225         }
00226     }
00227 
00228     coap_code = COAP_MSG_CODE_RESPONSE_CHANGED;
00229 
00230 done:
00231     coap_service_response_send(service_id, COAP_REQUEST_OPTIONS_ADDRESS_SHORT, request_ptr, coap_code, COAP_CT_NONE, NULL, 0);
00232     return 0;
00233 }
00234 
00235 /* Handle incoming "error" POSTs */
00236 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)
00237 {
00238     thread_resolution_client_t *this = thread_resolution_client_find_by_service(service_id);
00239     uint8_t *target_ip_addr = NULL;
00240     uint8_t *mleid = NULL;
00241     sn_coap_msg_code_e coap_code;
00242     (void) source_address;
00243     (void) source_port;
00244 
00245     tr_debug("Thread resolution error cb");
00246     if (!this) {
00247         coap_code = COAP_MSG_CODE_RESPONSE_INTERNAL_SERVER_ERROR;
00248         goto done;
00249     }
00250 
00251     /* Target EID and ML-EID are compulsory */
00252     if (16 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_TARGET_EID, &target_ip_addr) ||
00253             8 > thread_tmfcop_tlv_find(request_ptr->payload_ptr, request_ptr->payload_len, TMFCOP_TLV_ML_EID, &mleid)) {
00254         coap_code = COAP_MSG_CODE_RESPONSE_BAD_REQUEST;
00255         goto done;
00256     }
00257 
00258     if (this->error_cb_ptr) {
00259         this->error_cb_ptr(this->interface_id, target_ip_addr, mleid);
00260     } else {
00261         coap_code = COAP_MSG_CODE_RESPONSE_FORBIDDEN;
00262         goto done;
00263     }
00264 
00265     coap_code = COAP_MSG_CODE_RESPONSE_CHANGED;
00266 
00267 done:
00268     /* If the CoAP message was sent as confirmable, send response */
00269     if (request_ptr->msg_type == COAP_MSG_TYPE_CONFIRMABLE) {
00270         coap_service_response_send(service_id, COAP_REQUEST_OPTIONS_ADDRESS_SHORT, request_ptr, coap_code, COAP_CT_NONE, NULL, 0);
00271         return 0;
00272     }
00273     return -1;
00274 }
00275 
00276 
00277 /**
00278  * Public Api functions
00279  */
00280 void thread_resolution_client_init(int8_t interface_id)
00281 {
00282     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00283     if (this) {
00284         return;
00285     }
00286 
00287     this = ns_dyn_mem_alloc(sizeof(thread_resolution_client_t));
00288     if (!this) {
00289         return;
00290     }
00291 
00292     this->interface_id = interface_id;
00293     this->notification_cb_ptr = NULL;
00294     this->error_cb_ptr = NULL;
00295     ns_list_init(&this->queries);
00296     //TODO: Check if to use ephemeral port here
00297     this->coap_service_id = coap_service_initialize(this->interface_id, THREAD_MANAGEMENT_PORT, COAP_SERVICE_OPTIONS_NONE, NULL, NULL);
00298     ns_list_add_to_start(&instance_list, this);
00299 
00300     coap_service_register_uri(this->coap_service_id, THREAD_URI_ADDRESS_NOTIFICATION, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_resolution_client_notification_post_cb);
00301     coap_service_register_uri(this->coap_service_id, THREAD_URI_ADDRESS_ERROR, COAP_SERVICE_ACCESS_POST_ALLOWED, thread_resolution_client_error_post_cb);
00302 }
00303 
00304 void thread_resolution_client_delete(int8_t interface_id)
00305 {
00306     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00307     if (!this) {
00308         return;
00309     }
00310 
00311     coap_service_unregister_uri(this->coap_service_id, THREAD_URI_ADDRESS_NOTIFICATION);
00312     coap_service_delete(this->coap_service_id);
00313     ns_list_foreach_safe(address_query_t, query, &this->queries) {
00314         ns_list_remove(&this->queries, query);
00315         ns_dyn_mem_free(query);
00316     }
00317     ns_list_remove(&instance_list, this);
00318     ns_dyn_mem_free(this);
00319 }
00320 
00321 void thread_resolution_client_set_notification_cb(int8_t interface_id, thread_resolution_client_notification_cb notification_cb)
00322 {
00323     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00324     if (!this) {
00325         return;
00326     }
00327 
00328     this->notification_cb_ptr = notification_cb;
00329 }
00330 
00331 void thread_resolution_client_set_error_cb(int8_t interface_id, thread_resolution_client_error_cb error_cb)
00332 {
00333     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00334     if (!this) {
00335         return;
00336     }
00337 
00338     this->error_cb_ptr = error_cb;
00339 }
00340 
00341 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])
00342 {
00343     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00344     sn_coap_msg_type_e msg_type = COAP_MSG_TYPE_CONFIRMABLE;
00345     uint8_t payload[2 + 16 + 2 + 8];
00346     uint8_t *ptr;
00347     uint8_t options;
00348 
00349     if (!this || !target_ip_addr || !dest_ip_addr || !ml_eid) {
00350         return -1;
00351     }
00352 
00353     ptr = payload;
00354     ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_TARGET_EID, 16, target_ip_addr);
00355     ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_ML_EID, 8, ml_eid);
00356 
00357     options = COAP_REQUEST_OPTIONS_ADDRESS_SHORT;
00358     if (addr_is_ipv6_multicast(dest_ip_addr)) {
00359         options |= COAP_REQUEST_OPTIONS_MULTICAST;
00360         msg_type = COAP_MSG_TYPE_NON_CONFIRMABLE;
00361     }
00362 
00363     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));
00364 
00365     /* We don't expect a response to this POST, so we don't specify a callback. */
00366     coap_service_request_send(this->coap_service_id, options,
00367                               dest_ip_addr, THREAD_MANAGEMENT_PORT,
00368                               msg_type, COAP_MSG_CODE_REQUEST_POST,
00369                               THREAD_URI_ADDRESS_ERROR, COAP_CT_OCTET_STREAM,
00370                               payload, ptr - payload, NULL);
00371 
00372     return 0;
00373 
00374 }
00375 
00376 int thread_resolution_client_resolve(int8_t interface_id, uint8_t ip_addr[16], thread_resolution_client_response_cb *resp_cb)
00377 {
00378     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00379     uint8_t payload[2 + 16];
00380     uint8_t *ptr;
00381 
00382     if (!this || !ip_addr || !resp_cb) {
00383         return -1;
00384     }
00385 
00386     address_query_t *query = address_query_find(this, ip_addr);
00387     if (query) {
00388         if (query->aq_timeout != 0) {
00389             /* Already in progress */
00390             return 0;
00391         }
00392         if (query->aq_retry_timeout != 0) {
00393             /* Will not query - this will make the packet get dropped (with Address Unreachable) */
00394             return -1;
00395         }
00396         /* Otherwise both timeouts zero - fine to proceed */
00397         /* Remove so it can be readded to start of list */
00398         ns_list_remove(&this->queries, query);
00399     } else {
00400         /* Get a new set entry - periodic timer will clear up if we go above limit */
00401         query = ns_dyn_mem_alloc(sizeof *query);
00402         if (!query) {
00403             return -1;
00404         }
00405         memset(query, 0, sizeof *query);
00406         memcpy(query->eid, ip_addr, 16);
00407     }
00408     ns_list_add_to_start(&this->queries, query);
00409 
00410     query->aq_timeout = ADDRESS_QUERY_TIMEOUT + 1; // +1 to allow for timer vagaries
00411 
00412     this->response_cb_ptr = resp_cb;
00413     ptr = payload;
00414     ptr = thread_tmfcop_tlv_data_write(ptr, TMFCOP_TLV_TARGET_EID, 16, ip_addr);
00415 
00416     tr_debug("thread address query: target %s", trace_ipv6(ip_addr));
00417 
00418     /* We don't expect a response to this POST, so we don't specify a callback.
00419      * Instead, this POST may trigger an inbound POST, which will go to our
00420      * ADDRESS_QUERY_RESPONSE POST handler.
00421      */
00422     coap_service_request_send(this->coap_service_id, COAP_REQUEST_OPTIONS_MULTICAST | COAP_REQUEST_OPTIONS_ADDRESS_SHORT,
00423                               ADDR_MESH_LOCAL_ALL_ROUTERS, THREAD_MANAGEMENT_PORT,
00424                               COAP_MSG_TYPE_NON_CONFIRMABLE, COAP_MSG_CODE_REQUEST_POST,
00425                               THREAD_URI_ADDRESS_QUERY_REQUEST, COAP_CT_OCTET_STREAM,
00426                               payload, ptr - payload, NULL);
00427 
00428     return 0;
00429 }
00430 
00431 void thread_resolution_client_timer(int8_t interface_id, uint16_t seconds)
00432 {
00433     thread_resolution_client_t *this = thread_resolution_client_find(interface_id);
00434     if (!this) {
00435         return;
00436     }
00437     unsigned count = 0;
00438     /* Run timers on set entries */
00439     ns_list_foreach_safe(address_query_t, query, &this->queries) {
00440         ++count;
00441         if (query->aq_retry_timeout != 0) {
00442             if (query->aq_retry_timeout <= seconds) {
00443                 query->aq_retry_timeout = 0;
00444             } else {
00445                 query->aq_retry_timeout -= seconds;
00446             }
00447         }
00448         if (query->aq_timeout != 0) {
00449             if (query->aq_timeout <= seconds) {
00450                 query->aq_timeout = 0;
00451                 address_query_timeout(this, query);
00452             } else {
00453                 query->aq_timeout -= seconds;
00454             }
00455         }
00456     }
00457     /* Remove oldest excess entries that are not ongoing */
00458     if (count > ADDRESS_QUERY_SET_SIZE) {
00459         ns_list_foreach_reverse_safe(address_query_t, query, &this->queries) {
00460             if (query->aq_timeout == 0) {
00461                 address_query_delete(this, query);
00462                 if (--count <= ADDRESS_QUERY_SET_SIZE) {
00463                     break;
00464                 }
00465             }
00466         }
00467     }
00468 
00469 }
00470 #endif // HAVE_THREAD_NEIGHBOR_DISCOVERY