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);
+}
+
/** @} */