diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 450b75d476..67a54b314c 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -64,6 +64,7 @@ PSEUDOMODULES += evtimer_mbox PSEUDOMODULES += evtimer_on_ztimer PSEUDOMODULES += fatfs_vfs_format PSEUDOMODULES += fmt_% +PSEUDOMODULES += gcoap_forward_proxy PSEUDOMODULES += gcoap_dtls PSEUDOMODULES += fido2_tests PSEUDOMODULES += gnrc_dhcpv6_% diff --git a/sys/Makefile.dep b/sys/Makefile.dep index f513554cd5..bade0af719 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -604,6 +604,11 @@ ifneq (,$(filter l2filter_%,$(USEMODULE))) USEMODULE += l2filter endif +ifneq (,$(filter gcoap_forward_proxy,$(USEMODULE))) + USEMODULE += gcoap + USEMODULE += uri_parser +endif + ifneq (,$(filter gcoap_dtls,$(USEMODULE))) USEMODULE += gcoap USEMODULE += dsm diff --git a/sys/include/net/gcoap/forward_proxy.h b/sys/include/net/gcoap/forward_proxy.h new file mode 100644 index 0000000000..bee6898039 --- /dev/null +++ b/sys/include/net/gcoap/forward_proxy.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup net_gcoap_forward_proxy Gcoap Forward Proxy + * @ingroup net_gcoap + * @brief Forward proxy implementation for Gcoap + * @note Does not support CoAPS yet. + * @see + * RFC 7252 + * + * + * @{ + * + * @file + * @brief Definitions for the Gcoap forward proxy + * + * @author Cenk Gündoğan + */ + +#ifndef NET_GCOAP_FORWARD_PROXY_H +#define NET_GCOAP_FORWARD_PROXY_H + +#include +#include + +#include "net/nanocoap.h" +#include "net/gcoap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Registers a listener for forward proxy operation + */ +void gcoap_forward_proxy_init(void); + +/** + * @brief Handles proxied requests + * + * @param[in] pkt Packet to parse + * @param[in] client Endpoint of the client + * + * @return 0 if parsing was successful + * @return -ENOTSUP if the forward proxy is not compiled in + * @return -ENOENT if @p pkt does not contain a Proxy-Uri option + * @return -EINVAL if Proxy-Uri is malformed + */ +int gcoap_forward_proxy_request_process(coap_pkt_t *pkt, + sock_udp_ep_t *client); + +/** + * @brief Finds the memo for an outstanding request within the + * _coap_state.open_reqs array. Matches on remote endpoint and + * token. + * + * @param[out] memo_ptr Registered request memo, or NULL if not found + * @param[in] src_pdu PDU for token to match + * @param[in] remote Remote endpoint to match + */ +void gcoap_forward_proxy_find_req_memo(gcoap_request_memo_t **memo_ptr, + coap_pkt_t *src_pdu, + const sock_udp_ep_t *remote); + +/** + * @brief Sends a buffer containing a CoAP message to the @p remote endpoint + * + * @param[in] buf Buffer that contains the CoAP message to be sent + * @param[in] len Length of @p buf + * @param[in] remote Remote endpoint to send the message to + * + * @note see sock_udp_send() for all return valus. + * + * @return length of the packet + * @return < 0 on error + */ +ssize_t gcoap_forward_proxy_dispatch(const uint8_t *buf, + size_t len, sock_udp_ep_t *remote); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GCOAP_FORWARD_PROXY_H */ +/** + * @} + */ diff --git a/sys/include/net/nanocoap.h b/sys/include/net/nanocoap.h index 11e23d3404..c6dcd4d708 100644 --- a/sys/include/net/nanocoap.h +++ b/sys/include/net/nanocoap.h @@ -110,6 +110,8 @@ extern "C" { #define COAP_FETCH (0x10) #define COAP_PATCH (0x20) #define COAP_IPATCH (0x40) +#define COAP_IGNORE (0xFF) /**< For situations where the method + is not important */ #define COAP_MATCH_SUBTREE (0x8000) /**< Path is considered as a prefix when matching */ /** @} */ diff --git a/sys/net/application_layer/gcoap/Makefile b/sys/net/application_layer/gcoap/Makefile index df742e7364..cdf8b12734 100644 --- a/sys/net/application_layer/gcoap/Makefile +++ b/sys/net/application_layer/gcoap/Makefile @@ -1,3 +1,5 @@ -MODULE = gcoap +SRC := gcoap.c + +SUBMODULES := 1 include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/gcoap/forward_proxy.c b/sys/net/application_layer/gcoap/forward_proxy.c new file mode 100644 index 0000000000..01f6d9bc6f --- /dev/null +++ b/sys/net/application_layer/gcoap/forward_proxy.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2020 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Cenk Gündoğan + */ + +#include "net/gcoap.h" +#include "net/gcoap/forward_proxy.h" +#include "uri_parser.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +typedef struct { + int in_use; + sock_udp_ep_t ep; +} client_ep_t; + +static uint8_t proxy_req_buf[CONFIG_GCOAP_PDU_BUF_SIZE]; +static client_ep_t _client_eps[CONFIG_GCOAP_REQ_WAITING_MAX]; + +static int _request_matcher_forward_proxy(gcoap_listener_t *listener, + const coap_resource_t **resource, + coap_pkt_t *pdu); +static ssize_t _forward_proxy_handler(coap_pkt_t* pdu, uint8_t *buf, + size_t len, void *ctx); + +const coap_resource_t forward_proxy_resources[] = { + { "/", COAP_IGNORE, _forward_proxy_handler, NULL }, +}; + +gcoap_listener_t forward_proxy_listener = { + &forward_proxy_resources[0], + ARRAY_SIZE(forward_proxy_resources), + GCOAP_SOCKET_TYPE_UDP, + NULL, + NULL, + _request_matcher_forward_proxy +}; + +void gcoap_forward_proxy_init(void) +{ + gcoap_register_listener(&forward_proxy_listener); +} + +static client_ep_t *_allocate_client_ep(sock_udp_ep_t *ep) +{ + client_ep_t *cep; + for (cep = _client_eps; + cep < (_client_eps + CONFIG_GCOAP_REQ_WAITING_MAX); + cep++) { + if (!cep->in_use) { + cep->in_use = 1; + memcpy(&cep->ep, ep, sizeof(*ep)); + return cep; + } + } + return NULL; +} + +static void _free_client_ep(client_ep_t *cep) +{ + memset(cep, 0, sizeof(*cep)); +} + +static int _request_matcher_forward_proxy(gcoap_listener_t *listener, + const coap_resource_t **resource, + coap_pkt_t *pdu) +{ + (void) listener; + + char *offset; + + if (coap_get_proxy_uri(pdu, &offset) > 0) { + *resource = &listener->resources[0]; + return GCOAP_RESOURCE_FOUND; + } + + return GCOAP_RESOURCE_NO_PATH; +} + +static ssize_t _forward_proxy_handler(coap_pkt_t *pdu, uint8_t *buf, + size_t len, void *ctx) +{ + sock_udp_ep_t *remote = (sock_udp_ep_t *)ctx; + + int proxy_res = gcoap_forward_proxy_request_process(pdu, remote); + + /* Out of memory, reply with 5.00 */ + if (proxy_res == -ENOMEM) { + return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR); + } + /* Proxy-Uri malformed, reply with 4.02 */ + else if (proxy_res == -EINVAL) { + return gcoap_response(pdu, buf, len, COAP_CODE_BAD_OPTION); + } + /* scheme not supported */ + else if (proxy_res == -EPERM) { + return gcoap_response(pdu, buf, len, COAP_CODE_PROXYING_NOT_SUPPORTED); + } + + return 0; +} + +static bool _parse_endpoint(sock_udp_ep_t *remote, + uri_parser_result_t *urip) +{ + char scratch[8]; + ipv6_addr_t addr; + remote->family = AF_INET6; + + /* support IPv6 only for now */ + if (!urip->ipv6addr) { + return false; + } + + /* check for interface */ + if (urip->zoneid) { + /* only works with integer based zoneids */ + + if (urip->zoneid_len > (ARRAY_SIZE(scratch) - 1)) { + return false; + } + + memcpy(scratch, urip->zoneid, urip->zoneid_len); + + scratch[urip->zoneid_len] = '\0'; + + int pid = atoi(scratch); + + if (gnrc_netif_get_by_pid(pid) == NULL) { + return false; + } + remote->netif = pid; + } + /* no interface present */ + else { + if (gnrc_netif_numof() == 1) { + /* assign the single interface found in gnrc_netif_numof() */ + remote->netif = (uint16_t)gnrc_netif_iter(NULL)->pid; + } + else { + remote->netif = SOCK_ADDR_ANY_NETIF; + } + } + + /* parse destination address */ + if (ipv6_addr_from_buf(&addr, urip->ipv6addr, urip->ipv6addr_len) == NULL) { + return false; + } + if ((remote->netif == SOCK_ADDR_ANY_NETIF) && + ipv6_addr_is_link_local(&addr)) { + return false; + } + memcpy(&remote->addr.ipv6[0], &addr.u8[0], sizeof(addr.u8)); + + if (urip->port_len) { + /* copy port string into scratch for atoi */ + memcpy(scratch, urip->port, urip->port_len); + scratch[urip->port_len] = '\0'; + + remote->port = atoi(scratch); + + if (remote->port == 0) { + return false; + } + } + else { + remote->port = 5683; + } + + return true; +} + +static void _forward_resp_handler(const gcoap_request_memo_t *memo, + coap_pkt_t* pdu, + const sock_udp_ep_t *remote) +{ + (void) remote; /* this is the origin server */ + client_ep_t *cep = (client_ep_t *)memo->context; + + if (memo->state == GCOAP_MEMO_RESP) { + /* forward the response packet as-is to the client */ + gcoap_forward_proxy_dispatch((uint8_t *)pdu->hdr, + (pdu->payload - + (uint8_t *)pdu->hdr + pdu->payload_len), + &cep->ep); + } + _free_client_ep(cep); +} + +static int _gcoap_forward_proxy_add_uri_path(coap_pkt_t *pkt, + uri_parser_result_t *urip) +{ + ssize_t res = coap_opt_add_chars(pkt, COAP_OPT_URI_PATH, + urip->path, urip->path_len, '/'); + if (res < 0) { + return -EINVAL; + } + + if (urip->query) { + res = coap_opt_add_chars(pkt, COAP_OPT_URI_QUERY, + urip->query, urip->query_len, '&'); + if (res < 0) { + return -EINVAL; + } + } + + return 0; +} + +static int _gcoap_forward_proxy_copy_options(coap_pkt_t *pkt, + coap_pkt_t *client_pkt, + uri_parser_result_t *urip) +{ + /* copy all options from client_pkt to pkt */ + coap_optpos_t opt = {0, 0}; + uint8_t *value; + bool uri_path_added = false; + + for (int i = 0; i < client_pkt->options_len; i++) { + ssize_t optlen = coap_opt_get_next(client_pkt, &opt, &value, !i); + if (optlen >= 0) { + /* add URI-PATH before any larger opt num */ + if (!uri_path_added && (opt.opt_num > COAP_OPT_URI_PATH)) { + if (_gcoap_forward_proxy_add_uri_path(pkt, urip) == -EINVAL) { + return -EINVAL; + } + uri_path_added = true; + } + /* skip PROXY-URI in new packet */ + if (opt.opt_num == COAP_OPT_PROXY_URI) { + continue; + } + /* the actual copy operation */ + coap_opt_add_opaque(pkt, opt.opt_num, value, optlen); + } + } + + ssize_t len = coap_opt_finish(pkt, + (client_pkt->payload_len ? + COAP_OPT_FINISH_PAYLOAD : + COAP_OPT_FINISH_NONE)); + + /* copy payload from client_pkt to pkt */ + memcpy(pkt->payload, client_pkt->payload, client_pkt->payload_len); + len += client_pkt->payload_len; + + return len; +} + +static int _gcoap_forward_proxy_via_coap(coap_pkt_t *client_pkt, + client_ep_t *client_ep, + uri_parser_result_t *urip) +{ + coap_pkt_t pkt; + sock_udp_ep_t origin_server_ep; + + ssize_t len; + gcoap_request_memo_t *memo = NULL; + + if (!_parse_endpoint(&origin_server_ep, urip)) { + return -EINVAL; + } + + /* do not forward requests if they already exist, e.g., due to CON + and retransmissions. In the future, the proxy should set an + empty ACK message to stop the retransmissions of a client */ + gcoap_forward_proxy_find_req_memo(&memo, client_pkt, &origin_server_ep); + if (memo) { + DEBUG("gcoap_forward_proxy: request already exists, ignore!\n"); + _free_client_ep(client_ep); + return 0; + } + + unsigned token_len = coap_get_token_len(client_pkt); + + coap_pkt_init(&pkt, proxy_req_buf, CONFIG_GCOAP_PDU_BUF_SIZE, + sizeof(coap_hdr_t) + token_len); + + pkt.hdr->ver_t_tkl = client_pkt->hdr->ver_t_tkl; + pkt.hdr->code = client_pkt->hdr->code; + pkt.hdr->id = client_pkt->hdr->id; + + if (token_len) { + memcpy(pkt.token, client_pkt->token, token_len); + } + + /* copy all options from client_pkt to pkt */ + len = _gcoap_forward_proxy_copy_options(&pkt, client_pkt, urip); + + if (len == -EINVAL) { + return -EINVAL; + } + + len = gcoap_req_send((uint8_t *)pkt.hdr, len, + &origin_server_ep, + _forward_resp_handler, (void *)client_ep); + return len; +} + +int gcoap_forward_proxy_request_process(coap_pkt_t *pkt, + sock_udp_ep_t *client) { + char *uri; + uri_parser_result_t urip; + + ssize_t optlen = 0; + + client_ep_t *cep = _allocate_client_ep(client); + + if (!cep) { + return -ENOMEM; + } + + optlen = coap_get_proxy_uri(pkt, &uri); + + if (optlen < 0) { + /* -ENOENT, -EINVAL */ + _free_client_ep(cep); + return optlen; + } + + int ures = uri_parser_process(&urip, (const char *) uri, optlen); + + /* cannot parse Proxy-URI option, or URI is relative */ + if (ures || (!uri_parser_is_absolute((const char *) uri, optlen))) { + _free_client_ep(cep); + return -EINVAL; + } + + /* target is using CoAP */ + if (!strncmp("coap", urip.scheme, urip.scheme_len)) { + int res = _gcoap_forward_proxy_via_coap(pkt, cep, &urip); + if (res < 0) { + _free_client_ep(cep); + return -EINVAL; + } + } + /* no other scheme supported for now */ + else { + _free_client_ep(cep); + return -EPERM; + } + + return 0; +} + +/** @} */ diff --git a/sys/net/application_layer/gcoap/gcoap.c b/sys/net/application_layer/gcoap/gcoap.c index 88940b367d..23dbcba156 100644 --- a/sys/net/application_layer/gcoap/gcoap.c +++ b/sys/net/application_layer/gcoap/gcoap.c @@ -40,6 +40,8 @@ #include "net/dsm.h" #endif +#include "net/gcoap/forward_proxy.h" + #define ENABLE_DEBUG 0 #include "debug.h" @@ -635,7 +637,17 @@ static size_t _handle_req(gcoap_socket_t *sock, coap_pkt_t *pdu, uint8_t *buf, } pdu->tl_type = (uint32_t)sock->type; - ssize_t pdu_len = resource->handler(pdu, buf, len, resource->context); + + ssize_t pdu_len; + char *offset; + + if (coap_get_proxy_uri(pdu, &offset) > 0) { + pdu_len = resource->handler(pdu, buf, len, remote); + } + else { + pdu_len = resource->handler(pdu, buf, len, resource->context); + } + if (pdu_len < 0) { pdu_len = gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR); @@ -1090,6 +1102,11 @@ kernel_pid_t gcoap_init(void) /* randomize initial value */ atomic_init(&_coap_state.next_message_id, (unsigned)random_uint32()); + /* initialize the forward proxy operation, if compiled */ + if (IS_ACTIVE(MODULE_GCOAP_FORWARD_PROXY)) { + gcoap_forward_proxy_init(); + } + return _pid; } @@ -1437,4 +1454,16 @@ sock_dtls_t *gcoap_get_sock_dtls(void) /* */ +void gcoap_forward_proxy_find_req_memo(gcoap_request_memo_t **memo_ptr, + coap_pkt_t *src_pdu, + const sock_udp_ep_t *remote) +{ + _find_req_memo(memo_ptr, src_pdu, remote, false); +} + +ssize_t gcoap_forward_proxy_dispatch(const uint8_t *buf, size_t len, sock_udp_ep_t *remote) +{ + return sock_udp_send(&_sock_udp, buf, len, remote); +} + /** @} */