From 0cd07e828ce4326524b6466ada3d544d5aa170c7 Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Mon, 16 Dec 2019 17:01:56 +0000 Subject: [PATCH] RFC8768: Add in support and update Proxy-Uri Support Add in support for RFC8768 Hop Limit and add in code to better support the Proxy-Uri CoAP Option. README.md: Add in RFC8768 is supported by libcoap. examples/client.c: Add in the -H hoplimit option. 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. Always add in Hop-Limit: CoAP option if Proxy-Uri: is in use. 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. 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/pdu.h: Add in the default Hop-Limit: option COAP_OPTION_HOP_LIMIT. Re-organize the COAP_OPTION_ descriptions to include the C, U, N and R flags. include/coap2/resource.h: Track whether a resource definition is for Proxy-Uri:. 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) being over the 10 definition limit in man/coap_resource.txt.in. man/coap-client.txt.in: Document the new -H hoplimit option. 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.txt.in: Include the new RFC8768. man/coap_resource.txt.in: Include documentation for coap_resource_proxy_uri_init(). Update documentation for the CoAP options. src/coap_debug.c: Add in support for printing out COAP_OPTION_HOP_LIMIT. Output the Proxy-Scheme: Option as text. src/net.c: Make sure that COAP_OPTION_HOP_LIMIT is not returned in an error response. Sanity check and handle COAP_OPTION_PROXY_URI and COAP_OPTION_PROXY_SCHEME in handle_request(). src/pdu.c: Report on CoAP options that are repeated, but not defined as repeatable. Define new (5.08) Hop Limit Reached error response. 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. --- README.md | 3 + examples/client.c | 221 ++++++++-- examples/coap-server.c | 893 +++++++++++++++++++++++++++++++++++++- include/coap2/net.h | 2 + include/coap2/pdu.h | 74 +++- include/coap2/resource.h | 11 + include/coap2/uri.h | 40 +- libcoap-2.map | 2 + libcoap-2.sym | 2 + man/Makefile.am | 2 + man/coap-client.txt.in | 16 +- man/coap-server.txt.in | 13 +- man/coap.txt.in | 2 + man/coap_pdu_setup.txt.in | 58 ++- man/coap_resource.txt.in | 18 +- src/coap_debug.c | 5 +- src/net.c | 162 ++++++- src/pdu.c | 18 + src/resource.c | 39 ++ src/uri.c | 88 +++- 20 files changed, 1527 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index 36cf97734a..7ef9d36bd5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ The following RFCs are supported * RFC8132: PATCH and FETCH Methods for the Constrained Application Protocol (CoAP) * RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets + [No WebSockets support] + +* RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option There is (D)TLS support for the following libraries diff --git a/examples/client.c b/examples/client.c index 794148f504..3fd1bf6409 100644 --- a/examples/client.c +++ b/examples/client.c @@ -58,6 +58,9 @@ static coap_optlist_t *optlist = NULL; static coap_uri_t uri; static coap_string_t proxy = { 0, NULL }; static uint16_t proxy_port = COAP_DEFAULT_PORT; +static coap_uri_scheme_t proxy_scheme = COAP_URI_SCHEME_COAP; +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 */ @@ -79,6 +82,8 @@ static char *cert_file = NULL; /* Combined certificate and private key in PEM */ static char *ca_file = NULL; /* CA for cert_file - for cert checking in PEM */ static char *root_ca_file = NULL; /* List of trusted Root CAs in PEM */ +static int hop_limit = COAP_DEFAULT_HOP_LIMIT; + typedef struct ih_def_t { char* hint_match; coap_bin_const_t *new_identity; @@ -193,7 +198,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_safe(buf, sizeof(buf), length), buf); @@ -228,17 +233,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); @@ -247,9 +256,10 @@ clear_obs(coap_context_t *ctx, coap_session_t *session) { for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ if (!coap_add_option(pdu, option->number, option->length, option->data)) { goto error; @@ -264,10 +274,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); @@ -449,13 +472,14 @@ message_handler(struct coap_context_t *ctx, /* create pdu with request for next block */ pdu = coap_new_request(ctx, session, method, NULL, NULL, 0); /* first, create bare PDU w/o any option */ if ( pdu ) { - /* add URI components from optlist */ + /* add URI components from optlist in ascending order */ for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_HOST : - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_HOST : /* 3 */ + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ coap_add_option(pdu, option->number, option->length, option->data); break; @@ -469,11 +493,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) { @@ -546,11 +581,12 @@ message_handler(struct coap_context_t *ctx, /* add URI components from optlist */ for (option = optlist; option; option = option->next ) { switch (option->number) { - case COAP_OPTION_URI_HOST : - case COAP_OPTION_URI_PORT : - case COAP_OPTION_URI_PATH : - case COAP_OPTION_CONTENT_FORMAT : - case COAP_OPTION_URI_QUERY : + case COAP_OPTION_URI_HOST : /* 3 */ + case COAP_OPTION_URI_PORT : /* 7 */ + case COAP_OPTION_URI_PATH : /* 11 */ + case COAP_OPTION_CONTENT_FORMAT : /* 12 */ + case COAP_OPTION_URI_QUERY : /* 15 */ + case COAP_OPTION_HOP_LIMIT : /* 16 */ coap_add_option(pdu, option->number, option->length, option->data); break; @@ -566,12 +602,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_safe(buf, sizeof(buf), payload.length), buf); @@ -637,8 +685,8 @@ usage( const char *program, const char *version) { "%s\n\n" "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] [-K interval] [-N] [-O num,text]\n" - "\t\t[-P addr[:port]] [-T token] [-U]\n" + "\t\t[-v num] [-A type] [-B seconds] [-H hoplimit] [-K interval] [-N]\n" + "\t\t[-O num,text] [-P scheme://addr[:port]] [-T token] [-U]\n" "\t\t[[-h match_hint_file] [-k key] [-u user]]\n" "\t\t[[-c certfile] [-C cafile] [-R root_cafile]] URI\n\n" "\tURI can be an absolute URI or a URI prefixed with scheme and host\n\n" @@ -669,13 +717,18 @@ usage( const char *program, const char *version) { "\t-A type\t\tAccepted media type\n" "\t-B seconds\tBreak operation after waiting given seconds\n" "\t \t\t(default is %d)\n" - "\t-K interval\tsend a ping after interval seconds of inactivity\n" + "\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-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://addr[:port]\tScheme, address and optional port to define\n" + "\t \t\ta CoAP proxy (automatically adds Proxy-Uri option to\n" + "\t \t\trequest) to forward the request to. Scheme is one of coap,\n" + "\t \t\tcoaps, 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" "PSK Options (if supported by underlying (D)TLS library)\n" @@ -704,7 +757,10 @@ usage( const char *program, const char *version) { "\t \t\t'trusted' for the verification.\n" "\t \t\tAlternatively, this can point to a directory containing\n" "\t \t\ta set of CA PEM files\n" - "\n" + ,program, version, coap_string_tls_version(buffer, sizeof(buffer)) + ,program, wait_seconds); + + fprintf( stderr, "Examples:\n" "\tcoap-client -m get coap://[::1]/\n" "\tcoap-client -m get coap://[::1]/.well-known/core\n" @@ -713,8 +769,7 @@ usage( const char *program, const char *version) { "\tcoap-client -m get coaps+tcp://[::1]/.well-known/core\n" "\tcoap-client -m get -T cafe coap://[::1]/time\n" "\techo -n 1000 | coap-client -m put -T cafe coap://[::1]/time -f -\n" - ,program, version, coap_string_tls_version(buffer, sizeof(buffer)) - ,program, wait_seconds); + ); } typedef struct { @@ -794,17 +849,17 @@ 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.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; } + /* Add Hop Limit as per RFC8768 1.1 */ + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_HOP_LIMIT, + 1, + (const uint8_t *)&hop_limit)); coap_insert_optlist(&optlist, coap_new_optlist(COAP_OPTION_PROXY_URI, @@ -836,6 +891,14 @@ cmdline_uri(char *arg, int create_uri_opts) { return -1; } + if (proxy_scheme_option) { + /* Add Hop Limit as per RFC8768 1.1 */ + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_HOP_LIMIT, + 1, + (const uint8_t *)&hop_limit)); + } + if (uri.port != get_default_port(&uri) && create_uri_opts) { coap_insert_optlist(&optlist, coap_new_optlist(COAP_OPTION_URI_PORT, @@ -931,7 +994,35 @@ cmdline_subscribe(char *arg) { static int cmdline_proxy(char *arg) { - char *proxy_port_str = strrchr((const char *)arg, ':'); /* explicit port ? */ + char *proxy_port_str; + + if (strncmp(arg, "coap", 4) == 0) { + if (strncmp(arg, "coaps+tcp://", 12) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS_TCP; + proxy_port = COAPS_DEFAULT_PORT; + arg += 12; + } + else if (strncmp(arg, "coap+tcp://", 11) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAP_TCP; + proxy_port = COAP_DEFAULT_PORT; + arg += 11; + } + else if (strncmp(arg, "coaps://", 8) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS; + proxy_port = COAPS_DEFAULT_PORT; + arg += 8; + } + else if (strncmp(arg, "coap://", 7) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAP; + proxy_port = COAP_DEFAULT_PORT; + arg += 7; + } + else { + coap_log(LOG_WARNING, "Invalid proxy protocol prefix '%s'\n", arg); + return 0; + } + } + proxy_port_str = strrchr((const char *)arg, ':'); /* explicit port ? */ if (proxy_port_str) { char *ipv6_delimiter = strrchr((const char *)arg, ']'); if (!ipv6_delimiter) { @@ -1037,6 +1128,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 (strcmp(arg, "coaps+tcp") == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS_TCP; + proxy_port = COAPS_DEFAULT_PORT; + } + else if (strcmp(arg, "coap+tcp") == 0) { + proxy_scheme = COAP_URI_SCHEME_COAP_TCP; + proxy_port = COAP_DEFAULT_PORT; + } + else if (strcmp(arg, "coaps") == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS; + proxy_port = COAPS_DEFAULT_PORT; + } + else if (strcmp(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; + } } /** @@ -1500,11 +1616,12 @@ 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 - while ((opt = getopt(argc, argv, "NrUa:b:c:e:f:h:k:m:p:s:t:o:v:A:B:C:O:P:R:T:u:l:K:")) != -1) { + while ((opt = getopt(argc, argv, "NrUa:b:c:e:f:h:k:m:p:s:t:o:v:A:B:C:H:O:P:R:T:u:l:K:")) != -1) { switch (opt) { case 'a': strncpy(node_str, optarg, NI_MAXHOST - 1); @@ -1608,6 +1725,14 @@ main(int argc, char **argv) { exit(1); } break; + case 'H': + hop_limit = strtol(optarg, NULL, 10); + if (hop_limit < 1 || hop_limit > 255) { + fprintf(stderr, "Hop Limit has to be > 0 and < 255\n"); + usage(argv[0], LIBCOAP_PACKAGE_VERSION); + exit(1); + } + break; default: usage( argv[0], LIBCOAP_PACKAGE_VERSION ); exit( 1 ); @@ -1636,9 +1761,11 @@ main(int argc, char **argv) { server.length = proxy.length; server.s = proxy.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 */ @@ -1663,11 +1790,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, @@ -1701,11 +1828,11 @@ main(int argc, char **argv) { /* construct CoAP message */ - if (!proxy.length && addrptr + if (!uri_host_option && (!proxy.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, @@ -1788,6 +1915,8 @@ main(int argc, char **argv) { } if (valid_ihs.count) free(valid_ihs.ih_list); + if (proxy.s) + coap_free(proxy.s); coap_delete_optlist(optlist); coap_session_release( session ); coap_free_context( ctx ); diff --git a/examples/coap-server.c b/examples/coap-server.c index 7ae162be4f..b304d80a9e 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -45,6 +45,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 @@ -354,6 +358,545 @@ check_async(coap_context_t *ctx, } #endif /* WITHOUT_ASYNC */ +#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_string_t proxy = { 0, NULL }; +static uint16_t proxy_port = COAP_DEFAULT_PORT; +static coap_uri_scheme_t proxy_scheme = COAP_URI_SCHEME_COAP; + +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 hop_limit = COAP_DEFAULT_HOP_LIMIT; + int proxy_scheme_option = 0; + coap_uri_t uri; + coap_string_t *uri_path = NULL; + coap_string_t *uri_query = NULL; + + opt = coap_check_option(request, COAP_OPTION_HOP_LIMIT, &opt_iter); + if (opt) { + hop_limit = + coap_decode_var_bytes (coap_opt_value (opt), coap_opt_length (opt)); + if (hop_limit == 1) { + char buffer[INET6_ADDRSTRLEN]; + coap_address_t *addr = &session->addr_info.local; + + /* + * Need to return a 5.08 RFC8768 Section 3 + * The data is the IP address or unique name for this Proxy Server + * which is used for troubleshooting Proxy loops. + * For now, it is just returning the IP address. + */ + coap_log(LOG_INFO, "Hop Limit exhausted\n"); + response->code = COAP_RESPONSE_CODE(508); + switch (addr->addr.sa.sa_family) { + case AF_INET: + inet_ntop(addr->addr.sa.sa_family, &addr->addr.sin.sin_addr, + buffer, sizeof(buffer)); + break; + case AF_INET6: + /* It is possible the local address could be IPv4 in IPv6 */ + if (addr->addr.sin6.sin6_addr.s6_addr32[0] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[1] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[2] == htonl (0xffff)) { + inet_ntop(AF_INET, &addr->addr.sin6.sin6_addr.s6_addr32[3], + buffer, sizeof(buffer)); + } + else { + inet_ntop(addr->addr.sa.sa_family, &addr->addr.sin6.sin6_addr, + buffer, sizeof(buffer)); + } + break; + default: + snprintf(buffer, sizeof(buffer), "Unknown_Address_Type_%d", + addr->addr.sa.sa_family); + break; + } + coap_add_data(response, strlen(buffer), (uint8_t *)buffer); + goto cleanup; + } + else if (hop_limit < 1 || hop_limit > 255) { + /* Need to return a 4.00 RFC8768 Section 3 */ + coap_log(LOG_INFO, "Invalid Hop Limit\n"); + response->code = COAP_RESPONSE_CODE(400); + goto cleanup; + } + /* Decrement for forwarding */ + hop_limit--; + } + + /* + * 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 && + strncmp((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 && + strncmp((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 && + strncmp((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 && + strncmp((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() || !key_defined) { + 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.length) { + server.length = proxy.length; + server.s = proxy.s; + 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; + } + + /* MUST have Hop Limit as per RFC8768 1.1 */ + coap_insert_optlist(&optlist, + coap_new_optlist(COAP_OPTION_HOP_LIMIT, + 1, + (const uint8_t *)&hop_limit)); + + if (proxy.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_HOP_LIMIT: + 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_string_t *value; @@ -665,6 +1208,227 @@ hnd_unknown_put(coap_context_t *ctx, return; } +#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; + int hop_limit_error = 0; + 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", + (received->code >> 5), received->code & 0x1F); + if (coap_get_log_level() < LOG_DEBUG) + coap_show_pdu(LOG_INFO, received); + + if (received->code == COAP_RESPONSE_CODE(508)) { + hop_limit_error = 1; + } + /* + * 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) { + case COAP_OPTION_HOP_LIMIT: + break; + 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)) || + hop_limit_error) { + if (hop_limit_error) { + /* Need to prepend our IP address to diagnostic response */ + char buffer[INET6_ADDRSTRLEN]; + coap_address_t *addr = &incoming->addr_info.local; + uint8_t *payload; + + switch (addr->addr.sa.sa_family) { + case AF_INET: + inet_ntop(addr->addr.sa.sa_family, &addr->addr.sin.sin_addr, + buffer, sizeof(buffer)); + break; + case AF_INET6: + /* It is possible the local address could be IPv4 in IPv6 */ + if (addr->addr.sin6.sin6_addr.s6_addr32[0] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[1] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[2] == htonl (0xffff)) { + inet_ntop(AF_INET, &addr->addr.sin6.sin6_addr.s6_addr32[3], + buffer, sizeof(buffer)); + } + else { + inet_ntop(addr->addr.sa.sa_family, &addr->addr.sin6.sin6_addr, + buffer, sizeof(buffer)); + } + break; + default: + snprintf(buffer, sizeof(buffer), "Unknown_Address_Type_%d", + addr->addr.sa.sa_family); + break; + } + payload = coap_add_data_after(pdu, size + strlen(buffer) + 1); + if (payload) { + memcpy(payload, buffer, strlen(buffer)); + payload += strlen(buffer); + *payload = ' '; + payload++; + memcpy(payload, data, size); + } + else { + /* No space for prepend */ + if (!coap_add_data(pdu, size, data)) { + coap_log(LOG_DEBUG, "cannot add data to proxy response\n"); + } + } + } + else { + 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; @@ -705,6 +1469,14 @@ init_resources(coap_context_t *ctx) { coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); coap_add_resource(ctx, r); #endif /* WITHOUT_ASYNC */ + +#ifdef SERVER_CAN_PROXY + r = coap_resource_proxy_uri_init(hnd_proxy_uri); + 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 @@ -731,7 +1503,7 @@ static uint8_t *read_file_mem(const char* file, size_t *length) { return NULL; if (fstat(fileno(f), &statbuf) == -1) { - fclose(f); + fclose(f); return NULL; } @@ -964,9 +1736,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://addr[:port]]\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] [-C cafile] [-m] [-n] [-R root_cafile]]\n" "\t\t[-S match_pki_sni_file]]\n" "General Options\n" @@ -987,6 +1759,9 @@ usage( const char *program, const char *version) { "\t-N \t\tMake \"observe\" responses NON-confirmable. Even if set\n" "\t \t\tevery fifth response will still be sent as a confirmable\n" "\t \t\tresponse (RFC 7641 requirement)\n" + "\t-P scheme://addr[:port]\tScheme, address and optional port to define\n" + "\t \t\ta CoAP proxy to forward a received proxy request to. Scheme\n" + "\t \t\tis 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" @@ -1009,6 +1784,12 @@ 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" + , program, version, coap_string_tls_version(buffer, sizeof(buffer)), + program); + + fprintf( stderr, "PKI Options (if supported by underlying (D)TLS library)\n" "\t-c certfile\tPEM file containing both CERTIFICATE and PRIVATE KEY\n" "\t \t\tThis argument requires (D)TLS with PKI to be available\n" @@ -1038,8 +1819,7 @@ usage( const char *program, const char *version) { "\t \t\tE.g., per line\n" "\t \t\t sni_to_match,new_cert_file,new_ca_file\n" "\t \t\tNote: -c and -C still needs to be defined for the default case\n" - , program, version, coap_string_tls_version(buffer, sizeof(buffer)), - program); + ); } static coap_context_t * @@ -1127,6 +1907,79 @@ get_context(const char *node, const char *port) { return ctx; } +#if SERVER_CAN_PROXY +static int +cmdline_proxy(char *arg) { + char *proxy_port_str; + + if (strncmp(arg, "coap", 4) == 0) { + if (strncmp(arg, "coaps+tcp://", 12) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS_TCP; + proxy_port = COAPS_DEFAULT_PORT; + arg += 12; + } + else if (strncmp(arg, "coap+tcp://", 11) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAP_TCP; + proxy_port = COAP_DEFAULT_PORT; + arg += 11; + } + else if (strncmp(arg, "coaps://", 8) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAPS; + proxy_port = COAPS_DEFAULT_PORT; + arg += 8; + } + else if (strncmp(arg, "coap://", 7) == 0) { + proxy_scheme = COAP_URI_SCHEME_COAP; + proxy_port = COAP_DEFAULT_PORT; + arg += 7; + } + else { + coap_log(LOG_WARNING, "Invalid proxy protocol prefix '%s'\n", arg); + return 0; + } + } + 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; + } + + memcpy(proxy.s, arg, proxy.length+1); + 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); @@ -1314,7 +2167,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); - while ((opt = getopt(argc, argv, "A:d:c:C:g:h:i:k:l:mnNp:R:s:S:v:")) != -1) { + while ((opt = getopt(argc, argv, "A:d:c:C:g:h:i:k:l:mnNp:P:R:s:S:u:v:")) != -1) { switch (opt) { case 'A' : strncpy(addr_str, optarg, NI_MAXHOST-1); @@ -1372,6 +2225,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\n"); + exit(-1); + } + break; +#endif /* SERVER_CAN_PROXY */ case 'R' : root_ca_file = optarg; break; @@ -1387,6 +2248,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; @@ -1513,6 +2381,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); @@ -1521,6 +2390,18 @@ main(int argc, char **argv) { if (valid_pki_snis.count) free(valid_pki_snis.pki_sni_list); +#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.s) + coap_free(proxy.s); +#endif /* SERVER_CAN_PROXY */ + coap_free_context(ctx); coap_cleanup(); diff --git a/include/coap2/net.h b/include/coap2/net.h index a848b3ad35..e9cd5003de 100644 --- a/include/coap2/net.h +++ b/include/coap2/net.h @@ -150,6 +150,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/pdu.h b/include/coap2/pdu.h index 7cb1d83346..b39fbde982 100644 --- a/include/coap2/pdu.h +++ b/include/coap2/pdu.h @@ -32,6 +32,10 @@ struct coap_session_t; #define COAP_DEFAULT_MTU 1152 #endif /* COAP_DEFAULT_MTU */ +#ifndef COAP_DEFAULT_HOP_LIMIT +#define COAP_DEFAULT_HOP_LIMIT 16 +#endif /* COAP_DEFAULT_HOP_LIMIT */ + /* TCP Message format constants, do not modify */ #define COAP_MESSAGE_SIZE_OFFSET_TCP8 13 #define COAP_MESSAGE_SIZE_OFFSET_TCP16 269 /* 13 + 256 */ @@ -87,41 +91,63 @@ typedef enum coap_request_t { } coap_request_t; /* - * CoAP option types (be sure to update coap_option_check_critical() when - * adding options + * CoAP option types (be sure to update coap_option_check_critical() and + * coap_add_option() when adding options */ -#define COAP_OPTION_IF_MATCH 1 /* C, opaque, 0-8 B, (none) */ -#define COAP_OPTION_URI_HOST 3 /* C, String, 1-255 B, destination address */ -#define COAP_OPTION_ETAG 4 /* E, opaque, 1-8 B, (none) */ -#define COAP_OPTION_IF_NONE_MATCH 5 /* empty, 0 B, (none) */ -#define COAP_OPTION_URI_PORT 7 /* C, uint, 0-2 B, destination port */ -#define COAP_OPTION_LOCATION_PATH 8 /* E, String, 0-255 B, - */ -#define COAP_OPTION_URI_PATH 11 /* C, String, 0-255 B, (none) */ -#define COAP_OPTION_CONTENT_FORMAT 12 /* E, uint, 0-2 B, (none) */ +/* + * The C, U, and N flags indicate the properties + * Critical, UnSafe, and NoCacheKey, respectively. + * If U is set, then N has no meaning as per + * https://tools.ietf.org/html/rfc7252#section-5.10 + * and is set to a -. + * Separately, R is for the options that can be repeated + * + * The least significant byte of the option is set as followed + * as per https://tools.ietf.org/html/rfc7252#section-5.4.6 + * + * 0 1 2 3 4 5 6 7 + * --+---+---+---+---+---+---+---+ + * | NoCacheKey| U | C | + * --+---+---+---+---+---+---+---+ + * + */ + +#define COAP_OPTION_IF_MATCH 1 /* C__R, opaque, 0-8 B, (none) */ +#define COAP_OPTION_URI_HOST 3 /* CU-_, String, 1-255 B, destination address */ +#define COAP_OPTION_ETAG 4 /* ___R, opaque, 1-8 B, (none) */ +#define COAP_OPTION_IF_NONE_MATCH 5 /* C___, empty, 0 B, (none) */ +#define COAP_OPTION_URI_PORT 7 /* CU-_, uint, 0-2 B, destination port */ +#define COAP_OPTION_LOCATION_PATH 8 /* ___R, String, 0-255 B, (none) */ +#define COAP_OPTION_URI_PATH 11 /* CU-R, String, 0-255 B, (none) */ +#define COAP_OPTION_CONTENT_FORMAT 12 /* ____, uint, 0-2 B, (none) */ #define COAP_OPTION_CONTENT_TYPE COAP_OPTION_CONTENT_FORMAT -#define COAP_OPTION_MAXAGE 14 /* E, uint, 0--4 B, 60 Seconds */ -#define COAP_OPTION_URI_QUERY 15 /* C, String, 1-255 B, (none) */ -#define COAP_OPTION_ACCEPT 17 /* C, uint, 0-2 B, (none) */ -#define COAP_OPTION_LOCATION_QUERY 20 /* E, String, 0-255 B, (none) */ -#define COAP_OPTION_SIZE2 28 /* E, uint, 0-4 B, (none) */ -#define COAP_OPTION_PROXY_URI 35 /* C, String, 1-1034 B, (none) */ -#define COAP_OPTION_PROXY_SCHEME 39 /* C, String, 1-255 B, (none) */ -#define COAP_OPTION_SIZE1 60 /* E, uint, 0-4 B, (none) */ +#define COAP_OPTION_MAXAGE 14 /* _U-_, uint, 0--4 B, 60 Seconds */ +#define COAP_OPTION_URI_QUERY 15 /* CU-R, String, 1-255 B, (none) */ +#define COAP_OPTION_ACCEPT 17 /* C___, uint, 0-2 B, (none) */ +#define COAP_OPTION_LOCATION_QUERY 20 /* ___R, String, 0-255 B, (none) */ +#define COAP_OPTION_PROXY_URI 35 /* CU-_, String, 1-1034 B, (none) */ +#define COAP_OPTION_PROXY_SCHEME 39 /* CU-_, String, 1-255 B, (none) */ +#define COAP_OPTION_SIZE1 60 /* __N_, uint, 0-4 B, (none) */ /* option types from RFC 7641 */ -#define COAP_OPTION_OBSERVE 6 /* E, empty/uint, 0 B/0-3 B, (none) */ +#define COAP_OPTION_OBSERVE 6 /* _U-_, empty/uint, 0 B/0-3 B, (none) */ #define COAP_OPTION_SUBSCRIPTION COAP_OPTION_OBSERVE /* selected option types from RFC 7959 */ -#define COAP_OPTION_BLOCK2 23 /* C, uint, 0--3 B, (none) */ -#define COAP_OPTION_BLOCK1 27 /* C, uint, 0--3 B, (none) */ +#define COAP_OPTION_BLOCK2 23 /* CU-_, uint, 0--3 B, (none) */ +#define COAP_OPTION_BLOCK1 27 /* CU-_, uint, 0--3 B, (none) */ +#define COAP_OPTION_SIZE2 28 /* __N_, uint, 0-4 B, (none) */ + +/* option type from RFC 7967 */ + +#define COAP_OPTION_NORESPONSE 258 /* _U-_, uint, 0--1 B, 0 */ -/* selected option types from RFC 7967 */ +/* option type from RFC 8768 */ -#define COAP_OPTION_NORESPONSE 258 /* N, uint, 0--1 B, 0 */ +#define COAP_OPTION_HOP_LIMIT 16 /* ____, uint, 1 B, 16 */ #define COAP_MAX_OPT 65535 /**< the highest option number we know */ @@ -496,7 +522,7 @@ int coap_add_data(coap_pdu_t *pdu, /** * Adds given data to the pdu that is passed as first parameter but does not - * copyt it. Note that the PDU's data is destroyed by coap_add_option(). + * copy it. Note that the PDU's data is destroyed by coap_add_option(). * coap_add_data() must be have been called once for this PDU, otherwise the * result is undefined. * The actual data must be copied at the returned location. diff --git a/include/coap2/resource.h b/include/coap2/resource.h index e5acd44b78..54140ea719 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, @@ -189,6 +190,16 @@ 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. + * + * @param handler The PUT/POST/GET etc. handler that handles all request types. + * + * @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); + /** * 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 4d1670115e..bbe4fe88b6 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 funnction 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 ed17369469..598421e389 100644 --- a/libcoap-2.map +++ b/libcoap-2.map @@ -148,6 +148,7 @@ global: coap_remove_from_queue; coap_resource_init; coap_resource_notify_observers; + coap_resource_proxy_uri_init; coap_resource_set_dirty; coap_resource_unknown_init; coap_response_phrase; @@ -188,6 +189,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 51df132cbd..8a59f9b1fb 100644 --- a/libcoap-2.sym +++ b/libcoap-2.sym @@ -146,6 +146,7 @@ coap_remove_async coap_remove_from_queue coap_resource_init coap_resource_notify_observers +coap_resource_proxy_uri_init coap_resource_set_dirty coap_resource_unknown_init coap_response_phrase @@ -186,6 +187,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 876edce5bb..f97a7c8f40 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -67,6 +67,7 @@ EXTRA_PAGES = \ coap_encode_var_bytes.3 \ coap_split_path.3 \ coap_split_query.3 \ + coap_resource_get_userdata.3 \ coap_session_get_app_data.3 \ coap_session_set_app_data.3 \ coap_endpoint_str.3 @@ -84,6 +85,7 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_pdu_setup.3" > coap_encode_var_bytes.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_logging.3" > coap_endpoint_str.3 diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index 6fb965c421..244a1e3df7 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -16,8 +16,9 @@ SYNOPSIS -------- *coap-client* [*-a* addr] [*-b* [num,]size] [*-e* text] [*-f* file] [*-l* loss] [*-m* method] [*-o* file] [*-p* port] [*-r*] [*-s duration*] - [*-t* type] [*-v* num] [*-A* type] [*-B* seconds] [*-K* interval] - [*-N*] [*-O* num,text] [*-P* addr[:port]] [*-T* token] [*-U*] + [*-t* type] [*-v* num] [*-A* type] [*-B* seconds] + [*-H* hoplimit] [*-K* interval] [*-N*] [*-O* num,text] + [*-P* scheme://addr[:port]] [*-T* token] [*-U*] [[*-h* match_hint_file] [*-k* key] [*-u* user]] [[*-c* certfile] [*-C* cafile] [*-R* root_cafile]] URI @@ -108,6 +109,10 @@ OPTIONS - General *-B* seconds:: Break operation after waiting given seconds (default is 90). +*-H* hoplimit:: + Set the Hop Limit count to hoplimit for proxies. Must have a value between + 1 and 255 inclusive. Default is '16'. + *-K* interval:: Send a ping after interval seconds of inactivity. If not specified (or 0), keep-alive is disabled (default). @@ -120,9 +125,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 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 6fbc23432e..d84dae86bd 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]] [[*-h* hint] [*-i* match_identity_file] [*-k* key] - [*-s* match_psk_sni_file]] + [*-s* match_psk_sni_file] [-u user]] [[*-c* certfile] [*-n*] [*-C* cafile] [*-R* root_cafile] [*-S* match_pki_sni_file]] @@ -63,6 +63,12 @@ OPTIONS - General fifth response will still be sent as a confirmable response (RFC 7641 requirement). +*-P* scheme://addr[:port] :: + Scheme, address and optional port to define a CoAP proxy to forward a + received proxy request to. Scheme is one of coap, coaps, coap+tcp and + coaps+tcp. + + OPTIONS - PSK ------------- (If supported by underlying (D)TLS library) @@ -96,6 +102,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.txt.in b/man/coap.txt.in index 34f33179a9..cb31b87a60 100644 --- a/man/coap.txt.in +++ b/man/coap.txt.in @@ -61,6 +61,8 @@ See "RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets" +"RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option" + for further information. BUGS diff --git a/man/coap_pdu_setup.txt.in b/man/coap_pdu_setup.txt.in index cc5727189a..dfc48e1fb0 100644 --- a/man/coap_pdu_setup.txt.in +++ b/man/coap_pdu_setup.txt.in @@ -134,26 +134,44 @@ of the data of the option, and _data_ points to the content of the option. The following is the current list of options with their numeric value ---- -#define COAP_OPTION_IF_MATCH 1 /* opaque, 0-8 B */ -#define COAP_OPTION_URI_HOST 3 /* String, 1-255 B */ -#define COAP_OPTION_ETAG 4 /* opaque, 1-8 B */ -#define COAP_OPTION_IF_NONE_MATCH 5 /* empty, 0 B */ -#define COAP_OPTION_OBSERVE 6 /* empty/uint, 0 B/0-3 B */ -#define COAP_OPTION_URI_PORT 7 /* uint, 0-2 B */ -#define COAP_OPTION_LOCATION_PATH 8 /* String, 0-255 B */ -#define COAP_OPTION_URI_PATH 11 /* String, 0-255 B */ -#define COAP_OPTION_CONTENT_FORMAT 12 /* uint, 0-2 B */ -#define COAP_OPTION_MAXAGE 14 /* uint, 0-4 B, default 60 Seconds */ -#define COAP_OPTION_URI_QUERY 15 /* String, 1-255 B */ -#define COAP_OPTION_ACCEPT 17 /* uint, 0-2 B */ -#define COAP_OPTION_LOCATION_QUERY 20 /* String, 0-255 B */ -#define COAP_OPTION_BLOCK2 23 /* uint, 0-3 B */ -#define COAP_OPTION_BLOCK1 27 /* uint, 0-3 B */ -#define COAP_OPTION_SIZE2 28 /* uint, 0-4 B */ -#define COAP_OPTION_PROXY_URI 35 /* String, 1-1034 B */ -#define COAP_OPTION_PROXY_SCHEME 39 /* String, 1-255 B */ -#define COAP_OPTION_SIZE1 60 /* uint, 0-4 B */ -#define COAP_OPTION_NORESPONSE 258 /* uint, 0-1 B */ +/* + * The C, U, and N flags indicate the properties + * Critical, UnSafe, and NoCacheKey, respectively. + * If U is set, then N has no meaning as per + * https://tools.ietf.org/html/rfc7252#section-5.10 + * and is set to a -. + * Separately, R is for the options that can be repeated + * + * The least significant byte of the option is set as followed + * as per https://tools.ietf.org/html/rfc7252#section-5.4.6 + * + * 0 1 2 3 4 5 6 7 + * --+---+---+---+---+---+---+---+ + * | NoCacheKey| U | C | + * --+---+---+---+---+---+---+---+ + * + */ +#define COAP_OPTION_IF_MATCH 1 /* C__R, opaque, 0-8 B, RFC7252 */ +#define COAP_OPTION_URI_HOST 3 /* CU-_, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_ETAG 4 /* ___R, opaque, 1-8 B, RFC7252 */ +#define COAP_OPTION_IF_NONE_MATCH 5 /* C___, empty, 0 B, RFC7252 */ +#define COAP_OPTION_OBSERVE 6 /* _U-_, empty/uint, 0 B/0-3 B, RFC7641 */ +#define COAP_OPTION_URI_PORT 7 /* CU-_, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_PATH 8 /* ___R, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_URI_PATH 11 /* CU-R, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_CONTENT_FORMAT 12 /* ____, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_MAXAGE 14 /* _U-_, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_URI_QUERY 15 /* CU-R, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_HOP_LIMIT 16 /* ____, uint, 1 B, RFC8768 */ +#define COAP_OPTION_ACCEPT 17 /* C___, uint, 0-2 B, RFC7252 */ +#define COAP_OPTION_LOCATION_QUERY 20 /* ___R, String, 0-255 B, RFC7252 */ +#define COAP_OPTION_BLOCK2 23 /* CU-_, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_BLOCK1 27 /* CU-_, uint, 0-3 B, RFC7959 */ +#define COAP_OPTION_SIZE2 28 /* __N_, uint, 0-4 B, RFC7959 */ +#define COAP_OPTION_PROXY_URI 35 /* CU-_, String, 1-1034 B, RFC7252 */ +#define COAP_OPTION_PROXY_SCHEME 39 /* CU-_, String, 1-255 B, RFC7252 */ +#define COAP_OPTION_SIZE1 60 /* __N_, uint, 0-4 B, RFC7252 */ +#define COAP_OPTION_NORESPONSE 258 /* _U-_, uint, 0-1 B, RFC7967 */ ---- See FURTHER INFORMATION as to how to get the latest list. diff --git a/man/coap_resource.txt.in b/man/coap_resource.txt.in index 45b9a69b02..9b93da1ed5 100644 --- a/man/coap_resource.txt.in +++ b/man/coap_resource.txt.in @@ -11,9 +11,10 @@ coap_resource(3) 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, -coap_resource_set_mode, coap_resource_set_userdata, -coap_resource_get_userdata - Work with CoAP resources +coap_resource_set_mode, coap_resource_set_userdata +- Work with CoAP resources SYNOPSIS -------- @@ -25,6 +26,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_);* + *void coap_add_resource(coap_context_t *_context_, coap_resource_t *_resource_);* @@ -107,6 +111,11 @@ 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*() 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. + 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_ @@ -141,8 +150,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_debug.c b/src/coap_debug.c index 8b7e898a94..0a0d8ff897 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -316,14 +316,15 @@ msg_option_string(uint8_t code, uint16_t option_type) { { COAP_OPTION_CONTENT_FORMAT, "Content-Format" }, { COAP_OPTION_MAXAGE, "Max-Age" }, { COAP_OPTION_URI_QUERY, "Uri-Query" }, + { COAP_OPTION_HOP_LIMIT, "Hop-Limit" }, { COAP_OPTION_ACCEPT, "Accept" }, { COAP_OPTION_LOCATION_QUERY, "Location-Query" }, { COAP_OPTION_BLOCK2, "Block2" }, { COAP_OPTION_BLOCK1, "Block1" }, + { COAP_OPTION_SIZE2, "Size2" }, { COAP_OPTION_PROXY_URI, "Proxy-Uri" }, { COAP_OPTION_PROXY_SCHEME, "Proxy-Scheme" }, { COAP_OPTION_SIZE1, "Size1" }, - { COAP_OPTION_SIZE2, "Size2" }, { COAP_OPTION_NORESPONSE, "No-Response" } }; @@ -568,6 +569,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { case COAP_OPTION_OBSERVE: case COAP_OPTION_SIZE1: case COAP_OPTION_SIZE2: + case COAP_OPTION_HOP_LIMIT: /* show values as unsigned decimal value */ buf_len = snprintf((char *)buf, sizeof(buf), "%u", coap_decode_var_bytes(coap_opt_value(option), @@ -581,6 +583,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { opt_iter.type == COAP_OPTION_URI_HOST || opt_iter.type == COAP_OPTION_LOCATION_PATH || opt_iter.type == COAP_OPTION_LOCATION_QUERY || + opt_iter.type == COAP_OPTION_PROXY_SCHEME || opt_iter.type == COAP_OPTION_URI_QUERY) { encode = 0; } else { diff --git a/src/net.c b/src/net.c index 4543e45ef1..a3a69c0a85 100644 --- a/src/net.c +++ b/src/net.c @@ -1764,6 +1764,7 @@ coap_new_error_response(coap_pdu_t *request, unsigned char code, * request. We always need the Token, for 4.02 the unknown critical * options must be included as well. */ coap_option_clrb(opts, COAP_OPTION_CONTENT_TYPE); /* we do not want this */ + coap_option_clrb(opts, COAP_OPTION_HOP_LIMIT); /* we do not want this */ coap_option_iterator_init(request, &opt_iter, opts); @@ -2106,31 +2107,165 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu coap_method_handler_t h = NULL; coap_pdu_t *response = NULL; coap_opt_filter_t opt_filter; - coap_resource_t *resource; + coap_resource_t *resource = NULL; /* The respond field indicates whether a response must be treated * specially due to a No-Response option that declares disinterest * or interest in a specific response class. DEFAULT indicates that * No-Response has not been specified. */ 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 resp; coap_option_filter_clear(opt_filter); - /* try to find the resource from the request URI */ + 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); + } + } +#if ! defined WITH_CONTIKI && ! defined WITH_LWIP + if (uri.host.length) { + /* + * Check if the Proxy-Uri/Proxy-Scheme is to this endpoint. + * If so, process request locally */ + struct addrinfo *res, *ainfo; + struct addrinfo hints; + int error; + char addrstr[256]; + + memset(addrstr, 0, sizeof(addrstr)); + memcpy(addrstr, uri.host.s, min(uri.host.length, sizeof(addrstr)-1)); + + memset ((char *)&hints, 00, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = AF_UNSPEC; + + error = getaddrinfo(addrstr, NULL, &hints, &res); + if (error != 0) { + coap_log(LOG_INFO, "Unable to parse Proxy-%s host '%.*s'\n", + is_proxy_scheme ? "Scheme" : "Uri", + (int)uri.host.length, uri.host.s); + } + else { + coap_address_t *addr = &session->addr_info.local; + coap_address_t ipv4; + + if (addr->addr.sa.sa_family == AF_INET6) { + /* It is possible the local address could be IPv4 in IPv6 */ + if (addr->addr.sin6.sin6_addr.s6_addr32[0] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[1] == 0 && + addr->addr.sin6.sin6_addr.s6_addr32[2] == htonl (0xffff)) { + ipv4.addr.sa.sa_family = AF_INET; + ipv4.addr.sin.sin_addr.s_addr = + addr->addr.sin6.sin6_addr.s6_addr32[3]; + addr = &ipv4; + } + } + + for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) { + if (ainfo->ai_family == addr->addr.sa.sa_family) { + struct sockaddr_in *sin4; + struct sockaddr_in6 *sin6; + + switch (ainfo->ai_family) { + case AF_INET6: + sin6 = (struct sockaddr_in6*)ainfo->ai_addr; + if (memcmp(&addr->addr.sin6.sin6_addr, &sin6->sin6_addr, + sizeof(addr->addr.sin6.sin6_addr)) == 0) { + is_proxy_uri = 0; + is_proxy_scheme = 0; + goto finish; + } + break; + case AF_INET: + sin4 = (struct sockaddr_in*)ainfo->ai_addr; + if (memcmp(&addr->addr.sin.sin_addr, &sin4->sin_addr, + sizeof(addr->addr.sin.sin_addr)) == 0) { + is_proxy_uri = 0; + is_proxy_scheme = 0; + goto finish; + } + break; + default: + break; + } + } + } + finish: + freeaddrinfo(res); + } + } +#endif /* defined WITH_CONTIKI || defined WITH_LWIP */ + } + 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. @@ -2150,6 +2285,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))) && @@ -2232,7 +2369,6 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu if response == NULL */ if (coap_add_token(response, pdu->token_length, pdu->token)) { coap_binary_t token = { pdu->token_length, pdu->token }; - coap_opt_iterator_t opt_iter; coap_opt_t *observe = NULL; int observe_action = COAP_OBSERVE_CANCEL; @@ -2327,6 +2463,18 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu assert(response == NULL); coap_delete_string(uri_path); + return; + +fail_response: + response = + coap_new_error_response(pdu, COAP_RESPONSE_CODE(resp), + opt_filter); + if (response) { + if (coap_send(session, response) == COAP_INVALID_TID) + coap_log(LOG_WARNING, "cannot send response for transaction %u\n", + pdu->tid); + } + return; } static void diff --git a/src/pdu.c b/src/pdu.c index 4fe88352dd..2f154c3982 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -224,6 +224,23 @@ coap_add_option(coap_pdu_t *pdu, uint16_t type, size_t len, const uint8_t *data) "coap_add_option: options are not in correct order\n"); return 0; } + if (type == pdu->max_delta) { + /* Validate that the option is repeatable */ + switch (type) { + /* Ignore list of genuine repeatable */ + case COAP_OPTION_IF_MATCH: + case COAP_OPTION_ETAG: + case COAP_OPTION_LOCATION_PATH: + case COAP_OPTION_URI_PATH: + case COAP_OPTION_URI_QUERY: + case COAP_OPTION_LOCATION_QUERY: + break; + default: + coap_log(LOG_INFO, "Option %d is not defined as repeatable\n", type); + /* Accepting it after warning as there may be user defineable options */ + break; + } + } if (!coap_pdu_check_resize(pdu, pdu->used_size + coap_opt_encode_size(type - pdu->max_delta, len))) @@ -363,6 +380,7 @@ error_desc_t coap_error[] = { { COAP_RESPONSE_CODE(503), "Service Unavailable" }, { COAP_RESPONSE_CODE(504), "Gateway Timeout" }, { COAP_RESPONSE_CODE(505), "Proxying Not Supported" }, + { COAP_RESPONSE_CODE(508), "Hop Limit Reached" }, { 0, NULL } /* end marker */ }; diff --git a/src/resource.c b/src/resource.c index 448dcb1b85..4089f39917 100644 --- a/src/resource.c +++ b/src/resource.c @@ -343,6 +343,31 @@ 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) { + coap_resource_t *r; + + 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; + } + } 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, @@ -450,6 +475,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); @@ -475,6 +505,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); @@ -504,6 +539,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..b27cbbdf3b 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 { + /* https:// */ + 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