From 591728f1c6f7514cdc1b9b844f6821ba058fae1e Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Mon, 10 Jun 2024 09:18:51 +0100 Subject: [PATCH] reverse-proxy: Add in reverse proxy support to coap-server Tidy up the general proxy logic by moving common functionality into libcoap. New functions coap_proxy_forward_request() and coap_proxy_forward_response() added. Support for common shared connection to next hop. Support for round-robin balancing across multiple next hops. --- CMakeLists.txt | 2 + CMakeLists.txt.in | 2 + Makefile.am | 3 + configure.ac | 1 + examples/coap-server.c | 924 +++++---------------- examples/contiki/Makefile | 6 + examples/lwip/Makefile | 1 + examples/riot/pkg_libcoap/Makefile.libcoap | 1 + include/coap3/coap.h.in | 1 + include/coap3/coap.h.riot | 1 + include/coap3/coap.h.riot.in | 1 + include/coap3/coap.h.windows | 1 + include/coap3/coap.h.windows.in | 1 + include/coap3/coap_forward_decls.h | 8 + include/coap3/coap_internal.h | 1 + include/coap3/coap_mem.h | 1 + include/coap3/coap_net_internal.h | 4 + include/coap3/coap_proxy.h | 110 +++ include/coap3/coap_proxy_internal.h | 121 +++ libcoap-3.map | 3 + libcoap-3.sym | 3 + man/Makefile.am | 1 + man/coap-server.txt.in | 11 + man/coap_block.txt.in | 6 +- man/coap_proxy.txt.in | 102 +++ man/examples-code-check.c | 1 + src/coap_address.c | 74 +- src/coap_gnutls.c | 10 +- src/coap_io.c | 6 + src/coap_net.c | 7 + src/coap_proxy.c | 863 +++++++++++++++++++ win32/libcoap.vcxproj | 2 + win32/libcoap.vcxproj.filters | 6 + 33 files changed, 1562 insertions(+), 723 deletions(-) create mode 100644 include/coap3/coap_proxy.h create mode 100644 include/coap3/coap_proxy_internal.h create mode 100644 man/coap_proxy.txt.in create mode 100644 src/coap_proxy.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 11d1a143e6..c8c4a3d4ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -745,6 +745,7 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/src/coap_oscore.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_pdu.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_prng.c + ${CMAKE_CURRENT_LIST_DIR}/src/coap_proxy.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_resource.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_session.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_sha1.c @@ -784,6 +785,7 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_option.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_pdu.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_prng.h + ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_proxy.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_resource.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_session.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_str.h diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in index fbffe52c50..a21df1d0f8 100644 --- a/CMakeLists.txt.in +++ b/CMakeLists.txt.in @@ -745,6 +745,7 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/src/coap_oscore.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_pdu.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_prng.c + ${CMAKE_CURRENT_LIST_DIR}/src/coap_proxy.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_resource.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_session.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_sha1.c @@ -784,6 +785,7 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_option.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_pdu.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_prng.h + ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_proxy.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_resource.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_session.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_str.h diff --git a/Makefile.am b/Makefile.am index 2e2c245221..ec99bd2f80 100644 --- a/Makefile.am +++ b/Makefile.am @@ -113,6 +113,7 @@ EXTRA_DIST = \ include/coap$(LIBCOAP_API_VERSION)/coap_oscore_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_pdu_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_prng_internal.h \ + include/coap$(LIBCOAP_API_VERSION)/coap_proxy_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_resource_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_session_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_sha1_internal.h \ @@ -211,6 +212,7 @@ libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES = \ src/coap_option.c \ src/coap_oscore.c \ src/coap_pdu.c \ + src/coap_proxy.c \ src/coap_prng.c \ src/coap_resource.c \ src/coap_session.c \ @@ -260,6 +262,7 @@ libcoap_include_HEADERS = \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_oscore.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_pdu.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_prng.h \ + $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_proxy.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_resource.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_session.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_str.h \ diff --git a/configure.ac b/configure.ac index 21da465d56..a745d566ae 100644 --- a/configure.ac +++ b/configure.ac @@ -1337,6 +1337,7 @@ man/coap_oscore.txt man/coap_pdu_access.txt man/coap_pdu_setup.txt man/coap_persist.txt +man/coap_proxy.txt man/coap_recovery.txt man/coap_resource.txt man/coap_session.txt diff --git a/examples/coap-server.c b/examples/coap-server.c index 1fd421f009..3e5b5e3b1d 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -631,139 +631,10 @@ hnd_put_example_data(coap_resource_t *resource, static unsigned char *user = NULL; 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_binary_t *token; /* Incoming token */ - coap_string_t *query; /* Incoming query */ - coap_pdu_code_t req_code; /* Incoming request code */ - coap_pdu_type_t req_type; /* Incoming request type */ -} proxy_list_t; - -static proxy_list_t *proxy_list = NULL; -static size_t proxy_list_count = 0; -static coap_resource_t *proxy_resource = NULL; - -static int -get_uri_proxy_scheme_info(const coap_pdu_t *request, - coap_opt_t *opt, - coap_uri_t *uri, - coap_string_t **uri_path, - coap_string_t **uri_query) { - - const char *opt_val = (const char *)coap_opt_value(opt); - int opt_len = coap_opt_length(opt); - coap_opt_iterator_t opt_iter; - - if (opt_len == 9 && - strncasecmp(opt_val, "coaps+tcp", 9) == 0) { - uri->scheme = COAP_URI_SCHEME_COAPS_TCP; - uri->port = COAPS_DEFAULT_PORT; - } else if (opt_len == 8 && - strncasecmp(opt_val, "coap+tcp", 8) == 0) { - uri->scheme = COAP_URI_SCHEME_COAP_TCP; - uri->port = COAP_DEFAULT_PORT; - } else if (opt_len == 5 && - strncasecmp(opt_val, "coaps", 5) == 0) { - uri->scheme = COAP_URI_SCHEME_COAPS; - uri->port = COAPS_DEFAULT_PORT; - } else if (opt_len == 4 && - strncasecmp(opt_val, "coap", 4) == 0) { - uri->scheme = COAP_URI_SCHEME_COAP; - uri->port = COAP_DEFAULT_PORT; - } else if (opt_len == 7 && - strncasecmp(opt_val, "coap+ws", 7) == 0) { - uri->scheme = COAP_URI_SCHEME_COAP_WS; - uri->port = 80; - } else if (opt_len == 8 && - strncasecmp(opt_val, "coaps+ws", 8) == 0) { - uri->scheme = COAP_URI_SCHEME_COAPS_WS; - uri->port = 443; - } else { - coap_log_warn("Unsupported Proxy Scheme '%*.*s'\n", - opt_len, opt_len, opt_val); - return 0; - } - - 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_warn("Proxy Scheme requires Uri-Host\n"); - return 0; - } - 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; - } - return 1; -} - -static int -verify_proxy_scheme_supported(coap_uri_scheme_t scheme) { - - /* Sanity check that the connection can be forwarded on */ - switch (scheme) { - case COAP_URI_SCHEME_HTTP: - case COAP_URI_SCHEME_HTTPS: - coap_log_warn("Proxy URI http or https not supported\n"); - return 0; - case COAP_URI_SCHEME_COAP: - break; - case COAP_URI_SCHEME_COAPS: - if (!coap_dtls_is_supported()) { - coap_log_warn("coaps URI scheme not supported for proxy\n"); - return 0; - } - break; - case COAP_URI_SCHEME_COAP_TCP: - if (!coap_tcp_is_supported()) { - coap_log_warn("coap+tcp URI scheme not supported for proxy\n"); - return 0; - } - break; - case COAP_URI_SCHEME_COAPS_TCP: - if (!coap_tls_is_supported()) { - coap_log_warn("coaps+tcp URI scheme not supported for proxy\n"); - return 0; - } - break; - case COAP_URI_SCHEME_COAP_WS: - if (!coap_ws_is_supported()) { - coap_log_warn("coap+ws URI scheme not supported for proxy\n"); - return 0; - } - break; - case COAP_URI_SCHEME_COAPS_WS: - if (!coap_wss_is_supported()) { - coap_log_warn("coaps+ws URI scheme not supported for proxy\n"); - return 0; - } - break; - case COAP_URI_SCHEME_LAST: - default: - coap_log_warn("%d URI scheme not supported\n", scheme); - break; - } - return 1; -} +static coap_proxy_server_list_t forward_proxy = { NULL, 0, 0, COAP_PROXY_FORWARD, 0, 300}; +static coap_proxy_server_list_t reverse_proxy = { NULL, 0, 0, COAP_PROXY_REVERSE_STRIP, 0, 10}; static coap_dtls_cpsk_t * setup_cpsk(char *client_sni) { @@ -779,404 +650,36 @@ setup_cpsk(char *client_sni) { return &dtls_cpsk; } -static proxy_list_t * -get_proxy_session(coap_session_t *session, coap_pdu_t *response, - const coap_bin_const_t *token, const coap_string_t *query, - coap_pdu_code_t req_code, coap_pdu_type_t req_type) { - - size_t i; - proxy_list_t *new_proxy_list; - - /* Locate existing forwarding relationship */ - for (i = 0; i < proxy_list_count; i++) { - if (proxy_list[i].incoming == session) { - return &proxy_list[i]; - } - } - - /* Need to create a new forwarding mapping */ - new_proxy_list = realloc(proxy_list, (i+1)*sizeof(proxy_list[0])); - - if (new_proxy_list == NULL) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); - return NULL; - } - proxy_list = new_proxy_list; - proxy_list[i].incoming = session; - if (token) { - proxy_list[i].token = coap_new_binary(token->length); - if (!proxy_list[i].token) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); - return NULL; - } - memcpy(proxy_list[i].token->s, token->s, token->length); - } else - proxy_list[i].token = NULL; - - if (query) { - proxy_list[i].query = coap_new_string(query->length); - if (!proxy_list[i].query) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); - return NULL; - } - memcpy(proxy_list[i].query->s, query->s, query->length); - } else - proxy_list[i].query = NULL; - - proxy_list[i].ongoing = NULL; - proxy_list[i].req_code = req_code; - proxy_list[i].req_type = req_type; - proxy_list_count++; - return &proxy_list[i]; -} - 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) { - coap_pdu_t *response; - - coap_session_release(proxy_list[i].ongoing); - - /* Need to send back a gateway failure */ - response = coap_pdu_init(proxy_list[i].req_type, - COAP_RESPONSE_CODE_BAD_GATEWAY, - coap_new_message_id(proxy_list[i].incoming), - coap_session_max_pdu_size(proxy_list[i].incoming)); - if (!response) { - coap_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_debug("Cannot add token to incoming proxy response PDU\n"); - } - - if (coap_send(proxy_list[i].incoming, response) == - COAP_INVALID_MID) { - coap_log_info("Failed to send PDU with 5.02 gateway issue\n"); - } - break; - } - } - if (i != proxy_list_count) { - coap_delete_binary(proxy_list[i].token); - coap_delete_string(proxy_list[i].query); - 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--; - } -} - +hnd_forward_proxy_uri(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query COAP_UNUSED, + coap_pdu_t *response) { -static coap_session_t * -get_ongoing_proxy_session(coap_session_t *session, - coap_pdu_t *response, const coap_bin_const_t *token, - const coap_string_t *query, coap_pdu_code_t req_code, - coap_pdu_type_t req_type, const coap_uri_t *uri) { - - coap_address_t dst; - coap_uri_scheme_t scheme; - coap_proto_t proto; - static char client_sni[256]; - coap_str_const_t server; - uint16_t port; - coap_addr_info_t *info_list = NULL; - proxy_list_t *new_proxy_list; - coap_context_t *context = coap_session_get_context(session); - - new_proxy_list = get_proxy_session(session, response, token, query, req_code, - req_type); - if (!new_proxy_list) - return NULL; - - if (new_proxy_list->ongoing) - return new_proxy_list->ongoing; - - if (proxy.host.length) { - server = proxy.host; - port = proxy.port; - scheme = proxy.scheme; - } else { - server = uri->host; - port = uri->port; - scheme = uri->scheme; - } - - /* resolve destination address where data should be sent */ - info_list = coap_resolve_address_info(&server, port, port, port, port, - 0, - 1 << scheme, - COAP_RESOLVE_TYPE_REMOTE); - - if (info_list == NULL) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_GATEWAY); - remove_proxy_association(session, 0); - return NULL; - } - proto = info_list->proto; - memcpy(&dst, &info_list->addr, sizeof(dst)); - coap_free_address_info(info_list); - - switch (scheme) { - case COAP_URI_SCHEME_COAP: - case COAP_URI_SCHEME_COAP_TCP: - case COAP_URI_SCHEME_COAP_WS: - new_proxy_list->ongoing = - coap_new_client_session(context, NULL, &dst, proto); - break; - case COAP_URI_SCHEME_COAPS: - case COAP_URI_SCHEME_COAPS_TCP: - case COAP_URI_SCHEME_COAPS_WS: - snprintf(client_sni, sizeof(client_sni), "%*.*s", (int)server.length, (int)server.length, server.s); - if (!key_defined) { - /* Use our defined PKI certs (or NULL) */ - coap_dtls_pki_t *dtls_pki = setup_pki(context, COAP_DTLS_ROLE_CLIENT, - client_sni); - new_proxy_list->ongoing = - coap_new_client_session_pki(context, NULL, &dst, proto, dtls_pki); - } else { - /* Use our defined PSK */ - coap_dtls_cpsk_t *dtls_cpsk = setup_cpsk(client_sni); - - new_proxy_list->ongoing = - coap_new_client_session_psk2(context, NULL, &dst, proto, dtls_cpsk); - } - break; - case COAP_URI_SCHEME_HTTP: - case COAP_URI_SCHEME_HTTPS: - case COAP_URI_SCHEME_LAST: - default: - assert(0); - break; - } - if (new_proxy_list->ongoing == NULL) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED); - remove_proxy_association(session, 0); - return NULL; + if (!coap_proxy_forward_request(session, request, response, resource, + NULL, &forward_proxy)) { + coap_log_debug("hnd_forward_proxy_uri: Failed to forward PDU\n"); + /* Non ACK response code set on error detection */ } - return new_proxy_list->ongoing; -} -static void -release_proxy_body_data(coap_session_t *session COAP_UNUSED, - void *app_ptr) { - coap_delete_binary(app_ptr); + /* Leave response code as is */ } static void -hnd_proxy_uri(coap_resource_t *resource COAP_UNUSED, - coap_session_t *session, - const coap_pdu_t *request, - const coap_string_t *query, - 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; - coap_session_t *ongoing = NULL; - size_t size; - size_t offset; - size_t total; - coap_binary_t *body_data = NULL; - const uint8_t *data; - coap_pdu_t *pdu = NULL; - coap_optlist_t *optlist = NULL; - coap_opt_t *option; - coap_bin_const_t token = coap_pdu_get_token(request); +hnd_reverse_proxy_uri(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query COAP_UNUSED, + coap_pdu_t *response) { - memset(&uri, 0, sizeof(uri)); - /* - * See if Proxy-Scheme - */ - opt = coap_check_option(request, COAP_OPTION_PROXY_SCHEME, &opt_iter); - if (opt) { - if (!get_uri_proxy_scheme_info(request, opt, &uri, &uri_path, - &uri_query)) { - coap_pdu_set_code(response, - COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED); - goto cleanup; - } - proxy_scheme_option = 1; + if (!coap_proxy_forward_request(session, request, response, resource, + NULL, &reverse_proxy)) { + coap_log_debug("hnd_reverse_proxy_uri: Failed to forward PDU\n"); + /* Non ACK response code set on error detection */ } - /* - * See if Proxy-Uri - */ - proxy_uri = coap_check_option(request, COAP_OPTION_PROXY_URI, &opt_iter); - if (proxy_uri) { - coap_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_warn("Proxy URI not decodable\n"); - coap_pdu_set_code(response, - COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED); - goto cleanup; - } - } - - if (!(proxy_scheme_option || proxy_uri)) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND); - goto cleanup; - } - - if (uri.host.length == 0) { - /* Ongoing connection not well formed */ - coap_pdu_set_code(response, COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED); - goto cleanup; - } - - if (!verify_proxy_scheme_supported(uri.scheme)) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_PROXYING_NOT_SUPPORTED); - goto cleanup; - } - - /* 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 || - uri.scheme == COAP_URI_SCHEME_COAP_WS || - uri.scheme == COAP_URI_SCHEME_COAPS_WS) { - coap_pdu_code_t req_code = coap_pdu_get_code(request); - coap_pdu_type_t req_type = coap_pdu_get_type(request); - - if (!get_proxy_session(session, response, &token, query, req_code, req_type)) - goto cleanup; - - if (coap_get_data_large(request, &size, &data, &offset, &total)) { - /* COAP_BLOCK_SINGLE_BODY is set, so single body should be given */ - assert(size == total); - body_data = coap_new_binary(total); - if (!body_data) { - coap_log_debug("body build memory error\n"); - goto cleanup; - } - memcpy(body_data->s, data, size); - data = body_data->s; - } - - /* Send data on (opening session if appropriate) */ - - ongoing = get_ongoing_proxy_session(session, response, &token, - query, req_code, req_type, &uri); - if (!ongoing) - goto cleanup; - /* - * Build up the ongoing PDU that we are going to send - */ - pdu = coap_pdu_init(req_type, req_code, - coap_new_message_id(ongoing), - coap_session_max_pdu_size(ongoing)); - if (!pdu) { - coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); - goto cleanup; - } - - if (!coap_add_token(pdu, token.length, token.s)) { - coap_log_debug("cannot add token to proxy request\n"); - coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); - goto cleanup; - } - - if (proxy.host.length == 0) { - /* Use Uri-Path and Uri-Query - direct session */ - proxy_uri = NULL; - proxy_scheme_option = 0; - const coap_address_t *dst = coap_session_get_addr_remote(ongoing); - - if (!coap_uri_into_optlist(&uri, dst, &optlist, 1)) { - coap_log_err("Failed to create options for URI\n"); - goto cleanup; - } - } - - /* 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.number) { - case COAP_OPTION_PROXY_URI: - if (proxy_uri) { - /* Need to add back in */ - goto add_in; - } - break; - case COAP_OPTION_PROXY_SCHEME: - case COAP_OPTION_URI_PATH: - case COAP_OPTION_URI_PORT: - case COAP_OPTION_URI_HOST: - case COAP_OPTION_URI_QUERY: - if (proxy_scheme_option) { - /* Need to add back in */ - goto add_in; - } - break; - case COAP_OPTION_BLOCK1: - case COAP_OPTION_BLOCK2: - case COAP_OPTION_Q_BLOCK1: - case COAP_OPTION_Q_BLOCK2: - /* These are not passed on */ - break; - default: -add_in: - coap_insert_optlist(&optlist, - coap_new_optlist(opt_iter.number, - coap_opt_length(option), - coap_opt_value(option))); - break; - } - } - - /* Update pdu with options */ - coap_add_optlist_pdu(pdu, &optlist); - coap_delete_optlist(optlist); - if (size) { - if (!coap_add_data_large_request(ongoing, pdu, size, data, - release_proxy_body_data, body_data)) { - coap_log_debug("cannot add data to proxy request\n"); - } else { - body_data = NULL; - } - } - - if (coap_get_log_level() < COAP_LOG_DEBUG) - coap_show_pdu(COAP_LOG_INFO, pdu); - - coap_send(ongoing, pdu); - /* - * Do not update with response code (hence empty ACK) as will be sending - * separate response when response comes back from upstream server - */ - pdu = NULL; - goto cleanup; - } else { - /* TODO http & https */ - coap_log_err("Proxy-Uri scheme %d not currently supported\n", uri.scheme); - } -cleanup: - coap_delete_string(uri_path); - coap_delete_string(uri_query); - coap_delete_binary(body_data); - coap_delete_pdu(pdu); + /* Leave response code as is */ } #endif /* SERVER_CAN_PROXY */ @@ -1551,7 +1054,7 @@ hnd_put_post_unknown(coap_resource_t *resource COAP_UNUSED, #if SERVER_CAN_PROXY static int -proxy_event_handler(coap_session_t *session, +proxy_event_handler(coap_session_t *session COAP_UNUSED, coap_event_t event) { switch (event) { @@ -1566,9 +1069,6 @@ proxy_event_handler(coap_session_t *session, case COAP_EVENT_OSCORE_DECODE_ERROR: case COAP_EVENT_WS_PACKET_SIZE: case COAP_EVENT_WS_CLOSED: - /* Need to remove any proxy associations */ - remove_proxy_association(session, 0); - break; case COAP_EVENT_DTLS_CONNECTED: case COAP_EVENT_DTLS_RENEGOTIATE: case COAP_EVENT_DTLS_ERROR: @@ -1591,130 +1091,15 @@ proxy_event_handler(coap_session_t *session, } static coap_response_t -proxy_response_handler(coap_session_t *session, - const coap_pdu_t *sent COAP_UNUSED, - const coap_pdu_t *received, - const coap_mid_t id COAP_UNUSED) { - - coap_pdu_t *pdu = NULL; - coap_session_t *incoming = NULL; - size_t i; - size_t size; - const uint8_t *data; - coap_optlist_t *optlist = NULL; - coap_opt_t *option; - coap_opt_iterator_t opt_iter; - size_t offset; - size_t total; - proxy_list_t *proxy_entry = NULL; - uint16_t media_type = COAP_MEDIATYPE_TEXT_PLAIN; - int maxage = -1; - uint64_t etag = 0; - coap_pdu_code_t rcv_code = coap_pdu_get_code(received); - coap_bin_const_t rcv_token = coap_pdu_get_token(received); - coap_binary_t *body_data = NULL; - - for (i = 0; i < proxy_list_count; i++) { - if (proxy_list[i].ongoing == session) { - proxy_entry = &proxy_list[i]; - incoming = proxy_entry->incoming; - break; - } - } - if (i == proxy_list_count) { - coap_log_debug("Unknown proxy ongoing session response received\n"); - return COAP_RESPONSE_OK; - } - - coap_log_debug("** process upstream incoming %d.%02d response:\n", - COAP_RESPONSE_CLASS(rcv_code), rcv_code & 0x1F); - if (coap_get_log_level() < COAP_LOG_DEBUG) - coap_show_pdu(COAP_LOG_INFO, received); - - if (coap_get_data_large(received, &size, &data, &offset, &total)) { - /* COAP_BLOCK_SINGLE_BODY is set, so single body should be given */ - assert(size == total); - body_data = coap_new_binary(total); - if (!body_data) { - coap_log_debug("body build memory error\n"); - return COAP_RESPONSE_OK; - } - memcpy(body_data->s, data, size); - data = body_data->s; - } - - /* - * Build up the ongoing PDU that we are going to send to proxy originator - * as separate response - */ - pdu = coap_pdu_init(proxy_entry->req_type, rcv_code, - coap_new_message_id(incoming), - coap_session_max_pdu_size(incoming)); - if (!pdu) { - coap_log_debug("Failed to create ongoing proxy response PDU\n"); - return COAP_RESPONSE_OK; - } - - if (!coap_add_token(pdu, rcv_token.length, rcv_token.s)) { - coap_log_debug("cannot add token to ongoing proxy response PDU\n"); - } - - /* - * Copy the options across, skipping those needed for - * coap_add_data_response_large() - */ - coap_option_iterator_init(received, &opt_iter, COAP_OPT_ALL); - while ((option = coap_option_next(&opt_iter))) { - switch (opt_iter.number) { - case COAP_OPTION_CONTENT_FORMAT: - media_type = coap_decode_var_bytes(coap_opt_value(option), - coap_opt_length(option)); - break; - case COAP_OPTION_MAXAGE: - maxage = coap_decode_var_bytes(coap_opt_value(option), - coap_opt_length(option)); - break; - case COAP_OPTION_ETAG: - etag = coap_decode_var_bytes8(coap_opt_value(option), - coap_opt_length(option)); - break; - case COAP_OPTION_BLOCK2: - case COAP_OPTION_Q_BLOCK2: - case COAP_OPTION_SIZE2: - break; - default: - coap_insert_optlist(&optlist, - coap_new_optlist(opt_iter.number, - coap_opt_length(option), - coap_opt_value(option))); - break; - } - } - coap_add_optlist_pdu(pdu, &optlist); - coap_delete_optlist(optlist); - - if (size > 0) { - coap_pdu_t *dummy_pdu = coap_pdu_init(proxy_entry->req_type, - proxy_entry->req_code, 0, - coap_session_max_pdu_size(incoming)); - - coap_add_data_large_response(proxy_resource, incoming, dummy_pdu, pdu, - proxy_entry->query, - media_type, maxage, etag, size, data, - release_proxy_body_data, - body_data); - coap_delete_pdu(dummy_pdu); - } - - if (coap_get_log_level() < COAP_LOG_DEBUG) - coap_show_pdu(COAP_LOG_INFO, pdu); - - coap_send(incoming, pdu); - return COAP_RESPONSE_OK; +reverse_response_handler(coap_session_t *session, + const coap_pdu_t *sent COAP_UNUSED, + const coap_pdu_t *received, + const coap_mid_t id COAP_UNUSED) { + return coap_proxy_forward_response(session, received, NULL); } static void -proxy_nack_handler(coap_session_t *session, +proxy_nack_handler(coap_session_t *session COAP_UNUSED, const coap_pdu_t *sent COAP_UNUSED, const coap_nack_reason_t reason, const coap_mid_t mid COAP_UNUSED) { @@ -1727,9 +1112,6 @@ proxy_nack_handler(coap_session_t *session, case COAP_NACK_WS_FAILED: case COAP_NACK_TLS_LAYER_FAILED: case COAP_NACK_WS_LAYER_FAILED: - /* Need to remove any proxy associations */ - remove_proxy_association(session, 1); - break; case COAP_NACK_ICMP_ISSUE: case COAP_NACK_BAD_RESPONSE: default: @@ -1744,69 +1126,87 @@ static void init_resources(coap_context_t *ctx) { coap_resource_t *r; - r = coap_resource_init(NULL, COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT); - coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_index); - - 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("\"General Info\""), 0); - coap_add_resource(ctx, r); +#if SERVER_CAN_PROXY + if (reverse_proxy.entry_count) { + /* Create a reverse proxy resource to handle PUTs */ + r = coap_resource_unknown_init2(hnd_reverse_proxy_uri, + COAP_RESOURCE_HANDLE_WELLKNOWN_CORE); + /* Add in handling other requests as well */ + coap_register_handler(r, COAP_REQUEST_GET, hnd_reverse_proxy_uri); + coap_register_handler(r, COAP_REQUEST_POST, hnd_reverse_proxy_uri); + coap_register_handler(r, COAP_REQUEST_DELETE, hnd_reverse_proxy_uri); + coap_register_handler(r, COAP_REQUEST_FETCH, hnd_reverse_proxy_uri); + coap_register_handler(r, COAP_REQUEST_PATCH, hnd_reverse_proxy_uri); + coap_register_handler(r, COAP_REQUEST_IPATCH, hnd_reverse_proxy_uri); + coap_add_resource(ctx, r); + coap_register_event_handler(ctx, proxy_event_handler); + coap_register_response_handler(ctx, reverse_response_handler); + coap_register_nack_handler(ctx, proxy_nack_handler); + } else { +#endif /* SERVER_CAN_PROXY */ + r = coap_resource_init(NULL, COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_index); - /* store clock base to use in /time */ - my_clock_base = clock_offset; + 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("\"General Info\""), 0); + coap_add_resource(ctx, r); - r = coap_resource_init(coap_make_str_const("time"), resource_flags); - coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_fetch_time); - coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_fetch_time); - coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_time); - coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete_time); - coap_resource_set_get_observable(r, 1); + /* store clock base to use in /time */ + my_clock_base = clock_offset; - 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("\"Internal Clock\""), 0); - coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0); - coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0); + r = coap_resource_init(coap_make_str_const("time"), resource_flags); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_fetch_time); + coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_fetch_time); + coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_time); + coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete_time); + coap_resource_set_get_observable(r, 1); - coap_add_resource(ctx, r); - time_resource = r; + 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("\"Internal Clock\""), 0); + coap_add_attr(r, coap_make_str_const("rt"), coap_make_str_const("\"ticks\""), 0); + coap_add_attr(r, coap_make_str_const("if"), coap_make_str_const("\"clock\""), 0); - if (support_dynamic > 0) { - /* Create a resource to handle PUTs to unknown URIs */ - r = coap_resource_unknown_init2(hnd_put_post_unknown, 0); - /* Add in handling POST as well */ - coap_register_handler(r, COAP_REQUEST_POST, hnd_put_post_unknown); coap_add_resource(ctx, r); - } + time_resource = r; - if (coap_async_is_supported()) { - r = coap_resource_init(coap_make_str_const("async"), - resource_flags | - COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT | - COAP_RESOURCE_FLAGS_LIB_DIS_MCAST_DELAYS); - coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_async); + if (support_dynamic > 0) { + /* Create a resource to handle PUTs to unknown URIs */ + r = coap_resource_unknown_init2(hnd_put_post_unknown, 0); + /* Add in handling POST as well */ + coap_register_handler(r, COAP_REQUEST_POST, hnd_put_post_unknown); + coap_add_resource(ctx, r); + } - coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); - coap_add_resource(ctx, r); - } + if (coap_async_is_supported()) { + r = coap_resource_init(coap_make_str_const("async"), + resource_flags | + COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT | + COAP_RESOURCE_FLAGS_LIB_DIS_MCAST_DELAYS); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_async); - r = coap_resource_init(coap_make_str_const("example_data"), resource_flags); - coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_example_data); - coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_example_data); - coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_example_data); - coap_resource_set_get_observable(r, 1); + coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_resource(ctx, r); + } - 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); + r = coap_resource_init(coap_make_str_const("example_data"), resource_flags); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_example_data); + coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_example_data); + coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get_example_data); + coap_resource_set_get_observable(r, 1); + + 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); #if SERVER_CAN_PROXY + } if (proxy_host_name_count) { - r = coap_resource_proxy_uri_init2(hnd_proxy_uri, proxy_host_name_count, + r = coap_resource_proxy_uri_init2(hnd_forward_proxy_uri, proxy_host_name_count, proxy_host_name_list, 0); coap_add_resource(ctx, r); coap_register_event_handler(ctx, proxy_event_handler); - coap_register_response_handler(ctx, proxy_response_handler); + coap_register_response_handler(ctx, reverse_response_handler); coap_register_nack_handler(ctx, proxy_nack_handler); - proxy_resource = r; } #endif /* SERVER_CAN_PROXY */ } @@ -2122,6 +1522,40 @@ fill_keystore(coap_context_t *ctx) { } } +#if SERVER_CAN_PROXY +static void +proxy_dtls_setup(coap_context_t *ctx, coap_proxy_server_list_t *proxy_info) { + size_t i; + static char client_sni[256]; + + for (i = 0; i < proxy_info->entry_count; i++) { + coap_proxy_server_t *proxy_server = &proxy_info->entry[i]; + + if (proxy_info->type == COAP_PROXY_DIRECT || proxy_info->type == COAP_PROXY_DIRECT_STRIP) { + memset(client_sni, 0, sizeof(client_sni)); + } else { + snprintf(client_sni, sizeof(client_sni), "%*.*s", (int)proxy_server->uri.host.length, + (int)proxy_server->uri.host.length, proxy_server->uri.host.s); + } + if (!key_defined) { + /* Use our defined PKI certs (or NULL) */ + proxy_server->dtls_pki = setup_pki(ctx, COAP_DTLS_ROLE_CLIENT, + client_sni); + proxy_server->dtls_cpsk = NULL; + } else { + /* Use our defined PSK */ + proxy_server->dtls_cpsk = setup_cpsk(client_sni); + proxy_server->dtls_pki = NULL; + } + /* + * Set this to a client specific oscore_conf if needed. + * proxy_server->oscore_conf = oscore_conf; + */ + } +} +#endif /* SERVER_CAN_PROXY */ + + static void usage(const char *program, const char *version) { const char *p; @@ -2140,9 +1574,9 @@ usage(const char *program, const char *version) { coap_string_tls_version(buffer, sizeof(buffer))); fprintf(stderr, "%s\n", coap_string_tls_support(buffer, sizeof(buffer))); fprintf(stderr, "\n" - "Usage: %s [-a priority] [-b max_block_size] [-d max] [-e] [-g group]\n" - "\t\t[-l loss] [-p port] [-q tls_engine_conf_file] [-r] [-v num]\n" - "\t\t[-w [port][,secure_port]]\n" + "Usage: %s [-a priority] [-b max_block_size] [-d max] [-e]\n" + "\t\t[-f scheme://address[:port] [-g group] -l loss] [-p port]\n" + "\t\t[-q tls_engine_conf_file] [-r] [-v num] [-w [port][,secure_port]]\n" "\t\t[-A address] [-E oscore_conf_file[,seq_file]] [-G group_if]\n" "\t\t[-L value] [-N] [-P scheme://address[:port],[name1[,name2..]]]\n" "\t\t[-T max_token_size] [-U type] [-V num] [-X size]\n" @@ -2160,6 +1594,13 @@ usage(const char *program, const char *version) { "\t \t\tresources. If max is reached, a 4.06 code is returned\n" "\t \t\tuntil one of the dynamic resources has been deleted\n" "\t-e \t\tEcho back the data sent with a PUT\n" + "\t-f scheme://address[:port]\n" + "\t \t\tAct as a reverse proxy where scheme, address, optional\n" + "\t \t\tport define how to connect to the internal server.\n" + "\t \t\tScheme is one of coap, coaps, coap+tcp, coaps+tcp,\n" + "\t \t\tcoap+ws, and coaps+ws. http(s) is not currently supported.\n" + "\t \t\tThis option can be repeated to provide multiple internal\n" + "\t \t\tservers that are round-robin load balanced\n" "\t-g group\tJoin the given multicast group\n" "\t \t\tNote: DTLS over multicast is not currently supported\n" "\t-l list\t\tFail to send some datagrams specified by a comma\n" @@ -2214,7 +1655,9 @@ usage(const char *program, const char *version) { "\t \t\tdefined before the leading , (comma) of the first name,\n" "\t \t\tthen the ongoing connection will be a direct connection.\n" "\t \t\tScheme is one of coap, coaps, coap+tcp, coaps+tcp,\n" - "\t \t\tcoap+ws, and coaps+ws. http(s) is not currently supported\n" + "\t \t\tcoap+ws, and coaps+ws. http(s) is not currently supported.\n" + "\t \t\tThis option can be repeated to provide multiple upstream\n" + "\t \t\tservers that are round-robin load balanced\n" "\t-T max_token_length\tSet the maximum token length (8-65804)\n" "\t-U type\t\tTreat address defined by -A as a Unix socket address.\n" "\t \t\ttype is 'coap', 'coaps', 'coap+tcp' or 'coaps+tcp'\n" @@ -2376,6 +1819,8 @@ cmdline_proxy(char *arg) { char *host_start = strchr(arg, ','); char *next_name = host_start; size_t ofs; + coap_uri_t uri; + coap_proxy_server_t *new_entry; if (!host_start) { coap_log_warn("Zero or more proxy host names not defined\n"); @@ -2385,12 +1830,31 @@ cmdline_proxy(char *arg) { 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_err("invalid CoAP Proxy definition\n"); + if (coap_split_uri((unsigned char *)arg, strlen(arg), &uri) < 0 || + uri.path.length != 0 || uri.query.length != 0) { + coap_log_err("Invalid CoAP Proxy definition\n"); + return 0; + } + if (!coap_verify_proxy_scheme_supported(uri.scheme)) { + coap_log_err("Unsupported CoAP Proxy protocol\n"); return 0; } + forward_proxy.type = COAP_PROXY_FORWARD; + } else { + forward_proxy.type = COAP_PROXY_DIRECT_STRIP; + } + + new_entry = realloc(forward_proxy.entry, + (forward_proxy.entry_count + 1)*sizeof(forward_proxy.entry[0])); + if (!new_entry) { + coap_log_err("CoAP Reverse-Proxy realloc() error\n"); + return 0; } + forward_proxy.entry = new_entry; + memset(&forward_proxy.entry[forward_proxy.entry_count], 0, sizeof(forward_proxy.entry[0])); + forward_proxy.entry[forward_proxy.entry_count].uri = uri; + forward_proxy.entry_count++; + proxy_host_name_count = 0; while (next_name) { proxy_host_name_count++; @@ -2408,6 +1872,35 @@ cmdline_proxy(char *arg) { return 1; } +static int +cmdline_reverse_proxy(char *arg) { + /* upstream server is defined */ + coap_uri_t uri; + coap_proxy_server_t *new_entry; + + if (coap_split_uri((unsigned char *)arg, strlen(arg), &uri) < 0 || + uri.path.length != 0 || uri.query.length != 0) { + coap_log_err("Invalid CoAP Reverse-Proxy definition\n"); + return 0; + } + if (!coap_verify_proxy_scheme_supported(uri.scheme)) { + coap_log_err("Unsupported CoAP Reverse-Proxy protocol\n"); + return 0; + } + + new_entry = realloc(reverse_proxy.entry, + (reverse_proxy.entry_count + 1)*sizeof(reverse_proxy.entry[0])); + if (!new_entry) { + coap_log_err("CoAP Reverse-Proxy realloc() error\n"); + return 0; + } + reverse_proxy.entry = new_entry; + memset(&reverse_proxy.entry[reverse_proxy.entry_count], 0, sizeof(reverse_proxy.entry[0])); + reverse_proxy.entry[reverse_proxy.entry_count].uri = uri; + reverse_proxy.entry_count++; + return 1; +} + static ssize_t cmdline_read_user(char *arg, unsigned char **buf, size_t maxlen) { size_t len = strnlen(arg, maxlen); @@ -2853,7 +2346,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); while ((opt = getopt(argc, argv, - "a:b:c:d:eg:h:i:j:k:l:mnp:q:rs:tu:v:w:A:C:E:G:J:L:M:NP:R:S:T:U:V:X:2")) != -1) { + "a:b:c:d:ef:g:h:i:j:k:l:mnp:q:rs:tu:v:w:A:C:E:G:J:L:M:NP:R:S:T:U:V:X:2")) != -1) { switch (opt) { #ifndef _WIN32 case 'a': @@ -2888,6 +2381,18 @@ main(int argc, char **argv) { goto failed; } break; + case 'f': +#if SERVER_CAN_PROXY + if (!cmdline_reverse_proxy(optarg)) { + fprintf(stderr, "Reverse Proxy error specifying upstream address\n"); + goto failed; + } + block_mode |= COAP_BLOCK_SINGLE_BODY; +#else /* ! SERVER_CAN_PROXY */ + fprintf(stderr, "Reverse Proxy support not available as no Client mode code\n"); + goto failed; +#endif /* ! SERVER_CAN_PROXY */ + break; case 'g' : group = optarg; break; @@ -3073,6 +2578,14 @@ main(int argc, char **argv) { if (get_oscore_conf(ctx) == NULL) goto failed; } +#if SERVER_CAN_PROXY + if (reverse_proxy.entry_count) { + proxy_dtls_setup(ctx, &reverse_proxy); + } + if (forward_proxy.entry_count) { + proxy_dtls_setup(ctx, &forward_proxy); + } +#endif /* SERVER_CAN_PROXY */ if (extended_token_size > COAP_TOKEN_DEFAULT_MAX) coap_context_set_max_token_size(ctx, extended_token_size); @@ -3227,13 +2740,8 @@ main(int argc, char **argv) { free(dynamic_entry); release_resource_data(NULL, example_data_value); #if SERVER_CAN_PROXY - for (i = 0; i < proxy_list_count; i++) { - coap_delete_binary(proxy_list[i].token); - coap_delete_string(proxy_list[i].query); - } - free(proxy_list); - proxy_list = NULL; - proxy_list_count = 0; + free(reverse_proxy.entry); + free(forward_proxy.entry); #if defined(_WIN32) && !defined(__MINGW32__) #pragma warning( disable : 4090 ) #endif diff --git a/examples/contiki/Makefile b/examples/contiki/Makefile index a042d39741..949f3355cc 100644 --- a/examples/contiki/Makefile +++ b/examples/contiki/Makefile @@ -31,3 +31,9 @@ server: $(CONTIKI) clean: $(MAKE) -f Makefile.contiki CONTIKI=$(CONTIKI) TARGET=$(TARGET) clean rm -rf build + +distclean: + $(MAKE) -f Makefile.contiki CONTIKI=$(CONTIKI) TARGET=$(TARGET) distclean + +viewconf: + $(MAKE) -f Makefile.contiki CONTIKI=$(CONTIKI) TARGET=$(TARGET) viewconf diff --git a/examples/lwip/Makefile b/examples/lwip/Makefile index 9aa420e1eb..3eeaec3b67 100644 --- a/examples/lwip/Makefile +++ b/examples/lwip/Makefile @@ -148,6 +148,7 @@ COAP_SRC = coap_address.c \ coap_oscore.c \ coap_pdu.c \ coap_prng.c \ + coap_proxy.c \ coap_resource.c \ coap_session.c \ coap_sha1.c \ diff --git a/examples/riot/pkg_libcoap/Makefile.libcoap b/examples/riot/pkg_libcoap/Makefile.libcoap index e560ef9e21..ca00aa5b6b 100644 --- a/examples/riot/pkg_libcoap/Makefile.libcoap +++ b/examples/riot/pkg_libcoap/Makefile.libcoap @@ -24,6 +24,7 @@ SRC := coap_address.c \ coap_oscore.c \ coap_pdu.c \ coap_prng.c \ + coap_proxy.c \ coap_resource.c \ coap_session.c \ coap_sha1.c \ diff --git a/include/coap3/coap.h.in b/include/coap3/coap.h.in index f67d1b5dea..da87ee644b 100644 --- a/include/coap3/coap.h.in +++ b/include/coap3/coap.h.in @@ -58,6 +58,7 @@ extern "C" { #include "coap@LIBCOAP_API_VERSION@/coap_oscore.h" #include "coap@LIBCOAP_API_VERSION@/coap_pdu.h" #include "coap@LIBCOAP_API_VERSION@/coap_prng.h" +#include "coap@LIBCOAP_API_VERSION@/coap_proxy.h" #include "coap@LIBCOAP_API_VERSION@/coap_resource.h" #include "coap@LIBCOAP_API_VERSION@/coap_str.h" #include "coap@LIBCOAP_API_VERSION@/coap_subscribe.h" diff --git a/include/coap3/coap.h.riot b/include/coap3/coap.h.riot index 1b08398619..de5cdc9f38 100644 --- a/include/coap3/coap.h.riot +++ b/include/coap3/coap.h.riot @@ -58,6 +58,7 @@ extern "C" { #include "coap_oscore.h" #include "coap_pdu.h" #include "coap_prng.h" +#include "coap_proxy.h" #include "coap_resource.h" #include "coap_str.h" #include "coap_subscribe.h" diff --git a/include/coap3/coap.h.riot.in b/include/coap3/coap.h.riot.in index ebdde9e868..089ed4e5f9 100644 --- a/include/coap3/coap.h.riot.in +++ b/include/coap3/coap.h.riot.in @@ -58,6 +58,7 @@ extern "C" { #include "coap_oscore.h" #include "coap_pdu.h" #include "coap_prng.h" +#include "coap_proxy.h" #include "coap_resource.h" #include "coap_str.h" #include "coap_subscribe.h" diff --git a/include/coap3/coap.h.windows b/include/coap3/coap.h.windows index f0cf1eb9b3..eef6a77a89 100644 --- a/include/coap3/coap.h.windows +++ b/include/coap3/coap.h.windows @@ -58,6 +58,7 @@ extern "C" { #include "coap3/coap_oscore.h" #include "coap3/coap_pdu.h" #include "coap3/coap_prng.h" +#include "coap3/coap_proxy.h" #include "coap3/coap_resource.h" #include "coap3/coap_str.h" #include "coap3/coap_subscribe.h" diff --git a/include/coap3/coap.h.windows.in b/include/coap3/coap.h.windows.in index 415b79236f..ffe7ca924a 100644 --- a/include/coap3/coap.h.windows.in +++ b/include/coap3/coap.h.windows.in @@ -58,6 +58,7 @@ extern "C" { #include "coap@LIBCOAP_API_VERSION@/coap_oscore.h" #include "coap@LIBCOAP_API_VERSION@/coap_pdu.h" #include "coap@LIBCOAP_API_VERSION@/coap_prng.h" +#include "coap@LIBCOAP_API_VERSION@/coap_proxy.h" #include "coap@LIBCOAP_API_VERSION@/coap_resource.h" #include "coap@LIBCOAP_API_VERSION@/coap_str.h" #include "coap@LIBCOAP_API_VERSION@/coap_subscribe.h" diff --git a/include/coap3/coap_forward_decls.h b/include/coap3/coap_forward_decls.h index 9636d7bbf3..a489507334 100644 --- a/include/coap3/coap_forward_decls.h +++ b/include/coap3/coap_forward_decls.h @@ -89,6 +89,14 @@ typedef struct coap_oscore_conf_t coap_oscore_conf_t; */ typedef struct coap_pdu_t coap_pdu_t; +/* ************* coap_proxy_internal.h ***************** */ + +/** + * Proxy information. + */ +typedef struct coap_proxy_list_t coap_proxy_list_t; + + /* ************* coap_resource_internal.h ***************** */ /* diff --git a/include/coap3/coap_internal.h b/include/coap3/coap_internal.h index 949fc9f5c8..641ad1313f 100644 --- a/include/coap3/coap_internal.h +++ b/include/coap3/coap_internal.h @@ -117,6 +117,7 @@ typedef struct oscore_ctx_t oscore_ctx_t; #endif /* COAP_OSCORE_SUPPORT */ #include "coap_pdu_internal.h" #include "coap_prng_internal.h" +#include "coap_proxy_internal.h" #include "coap_resource_internal.h" #include "coap_session_internal.h" #include "coap_sha1_internal.h" diff --git a/include/coap3/coap_mem.h b/include/coap3/coap_mem.h index bdc22f61bf..1d72a6ded2 100644 --- a/include/coap3/coap_mem.h +++ b/include/coap3/coap_mem.h @@ -18,6 +18,7 @@ #define COAP_MEM_H_ #include "coap3/coap_oscore.h" +#include "coap3/coap_proxy.h" #include #ifndef WITH_LWIP diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index 920616ce05..de7188ab62 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -193,6 +193,10 @@ struct coap_context_t { resource */ uint8_t mcast_per_resource; /**< Mcast controlled on a per resource basis */ +#if COAP_CLIENT_SUPPORT + coap_proxy_list_t *proxy_list; /**< Set of active proxy sessions */ + size_t proxy_list_count; /**< Number of active proxy sessions */ +#endif /* COAP_CLIENT_SUPPORT */ #endif /* COAP_SERVER_SUPPORT */ #if COAP_CLIENT_SUPPORT uint8_t testing_cids; /**< Change client's source port every testing_cids */ diff --git a/include/coap3/coap_proxy.h b/include/coap3/coap_proxy.h new file mode 100644 index 0000000000..48809f2b8d --- /dev/null +++ b/include/coap3/coap_proxy.h @@ -0,0 +1,110 @@ +/* + * coap_proxy.h -- helper functions for proxy handling + * + * Copyright (C) 2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_proxy.h + * @brief Helper functions for proxy handling + */ + +#ifndef COAP_PROXY_H_ +#define COAP_PROXY_H_ + +/** + * @ingroup application_api + * @defgroup proxy Proxy + * API for Proxies + * @{ + */ + +typedef enum { + COAP_PROXY_REVERSE, /**< Act as a reverse proxy */ + COAP_PROXY_REVERSE_STRIP, /**< Act as a reverse proxy, strip out proxy options */ + COAP_PROXY_FORWARD, /**< Act as a forward proxy */ + COAP_PROXY_FORWARD_STRIP, /**< Act as a forward proxy, strip out proxy options */ + COAP_PROXY_DIRECT, /**< Act as a direct proxy */ + COAP_PROXY_DIRECT_STRIP, /**< Act as a direct proxy, strip out proxy options */ +} coap_proxy_t; + +typedef struct coap_proxy_server_t { + coap_uri_t uri; /**< host and port define the server, scheme method */ + coap_dtls_pki_t *dtls_pki; /**< PKI configuration to use if not NULL */ + coap_dtls_cpsk_t *dtls_cpsk; /**< PSK configuration to use if not NULL */ + coap_oscore_conf_t *oscore_conf; /**< OSCORE configuration if not NULL */ +} coap_proxy_server_t; + +typedef struct coap_proxy_server_list_t { + coap_proxy_server_t *entry; /**< Set of servers to connect to */ + size_t entry_count; /**< The number of servers */ + size_t next_entry; /**< Next server to us (% entry_count) */ + coap_proxy_t type; /**< The proxy type */ + int track_client_session; /**< If 1, track individual connections to upstream + server, else 0 */ + unsigned int idle_timeout_secs; /**< Proxy session idle timeout (0 is no timeout) */ +} coap_proxy_server_list_t; + +/** + * Verify that the CoAP Scheme is supported for an ongoing proxy connection. + * + * @param scheme The CoAP scheme to check. + * + * @return @c 1 if supported, or @c 0 if not supported. + */ +int coap_verify_proxy_scheme_supported(coap_uri_scheme_t scheme); + +/** + * Forward incoming request upstream to the next proxy/server. + * + * Possible scenarios: + * Acting as a reverse proxy - connect to internal server + * (possibly round robin load balancing over multiple servers). + * Acting as a forward proxy - connect to host defined in Proxy-Uri + * or Proxy-Scheme with Uri-Host (and maybe Uri-Port). + * Acting as a relay proxy - connect to defined upstream server + * (possibly round robin load balancing over multiple servers). + * + * A request that should go direct to this server is not supported here. + * + * @param session The client session. + * @param request The client's request PDU. + * @param response The response PDU that will get sent back to the client. + * @param resource The resource associated with this request. + * @param cache_key A cache key generated from the request PDU or NULL. + * @param server_list The upstream server list to connect to. + * + * @return @c 1 if success, or @c 0 if failure (@p response code set to + * appropriate value). + */ +int COAP_API coap_proxy_forward_request(coap_session_t *session, + const coap_pdu_t *request, + coap_pdu_t *response, + coap_resource_t *resource, + coap_cache_key_t *cache_key, + coap_proxy_server_list_t *server_list); + +/** + * Forward the returning response back to the appropriate client. + * + * @param session The session handling the response. + * @param received The received PDU. + * @param cache_key Updated with the cache key pointer provided to + * coap_proxy_forward_request(). The caller should + * delete this cach key (unless the client request set up an + * Observe and there will be unsolicited responses). + * + * @return One of COAP_RESPONSE_FAIL or COAP_RESPONSE_OK. + */ +coap_response_t COAP_API coap_proxy_forward_response(coap_session_t *session, + const coap_pdu_t *received, + coap_cache_key_t **cache_key); + +/** @} */ + +#endif /* COAP_PROXY_H_ */ diff --git a/include/coap3/coap_proxy_internal.h b/include/coap3/coap_proxy_internal.h new file mode 100644 index 0000000000..830363c9e8 --- /dev/null +++ b/include/coap3/coap_proxy_internal.h @@ -0,0 +1,121 @@ +/* + * coap_proxy_internal.h -- Proxy functions for libcoap + * + * Copyright (C) 2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_proxy_internal.h + * @brief CoAP Proxy internal information + */ + +#ifndef COAP_PROXY_INTERNAL_H_ +#define COAP_PROXY_INTERNAL_H_ + +#include "coap_internal.h" + +/** + * @ingroup internal_api + * @defgroup Proxy Support + * Internal API for handling CoAP proxies + * @{ + */ + +typedef struct coap_proxy_req_t { + coap_pdu_t *pdu; + coap_resource_t *resource; + coap_session_t *incoming; + coap_bin_const_t *token_used; + coap_cache_key_t *cache_key; +} coap_proxy_req_t; + +struct coap_proxy_list_t { + coap_session_t *ongoing; /**< Ongoing session */ + coap_session_t *incoming; /**< Incoming session (used if client tracking( */ + coap_proxy_req_t *req_list; /**< Incoming list of request info */ + size_t req_count; /**< Count of incoming request info */ + coap_uri_t uri; /**< URI info for connection */ + coap_tick_t idle_timeout_ticks; /**< Idle timeout (0 == no timeout) */ + coap_tick_t last_used; /**< Last time entry was used */ +}; + +/** + * Close down proxy tracking, releasing any memory used. + * + * @param context The current CoAP context. + */ +void coap_proxy_cleanup(coap_context_t *context); + +/** + * Idle timeout inactive proxy sessions as well as return in @p tim_rem the time + * to remaining to timeout the inactive proxy. + * + * @param context Context to check against. + * @param now Current time in ticks. + * @param tim_rem Where to update timeout time to the next expiry. + * + * @return Return 1 if there is a future expire time, else 0. + */ +int coap_proxy_check_timeouts(coap_context_t *context, coap_tick_t now, + coap_tick_t *tim_rem); + +void coap_proxy_remove_association(coap_session_t *session, int send_failure); + +/** + * Forward incoming request upstream to the next proxy/server. + * + * Possible scenarios: + * Acting as a reverse proxy - connect to internal server + * (possibly round robin load balancing over multiple servers). + * Acting as a forward proxy - connect to host defined in Proxy-Uri + * or Proxy-Scheme with Uri-Host (and maybe Uri-Port). + * Acting as a relay proxy - connect to defined upstream server + * (possibly round robin load balancing over multiple servers). + * + * A request that should go direct to this server is not supported here. + * + * Note: This function must be called in the locked state, + * + * @param session The client session. + * @param request The client's request PDU. + * @param response The response PDU that will get sent back to the client. + * @param resource The resource associated with this request. + * @param cache_key A cache key generated from the request PDU or NULL. + * @param server_list The upstream server list to connect to. + * + * @return @c 1 if success, or @c 0 if failure (@p response code set to + * appropriate value). + */ +int coap_proxy_forward_request_lkd(coap_session_t *session, + const coap_pdu_t *request, + coap_pdu_t *response, + coap_resource_t *resource, + coap_cache_key_t *cache_key, + coap_proxy_server_list_t *server_list); + +/** + * Forward the returning response back to the appropriate client. + * + * Note: This function must be called in the locked state, + * + * @param session The session handling the response. + * @param received The received PDU. + * @param cache_key Updated with the cache key pointer provided to + * coap_proxy_forward_request_lkd(). The caller should + * delete this cach key (unless the client request set up an + * Observe and there will be unsolicited responses). + * + * @return One of COAP_RESPONSE_FAIL or COAP_RESPONSE_OK. + */ +coap_response_t coap_proxy_forward_response_lkd(coap_session_t *session, + const coap_pdu_t *received, + coap_cache_key_t **cache_key); + +/** @} */ + +#endif /* COAP_PROXY_INTERNAL_H_ */ diff --git a/libcoap-3.map b/libcoap-3.map index 7d34f4f192..70a653b7e1 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -195,6 +195,8 @@ global: coap_print_wellknown; coap_prng; coap_prng_init; + coap_proxy_forward_request; + coap_proxy_forward_response; coap_q_block_is_supported; coap_query_into_optlist; coap_realloc_type; @@ -300,6 +302,7 @@ global: coap_tls_is_supported; coap_uri_into_options; coap_uri_into_optlist; + coap_verify_proxy_scheme_supported; coap_write_block_b_opt; coap_write_block_opt; coap_ws_is_supported; diff --git a/libcoap-3.sym b/libcoap-3.sym index 9bf070e68a..4eff88bbce 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -193,6 +193,8 @@ coap_print_link coap_print_wellknown coap_prng coap_prng_init +coap_proxy_forward_request +coap_proxy_forward_response coap_q_block_is_supported coap_query_into_optlist coap_realloc_type @@ -298,6 +300,7 @@ coap_tls_engine_remove coap_tls_is_supported coap_uri_into_options coap_uri_into_optlist +coap_verify_proxy_scheme_supported coap_write_block_b_opt coap_write_block_opt coap_ws_is_supported diff --git a/man/Makefile.am b/man/Makefile.am index 7c6840b9ae..f638c5da54 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -40,6 +40,7 @@ TXT3 = coap_address.txt \ coap_pdu_access.txt \ coap_pdu_setup.txt \ coap_persist.txt \ + coap_proxy.txt \ coap_recovery.txt \ coap_resource.txt \ coap_session.txt \ diff --git a/man/coap-server.txt.in b/man/coap-server.txt.in index 13284f107a..4b420d12cc 100644 --- a/man/coap-server.txt.in +++ b/man/coap-server.txt.in @@ -20,6 +20,7 @@ coap-server-notls SYNOPSIS -------- *coap-server* [*-a* priority] [*-b* max_block_size] [*-d* max] [*-e*] + [*-f* scheme://addr[:port] [*-g* group] [*-l* loss] [*-p* port] [*-q* tls_engine_conf_file] [*-r*] [*-t*] [*-v* num] [*-w* [port][,secure_port]] [*-A* address] [*-E* oscore_conf_file[,seq_file]] @@ -59,6 +60,14 @@ OPTIONS - General *-e* :: Echo back the data sent with a PUT. +*-f* scheme://address[:port]:: + Act as a reverse proxy where scheme, address, optional + port define how to connect to the internal server. + Scheme is one of coap, coaps, coap+tcp, coaps+tcp, + coap+ws, and coaps+ws. http(s) is not currently supported. + This option can be repeated to provide multiple internal + servers that are round-robin load balanced. + *-g* group:: Join specified multicast 'group' on start up. *Note:* DTLS over multicast is not currently supported. @@ -140,6 +149,8 @@ OPTIONS - General first name, then the ongoing connection will be a direct connection. Scheme is one of coap, coaps, coap+tcp, coaps+tcp, coap+ws, and coaps+ws. http and https not currently supported. + This option can be repeated to provide multiple upstream + servers that are round-robin load balanced. *-T* max_token_size:: Set the maximum token length (8-65804). diff --git a/man/coap_block.txt.in b/man/coap_block.txt.in index bec6170e0a..3fa6af0bbb 100644 --- a/man/coap_block.txt.in +++ b/man/coap_block.txt.in @@ -249,7 +249,8 @@ transmitted. The callback function _release_func_ can be used to release storage that has been dynamically allocated to hold the transmit data. If not NULL, the callback function is called once the final block of _data_ has been transmitted. The user-defined parameter _app_ptr_ is the same value that was -passed to *coap_add_data_large_request*(). +passed to *coap_add_data_large_request*(). Even if there is an error return, +_release_func_ (if set) is always called. *NOTE:* This function must only be called once per _pdu_. @@ -271,7 +272,8 @@ transmitted. The callback function _release_func_ can be used to release storage that has been dynamically allocated to hold the transmit data. If not NULL, the callback function is called once the final block of _data_ has been transmitted. The user-defined parameter _app_ptr_ is the same value that was -passed to *coap_add_data_large_response*(). +passed to *coap_add_data_large_response*(). Even if there is an error return, +_release_func_ (if set) is always called. It also adds in the appropriate CoAP options such as Block2, Size2 and ETag to handle block-wise transfer if the data does not fit in a single PDU. diff --git a/man/coap_proxy.txt.in b/man/coap_proxy.txt.in new file mode 100644 index 0000000000..a83810fddf --- /dev/null +++ b/man/coap_proxy.txt.in @@ -0,0 +1,102 @@ +// -*- mode:doc; -*- +// vim: set syntax=asciidoc tw=0 + +coap_proxy(3) +============= +:doctype: manpage +:man source: coap_proxy +:man version: @PACKAGE_VERSION@ +:man manual: libcoap Manual + +NAME +---- +coap_proxy, +coap_proxy_forward_request, +coap_proxy_forward_response, +coap_verify_proxy_scheme_supported +- Work with CoAP proxies + +SYNOPSIS +-------- +*#include * + +*int coap_proxy_forward_request(coap_session_t *_session_, +const coap_pdu_t *_request_, coap_pdu_t *_response_, +coap_resource_t *_resource_, coap_cache_key_t *_cache_key_, +coap_proxy_server_list_t *_server_list_);* + +*coap_response_t coap_proxy_forward_response(coap_session_t *_session_, + const coap_pdu_t *received_, + coap_cache_key_t **_cache_key_);* + +*int coap_verify_proxy_scheme_supported(coap_uri_scheme_t _scheme_);* + +For specific (D)TLS library support, link with +*-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, +*-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls*, +*-lcoap-@LIBCOAP_API_VERSION@-wolfssl* +or *-lcoap-@LIBCOAP_API_VERSION@-tinydtls*. Otherwise, link with +*-lcoap-@LIBCOAP_API_VERSION@* to get the default (D)TLS library support. + +DESCRIPTION +----------- + +To simplify some of the CoAP proxy requirements, some of the functionaliy +is provided by libcoap. + +FUNCTIONS +--------- + +*Function: coap_proxy_forward_request()* + +The *coap_proxy_forward_request*() function is called from a request handler +when the request needs to be forwarded to an upstream server with a possible +change in protocol. + +*Function: coap_proxy_forward_response()* + +The *coap_proxy_forward_response*() function is used to cleanup / free any information set +up by the *coap_startup*() function and should be the last *coap_**() function +called. The only safe function that can be called after *coap_cleanup*() is +*coap_startup*() to re-initialize the libcoap logic. + +*NOTE:* Calling *coap_cleanup*() in one thread while continuing to use other +*coap_**() function calls in a different thread is not supported - even if they +are using a different coap_context_t. + +*NOTE:* All other libcoap cleanups should called prior to *coap_cleanup*(), e.g. +*coap_free_context*(3). + +*Function: coap_verify_proxy_scheme_supported()* + +The *coap_proxy_forward_request*() function is called from a request handler +when the request needs to be forwarded to an upstream server with a possible +change in protocol. + +RETURN VALUES +------------- +*coap_proxy_forward_request*() and *coap_verify_proxy_scheme_supported*() +return 1 on success and 0 on failure. + +*coap_proxy_forward_response*() returns one of COAP_RESPONSE_OK or +COAP_RESPONSE_FAIL. + +FURTHER INFORMATION +------------------- +See + +"https://rfc-editor.org/rfc/rfc7252[RFC7252: The Constrained Application Protocol (CoAP)]" + +for further information. + +BUGS +---- +Please raise an issue on GitHub at +https://github.com/obgm/libcoap/issues to report any bugs. + +Please raise a Pull Request at https://github.com/obgm/libcoap/pulls +for any fixes. + +AUTHORS +------- +The libcoap project diff --git a/man/examples-code-check.c b/man/examples-code-check.c index ea5987dd6e..bb1f469143 100644 --- a/man/examples-code-check.c +++ b/man/examples-code-check.c @@ -127,6 +127,7 @@ const char *number_list[] = { "coap_pdu_code_t ", "coap_print_status_t ", "coap_proto_t ", + "coap_response_t ", "coap_session_state_t ", "coap_session_type_t ", "int ", diff --git a/src/coap_address.c b/src/coap_address.c index 71ce05749b..6be5bc43af 100644 --- a/src/coap_address.c +++ b/src/coap_address.c @@ -292,7 +292,6 @@ coap_address_set_unix_domain(coap_address_t *addr, #endif /* ! COAP_AF_UNIX_SUPPORT */ } -#if !defined(WITH_CONTIKI) static void update_port(coap_address_t *addr, uint16_t port, uint16_t default_port, int update_port0) { @@ -492,7 +491,7 @@ coap_resolve_address_info(const coap_str_const_t *address, int ai_hints_flags, int scheme_hint_bits, coap_resolve_type_t type) { -#if !defined(RIOT_VERSION) +#if !defined(RIOT_VERSION) && !defined(WITH_CONTIKI) struct addrinfo *res, *ainfo; struct addrinfo hints; @@ -643,7 +642,9 @@ coap_resolve_address_info(const coap_str_const_t *address, freeaddrinfo(res); return info_list; -#else /* RIOT_VERSION */ + +#elif defined(RIOT_VERSION) + #include "net/utils.h" #if COAP_IPV6_SUPPORT ipv6_addr_t addr_ipv6; @@ -724,9 +725,72 @@ coap_resolve_address_info(const coap_str_const_t *address, } } return info_list; -#endif /* RIOT_VERSION */ + +#elif defined(WITH_CONTIKI) + +#include + uip_ipaddr_t *addr_ip; + coap_addr_info_t *info = NULL; + coap_addr_info_t *info_prev = NULL; + coap_addr_info_t *info_list = NULL; + coap_uri_scheme_t scheme; + int parsed_ip = 0; + uip_ipaddr_t all_zeros = { + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + } + }; + + (void)ai_hints_flags; + + if (address == NULL || address->length == 0) { + addr_ip = &all_zeros; + } else { +#if COAP_IPV6_SUPPORT + if (uiplib_ip6addrconv((const char *)address->s, (uip_ip6addr_t *)&addr_ip) > 0) { + parsed_ip = 1; + } +#endif /* COAP_IPV6_SUPPORT */ +#if COAP_IPV4_SUPPORT + if (family == AF_UNSPEC && + uiplib_ip4addrconv((const char *)address->s, (uip_ip4addr_t *)&addr_ip) > 0) { + parsed_ip = 1; + } +#endif /* COAP_IPV4_SUPPORT */ + if (!parsed_ip) { + coap_log_err("coap_resolve_address_info: Unable to parse '%s'\n", address->s); + return NULL; + } + } + for (scheme = 0; scheme < COAP_URI_SCHEME_LAST; scheme++) { + if (scheme_hint_bits & (1 << scheme)) { + info = get_coap_addr_info(scheme); + if (info == NULL) { + continue; + } + + /* Need to return in same order as getaddrinfo() */ + if (!info_prev) { + info_list = info; + info_prev = info; + } else { + info_prev->next = info; + info_prev = info; + } + + memcpy(&info->addr.addr, &addr_ip, sizeof(info->addr.addr)); + + update_coap_addr_port(scheme, info, port, secure_port, ws_port, + ws_secure_port, type); + } + } + return info_list; +#else +#bad OS type not supported + return NULL; +#endif } -#endif /* !WITH_CONTIKI */ void coap_free_address_info(coap_addr_info_t *info) { diff --git a/src/coap_gnutls.c b/src/coap_gnutls.c index 816ede38d9..3a1c3021f2 100644 --- a/src/coap_gnutls.c +++ b/src/coap_gnutls.c @@ -2328,6 +2328,8 @@ coap_dtls_send(coap_session_t *c_session, assert(g_env != NULL); c_session->dtls_event = -1; + coap_log_debug("* %s: dtls: sent %4d bytes\n", + coap_session_str(c_session), (int)data_len); if (g_env->established) { ret = gnutls_record_send(g_env->g_session, data, data_len); @@ -2372,14 +2374,6 @@ coap_dtls_send(coap_session_t *c_session, } } - if (ret > 0) { - if (ret == (ssize_t)data_len) - coap_log_debug("* %s: dtls: sent %4d bytes\n", - coap_session_str(c_session), ret); - else - coap_log_debug("* %s: dtls: sent %4d of %4zd bytes\n", - coap_session_str(c_session), ret, data_len); - } return ret; } diff --git a/src/coap_io.c b/src/coap_io.c index 0ca9e096b3..3b02bae266 100644 --- a/src/coap_io.c +++ b/src/coap_io.c @@ -1376,6 +1376,12 @@ coap_io_prepare_io_lkd(coap_context_t *ctx, #endif /* COAP_SERVER_SUPPORT */ } } +#if COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT + if (coap_proxy_check_timeouts(ctx, now, &s_timeout)) { + if (timeout == 0 || s_timeout < timeout) + timeout = s_timeout; + } +#endif /* COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT */ #if COAP_SERVER_SUPPORT coap_endpoint_t *ep; coap_tick_t session_timeout; diff --git a/src/coap_net.c b/src/coap_net.c index 5827a023ca..850ac95784 100644 --- a/src/coap_net.c +++ b/src/coap_net.c @@ -769,6 +769,9 @@ coap_free_context_lkd(coap_context_t *context) { #if COAP_WITH_OBSERVE_PERSIST coap_persist_cleanup(context); #endif /* COAP_WITH_OBSERVE_PERSIST */ +#if COAP_CLIENT_SUPPORT + coap_proxy_cleanup(context); +#endif /* COAP_CLIENT_SUPPORT */ #endif /* COAP_SERVER_SUPPORT */ coap_free_type(COAP_CONTEXT, context); @@ -4296,6 +4299,10 @@ coap_handle_event_lkd(coap_context_t *context, coap_event_t event, int ret; coap_lock_callback_ret(ret, context, context->handle_event(session, event)); +#if COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT + if (event == COAP_EVENT_SERVER_SESSION_DEL) + coap_proxy_remove_association(session, 0); +#endif /* COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT */ return ret; } return 0; diff --git a/src/coap_proxy.c b/src/coap_proxy.c new file mode 100644 index 0000000000..3d591d8021 --- /dev/null +++ b/src/coap_proxy.c @@ -0,0 +1,863 @@ +/* coap_proxy.c -- helper functions for proxy handling + * + * Copyright (C) 2024 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +/** + * @file coap_proxy.c + * @brief Proxy handling functions + */ + +#include "coap3/coap_libcoap_build.h" + +#if COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT +#include + +#ifdef _WIN32 +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#endif + +void +coap_proxy_cleanup(coap_context_t *context) { + size_t i; + size_t j; + + for (i = 0; i < context->proxy_list_count; i++) { + for (j = 0; j < context->proxy_list[i].req_count; j++) { + coap_delete_pdu(context->proxy_list[i].req_list[j].pdu); + coap_delete_cache_key(context->proxy_list[i].req_list[j].cache_key); + } + coap_free_type(COAP_STRING, context->proxy_list[i].req_list); + } + coap_free_type(COAP_STRING, context->proxy_list); +} + +/* + * return 1 if there is a future expire time, else 0. + * update tim_rem with remaining value if return is 1. + */ +int +coap_proxy_check_timeouts(coap_context_t *context, coap_tick_t now, + coap_tick_t *tim_rem) { + size_t i; + int ret = 0; + + *tim_rem = -1; + for (i = 0; i < context->proxy_list_count; i++) { + coap_proxy_list_t *proxy_list = &context->proxy_list[i]; + + if (proxy_list->ongoing && proxy_list->idle_timeout_ticks) { + if (proxy_list->last_used + proxy_list->idle_timeout_ticks <= now) { + /* Drop session to upstream server */ + coap_session_release_lkd(proxy_list->ongoing); + proxy_list->ongoing = NULL; + } else { + if (*tim_rem > proxy_list->last_used + proxy_list->idle_timeout_ticks - now) { + *tim_rem = proxy_list->last_used + proxy_list->idle_timeout_ticks - now; + } + ret = 1; + } + } + } + return ret; +} + +static int +coap_get_uri_proxy_scheme_info(const coap_pdu_t *request, + coap_opt_t *opt, + coap_uri_t *uri, + coap_string_t **uri_path, + coap_string_t **uri_query) { + + const char *opt_val = (const char *)coap_opt_value(opt); + int opt_len = coap_opt_length(opt); + coap_opt_iterator_t opt_iter; + + if (opt_len == 9 && + strncasecmp(opt_val, "coaps+tcp", 9) == 0) { + uri->scheme = COAP_URI_SCHEME_COAPS_TCP; + uri->port = COAPS_DEFAULT_PORT; + } else if (opt_len == 8 && + strncasecmp(opt_val, "coap+tcp", 8) == 0) { + uri->scheme = COAP_URI_SCHEME_COAP_TCP; + uri->port = COAP_DEFAULT_PORT; + } else if (opt_len == 5 && + strncasecmp(opt_val, "coaps", 5) == 0) { + uri->scheme = COAP_URI_SCHEME_COAPS; + uri->port = COAPS_DEFAULT_PORT; + } else if (opt_len == 4 && + strncasecmp(opt_val, "coap", 4) == 0) { + uri->scheme = COAP_URI_SCHEME_COAP; + uri->port = COAP_DEFAULT_PORT; + } else if (opt_len == 7 && + strncasecmp(opt_val, "coap+ws", 7) == 0) { + uri->scheme = COAP_URI_SCHEME_COAP_WS; + uri->port = 80; + } else if (opt_len == 8 && + strncasecmp(opt_val, "coaps+ws", 8) == 0) { + uri->scheme = COAP_URI_SCHEME_COAPS_WS; + uri->port = 443; + } else { + coap_log_warn("Unsupported Proxy Scheme '%*.*s'\n", + opt_len, opt_len, opt_val); + return 0; + } + + 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_warn("Proxy Scheme requires Uri-Host\n"); + return 0; + } + 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; + } else { + uri->path.s = NULL; + uri->path.length = 0; + } + *uri_query = coap_get_query(request); + if (*uri_query) { + uri->query.s = (*uri_query)->s; + uri->query.length = (*uri_query)->length; + } else { + uri->query.s = NULL; + uri->query.length = 0; + } + return 1; +} + +int +coap_verify_proxy_scheme_supported(coap_uri_scheme_t scheme) { + + /* Sanity check that the connection can be forwarded on */ + switch (scheme) { + case COAP_URI_SCHEME_HTTP: + case COAP_URI_SCHEME_HTTPS: + coap_log_warn("Proxy URI http or https not supported\n"); + return 0; + case COAP_URI_SCHEME_COAP: + break; + case COAP_URI_SCHEME_COAPS: + if (!coap_dtls_is_supported()) { + coap_log_warn("coaps URI scheme not supported for proxy\n"); + return 0; + } + break; + case COAP_URI_SCHEME_COAP_TCP: + if (!coap_tcp_is_supported()) { + coap_log_warn("coap+tcp URI scheme not supported for proxy\n"); + return 0; + } + break; + case COAP_URI_SCHEME_COAPS_TCP: + if (!coap_tls_is_supported()) { + coap_log_warn("coaps+tcp URI scheme not supported for proxy\n"); + return 0; + } + break; + case COAP_URI_SCHEME_COAP_WS: + if (!coap_ws_is_supported()) { + coap_log_warn("coap+ws URI scheme not supported for proxy\n"); + return 0; + } + break; + case COAP_URI_SCHEME_COAPS_WS: + if (!coap_wss_is_supported()) { + coap_log_warn("coaps+ws URI scheme not supported for proxy\n"); + return 0; + } + break; + case COAP_URI_SCHEME_LAST: + default: + coap_log_warn("%d URI scheme not supported\n", scheme); + return 0; + } + return 1; +} + +static coap_proxy_list_t * +coap_proxy_get_session(coap_session_t *session, const coap_pdu_t *request, + coap_pdu_t *response, + coap_proxy_server_list_t *server_list, + coap_proxy_server_t *server_use) { + size_t i; + coap_proxy_list_t *new_proxy_list; + coap_proxy_list_t *proxy_list = session->context->proxy_list; + size_t proxy_list_count = session->context->proxy_list_count; + + coap_opt_iterator_t opt_iter; + coap_opt_t *proxy_scheme; + coap_opt_t *proxy_uri; + coap_string_t *uri_path = NULL; + coap_string_t *uri_query = NULL; + + /* Round robin the defined next server list (which usually is just one */ + server_list->next_entry++; + if (server_list->next_entry >= server_list->entry_count) + server_list->next_entry = 0; + + memcpy(server_use, &server_list->entry[server_list->next_entry], sizeof(*server_use)); + + switch (server_list->type) { + case COAP_PROXY_REVERSE: + case COAP_PROXY_FORWARD: + case COAP_PROXY_DIRECT: + /* Nothing else needs to be done */ + break; + case COAP_PROXY_REVERSE_STRIP: + case COAP_PROXY_FORWARD_STRIP: + case COAP_PROXY_DIRECT_STRIP: + /* Need to get actual server from CoAP options */ + /* + * See if Proxy-Scheme + */ + proxy_scheme = coap_check_option(request, COAP_OPTION_PROXY_SCHEME, &opt_iter); + if (proxy_scheme) { + if (!coap_get_uri_proxy_scheme_info(request, proxy_scheme, &server_use->uri, &uri_path, + &uri_query)) { + response->code = COAP_RESPONSE_CODE(505); + return NULL; + } + } + /* + * See if Proxy-Uri + */ + proxy_uri = coap_check_option(request, COAP_OPTION_PROXY_URI, &opt_iter); + if (proxy_uri) { + coap_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), + &server_use->uri) < 0) { + /* Need to return a 5.05 RFC7252 Section 5.7.2 */ + coap_log_warn("Proxy URI not decodable\n"); + response->code = COAP_RESPONSE_CODE(505); + return NULL; + } + } + + if (!(proxy_scheme || proxy_uri)) { + response->code = COAP_RESPONSE_CODE(404); + return NULL; + } + + if (server_use->uri.host.length == 0) { + /* Ongoing connection not well formed */ + response->code = COAP_RESPONSE_CODE(505); + return NULL; + } + + if (!coap_verify_proxy_scheme_supported(server_use->uri.scheme)) { + response->code = COAP_RESPONSE_CODE(505); + return NULL; + } + break; + default: + assert(0); + return NULL; + } + + /* See if we are already connected to the Server */ + for (i = 0; i < proxy_list_count; i++) { + if (coap_string_equal(&proxy_list[i].uri.host, &server_use->uri.host) && + proxy_list[i].uri.port == server_use->uri.port && + proxy_list[i].uri.scheme == server_use->uri.scheme) { + if (!server_list->track_client_session) { + coap_ticks(&proxy_list[i].last_used); + return &proxy_list[i]; + } else { + if (proxy_list[i].incoming == session) { + coap_ticks(&proxy_list[i].last_used); + return &proxy_list[i]; + } + } + } + } + + /* Need to create a new forwarding mapping */ + new_proxy_list = coap_realloc_type(COAP_STRING, proxy_list, (i+1)*sizeof(proxy_list[0])); + + if (new_proxy_list == NULL) { + response->code = COAP_RESPONSE_CODE(500); + return NULL; + } + session->context->proxy_list = proxy_list = new_proxy_list; + memset(&proxy_list[i], 0, sizeof(proxy_list[i])); + + proxy_list[i].uri = server_use->uri; + if (server_list->track_client_session) { + proxy_list[i].incoming = session; + } + session->context->proxy_list_count++; + proxy_list[i].idle_timeout_ticks = server_list->idle_timeout_secs * COAP_TICKS_PER_SECOND; + coap_ticks(&proxy_list[i].last_used); + return &proxy_list[i]; +} + +void +coap_proxy_remove_association(coap_session_t *session, int send_failure) { + + size_t i; + size_t j; + coap_proxy_list_t *proxy_list = session->context->proxy_list; + size_t proxy_list_count = session->context->proxy_list_count; + + for (i = 0; i < proxy_list_count; i++) { + /* Check for incoming match */ + for (j = 0; j < proxy_list[i].req_count; j++) { + if (proxy_list[i].req_list[j].incoming == session) { + coap_delete_pdu(proxy_list[i].req_list[j].pdu); + coap_delete_bin_const(proxy_list[i].req_list[j].token_used); + coap_delete_cache_key(proxy_list[i].req_list[j].cache_key); + if (proxy_list[i].req_count-j > 1) { + memmove(&proxy_list[i].req_list[j], &proxy_list[i].req_list[j+1], + (proxy_list[i].req_count-j-1) * sizeof(proxy_list[i].req_list[0])); + } + proxy_list[i].req_count--; + break; + } + } + if (proxy_list[i].incoming == session) { + /* Only if there is a one-to-one tracking */ + coap_session_release_lkd(proxy_list[i].ongoing); + break; + } + + /* Check for outgoing match */ + if (proxy_list[i].ongoing == session) { + coap_session_t *ongoing; + + for (j = 0; j < proxy_list[i].req_count; j++) { + if (send_failure) { + coap_pdu_t *response; + coap_bin_const_t l_token; + + /* Need to send back a gateway failure */ + response = coap_pdu_init(proxy_list[i].req_list[j].pdu->type, + COAP_RESPONSE_CODE(502), + coap_new_message_id_lkd(proxy_list[i].incoming), + coap_session_max_pdu_size_lkd(proxy_list[i].incoming)); + if (!response) { + coap_log_info("PDU creation issue\n"); + goto cleanup; + } + + l_token = coap_pdu_get_token(proxy_list[i].req_list[j].pdu); + if (!coap_add_token(response, l_token.length, + l_token.s)) { + coap_log_debug("Cannot add token to incoming proxy response PDU\n"); + } + + if (coap_send_lkd(proxy_list[i].incoming, response) == COAP_INVALID_MID) { + coap_log_info("Failed to send PDU with 5.02 gateway issue\n"); + } +cleanup: + coap_delete_pdu(proxy_list[i].req_list[j].pdu); + coap_delete_bin_const(proxy_list[i].req_list[j].token_used); + coap_delete_cache_key(proxy_list[i].req_list[j].cache_key); + } + } + ongoing = proxy_list[i].ongoing; + coap_free_type(COAP_STRING, proxy_list[i].req_list); + if (proxy_list_count-i > 1) { + memmove(&proxy_list[i], + &proxy_list[i+1], + (proxy_list_count-i-1) * sizeof(proxy_list[0])); + } + session->context->proxy_list_count--; + coap_session_release_lkd(ongoing); + break; + } + } +} + +static coap_proxy_list_t * +coap_proxy_get_ongoing_session(coap_session_t *session, + const coap_pdu_t *request, + coap_pdu_t *response, + coap_proxy_server_list_t *server_list, + coap_proxy_server_t *server_use) { + + coap_address_t dst; + coap_proto_t proto; + coap_addr_info_t *info_list = NULL; + coap_proxy_list_t *proxy_entry; + coap_context_t *context = session->context; + static char client_sni[256]; + + proxy_entry = coap_proxy_get_session(session, request, response, server_list, server_use); + if (!proxy_entry) { + /* Response code should be set */ + return NULL; + } + + if (!proxy_entry->ongoing) { + /* Need to create a new session */ + + /* resolve destination address where data should be sent */ + info_list = coap_resolve_address_info(&server_use->uri.host, + server_use->uri.port, + server_use->uri.port, + server_use->uri.port, + server_use->uri.port, + 0, + 1 << server_use->uri.scheme, + COAP_RESOLVE_TYPE_REMOTE); + + if (info_list == NULL) { + response->code = COAP_RESPONSE_CODE(502); + coap_proxy_remove_association(session, 0); + return NULL; + } + proto = info_list->proto; + memcpy(&dst, &info_list->addr, sizeof(dst)); + coap_free_address_info(info_list); + + snprintf(client_sni, sizeof(client_sni), "%*.*s", (int)server_use->uri.host.length, + (int)server_use->uri.host.length, server_use->uri.host.s); + + switch (server_use->uri.scheme) { + case COAP_URI_SCHEME_COAP: + case COAP_URI_SCHEME_COAP_TCP: + case COAP_URI_SCHEME_COAP_WS: +#if COAP_OSCORE_SUPPORT + if (server_use->oscore_conf) { + proxy_entry->ongoing = + coap_new_client_session_oscore_lkd(context, NULL, &dst, + proto, server_use->oscore_conf); + } else { +#endif /* COAP_OSCORE_SUPPORT */ + proxy_entry->ongoing = + coap_new_client_session_lkd(context, NULL, &dst, proto); +#if COAP_OSCORE_SUPPORT + } +#endif /* COAP_OSCORE_SUPPORT */ + break; + case COAP_URI_SCHEME_COAPS: + case COAP_URI_SCHEME_COAPS_TCP: + case COAP_URI_SCHEME_COAPS_WS: +#if COAP_OSCORE_SUPPORT + if (server_use->oscore_conf) { + if (server_use->dtls_pki) { + server_use->dtls_pki->client_sni = client_sni; + proxy_entry->ongoing = + coap_new_client_session_oscore_pki_lkd(context, NULL, &dst, + proto, server_use->dtls_pki, server_use->oscore_conf); + } else if (server_use->dtls_pki) { + server_use->dtls_cpsk->client_sni = client_sni; + proxy_entry->ongoing = + coap_new_client_session_oscore_psk_lkd(context, NULL, &dst, + proto, server_use->dtls_cpsk, server_use->oscore_conf); + } else { + coap_log_warn("Proxy: (D)TLS not configured for secure session\n"); + } + } else { +#endif /* COAP_OSCORE_SUPPORT */ + /* Not doing OSCORE */ + if (server_use->dtls_pki) { + server_use->dtls_pki->client_sni = client_sni; + proxy_entry->ongoing = + coap_new_client_session_pki_lkd(context, NULL, &dst, + proto, server_use->dtls_pki); + } else if (server_use->dtls_cpsk) { + server_use->dtls_cpsk->client_sni = client_sni; + proxy_entry->ongoing = + coap_new_client_session_psk2_lkd(context, NULL, &dst, + proto, server_use->dtls_cpsk); + } else { + coap_log_warn("Proxy: (D)TLS not configured for secure session\n"); + } +#if COAP_OSCORE_SUPPORT + } +#endif /* COAP_OSCORE_SUPPORT */ + break; + case COAP_URI_SCHEME_HTTP: + case COAP_URI_SCHEME_HTTPS: + case COAP_URI_SCHEME_LAST: + default: + assert(0); + break; + } + if (proxy_entry->ongoing == NULL) { + response->code = COAP_RESPONSE_CODE(505); + coap_proxy_remove_association(session, 0); + return NULL; + } + } + + return proxy_entry; +} + +static void +coap_proxy_release_body_data(coap_session_t *session COAP_UNUSED, + void *app_ptr) { + coap_delete_binary(app_ptr); +} + +int COAP_API +coap_proxy_forward_request(coap_session_t *session, + const coap_pdu_t *request, + coap_pdu_t *response, + coap_resource_t *resource, + coap_cache_key_t *cache_key, + coap_proxy_server_list_t *server_list) { + int ret; + + coap_lock_lock(session->context, return 0); + ret = coap_proxy_forward_request_lkd(session, + request, + response, + resource, + cache_key, + server_list); + coap_lock_unlock(session->context); + return ret; +} + +int +coap_proxy_forward_request_lkd(coap_session_t *session, + const coap_pdu_t *request, + coap_pdu_t *response, + coap_resource_t *resource, + coap_cache_key_t *cache_key, + coap_proxy_server_list_t *server_list) { + coap_proxy_list_t *proxy_entry; + size_t size; + size_t offset; + size_t total; + coap_binary_t *body_data = NULL; + const uint8_t *data; + coap_pdu_t *pdu; + coap_bin_const_t r_token = coap_pdu_get_token(request); + uint8_t token[8]; + size_t token_len; + coap_proxy_req_t *new_req_list; + coap_optlist_t *optlist = NULL; + coap_opt_t *option; + coap_opt_iterator_t opt_iter; + coap_proxy_server_t server_use; + + /* Set up ongoing session (if not already done) */ + + proxy_entry = coap_proxy_get_ongoing_session(session, request, response, + server_list, &server_use); + if (!proxy_entry) + /* response code already set */ + return 0; + + /* Need to save the request pdu entry */ + new_req_list = coap_realloc_type(COAP_STRING, proxy_entry->req_list, + (proxy_entry->req_count + 1)*sizeof(coap_proxy_req_t)); + + if (new_req_list == NULL) { + goto failed; + } + proxy_entry->req_list = new_req_list; + /* Get a new token for ongoing session */ + coap_session_new_token(proxy_entry->ongoing, &token_len, token); + new_req_list[proxy_entry->req_count].token_used = coap_new_bin_const(token, token_len); + if (new_req_list[proxy_entry->req_count].token_used == NULL) { + goto failed; + } + new_req_list[proxy_entry->req_count].pdu = coap_pdu_duplicate_lkd(request, session, + r_token.length, r_token.s, NULL); + if (new_req_list[proxy_entry->req_count].pdu == NULL) { + coap_delete_bin_const(new_req_list[proxy_entry->req_count].token_used); + new_req_list[proxy_entry->req_count].token_used = NULL; + goto failed; + } + new_req_list[proxy_entry->req_count].resource = resource; + new_req_list[proxy_entry->req_count].incoming = session; + new_req_list[proxy_entry->req_count].cache_key = cache_key; + proxy_entry->req_count++; + + switch (server_list->type) { + case COAP_PROXY_REVERSE_STRIP: + case COAP_PROXY_FORWARD_STRIP: + case COAP_PROXY_DIRECT_STRIP: + /* + * Need to replace Proxy-Uri with Uri-Host (and Uri-Port) + * or strip out Proxy-Scheme. + */ + + /* + * Build up the ongoing PDU that we are going to send + */ + pdu = coap_pdu_init(request->type, request->code, + coap_new_message_id_lkd(proxy_entry->ongoing), + coap_session_max_pdu_size_lkd(proxy_entry->ongoing)); + if (!pdu) { + goto failed; + } + + if (!coap_add_token(pdu, token_len, token)) { + coap_delete_pdu(pdu); + goto failed; + } + + if (!coap_uri_into_optlist(&server_use.uri, + &proxy_entry->ongoing->addr_info.remote, + &optlist, 1)) { + coap_log_err("Failed to create options for URI\n"); + goto failed; + } + + /* 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.number) { + case COAP_OPTION_PROXY_URI: + break; + case COAP_OPTION_PROXY_SCHEME: + break; + case COAP_OPTION_BLOCK1: + case COAP_OPTION_BLOCK2: + case COAP_OPTION_Q_BLOCK1: + case COAP_OPTION_Q_BLOCK2: + /* These are not passed on */ + break; + default: + coap_insert_optlist(&optlist, + coap_new_optlist(opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))); + break; + } + } + + /* Update pdu with options */ + coap_add_optlist_pdu(pdu, &optlist); + coap_delete_optlist(optlist); + break; + case COAP_PROXY_REVERSE: + case COAP_PROXY_FORWARD: + case COAP_PROXY_DIRECT: + default: + /* + * Duplicate request PDU for onward transmission (with new token). + */ + pdu = coap_pdu_duplicate_lkd(request, proxy_entry->ongoing, token_len, token, NULL); + if (!pdu) { + coap_log_debug("proxy: PDU generation error\n"); + goto failed; + } + break; + } + + if (coap_get_data_large(request, &size, &data, &offset, &total)) { + /* COAP_BLOCK_SINGLE_BODY is set, so single body should be given */ + assert(size == total); + /* + * Need to take a copy of the data as request PDU may go away before + * all data is transmitted. + */ + body_data = coap_new_binary(total); + if (!body_data) { + coap_log_debug("proxy: body build memory error\n"); + goto failed; + } + memcpy(body_data->s, data, size); + if (!coap_add_data_large_request_lkd(proxy_entry->ongoing, pdu, total, data, + coap_proxy_release_body_data, body_data)) { + coap_log_debug("proxy: add data error\n"); + goto failed; + } + } + + if (coap_send_lkd(proxy_entry->ongoing, pdu) == COAP_INVALID_MID) { + coap_log_debug("proxy: upstream PDU send error\n"); + goto failed; + } + + /* + * Do not update the response code (hence empty ACK) as will be sending + * separate response when response comes back from upstream server + */ + + return 1; + +failed: + response->code = COAP_RESPONSE_CODE(500); + return 0; +} + +coap_response_t COAP_API +coap_proxy_forward_response(coap_session_t *session, + const coap_pdu_t *received, + coap_cache_key_t **cache_key) { + int ret; + + coap_lock_lock(session->context, return 0); + ret = coap_proxy_forward_response_lkd(session, + received, + cache_key); + coap_lock_unlock(session->context); + return ret; +} + +coap_response_t +coap_proxy_forward_response_lkd(coap_session_t *session, + const coap_pdu_t *received, + coap_cache_key_t **cache_key) { + coap_pdu_t *pdu = NULL; + coap_session_t *incoming = NULL; + size_t i; + size_t j = 0; + size_t size; + const uint8_t *data; + coap_optlist_t *optlist = NULL; + coap_opt_t *option; + coap_opt_iterator_t opt_iter; + size_t offset; + size_t total; + coap_proxy_list_t *proxy_entry = NULL; + uint16_t media_type = COAP_MEDIATYPE_TEXT_PLAIN; + int maxage = -1; + uint64_t etag = 0; + coap_pdu_code_t rcv_code = coap_pdu_get_code(received); + coap_bin_const_t rcv_token = coap_pdu_get_token(received); + coap_bin_const_t req_token; + coap_binary_t *body_data = NULL; + coap_pdu_t *req_pdu; + coap_proxy_list_t *proxy_list = session->context->proxy_list; + size_t proxy_list_count = session->context->proxy_list_count; + coap_resource_t *resource; + struct coap_proxy_req_t *proxy_req = NULL; + + for (i = 0; i < proxy_list_count; i++) { + proxy_entry = &proxy_list[i]; + for (j = 0; j < proxy_entry->req_count; j++) { + if (coap_binary_equal(&rcv_token, proxy_entry->req_list[j].token_used)) { + proxy_req = &proxy_entry->req_list[j]; + break; + } + } + if (j != proxy_entry->req_count) { + break; + } + } + if (i == proxy_list_count) { + coap_log_warn("Unknown proxy ongoing session response received\n"); + return COAP_RESPONSE_OK; + } + + req_pdu = proxy_req->pdu; + req_token = coap_pdu_get_token(req_pdu); + resource = proxy_req->resource; + incoming = proxy_req->incoming; + + coap_log_debug("** process upstream incoming %d.%02d response:\n", + COAP_RESPONSE_CLASS(rcv_code), rcv_code & 0x1F); + + if (coap_get_data_large(received, &size, &data, &offset, &total)) { + /* COAP_BLOCK_SINGLE_BODY is set, so single body should be given */ + assert(size == total); + body_data = coap_new_binary(total); + if (!body_data) { + coap_log_debug("body build memory error\n"); + goto remove_match; + } + memcpy(body_data->s, data, size); + data = body_data->s; + } + + /* + * Build up the ongoing PDU that we are going to send to proxy originator + * as separate response + */ + pdu = coap_pdu_init(req_pdu->type, rcv_code, + coap_new_message_id_lkd(incoming), + coap_session_max_pdu_size_lkd(incoming)); + if (!pdu) { + coap_log_debug("Failed to create ongoing proxy response PDU\n"); + goto remove_match; + } + + if (!coap_add_token(pdu, req_token.length, req_token.s)) { + coap_log_debug("cannot add token to ongoing proxy response PDU\n"); + } + + /* + * Copy the options across, skipping those needed for + * coap_add_data_response_large() + */ + coap_option_iterator_init(received, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + switch (opt_iter.number) { + case COAP_OPTION_CONTENT_FORMAT: + media_type = coap_decode_var_bytes(coap_opt_value(option), + coap_opt_length(option)); + break; + case COAP_OPTION_MAXAGE: + maxage = coap_decode_var_bytes(coap_opt_value(option), + coap_opt_length(option)); + break; + case COAP_OPTION_ETAG: + etag = coap_decode_var_bytes8(coap_opt_value(option), + coap_opt_length(option)); + break; + case COAP_OPTION_BLOCK2: + case COAP_OPTION_Q_BLOCK2: + case COAP_OPTION_SIZE2: + break; + default: + coap_insert_optlist(&optlist, + coap_new_optlist(opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))); + break; + } + } + coap_add_optlist_pdu(pdu, &optlist); + coap_delete_optlist(optlist); + + if (size > 0) { + coap_string_t *l_query = coap_get_query(req_pdu); + + coap_add_data_large_response_lkd(resource, incoming, req_pdu, pdu, + l_query, + media_type, maxage, etag, size, data, + coap_proxy_release_body_data, + body_data); + coap_delete_string(l_query); + } + + if (cache_key) + *cache_key = proxy_req->cache_key; + + coap_send_lkd(incoming, pdu); + +remove_match: + option = coap_check_option(received, COAP_OPTION_OBSERVE, &opt_iter); + /* Need to remove matching token entry (apart from on Observe response) */ + if (option == NULL && proxy_entry->req_count) { + coap_delete_pdu(proxy_entry->req_list[j].pdu); + coap_delete_bin_const(proxy_entry->req_list[j].token_used); + /* Do not delete cache key here - caller's responsibility */ + proxy_entry->req_count--; + if (proxy_entry->req_count-j > 0) { + memmove(&proxy_entry->req_list[j], &proxy_entry->req_list[j+1], + (proxy_entry->req_count-j) * sizeof(proxy_entry->req_list[0])); + } + } + return COAP_RESPONSE_OK; +} + +#endif /* COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT */ diff --git a/win32/libcoap.vcxproj b/win32/libcoap.vcxproj index bba1b72cf5..b9906cc79d 100644 --- a/win32/libcoap.vcxproj +++ b/win32/libcoap.vcxproj @@ -61,6 +61,7 @@ + @@ -116,6 +117,7 @@ + diff --git a/win32/libcoap.vcxproj.filters b/win32/libcoap.vcxproj.filters index 87a41ea733..7a58cadbbd 100644 --- a/win32/libcoap.vcxproj.filters +++ b/win32/libcoap.vcxproj.filters @@ -77,6 +77,9 @@ Source Files + + Source Files + Source Files @@ -238,6 +241,9 @@ Header Files + + Header Files + Header Files