From e4b585cc0e894bb5b32ec1034b9ced4f847f3a8b Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Mon, 15 Jun 2020 09:48:02 +0100 Subject: [PATCH] Proxy: Update Proxy support for Proxy-Uri: CoAP option Add code to better support the Proxy-Uri: and Proxy-Scheme: CoAP Options. examples/client.c: Make sure that the Proxy-Uri: option is added (if set) when clearing down an observation (fixes #112). Check that Proxy-Uri: option has a maximum data length of 1034 bytes. Handle user defined Proxy-Scheme: option correctly. Update -P option to include scheme to be used when connecting to the proxy. examples/coap-server.c: Add in hnd_proxy_uri() handler function. This has support for a direct or ongoing proxy connection for the coap* protocols. The ongoing connection is set up as a different CoAP session and is assocated with the incoming session for relaying requests / responses. Internal functions taken from client.c are used where appropriate. Proxy support code has a SERVER_CAN_PROXY wrapper, so footprint can easily be reduced if required. New -P and -u options added to support ongoing connections. include/coap2/net.h: Add in tracking the proxy uri handler into coap_context_t. include/coap2/resource.h: Track whether a resource definition is for Proxy-Uri:/Proxy-Scheme:. Define the coap_resource_proxy_uri_init() function for handling the Proxy-Uri: option or Proxy-Scheme: option in an incoming request. include/coap2/uri.h: Add in COAP_URI_SCHEME_HTTP and COAP_URI_SCHEME_HTTPS as valid Proxy-Uri: schemes. Define the coap_split_proxy_uri() function (similar to coap_split_uri() but supports the proxy extras. Both now use a common sub function internally. libcoap-2.map: libcoap-2.sym: Expose the new coap_resource_proxy_uri_init() and coap_split_proxy_uri() functions. man/Makefile.am: Handle coap_resource_get_userdata(3) now being over the 10 definition limit in man/coap_resource.txt.in. man/coap-client.txt.in: Update documentation for the -P option. man/coap-server.txt.in: Document the new -P proxy option. Document the new -u user option. man/coap_resource.txt.in: Include documentation for coap_resource_proxy_uri_init(). src/coap_debug.c: Output the Proxy-Scheme: Option as text. src/net.c: Sanity check and handle COAP_OPTION_PROXY_URI and COAP_OPTION_PROXY_SCHEME in handle_request(). src/resource.c: Add in coap_resource_proxy_uri_init() function. Handle the addition of a proxy uri handler in coap_add_resource(). src/uri.c: Rename coap_split_uri() to coap_split_uri_sub() and add in extra proxy handling support based on input parameters. Create new coap_split_uri() and coap_split_proxy_uri() which call coap_split_uri_sub() appropriately. TODO: Remove reference to Proxy support required src/coap_gnutls.c: Fix a double deallocate found during testing --- TODO | 2 - examples/client.c | 170 +++++---- examples/coap-server.c | 762 ++++++++++++++++++++++++++++++++++++++- include/coap2/net.h | 2 + include/coap2/resource.h | 29 ++ include/coap2/uri.h | 40 +- libcoap-2.map | 2 + libcoap-2.sym | 2 + man/Makefile.am | 1 + man/coap-client.txt.in | 9 +- man/coap-server.txt.in | 17 +- man/coap_cache.txt.in | 2 +- man/coap_resource.txt.in | 22 +- src/coap_gnutls.c | 6 +- src/net.c | 86 ++++- src/resource.c | 74 ++++ src/uri.c | 88 ++++- 17 files changed, 1200 insertions(+), 114 deletions(-) diff --git a/TODO b/TODO index 17decab9d4..52df83af38 100644 --- a/TODO +++ b/TODO @@ -11,8 +11,6 @@ Classification of issues: ================= * CRITICAL ISSUES ================= --> Proxy functionality - -> A coap-server should be able to act as proxy server ================ * SERIOUS ISSUES diff --git a/examples/client.c b/examples/client.c index 3f688ffa7d..8bade5f15c 100644 --- a/examples/client.c +++ b/examples/client.c @@ -56,8 +56,9 @@ static coap_optlist_t *optlist = NULL; /* Request URI. * TODO: associate the resources with transaction id and make it expireable */ static coap_uri_t uri; -static coap_string_t proxy = { 0, NULL }; -static uint16_t proxy_port = COAP_DEFAULT_PORT; +static coap_uri_t proxy = { {0, NULL}, 0, {0, NULL}, {0, NULL}, 0 }; +static int proxy_scheme_option = 0; +static int uri_host_option = 0; static unsigned int ping_seconds = 0; /* reading is done when this flag is set */ @@ -202,7 +203,7 @@ coap_new_request(coap_context_t *ctx, else { unsigned char buf[4]; coap_add_option(pdu, - COAP_OPTION_SIZE1, + COAP_OPTION_SIZE1, /* 60 */ coap_encode_var_safe8(buf, sizeof(buf), length), buf); @@ -237,17 +238,21 @@ clear_obs(coap_context_t *ctx, coap_session_t *session) { } for (option = optlist; option; option = option->next ) { - if (option->number == COAP_OPTION_URI_HOST) { + switch (option->number) { + case COAP_OPTION_URI_HOST : /* 3 */ if (!coap_add_option(pdu, option->number, option->length, option->data)) { goto error; } break; + default: + ; } } + /* Observe option comes after Uri-Host, but before the others */ if (!coap_add_option(pdu, - COAP_OPTION_OBSERVE, + COAP_OPTION_OBSERVE, /* 6 */ coap_encode_var_safe(buf, sizeof(buf), COAP_OBSERVE_CANCEL), buf)) { coap_log(LOG_CRIT, "cannot add option Observe: %u\n", COAP_OBSERVE_CANCEL); @@ -274,10 +279,23 @@ clear_obs(coap_context_t *ctx, coap_session_t *session) { block.num = 0; block.m = 0; coap_add_option(pdu, - COAP_OPTION_BLOCK2, + COAP_OPTION_BLOCK2, /* 23 */ coap_encode_var_safe(buf, sizeof(buf), (block.num << 4 | block.m << 3 | block.szx)), buf); } + for (option = optlist; option; option = option->next ) { + switch (option->number) { + case COAP_OPTION_PROXY_URI : /* 35 */ + case COAP_OPTION_PROXY_SCHEME : /* 39 */ + if (!coap_add_option(pdu, option->number, option->length, + option->data)) { + goto error; + } + break; + default: + ; + } + } if (coap_get_log_level() < LOG_DEBUG) coap_show_pdu(LOG_INFO, pdu); @@ -382,7 +400,7 @@ nack_handler(coap_context_t *context UNUSED_PARAM, break; case COAP_NACK_ICMP_ISSUE: default: - break; + ; } return; } @@ -404,7 +422,7 @@ message_handler(struct coap_context_t *ctx, coap_tid_t tid; coap_log(LOG_DEBUG, "** process incoming %d.%02d response:\n", - (received->code >> 5), received->code & 0x1F); + COAP_RESPONSE_CLASS(received->code), received->code & 0x1F); if (coap_get_log_level() < LOG_DEBUG) coap_show_pdu(LOG_INFO, received); @@ -480,11 +498,22 @@ message_handler(struct coap_context_t *ctx, coap_log(LOG_DEBUG, "query block %d\n", (coap_opt_block_num(block_opt) + 1)); coap_add_option(pdu, - blktype, + blktype, /* 23 or 27 */ coap_encode_var_safe(buf, sizeof(buf), ((coap_opt_block_num(block_opt) + 1) << 4) | COAP_OPT_BLOCK_SZX(block_opt)), buf); + for (option = optlist; option; option = option->next ) { + switch (option->number) { + case COAP_OPTION_PROXY_URI : /* 35 */ + case COAP_OPTION_PROXY_SCHEME : /* 39 */ + coap_add_option(pdu, option->number, option->length, + option->data); + break; + default: + ; /* skip other options */ + } + } tid = coap_send(session, pdu); if (tid == COAP_INVALID_TID) { @@ -567,7 +596,7 @@ message_handler(struct coap_context_t *ctx, option->data); break; default: - ; /* skip other options */ + ; /* skip other options */ } } @@ -578,12 +607,24 @@ message_handler(struct coap_context_t *ctx, coap_log(LOG_DEBUG, "send block %d\n", block.num); coap_add_option(pdu, - COAP_OPTION_BLOCK1, + COAP_OPTION_BLOCK1, /* 27 */ coap_encode_var_safe(buf, sizeof(buf), (block.num << 4) | (block.m << 3) | block.szx), buf); + for (option = optlist; option; option = option->next ) { + switch (option->number) { + case COAP_OPTION_PROXY_URI : /* 35 */ + case COAP_OPTION_PROXY_SCHEME : /* 39 */ + coap_add_option(pdu, option->number, option->length, + option->data); + break; + default: + ; /* skip other options */ + } + } + coap_add_option(pdu, - COAP_OPTION_SIZE1, + COAP_OPTION_SIZE1, /* 60 */ coap_encode_var_safe8(buf, sizeof(buf), payload.length), buf); @@ -650,7 +691,7 @@ usage( const char *program, const char *version) { "Usage: %s [-a addr] [-b [num,]size] [-e text] [-f file] [-l loss]\n" "\t\t[-m method] [-o file] [-p port] [-r] [-s duration] [-t type]\n" "\t\t[-v num] [-A type] [-B seconds] [-H hoplimit] [-K interval] [-N]\n" - "\t\t[-O num,text] [-P addr[:port]] [-T token] [-U]\n" + "\t\t[-O num,text] [-P scheme://address[:port]] [-T token] [-U]\n" "\t\t[[-h match_hint_file] [-k key] [-u user]]\n" "\t\t[[-c certfile] [-j keyfile] [-C cafile] [-J pkcs11_pin]\n" "\t\t[-M raw_pk] [-R root_cafile] [-S match_pki_sni_file]] URI\n" @@ -685,13 +726,15 @@ usage( const char *program, const char *version) { "\t-H hoplimit\tSet the Hop Limit count to hoplimit for proxies. Must\n" "\t \t\thave a value between 1 and 255 inclusive.\n" "\t \t\tDefault is '16'\n" - "\t-K interval\tsend a ping after interval seconds of inactivity\n" + "\t-K interval\tSend a ping after interval seconds of inactivity\n" "\t-N \t\tSend NON-confirmable message\n" "\t-O num,text\tAdd option num with contents text to request. If the\n" "\t \t\ttext begins with 0x, then the hex text is converted to\n" "\t \t\tbinary data\n" - "\t-P addr[:port]\tUse proxy (automatically adds Proxy-Uri option to\n" - "\t \t\trequest)\n" + "\t-P scheme://address[:port]\tScheme, address and optional port to\n" + "\t \t\tdefine how to connect to a CoAP proxy (automatically adds\n" + "\t \t\tProxy-Uri option to request) to forward the request to.\n" + "\t \t\tScheme is one of coap, coaps, coap+tcp and coaps+tcp\n" "\t-T token\tInclude specified token\n" "\t-U \t\tNever include Uri-Host or Uri-Port options\n" ,program, version, coap_string_tls_version(buffer, sizeof(buffer)) @@ -844,16 +887,12 @@ cmdline_uri(char *arg, int create_uri_opts) { size_t buflen; int res; - if (proxy.length) { /* create Proxy-Uri from argument */ + if (!proxy_scheme_option && proxy.host.length) { + /* create Proxy-Uri from argument */ size_t len = strlen(arg); - while (len > 270) { - coap_insert_optlist(&optlist, - coap_new_optlist(COAP_OPTION_PROXY_URI, - 270, - (unsigned char *)arg)); - - len -= 270; - arg += 270; + if (len > 1034) { + coap_log(LOG_ERR, "Absolute URI length must be <= 1034 bytes for a proxy\n"); + return -1; } coap_insert_optlist(&optlist, @@ -986,34 +1025,11 @@ cmdline_subscribe(char *arg) { static int cmdline_proxy(char *arg) { - char *proxy_port_str = strrchr((const char *)arg, ':'); /* explicit port ? */ - if (proxy_port_str) { - char *ipv6_delimiter = strrchr((const char *)arg, ']'); - if (!ipv6_delimiter) { - if (proxy_port_str == strchr((const char *)arg, ':')) { - /* host:port format - host not in ipv6 hexadecimal string format */ - *proxy_port_str++ = '\0'; /* split */ - proxy_port = atoi(proxy_port_str); - } - } else { - arg = strchr((const char *)arg, '['); - if (!arg) return 0; - arg++; - *ipv6_delimiter = '\0'; /* split */ - if (ipv6_delimiter + 1 == proxy_port_str++) { - /* [ipv6 address]:port */ - proxy_port = atoi(proxy_port_str); - } - } - } - - proxy.length = strlen(arg); - if ( (proxy.s = coap_malloc(proxy.length + 1)) == NULL) { - proxy.length = 0; - return 0; + if (coap_split_uri((unsigned char *)arg, strlen(arg), &proxy) < 0 || + proxy.path.length != 0 || proxy.query.length != 0) { + coap_log(LOG_ERR, "invalid CoAP Proxy definition\n"); + return -1; } - - memcpy(proxy.s, arg, proxy.length+1); return 1; } @@ -1092,6 +1108,31 @@ cmdline_option(char *arg) { coap_insert_optlist(&optlist, coap_new_optlist(num, strlen(arg), (unsigned char *)arg)); } + if (num == COAP_OPTION_PROXY_SCHEME) { + proxy_scheme_option = 1; + if (strcasecmp(arg, "coaps+tcp") == 0) { + proxy.scheme = COAP_URI_SCHEME_COAPS_TCP; + proxy.port = COAPS_DEFAULT_PORT; + } + else if (strcasecmp(arg, "coap+tcp") == 0) { + proxy.scheme = COAP_URI_SCHEME_COAP_TCP; + proxy.port = COAP_DEFAULT_PORT; + } + else if (strcasecmp(arg, "coaps") == 0) { + proxy.scheme = COAP_URI_SCHEME_COAPS; + proxy.port = COAPS_DEFAULT_PORT; + } + else if (strcasecmp(arg, "coap") == 0) { + proxy.scheme = COAP_URI_SCHEME_COAP; + proxy.port = COAP_DEFAULT_PORT; + } + else { + coap_log(LOG_WARNING, "%s is not a supported CoAP Proxy-Scheme\n", arg); + } + } + if (num == COAP_OPTION_URI_HOST) { + uri_host_option = 1; + } } /** @@ -1613,6 +1654,7 @@ main(int argc, char **argv) { ssize_t user_length = -1, key_length = 0; int create_uri_opts = 1; size_t i; + coap_uri_scheme_t scheme; #ifndef _WIN32 struct sigaction sa; #endif @@ -1759,13 +1801,15 @@ main(int argc, char **argv) { goto finish; } - if (proxy.length) { - server.length = proxy.length; - server.s = proxy.s; - port = proxy_port; + if (proxy.host.length) { + server.length = proxy.host.length; + server.s = proxy.host.s; + port = proxy.port; + scheme = proxy.scheme; } else { server = uri.host; - port = uri.port; + port = proxy_scheme_option ? proxy.port : uri.port; + scheme = proxy_scheme_option ? proxy.scheme : uri.scheme; } /* resolve destination address where server should be sent */ @@ -1790,11 +1834,11 @@ main(int argc, char **argv) { session = get_session( ctx, node_str[0] ? node_str : NULL, port_str, - uri.scheme==COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP : - uri.scheme==COAP_URI_SCHEME_COAPS_TCP ? COAP_PROTO_TLS : + scheme==COAP_URI_SCHEME_COAP_TCP ? COAP_PROTO_TCP : + scheme==COAP_URI_SCHEME_COAPS_TCP ? COAP_PROTO_TLS : (reliable ? - uri.scheme==COAP_URI_SCHEME_COAPS ? COAP_PROTO_TLS : COAP_PROTO_TCP - : uri.scheme==COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_UDP), + scheme==COAP_URI_SCHEME_COAPS ? COAP_PROTO_TLS : COAP_PROTO_TCP + : scheme==COAP_URI_SCHEME_COAPS ? COAP_PROTO_DTLS : COAP_PROTO_UDP), &dst, user_length >= 0 ? user : NULL, user_length >= 0 ? user_length : 0, @@ -1828,11 +1872,11 @@ main(int argc, char **argv) { /* construct CoAP message */ - if (!proxy.length && addrptr + if (!uri_host_option && (!proxy.host.length && addrptr && (inet_ntop(dst.addr.sa.sa_family, addrptr, addr, sizeof(addr)) != 0) && (strlen(addr) != uri.host.length || memcmp(addr, uri.host.s, uri.host.length) != 0) - && create_uri_opts) { + && create_uri_opts)) { /* add Uri-Host */ coap_insert_optlist(&optlist, diff --git a/examples/coap-server.c b/examples/coap-server.c index c806c98739..4b05c140b5 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -48,6 +48,10 @@ static char* strndup(const char* s1, size_t n) #include #endif +#ifndef SERVER_CAN_PROXY +#define SERVER_CAN_PROXY 1 +#endif + /* Need to refresh time once per sec */ #define COAP_RESOURCE_CHECK_TIME 1 @@ -572,6 +576,486 @@ hnd_put_example_data(coap_context_t *ctx UNUSED_PARAM, } } +#if SERVER_CAN_PROXY +static int +resolve_address(const coap_str_const_t *server, struct sockaddr *dst) { + + struct addrinfo *res, *ainfo; + struct addrinfo hints; + static char addrstr[256]; + int error, len=-1; + + memset(addrstr, 0, sizeof(addrstr)); + if (server->length) + memcpy(addrstr, server->s, server->length); + else + memcpy(addrstr, "localhost", 9); + + memset ((char *)&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = AF_UNSPEC; + + error = getaddrinfo(addrstr, NULL, &hints, &res); + + if (error != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(error)); + return error; + } + + for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) { + switch (ainfo->ai_family) { + case AF_INET6: + case AF_INET: + len = ainfo->ai_addrlen; + memcpy(dst, ainfo->ai_addr, len); + goto finish; + default: + ; + } + } + + finish: + freeaddrinfo(res); + return len; +} + +#define MAX_USER 128 /* Maximum length of a user name (i.e., PSK + * identity) in bytes. */ +static unsigned char user[MAX_USER + 1]; +static ssize_t user_length = -1; + +static coap_uri_t proxy = { {0, NULL}, 0, {0, NULL}, {0, NULL}, 0 }; +static size_t proxy_host_name_count = 0; +static const char **proxy_host_name_list = NULL; + +typedef struct proxy_list_t { + coap_session_t *ongoing; /* Ongoing session */ + coap_session_t *incoming; /* Incoming session */ + coap_bin_const_t *token; /* Incoming token in case of gateway issues */ +} proxy_list_t; + +static proxy_list_t *proxy_list = NULL; +static size_t proxy_list_count = 0; + +static void +hnd_proxy_uri(coap_context_t *ctx, + struct coap_resource_t *resource UNUSED_PARAM, + coap_session_t *session, + coap_pdu_t *request, + coap_binary_t *token, + coap_string_t *query UNUSED_PARAM, + coap_pdu_t *response) { + coap_opt_iterator_t opt_iter; + coap_opt_t *opt; + coap_opt_t *proxy_uri; + int proxy_scheme_option = 0; + coap_uri_t uri; + coap_string_t *uri_path = NULL; + coap_string_t *uri_query = NULL; + + /* + * See if Proxy-Scheme + */ + opt = coap_check_option(request, COAP_OPTION_PROXY_SCHEME, &opt_iter); + if (opt) { + memset(&uri, 0, sizeof(uri)); + if (coap_opt_length(opt) == 9 && + strncasecmp((const char*)coap_opt_value(opt), "coaps+tcp", 9) == 0) { + uri.scheme = COAP_URI_SCHEME_COAPS_TCP; + uri.port = COAPS_DEFAULT_PORT; + } + else if (coap_opt_length(opt) == 8 && + strncasecmp((const char*)coap_opt_value(opt), "coap+tcp", 8) == 0) { + uri.scheme = COAP_URI_SCHEME_COAP_TCP; + uri.port = COAP_DEFAULT_PORT; + } + else if (coap_opt_length(opt) == 5 && + strncasecmp((const char*)coap_opt_value(opt), "coaps", 5) == 0) { + uri.scheme = COAP_URI_SCHEME_COAPS; + uri.port = COAPS_DEFAULT_PORT; + } + else if (coap_opt_length(opt) == 4 && + strncasecmp((const char*)coap_opt_value(opt), "coap", 4) == 0) { + uri.scheme = COAP_URI_SCHEME_COAP; + uri.port = COAP_DEFAULT_PORT; + } + else { + coap_log(LOG_INFO, "Unsupported Proxy Scheme\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + + opt = coap_check_option(request, COAP_OPTION_URI_HOST, &opt_iter); + if (opt) { + uri.host.length = coap_opt_length(opt); + uri.host.s = coap_opt_value(opt); + } + else { + coap_log(LOG_INFO, "Proxy Scheme requires Uri-Host\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + opt = coap_check_option(request, COAP_OPTION_URI_PORT, &opt_iter); + if (opt) { + uri.port = + coap_decode_var_bytes (coap_opt_value (opt), + coap_opt_length (opt)); + } + uri_path = coap_get_uri_path(request); + if (uri_path) { + uri.path.s = uri_path->s; + uri.path.length = uri_path->length; + } + uri_query = coap_get_query(request); + if (uri_query) { + uri.query.s = uri_query->s; + uri.query.length = uri_query->length; + } + proxy_scheme_option = 1; + } + /* + * See if Proxy-Uri + */ + proxy_uri = coap_check_option(request, COAP_OPTION_PROXY_URI, &opt_iter); + if (proxy_uri) { + coap_log(LOG_INFO, "Proxy URI '%.*s'\n", + coap_opt_length(proxy_uri), + (const char*)coap_opt_value(proxy_uri)); + if (coap_split_proxy_uri(coap_opt_value(proxy_uri), + coap_opt_length(proxy_uri), + &uri) < 0) { + /* Need to return a 5.05 RFC7252 Section 5.7.2 */ + coap_log(LOG_INFO, "Proxy URI not decodable\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + } + if (proxy_scheme_option || proxy_uri) { + if (uri.host.length) { + /* + * TODO + * Add support for ongoing http(s) connections + */ + coap_session_t *ongoing = NULL; + size_t size; + uint8_t *data; + coap_pdu_t *pdu; + size_t i; + coap_address_t dst; + coap_optlist_t *optlist = NULL; + coap_opt_t *option; + unsigned char portbuf[2]; +#define BUFSIZE 100 + unsigned char _buf[BUFSIZE]; + unsigned char *buf = _buf; + size_t buflen; + int res; + coap_uri_scheme_t scheme; + static char client_sni[256]; + + /* Sanity check that the connection can be forwarded on */ + switch (uri.scheme) { + case COAP_URI_SCHEME_HTTP: + case COAP_URI_SCHEME_HTTPS: + coap_log(LOG_INFO, "Proxy URI http or https not supported\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + case COAP_URI_SCHEME_COAP: + break; + case COAP_URI_SCHEME_COAPS: + if (!coap_dtls_is_supported()) { + coap_log(LOG_INFO, + "coaps URI scheme not supported for proxy\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + break; + case COAP_URI_SCHEME_COAP_TCP: + if (!coap_tcp_is_supported()) { + coap_log(LOG_INFO, + "coap+tcp URI scheme not supported for proxy\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + break; + case COAP_URI_SCHEME_COAPS_TCP: + if (!coap_tls_is_supported()) { + coap_log(LOG_INFO, + "coaps+tcp URI scheme not supported for proxy\n"); + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + break; + default: + break; + } + + /* Handle the CoAP forwarding mapping */ + if (uri.scheme == COAP_URI_SCHEME_COAP || + uri.scheme == COAP_URI_SCHEME_COAPS || + uri.scheme == COAP_URI_SCHEME_COAP_TCP || + uri.scheme == COAP_URI_SCHEME_COAPS_TCP) { + + /* Locate existing forwarding relationship */ + for (i = 0; i < proxy_list_count; i++) { + if (proxy_list[i].incoming == session) { + ongoing = proxy_list[i].ongoing; + break; + } + } + if (i == proxy_list_count) { + /* Need to create a new forwarding mapping */ + coap_str_const_t server; + uint16_t port = COAP_DEFAULT_PORT; + proxy_list_t *new_proxy_list = realloc(proxy_list, + (i+1)*sizeof(proxy_list[0])); + + if (new_proxy_list == NULL) { + response->code = COAP_RESPONSE_CODE(500); + goto cleanup; + } + proxy_list = new_proxy_list; + proxy_list[i].incoming = session; + proxy_list[i].token = NULL; + coap_address_init(&dst); + + if (proxy.host.length) { + server = proxy.host; + server = proxy.host; + port = proxy.port; + scheme = proxy.scheme; + } else { + server = uri.host; + port = uri.port; + scheme = uri.scheme; + } + if (resolve_address(&server, &dst.addr.sa) < 0) { + response->code = COAP_RESPONSE_CODE(502); + goto cleanup; + } + switch (dst.addr.sa.sa_family) { + case AF_INET: + dst.addr.sin.sin_port = ntohs(port); + break; + case AF_INET6: + dst.addr.sin6.sin6_port = ntohs(port); + break; + default: + break; + } + switch (scheme) { + case COAP_URI_SCHEME_COAP: + case COAP_URI_SCHEME_COAP_TCP: + ongoing = proxy_list[i].ongoing = + coap_new_client_session(ctx, NULL, &dst, + scheme == COAP_URI_SCHEME_COAP ? + COAP_PROTO_UDP : COAP_PROTO_TCP); + break; + case COAP_URI_SCHEME_COAPS: + case COAP_URI_SCHEME_COAPS_TCP: + if (!key_length) { + /* Use our defined PKI certs (or NULL) */ + coap_dtls_pki_t dtls_pki; + memset(client_sni, 0, sizeof(client_sni)); + memset (&dtls_pki, 0, sizeof(dtls_pki)); + + if (cert_file) { + /* PKI Certs actually defined - control usage checking */ + dtls_pki.verify_peer_cert = 1; + dtls_pki.require_peer_cert = 1; + dtls_pki.allow_self_signed = 1; + dtls_pki.allow_expired_certs = 1; + dtls_pki.cert_chain_validation = 1; + dtls_pki.cert_chain_verify_depth = 2; + dtls_pki.check_cert_revocation = 1; + dtls_pki.allow_no_crl = 1; + dtls_pki.allow_expired_crl = 1; + dtls_pki.validate_cn_call_back = NULL; + dtls_pki.cn_call_back_arg = NULL; + dtls_pki.validate_sni_call_back = NULL; + dtls_pki.sni_call_back_arg = NULL; + } + if (server.length) + memcpy(client_sni, server.s, min(server.length, sizeof(client_sni)-1)); + else + memcpy(client_sni, "localhost", 9); + dtls_pki.client_sni = client_sni; + dtls_pki.pki_key.key_type = COAP_PKI_KEY_PEM; + dtls_pki.pki_key.key.pem.public_cert = cert_file; + dtls_pki.pki_key.key.pem.private_key = cert_file; + dtls_pki.pki_key.key.pem.ca_file = ca_file; + + ongoing = proxy_list[i].ongoing = + coap_new_client_session_pki(ctx, NULL, &dst, + scheme == COAP_URI_SCHEME_COAPS ? + COAP_PROTO_DTLS : COAP_PROTO_TLS, + &dtls_pki); + } + else { + /* Use our defined PSK */ + coap_dtls_cpsk_t dtls_psk; + + memset(client_sni, 0, sizeof(client_sni)); + memset (&dtls_psk, 0, sizeof(dtls_psk)); + dtls_psk.version = COAP_DTLS_CPSK_SETUP_VERSION; + dtls_psk.validate_ih_call_back = NULL; + dtls_psk.ih_call_back_arg = &dtls_psk.psk_info; + if (server.length) + memcpy(client_sni, server.s, + min(server.length, sizeof(client_sni) - 1)); + else + memcpy(client_sni, "localhost", 9); + dtls_psk.client_sni = client_sni; + dtls_psk.psk_info.identity.s = user; + dtls_psk.psk_info.identity.length = user_length; + dtls_psk.psk_info.key.s = key; + dtls_psk.psk_info.key.length = key_length; + ongoing = proxy_list[i].ongoing = + coap_new_client_session_psk2(ctx, NULL, &dst, + scheme == COAP_URI_SCHEME_COAPS ? + COAP_PROTO_DTLS : COAP_PROTO_TLS, + &dtls_psk); + } + break; + case COAP_URI_SCHEME_HTTP: + case COAP_URI_SCHEME_HTTPS: + default: + assert(0); + break; + } + if (proxy_list[i].ongoing == NULL) { + response->code = COAP_RESPONSE_CODE(505); + goto cleanup; + } + proxy_list_count++; + } + if (proxy_list[i].token) + coap_delete_bin_const(proxy_list[i].token); + + proxy_list[i].token = token ? + coap_new_bin_const(token->s, token->length) : NULL; + + /* + * Build up the ongoing PDU that we are going to send + */ + pdu = coap_pdu_init(request->type, request->code, + coap_new_message_id(ongoing), + coap_session_max_pdu_size(session)); + if (!pdu) { + response->code = COAP_RESPONSE_CODE(500); + goto cleanup; + } + + if (!coap_add_token(pdu, token->length, token->s)) { + coap_log(LOG_DEBUG, "cannot add token to proxy request\n"); + response->code = COAP_RESPONSE_CODE(500); + goto cleanup; + } + + if (proxy.host.length) { /* Use Proxy-Uri */ + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_PROXY_URI, + coap_opt_length(proxy_uri), + coap_opt_value(proxy_uri))); + + } + else { /* Use Uri-Path and Uri-Query */ + if (uri.port != (coap_uri_scheme_is_secure(&uri) ? + COAPS_DEFAULT_PORT : COAP_DEFAULT_PORT)) { + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_URI_PORT, + coap_encode_var_safe(portbuf, sizeof(portbuf), + (uri.port & 0xffff)), + portbuf)); + } + + if (uri.path.length) { + buflen = BUFSIZE; + if (uri.path.length > BUFSIZE) + coap_log(LOG_WARNING, + "URI path will be truncated (max buffer %d)\n", BUFSIZE); + res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen); + + while (res--) { + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_URI_PATH, + coap_opt_length(buf), + coap_opt_value(buf))); + + buf += coap_opt_size(buf); + } + } + + if (uri.query.length) { + buflen = BUFSIZE; + buf = _buf; + res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen); + + while (res--) { + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_URI_QUERY, + coap_opt_length(buf), + coap_opt_value(buf))); + + buf += coap_opt_size(buf); + } + } + } + + /* Copy the remaining options across */ + coap_option_iterator_init(request, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + switch (opt_iter.type) { + case COAP_OPTION_PROXY_URI: + case COAP_OPTION_PROXY_SCHEME: + case COAP_OPTION_URI_PATH: + case COAP_OPTION_URI_PORT: + case COAP_OPTION_URI_QUERY: + /* Skip those potentially already added */ + break; + default: + coap_insert_optlist(&optlist, + coap_new_optlist(opt_iter.type, + coap_opt_length(option), + coap_opt_value(option))); + break; + } + } + + /* Update pdu with options */ + coap_add_optlist_pdu(pdu, &optlist); + coap_delete_optlist(optlist); + + if (coap_get_data(request, &size, &data) && (size > 0)) { + if (!coap_add_data(pdu, size, data)) { + coap_log(LOG_DEBUG, "cannot add data to proxy request\n"); + } + } + + if (coap_get_log_level() < LOG_DEBUG) + coap_show_pdu(LOG_INFO, pdu); + + coap_send(ongoing, pdu); + goto cleanup; + } + else { + /* TODO http & https */ + coap_log(LOG_ERR, "Proxy-Uri scheme %d unknown\n", uri.scheme); + } + } + /* For now */ + response->code = COAP_RESPONSE_CODE(505); + } + else { + response->code = COAP_RESPONSE_CODE(404); + } +cleanup: + if (uri_path) coap_delete_string(uri_path); + if (uri_query) coap_delete_string(uri_query); +} + +#endif /* SERVER_CAN_PROXY */ + typedef struct dynamic_resource_t { coap_string_t *uri_path; coap_binary_t *value; @@ -933,6 +1417,177 @@ hnd_unknown_put(coap_context_t *ctx, hnd_put(ctx, r, session, request, token, query, response); } +#if SERVER_CAN_PROXY + +static void +remove_proxy_association(coap_session_t *session, int send_failure) { + + size_t i; + + for (i = 0; i < proxy_list_count; i++) { + if (proxy_list[i].incoming == session) { + coap_session_release(proxy_list[i].ongoing); + break; + } + if (proxy_list[i].ongoing == session && send_failure) { + if (send_failure) { + /* Need to send back a gateway failure */ + coap_pdu_t *response; + + response = coap_pdu_init(COAP_MESSAGE_NON, + COAP_RESPONSE_CODE(502), + coap_new_message_id(proxy_list[i].incoming), + coap_session_max_pdu_size(proxy_list[i].incoming)); + if (!response) { + coap_log(LOG_INFO, "PDU creation issue\n"); + return; + } + + if (proxy_list[i].token && + !coap_add_token(response, proxy_list[i].token->length, + proxy_list[i].token->s)) { + coap_log(LOG_DEBUG, + "Cannot add token to incoming proxy response PDU\n"); + } + + if (coap_send(proxy_list[i].incoming, response) == COAP_INVALID_TID) { + coap_log(LOG_INFO, "Failed to send PDU with 5.02 gateway issue\n"); + } + } + break; + } + } + if (i != proxy_list_count) { + /* remove no longer needed entry */ + if (proxy_list[i].token) + coap_delete_bin_const(proxy_list[i].token); + if (proxy_list_count-i > 1) { + memmove (&proxy_list[i], + &proxy_list[i+1], + (proxy_list_count-i-1) * sizeof (proxy_list[0])); + } + proxy_list_count--; + } +} + +static int +event_handler(coap_context_t *ctx UNUSED_PARAM, + coap_event_t event, + struct coap_session_t *session) { + + switch(event) { + case COAP_EVENT_DTLS_CLOSED: + case COAP_EVENT_TCP_CLOSED: + case COAP_EVENT_SESSION_CLOSED: + /* Need to remove any proxy associations */ + remove_proxy_association(session, 0); + break; + default: + break; + } + return 0; +} + +static void +message_handler(struct coap_context_t *ctx UNUSED_PARAM, + coap_session_t *session, + coap_pdu_t *sent UNUSED_PARAM, + coap_pdu_t *received, + const coap_tid_t id UNUSED_PARAM) { + + coap_pdu_t *pdu = NULL; + coap_session_t *incoming = NULL; + size_t i; + size_t size; + uint8_t *data; + coap_optlist_t *optlist = NULL; + coap_opt_t *option; + coap_opt_iterator_t opt_iter; + + for (i = 0; i < proxy_list_count; i++) { + if (proxy_list[i].ongoing == session) { + incoming = proxy_list[i].incoming; + break; + } + } + if (i == proxy_list_count) { + coap_log(LOG_DEBUG, "Unknown proxy ongoing session response received\n"); + return; + } + + coap_log(LOG_DEBUG, "** process incoming %d.%02d response:\n", + COAP_RESPONSE_CLASS(received->code), received->code & 0x1F); + if (coap_get_log_level() < LOG_DEBUG) + coap_show_pdu(LOG_INFO, received); + + /* + * Build up the ongoing PDU that we are going to send to proxy originator + */ + pdu = coap_pdu_init(received->type, received->code, + coap_new_message_id(incoming), + coap_session_max_pdu_size(incoming)); + if (!pdu) { + coap_log(LOG_DEBUG, "Failed to create ongoing proxy response PDU\n"); + return; + } + + if (!coap_add_token(pdu, received->token_length, received->token)) { + coap_log(LOG_DEBUG, "cannot add token to ongoing proxy response PDU\n"); + } + + /* Copy the remaining options across */ + coap_option_iterator_init(received, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + switch (opt_iter.type) { + /* In case any options need to be dropped in the future */ + default: + coap_insert_optlist(&optlist, + coap_new_optlist(opt_iter.type, + coap_opt_length(option), + coap_opt_value(option))); + break; + } + } + coap_add_optlist_pdu(pdu, &optlist); + coap_delete_optlist(optlist); + + if (coap_get_data(received, &size, &data) && (size > 0)) { + if (!coap_add_data(pdu, size, data)) { + coap_log(LOG_DEBUG, "cannot add data to proxy response\n"); + } + } + + if (coap_get_log_level() < LOG_DEBUG) + coap_show_pdu(LOG_INFO, pdu); + + coap_send(incoming, pdu); + return; +} + +static void +nack_handler(coap_context_t *context UNUSED_PARAM, + coap_session_t *session, + coap_pdu_t *sent UNUSED_PARAM, + coap_nack_reason_t reason, + const coap_tid_t id UNUSED_PARAM) { + + switch(reason) { + case COAP_NACK_TOO_MANY_RETRIES: + case COAP_NACK_NOT_DELIVERABLE: + case COAP_NACK_RST: + case COAP_NACK_TLS_FAILED: + /* Need to remove any proxy associations */ + remove_proxy_association(session, 1); + break; + case COAP_NACK_ICMP_ISSUE: + default: + break; + } + return; +} + +#endif /* SERVER_CAN_PROXY */ + static void init_resources(coap_context_t *ctx) { coap_resource_t *r; @@ -981,6 +1636,17 @@ init_resources(coap_context_t *ctx) { coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); coap_add_attr(r, coap_make_str_const("title"), coap_make_str_const("\"Example Data\""), 0); coap_add_resource(ctx, r); + +#ifdef SERVER_CAN_PROXY + if (proxy_host_name_count) { + r = coap_resource_proxy_uri_init(hnd_proxy_uri, proxy_host_name_count, + proxy_host_name_list); + coap_add_resource(ctx, r); + coap_register_event_handler(ctx, event_handler); + coap_register_response_handler(ctx, message_handler); + coap_register_nack_handler(ctx, nack_handler); + } +#endif /* SERVER_CAN_PROXY */ } static int @@ -1269,9 +1935,9 @@ usage( const char *program, const char *version) { "(c) 2010,2011,2015-2020 Olaf Bergmann and others\n\n" "%s\n\n" "Usage: %s [-d max] [-g group] [-l loss] [-p port] [-v num]\n" - "\t\t[-A address] [-N]\n" + "\t\t[-A address] [-N] [-P scheme://address[:port],name1[,name2..]]\n" "\t\t[[-h hint] [-i match_identity_file] [-k key]\n" - "\t\t[-s match_psk_sni_file]]\n" + "\t\t[-s match_psk_sni_file] [-u user]]\n" "\t\t[[-c certfile] [-j keyfile] [-m] [-n] [-C cafile] [-J pkcs11_pin]\n" "\t\t[-M rpk_file] [-R root_cafile] [-S match_pki_sni_file]]\n" "General Options\n" @@ -1295,6 +1961,16 @@ usage( const char *program, const char *version) { , program, version, coap_string_tls_version(buffer, sizeof(buffer)), program); fprintf( stderr, + "\t-P scheme://address[:port],name1[,name2[,name3..]]\tScheme, address,\n" + "\t \t\toptional port of how to connect to the next proxy server\n" + "\t \t\tand one or more names (comma separated) that this proxy\n" + "\t \t\tserver is known by. If the hostname of the incoming proxy\n" + "\t \t\trequest matches one of these names, then this server is\n" + "\t \t\tconsidered to be the final endpoint. If\n" + "\t \t\tscheme://address[:port] is not defined before the leading\n" + "\t \t\t, (comma) of the first name, then the ongoing connection\n" + "\t \t\twill be a direct connection.\n" + "\t \t\tScheme is one of coap, coaps, coap+tcp and coaps+tcp\n" "PSK Options (if supported by underlying (D)TLS library)\n" "\t-h hint\t\tIdentity Hint. Default is CoAP. Zero length is no hint\n" "\t-i match_identity_file\n" @@ -1317,6 +1993,10 @@ usage( const char *program, const char *version) { "\t \t\tNote: -k still needs to be defined for the default case\n" "\t \t\tNote: the new Pre-Shared Key will get updated if there is\n" "\t \t\talso a -i match\n" + "\t-u user\t\tUser identity for pre-shared key mode (only used if option P\n" + "\t \t\t is set)\n" + ); + fprintf(stderr, "PKI Options (if supported by underlying (D)TLS library)\n" "\tNote: If any one of '-c certfile', '-j keyfile' or '-C cafile' is in\n" "\tPKCS11 URI naming format (pkcs11: prefix), then any remaining non\n" @@ -1449,6 +2129,55 @@ get_context(const char *node, const char *port) { return ctx; } +#if SERVER_CAN_PROXY +static int +cmdline_proxy(char *arg) { + char *host_start = strchr(arg, ','); + char *next_name = host_start; + size_t ofs; + + if (!host_start) { + coap_log(LOG_WARNING, "One or more proxy host names not defined\n"); + return 0; + } + *host_start = '\000'; + + if (host_start != arg) { + /* Next upstream proxy is defined */ + if (coap_split_uri((unsigned char *)arg, strlen(arg), &proxy) < 0 || + proxy.path.length != 0 || proxy.query.length != 0) { + coap_log(LOG_ERR, "invalid CoAP Proxy definition\n"); + return 0; + } + } + proxy_host_name_count = 0; + while (next_name) { + proxy_host_name_count++; + next_name = strchr(next_name+1, ','); + } + proxy_host_name_list = coap_malloc(proxy_host_name_count * sizeof(char*)); + next_name = host_start; + ofs = 0; + while (next_name) { + proxy_host_name_list[ofs++] = next_name+1; + next_name = strchr(next_name+1, ','); + if (next_name) + *next_name = '\000'; + } + return 1; +} + +static ssize_t +cmdline_read_user(char *arg, unsigned char *buf, size_t maxlen) { + size_t len = strnlen(arg, maxlen); + if (len) { + memcpy(buf, arg, len); + } + /* 0 length Identity is valid */ + return len; +} +#endif /* SERVER_CAN_PROXY */ + static ssize_t cmdline_read_key(char *arg, unsigned char *buf, size_t maxlen) { size_t len = strnlen(arg, maxlen); @@ -1644,7 +2373,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); - while ((opt = getopt(argc, argv, "c:d:g:h:i:j:J:k:l:mnp:s:v:A:C:M:NR:S:")) != -1) { + while ((opt = getopt(argc, argv, "c:d:g:h:i:j:J:k:l:mnp:s:u:v:A:C:M:NP:R:S:")) != -1) { switch (opt) { case 'A' : strncpy(addr_str, optarg, NI_MAXHOST-1); @@ -1712,6 +2441,14 @@ main(int argc, char **argv) { strncpy(port_str, optarg, NI_MAXSERV-1); port_str[NI_MAXSERV - 1] = '\0'; break; +#if SERVER_CAN_PROXY + case 'P': + if (!cmdline_proxy(optarg)) { + fprintf(stderr, "error specifying proxy address or host names\n"); + exit(-1); + } + break; +#endif /* SERVER_CAN_PROXY */ case 'R' : root_ca_file = optarg; break; @@ -1727,6 +2464,13 @@ main(int argc, char **argv) { exit(1); } break; +#if SERVER_CAN_PROXY + case 'u': + user_length = cmdline_read_user(optarg, user, MAX_USER); + if (user_length >= 0) + user[user_length] = 0; + break; +#endif /* SERVER_CAN_PROXY */ case 'v' : log_level = strtol(optarg, NULL, 10); break; @@ -1880,6 +2624,7 @@ main(int argc, char **argv) { } if (valid_ids.count) free(valid_ids.id_list); + for (i = 0; i < valid_pki_snis.count; i++) { free(valid_pki_snis.pki_sni_list[i].sni_match); free(valid_pki_snis.pki_sni_list[i].new_cert); @@ -1894,6 +2639,17 @@ main(int argc, char **argv) { } if (dynamic_entry) free(dynamic_entry); if (example_data_ptr) coap_delete_binary(example_data_ptr); +#if SERVER_CAN_PROXY + for (i = 0; i < proxy_list_count; i++) { + if (proxy_list[i].token) + coap_delete_bin_const(proxy_list[i].token); + } + free(proxy_list); + proxy_list = NULL; + proxy_list_count = 0; + if (proxy_host_name_list) + coap_free(proxy_host_name_list); +#endif /* SERVER_CAN_PROXY */ coap_free_context(ctx); coap_cleanup(); diff --git a/include/coap2/net.h b/include/coap2/net.h index ff0eebb90b..8eb69e7972 100644 --- a/include/coap2/net.h +++ b/include/coap2/net.h @@ -152,6 +152,8 @@ typedef struct coap_context_t { resources */ struct coap_resource_t *unknown_resource; /**< can be used for handling unknown resources */ + struct coap_resource_t *proxy_uri_resource; /**< can be used for handling + proxy URI resources */ #ifndef WITHOUT_ASYNC /** diff --git a/include/coap2/resource.h b/include/coap2/resource.h index ccceb42cbb..2c83c00e73 100644 --- a/include/coap2/resource.h +++ b/include/coap2/resource.h @@ -89,6 +89,7 @@ typedef struct coap_resource_t { unsigned int observable:1; /**< can be observed */ unsigned int cacheable:1; /**< can be cached */ unsigned int is_unknown:1; /**< resource created for unknown handler */ + unsigned int is_proxy_uri:1; /**< resource created for proxy URI handler */ /** * Used to store handlers for the seven coap methods @c GET, @c POST, @c PUT, @@ -123,6 +124,16 @@ typedef struct coap_resource_t { */ coap_context_t *context; + /** + * Count of valid names this host is known by (proxy support) + */ + size_t proxy_name_count; + + /** + * Array valid names this host is known by (proxy support) + */ + coap_str_const_t ** proxy_name_list; + /** * This pointer is under user control. It can be used to store context for * the coap handler. @@ -194,6 +205,24 @@ coap_resource_t *coap_resource_init(coap_str_const_t *uri_path, */ coap_resource_t *coap_resource_unknown_init(coap_method_handler_t put_handler); +/** + * Creates a new resource object for handling proxy URIs. + * This function returns the new coap_resource_t object. + * + * Note: There can only be one proxy resource handler per context - attaching + * a new one overrides the previous definition. + * + * @param handler The PUT/POST/GET etc. handler that handles all request types. + * @param host_name_count The number of provided host_name_list entries. A + * minimum of 1 must be provided. + * @param host_name_list Array of depth host_name_count names that this proxy + * is known by. + * + * @return A pointer to the new object or @c NULL on error. + */ +coap_resource_t *coap_resource_proxy_uri_init(coap_method_handler_t handler, + size_t host_name_count, const char *host_name_list[]); + /** * Sets the notification message type of resource @p resource to given * @p mode diff --git a/include/coap2/uri.h b/include/coap2/uri.h index 8da292c830..6c29ec78aa 100644 --- a/include/coap2/uri.h +++ b/include/coap2/uri.h @@ -19,19 +19,22 @@ struct coap_pdu_t; * The scheme specifiers. Secure schemes have an odd numeric value, * others are even. */ -enum coap_uri_scheme_t { - COAP_URI_SCHEME_COAP=0, - COAP_URI_SCHEME_COAPS=1, - COAP_URI_SCHEME_COAP_TCP=2, - COAP_URI_SCHEME_COAPS_TCP=3 -}; +typedef enum coap_uri_scheme_t { + COAP_URI_SCHEME_COAP = 0, + COAP_URI_SCHEME_COAPS, /* 1 */ + COAP_URI_SCHEME_COAP_TCP, /* 2 */ + COAP_URI_SCHEME_COAPS_TCP, /* 3 */ + COAP_URI_SCHEME_HTTP, /* 4 Proxy-Uri only */ + COAP_URI_SCHEME_HTTPS /* 5 Proxy-Uri only */ +} coap_uri_scheme_t; /** This mask can be used to check if a parsed URI scheme is secure. */ #define COAP_URI_SCHEME_SECURE_MASK 0x01 /** * Representation of parsed URI. Components may be filled from a string with - * coap_split_uri() and can be used as input for option-creation functions. + * coap_split_uri() or coap_split_proxy_uri() and can be used as input for + * option-creation functions. */ typedef struct { coap_str_const_t host; /**< host part of the URI */ @@ -79,17 +82,36 @@ coap_uri_t *coap_clone_uri(const coap_uri_t *uri); * Parses a given string into URI components. The identified syntactic * components are stored in the result parameter @p uri. Optional URI * components that are not specified will be set to { 0, 0 }, except for the - * port which is set to @c COAP_DEFAULT_PORT. This function returns @p 0 if - * parsing succeeded, a value less than zero otherwise. + * port which is set to the default port for the protocol. This function + * returns @p 0 if parsing succeeded, a value less than zero otherwise. * * @param str_var The string to split up. * @param len The actual length of @p str_var * @param uri The coap_uri_t object to store the result. + * * @return @c 0 on success, or < 0 on error. * */ int coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri); +/** + * Parses a given string into URI components. The identified syntactic + * components are stored in the result parameter @p uri. Optional URI + * components that are not specified will be set to { 0, 0 }, except for the + * port which is set to default port for the protocol. This function returns + * @p 0 if parsing succeeded, a value less than zero otherwise. + * Note: This function enforces that the given string is in Proxy-Uri format + * as well as supports different schema such as http. + * + * @param str_var The string to split up. + * @param len The actual length of @p str_var + * @param uri The coap_uri_t object to store the result. + * + * @return @c 0 on success, or < 0 on error. + * + */ +int coap_split_proxy_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri); + /** * Splits the given URI path into segments. Each segment is preceded * by an option pseudo-header with delta-value 0 and the actual length diff --git a/libcoap-2.map b/libcoap-2.map index 7c9daab582..e0853a141f 100644 --- a/libcoap-2.map +++ b/libcoap-2.map @@ -163,6 +163,7 @@ global: coap_resize_binary; coap_resource_init; coap_resource_notify_observers; + coap_resource_proxy_uri_init; coap_resource_set_dirty; coap_resource_unknown_init; coap_response_phrase; @@ -204,6 +205,7 @@ global: coap_show_tls_version; coap_socket_strerror; coap_split_path; + coap_split_proxy_uri; coap_split_query; coap_split_uri; coap_startup; diff --git a/libcoap-2.sym b/libcoap-2.sym index c479219b8b..15073ef6cb 100644 --- a/libcoap-2.sym +++ b/libcoap-2.sym @@ -161,6 +161,7 @@ coap_remove_from_queue coap_resize_binary coap_resource_init coap_resource_notify_observers +coap_resource_proxy_uri_init coap_resource_set_dirty coap_resource_unknown_init coap_response_phrase @@ -202,6 +203,7 @@ coap_show_pdu coap_show_tls_version coap_socket_strerror coap_split_path +coap_split_proxy_uri coap_split_query coap_split_uri coap_startup diff --git a/man/Makefile.am b/man/Makefile.am index ef52189718..39ed9da757 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -87,6 +87,7 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_pdu_setup.3" > coap_encode_var_safe8.3 @echo ".so man3/coap_pdu_setup.3" > coap_split_path.3 @echo ".so man3/coap_pdu_setup.3" > coap_split_query.3 + @echo ".so man3/coap_resource.3" > coap_resource_get_userdata.3 @echo ".so man3/coap_session.3" > coap_session_get_app_data.3 @echo ".so man3/coap_session.3" > coap_session_set_app_data.3 @echo ".so man3/coap_session.3" > coap_tcp_is_supported.3 diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index 8d5e45d9c5..150c697346 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -18,7 +18,7 @@ SYNOPSIS [*-m* method] [*-o* file] [*-p* port] [*-r*] [*-s duration*] [*-t* type] [*-v* num] [*-A* type] [*-B* seconds] [*-H* hoplimit] [*-K* interval] [*-N*] [*-O* num,text] - [*-P* addr[:port]] [*-T* token] [*-U*] + [*-P* scheme://addr[:port]] [*-T* token] [*-U*] [[*-h* match_hint_file] [*-k* key] [*-u* user]] [[*-c* certfile] [*-j* keyfile] [*-C* cafile] [*-J* pkcs11_pin] [*-M* rpk_file] [*-R* root_cafile]] URI @@ -126,9 +126,10 @@ OPTIONS - General Add option 'num' with contents of 'text' to the request. If the text begins with 0x, then the hex text is converted to binary data. -*-P* addr[:port]:: - Address (and port) for proxy to use (automatically adds Proxy-Uri option - to request). +*-P* scheme://addr[:port]:: + Scheme, address and optional port to define how to connect to a CoAP proxy + (automatically adds Proxy-Uri option to request) to forward the request to. + Scheme is one of coap, coaps, coap+tcp and coaps+tcp. *-T* token:: Include the 'token' to the request. diff --git a/man/coap-server.txt.in b/man/coap-server.txt.in index 93606c98fb..ac729322e9 100644 --- a/man/coap-server.txt.in +++ b/man/coap-server.txt.in @@ -15,9 +15,9 @@ coap-server - CoAP Server based on libcoap SYNOPSIS -------- *coap-server* [*-d* max] [*-g* group] [*-l* loss] [*-p* port] [*-v* num] - [*-A* address] [*-N*] + [*-A* address] [*-N*] [*-P* scheme://addr[:port],name1[,name2..]] [[*-h* hint] [*-i* match_identity_file] [*-k* key] - [*-s* match_psk_sni_file]] + [*-s* match_psk_sni_file] [*-u* user]] [[*-c* certfile] [*-j* keyfile] [*-n*] [*-C* cafile] [*-J* pkcs11_pin] [*-M* rpk_file] [*-R* root_cafile] [*-S* match_pki_sni_file]] @@ -64,6 +64,16 @@ OPTIONS - General fifth response will still be sent as a confirmable response (RFC 7641 requirement). +*-P* scheme://address[:port],name1[,name2[,name3..]] :: + Scheme, address, optional port of how to connect to the next proxy server + and one or more names (comma separated) that this proxy server is known by. + If the hostname of the incoming proxy request matches one of these names, + then this server is considered to be the final endpoint. If + scheme://address[:port] is not defined before the leading , (comma) of the + first name, then the ongoing connection will be a direct connection. + Scheme is one of coap, coaps, coap+tcp and coaps+tcp. + + OPTIONS - PSK ------------- (If supported by underlying (D)TLS library) @@ -97,6 +107,9 @@ OPTIONS - PSK Note: -k still needs to be defined for the default case + Note: the new Pre-Shared Key will get updated if there is also a -i match +*-u* user :: + User identity for pre-shared key mode (only used if option P is set). + OPTIONS - PKI ------------- (If supported by underlying (D)TLS library) diff --git a/man/coap_cache.txt.in b/man/coap_cache.txt.in index 72ebd58e4d..92e2827a8e 100644 --- a/man/coap_cache.txt.in +++ b/man/coap_cache.txt.in @@ -232,7 +232,7 @@ hnd_put_example_data(coap_context_t *ctx, if (!cache_entry && block1.num == 0) { /* * Set idle_timeout parameter to COAP_MAX_TRANSMIT_WAIT if you want - * early removal on transmission failure. -1 means only delete when + * early removal on transmission failure. 0 means only delete when * the session is deleted as session_based is set here. */ cache_entry = coap_new_cache_entry(session, request, diff --git a/man/coap_resource.txt.in b/man/coap_resource.txt.in index e79d3bbdda..e05ae3a3e0 100644 --- a/man/coap_resource.txt.in +++ b/man/coap_resource.txt.in @@ -13,6 +13,7 @@ NAME coap_resource, coap_resource_init, coap_resource_unknown_init, +coap_resource_proxy_uri_init, coap_add_resource, coap_delete_resource, coap_delete_all_resources, @@ -31,6 +32,9 @@ int _flags_);* *coap_resource_t *coap_resource_unknown_init(coap_method_handler_t _put_handler_);* +*coap_resource_t *coap_resource_proxy_uri_init(coap_method_handler_t +_proxy_handler_, size_t _host_name_count_, const char *_host_name_list_[]);* + *void coap_add_resource(coap_context_t *_context_, coap_resource_t *_resource_);* @@ -108,11 +112,20 @@ Free off the coap_str_const_t for _uri_path_ when the _resource_ is deleted. *NOTE:* _uri_path_, if not 7 bit readable ASCII, binary bytes must be hex encoded according to the rules defined in RFC3968 Section 2.1. -The *coap_resource_unknown_init*() returns a newly created _resource_ of -type _coap_resource_t_ *. _put_handler_ is automatically added to the +The *coap_resource_unknown_init*() function returns a newly created _resource_ +of type _coap_resource_t_ *. _put_handler_ is automatically added to the _resource_ to handle PUT requests to resources that are unknown. Additional handlers can be added to this resource if required. +The *coap_resource_proxy_uri_init*() function returns a newly created +_resource_ of type _coap_resource_t_ *. _proxy_handler_ is automatically added +to the _resource_ to handle PUT/POST/GET etc. requests that use the Proxy-Uri: +option. There is no need to add explicit request type handlers. One or more +names by which the proxy is known by (IP address, DNS name etc.) must be +supplied in the array defined by _host_name_list_[] which has a count of +_host_name_count_. This is used to check whether the current endpoint is +the proxy target address. + The *coap_add_resource*() function registers the given _resource_ with the _context_. The _resource_ must have been created by *coap_resource_init*(), or *coap_resource_unknown_init*(). The storage allocated for the _resource_ @@ -147,8 +160,9 @@ from the _resource_ that had previously been set up by RETURN VALUES ------------- -The *coap_resource_init*() and *coap_resource_unknown_init*() functions -return a newly created resource or NULL if there is a malloc failure. +The *coap_resource_init*(), *coap_resource_unknown_init*() and +*coap_resource_proxy_uri_init*() functions return a newly created resource +or NULL if there is a malloc failure. The *coap_delete_resource*() function return 0 on failure (_resource_ not found), 1 on success. diff --git a/src/coap_gnutls.c b/src/coap_gnutls.c index 17cfc60865..fd3bbdd934 100644 --- a/src/coap_gnutls.c +++ b/src/coap_gnutls.c @@ -1921,13 +1921,15 @@ coap_dtls_free_gnutls_env(coap_gnutls_context_t *g_context, gnutls_deinit(g_env->g_session); g_env->g_session = NULL; if (g_context->psk_pki_enabled & IS_PSK) { - if (g_context->psk_pki_enabled & IS_CLIENT) { + if ((g_context->psk_pki_enabled & IS_CLIENT) && + g_env->psk_cl_credentials != NULL) { gnutls_psk_free_client_credentials(g_env->psk_cl_credentials); g_env->psk_cl_credentials = NULL; } else { /* YUK - A memory leak in 3.3.0 (fixed by 3.3.26) of hint */ - gnutls_psk_free_server_credentials(g_env->psk_sv_credentials); + if (g_env->psk_sv_credentials != NULL) + gnutls_psk_free_server_credentials(g_env->psk_sv_credentials); g_env->psk_sv_credentials = NULL; } } diff --git a/src/net.c b/src/net.c index e43e7d6b4c..c9fd37a698 100644 --- a/src/net.c +++ b/src/net.c @@ -2308,12 +2308,77 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu enum respond_t respond = RESPONSE_DEFAULT; coap_opt_iterator_t opt_iter; coap_opt_t *opt; + int is_proxy_uri = 0; + int is_proxy_scheme = 0; int skip_hop_limit_check = 0; int resp; coap_option_filter_clear(opt_filter); + opt = coap_check_option(pdu, COAP_OPTION_PROXY_SCHEME, &opt_iter); + if (opt) + is_proxy_scheme = 1; + + opt = coap_check_option(pdu, COAP_OPTION_PROXY_URI, &opt_iter); + if (opt) + is_proxy_uri = 1; + + if (is_proxy_scheme || is_proxy_uri) { + coap_uri_t uri; + + if (!context->proxy_uri_resource) { + /* Need to return a 5.05 RFC7252 Section 5.7.2 */ + coap_log(LOG_DEBUG, "Proxy-%s support not configured\n", + is_proxy_scheme ? "Scheme" : "Uri"); + resp = 505; + goto fail_response; + } + if (((size_t)pdu->code - 1 < + (sizeof(resource->handler) / sizeof(resource->handler[0]))) && + !(context->proxy_uri_resource->handler[pdu->code - 1])) { + /* Need to return a 5.05 RFC7252 Section 5.7.2 */ + coap_log(LOG_DEBUG, "Proxy-%s code %d.%02d handler not supported\n", + is_proxy_scheme ? "Scheme" : "Uri", + pdu->code/100, pdu->code%100); + resp = 505; + goto fail_response; + } + + /* Need to check if authority is the proxy endpoint RFC7252 Section 5.7.2 */ + if (is_proxy_uri) { + if (coap_split_proxy_uri(coap_opt_value(opt), + coap_opt_length(opt), &uri) < 0) { + /* Need to return a 5.05 RFC7252 Section 5.7.2 */ + coap_log(LOG_DEBUG, "Proxy-URI not decodable\n"); + resp = 505; + goto fail_response; + } + } + else { + memset(&uri, 0, sizeof(uri)); + opt = coap_check_option(pdu, COAP_OPTION_URI_HOST, &opt_iter); + if (opt) { + uri.host.length = coap_opt_length(opt); + uri.host.s = coap_opt_value(opt); + } + } + resource = context->proxy_uri_resource; + if (uri.host.length && resource->proxy_name_count && resource->proxy_name_list) { + size_t i; + for (i = 0; i < resource->proxy_name_count; i++) { + if (coap_string_equal(&uri.host, resource->proxy_name_list[i])) { + break; + } + } + if (i != resource->proxy_name_count) { + /* This server is hosting the proxy connection endpoint */ + is_proxy_uri = 0; + is_proxy_scheme = 0; + skip_hop_limit_check = 1; + } + } + resource = NULL; + } - /* If !skip_hop_limit_check test placeholder for Proxy-Uri: code */ if (!skip_hop_limit_check) { opt = coap_check_option(pdu, COAP_OPTION_HOP_LIMIT, &opt_iter); if (opt) { @@ -2340,22 +2405,29 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu } } - /* try to find the resource from the request URI */ coap_string_t *uri_path = coap_get_uri_path(pdu); if (!uri_path) return; - coap_str_const_t uri_path_c = { uri_path->length, uri_path->s }; - resource = coap_get_resource_from_uri_path(context, &uri_path_c); - if ((resource == NULL) || (resource->is_unknown == 1)) { + if (!is_proxy_uri && !is_proxy_scheme) { + /* try to find the resource from the request URI */ + coap_str_const_t uri_path_c = { uri_path->length, uri_path->s }; + resource = coap_get_resource_from_uri_path(context, &uri_path_c); + } + + if ((resource == NULL) || (resource->is_unknown == 1) || + (resource->is_proxy_uri == 1)) { /* The resource was not found or there is an unexpected match against the - * resource defined for handling unknown URIs. + * resource defined for handling unknown or proxy URIs. * Check if the request URI happens to be the well-known URI, or if the * unknown resource handler is defined, a PUT or optionally other methods, * if configured, for the unknown handler. * * if well-known URI generate a default response * + * else if a PROXY URI/Scheme request and proxy URI handler defined, call the + * proxy URI handler + * * else if unknown URI handler defined, call the unknown * URI handler (to allow for potential generation of resource * [RFC7272 5.8.3]) if the appropriate method is defined. @@ -2375,6 +2447,8 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu response = coap_new_error_response(pdu, COAP_RESPONSE_CODE(405), opt_filter); } + } else if (is_proxy_uri || is_proxy_scheme) { + resource = context->proxy_uri_resource; } else if ((context->unknown_resource != NULL) && ((size_t)pdu->code - 1 < (sizeof(resource->handler) / sizeof(coap_method_handler_t))) && diff --git a/src/resource.c b/src/resource.c index 5b6a37327e..ad69548227 100644 --- a/src/resource.c +++ b/src/resource.c @@ -349,6 +349,58 @@ coap_resource_unknown_init(coap_method_handler_t put_handler) { return r; } +static const uint8_t coap_proxy_resource_uri[] = + "- Proxy URI -"; + +coap_resource_t * +coap_resource_proxy_uri_init(coap_method_handler_t handler, + size_t host_name_count, const char *host_name_list[]) { + coap_resource_t *r; + + if (host_name_count == 0) { + coap_log(LOG_ERR, + "coap_resource_proxy_uri_init: Must have one or more host names defined\n"); + return NULL; + } + r = (coap_resource_t *)coap_malloc_type(COAP_RESOURCE, sizeof(coap_resource_t)); + if (r) { + size_t i; + memset(r, 0, sizeof(coap_resource_t)); + r->is_proxy_uri = 1; + /* Something unlikely to be used, but it shows up in the logs */ + r->uri_path = coap_new_str_const(coap_proxy_resource_uri, sizeof(coap_proxy_resource_uri)-1); + /* Preset all the handlers */ + for (i = 0; i < (sizeof(r->handler) / sizeof(r->handler[0])); i++) { + r->handler[i] = handler; + } + if (host_name_count) { + r->proxy_name_list = coap_malloc(host_name_count * + sizeof(coap_str_const_t*)); + if (r->proxy_name_list) { + for (i = 0; i < host_name_count; i++) { + r->proxy_name_list[i] = + coap_new_str_const((const uint8_t*)host_name_list[i], + strlen(host_name_list[i])); + if (!r->proxy_name_list[i]) { + coap_log(LOG_ERR, + "coap_resource_proxy_uri_init: unable to add host name\n"); + if (i == 0) { + coap_free(r->proxy_name_list); + r->proxy_name_list = NULL; + } + break; + } + } + r->proxy_name_count = i; + } + } + } else { + coap_log(LOG_DEBUG, "coap_resource_proxy_uri_init: no memory left\n"); + } + + return r; +} + coap_attr_t * coap_add_attr(coap_resource_t *resource, coap_str_const_t *name, @@ -451,6 +503,14 @@ coap_free_resource(coap_resource_t *resource) { coap_delete_string(obs->query); COAP_FREE_TYPE( subscription, obs ); } + if (resource->proxy_name_count && resource->proxy_name_list) { + size_t i; + + for (i = 0; i < resource->proxy_name_count; i++) { + coap_delete_str_const(resource->proxy_name_list[i]); + } + coap_free(resource->proxy_name_list); + } #ifdef WITH_LWIP memp_free(MEMP_COAP_RESOURCE, resource); @@ -467,6 +527,11 @@ coap_add_resource(coap_context_t *context, coap_resource_t *resource) { coap_free_resource(context->unknown_resource); context->unknown_resource = resource; } + else if (resource->is_proxy_uri) { + if (context->proxy_uri_resource) + coap_free_resource(context->proxy_uri_resource); + context->proxy_uri_resource = resource; + } else { coap_resource_t *r = coap_get_resource_from_uri_path(context, resource->uri_path); @@ -494,6 +559,11 @@ coap_delete_resource(coap_context_t *context, coap_resource_t *resource) { context->unknown_resource = NULL; return 1; } + if (resource->is_proxy_uri && (context->proxy_uri_resource == resource)) { + coap_free_resource(context->proxy_uri_resource); + context->proxy_uri_resource = NULL; + return 1; + } /* remove resource from list */ RESOURCES_DELETE(context->resources, resource); @@ -523,6 +593,10 @@ coap_delete_all_resources(coap_context_t *context) { coap_free_resource(context->unknown_resource); context->unknown_resource = NULL; } + if (context->proxy_uri_resource) { + coap_free_resource(context->proxy_uri_resource); + context->proxy_uri_resource = NULL; + } } coap_resource_t * diff --git a/src/uri.c b/src/uri.c index 67ab860309..454b8b171f 100644 --- a/src/uri.c +++ b/src/uri.c @@ -39,10 +39,20 @@ strnchr(const uint8_t *s, size_t len, unsigned char c) { #define ISEQUAL_CI(a,b) \ ((a) == (b) || (islower(b) && ((a) == ((b) - 0x20)))) -int -coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { +typedef enum coap_uri_check_t { + COAP_URI_CHECK_URI, + COAP_URI_CHECK_PROXY +} coap_uri_check_t; + +static int +coap_split_uri_sub(const uint8_t *str_var, + size_t len, + coap_uri_t *uri, + coap_uri_check_t check_proxy) { const uint8_t *p, *q; int res = 0; + int is_http_proxy_scheme = 0; + size_t keep_len = len; if (!str_var || !uri) return -1; @@ -53,6 +63,8 @@ coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { /* search for scheme */ p = str_var; if (*p == '/') { + if (check_proxy == COAP_URI_CHECK_PROXY) + return -1; q = p; goto path; } @@ -61,6 +73,34 @@ coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { while (len && *q && ISEQUAL_CI(*p, *q)) { ++p; ++q; --len; } + if (*q && check_proxy == COAP_URI_CHECK_PROXY) { + /* Scheme could be something other than coap */ + len = keep_len; + p = str_var; + q = (const uint8_t *)"http"; + while (len && *q && ISEQUAL_CI(*p, *q)) { + ++p; ++q; --len; + } + if (*q == 0) { + if (len && ISEQUAL_CI(*p, 's')) { + /* https:// */ + ++p; --len; + uri->scheme = COAP_URI_SCHEME_HTTPS; + uri->port = 443; + } + else { + /* http:// */ + uri->scheme = COAP_URI_SCHEME_HTTP; + uri->port = 80; + } + } + else { + /* Unknown scheme */ + res = -1; + goto error; + } + is_http_proxy_scheme = 1; + } /* If q does not point to the string end marker '\0', the schema * identifier is wrong. */ @@ -69,23 +109,25 @@ coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { goto error; } - /* There might be an additional 's', indicating the secure version: */ - if (len && (*p == 's')) { - ++p; --len; - uri->scheme = COAP_URI_SCHEME_COAPS; - uri->port = COAPS_DEFAULT_PORT; - } else { - uri->scheme = COAP_URI_SCHEME_COAP; - } + if (is_http_proxy_scheme == 0) { + /* There might be an additional 's', indicating the secure version: */ + if (len && (*p == 's')) { + ++p; --len; + uri->scheme = COAP_URI_SCHEME_COAPS; + uri->port = COAPS_DEFAULT_PORT; + } else { + uri->scheme = COAP_URI_SCHEME_COAP; + } - /* There might be and addition "+tcp", indicating reliable transport: */ - if (len>=4 && p[0] == '+' && p[1] == 't' && p[2] == 'c' && p[3] == 'p' ) { - p += 4; - len -= 4; - if (uri->scheme == COAP_URI_SCHEME_COAPS) - uri->scheme = COAP_URI_SCHEME_COAPS_TCP; - else - uri->scheme = COAP_URI_SCHEME_COAP_TCP; + /* There might be an addition "+tcp", indicating reliable transport: */ + if (len>=4 && p[0] == '+' && p[1] == 't' && p[2] == 'c' && p[3] == 'p' ) { + p += 4; + len -= 4; + if (uri->scheme == COAP_URI_SCHEME_COAPS) + uri->scheme = COAP_URI_SCHEME_COAPS_TCP; + else + uri->scheme = COAP_URI_SCHEME_COAP_TCP; + } } q = (const uint8_t *)"://"; while (len && *q && *p == *q) { @@ -188,6 +230,16 @@ coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { return res; } +int +coap_split_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { + return coap_split_uri_sub(str_var, len, uri, COAP_URI_CHECK_URI); +} + +int +coap_split_proxy_uri(const uint8_t *str_var, size_t len, coap_uri_t *uri) { + return coap_split_uri_sub(str_var, len, uri, COAP_URI_CHECK_PROXY); +} + /** * Calculates decimal value from hexadecimal ASCII character given in * @p c. The caller must ensure that @p c actually represents a valid