Skip to content

Commit

Permalink
gcoap_forward_proxy: only leave upstream validation for caching
Browse files Browse the repository at this point in the history
Most of the caching operation was moved to the client code. Since the
forward proxy is using that code for upstream messaging, interacting
with the cache directly is not necessary anymore.

The only cache-related thing necessary for the proxy is validating ETags
from upstream. However, that can be done by just looking at the ETags
from the upstream response (which may or may not have come from the
cache).
  • Loading branch information
miri64 committed Apr 1, 2022
1 parent 60fe9e5 commit 11631a6
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 170 deletions.
220 changes: 54 additions & 166 deletions sys/net/application_layer/gcoap/forward_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

typedef struct {
uint16_t in_use;
uint16_t validating;
uint8_t req_etag_len;
sock_udp_ep_t ep;
#if IS_ACTIVE(MODULE_NANOCOAP_CACHE)
uint8_t cache_key[CONFIG_NANOCOAP_CACHE_KEY_LENGTH];
#if IS_USED(MODULE_NANOCOAP_CACHE)
uint8_t req_etag[COAP_ETAG_LENGTH_MAX];
#endif
} client_ep_t;

Expand Down Expand Up @@ -55,81 +55,8 @@ gcoap_listener_t forward_proxy_listener = {
void gcoap_forward_proxy_init(void)
{
gcoap_register_listener(&forward_proxy_listener);

/* initialize the nanocoap cache operation, if compiled */
if (IS_ACTIVE(MODULE_NANOCOAP_CACHE)) {
nanocoap_cache_init();
}
}

static int _cache_build_response(nanocoap_cache_entry_t *ce,
coap_pkt_t *pdu,
uint8_t *buf,
size_t len)
{
if ((pdu->hdr->code == COAP_METHOD_GET) || (pdu->hdr->code == COAP_METHOD_FETCH)) {
uint8_t *req_etag;
/* Searching for more ETags might become necessary in the future */
ssize_t req_etag_len = coap_opt_get_opaque(pdu, COAP_OPT_ETAG, &req_etag);

if (req_etag_len > 0) {
/* ETag found, validate from cache entry */
uint8_t *cache_etag;
ssize_t cache_etag_len = coap_opt_get_opaque(&ce->response_pkt, COAP_OPT_ETAG,
&cache_etag);

if ((cache_etag_len == req_etag_len) &&
(memcmp(req_etag, cache_etag, req_etag_len) == 0)) {
gcoap_resp_init(pdu, buf, len, COAP_CODE_VALID);
coap_opt_add_opaque(pdu, COAP_OPT_ETAG, req_etag, req_etag_len);
return coap_get_total_hdr_len(pdu);
}
}
}
/* Use the same code from the cached content. Use other header
* fields from the incoming request */
gcoap_resp_init(pdu, buf, len, ce->response_pkt.hdr->code);
/* copy all options and possible payload from the cached response
* to the new response */
unsigned header_len_req = coap_get_total_hdr_len(pdu);
unsigned header_len_cached = coap_get_total_hdr_len(&ce->response_pkt);
unsigned opt_payload_len = ce->response_len - header_len_cached;

memcpy((buf + header_len_req),
(ce->response_buf + header_len_cached),
opt_payload_len);
return header_len_req + opt_payload_len;
}

static int _cache_lookup_and_process(coap_pkt_t *pdu,
uint8_t *buf,
size_t len,
client_ep_t *cep,
nanocoap_cache_entry_t **ce)
{
(void) cep;

uint8_t cache_key[SHA256_DIGEST_LENGTH];
ztimer_now_t now = ztimer_now(ZTIMER_SEC);
nanocoap_cache_key_generate(pdu, cache_key);
*ce = nanocoap_cache_key_lookup(cache_key);

/* cache hit, methods are equal, and cache entry is not stale */
if (*ce &&
((*ce)->request_method == coap_get_code(pdu)) &&
((*ce)->max_age > now)) {
/* use response from cache */
return _cache_build_response(*ce, pdu, buf, len);
}

#if IS_ACTIVE(MODULE_NANOCOAP_CACHE)
memcpy(cep->cache_key, cache_key, CONFIG_NANOCOAP_CACHE_KEY_LENGTH);
#endif

return 0;
}


static client_ep_t *_allocate_client_ep(sock_udp_ep_t *ep)
{
client_ep_t *cep;
Expand All @@ -138,7 +65,7 @@ static client_ep_t *_allocate_client_ep(sock_udp_ep_t *ep)
cep++) {
if (!cep->in_use) {
cep->in_use = 1U;
cep->validating = 0U;
cep->req_etag_len = 0U;
memcpy(&cep->ep, ep, sizeof(*ep));
return cep;
}
Expand Down Expand Up @@ -268,60 +195,30 @@ static void _forward_resp_handler(const gcoap_request_memo_t *memo,
(void) remote; /* this is the origin server */
client_ep_t *cep = (client_ep_t *)memo->context;

if (memo->state == GCOAP_MEMO_RESP) {
if (!IS_USED(MODULE_NANOCOAP_CACHE) ||
/* only forward 2.03 Valid, if client endpoint sent an ETag to validate cached content */
(pdu->hdr->code != COAP_CODE_VALID) || cep->validating) {
/* 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);
}

#if IS_USED(MODULE_NANOCOAP_CACHE)
/* if response is a 2.03, but client endpoint did not send an ETag to validate content */
if ((pdu->hdr->code == COAP_CODE_VALID) && !cep->validating) {
nanocoap_cache_entry_t *ce = NULL;

if ((ce = nanocoap_cache_key_lookup(cep->cache_key))) {
/* update max_age from response and send cached response */
uint32_t max_age = 60;

coap_opt_get_uint(pdu, COAP_OPT_MAX_AGE, &max_age);
ce->max_age = ztimer_now(ZTIMER_SEC) + max_age;
/* copy all options and possible payload from the cached response
* to the new response */
unsigned header_len_req = coap_get_total_hdr_len(pdu);
unsigned header_len_cached = coap_get_total_hdr_len(&ce->response_pkt);
uint8_t *buf = (uint8_t *)pdu->hdr;
size_t len = pdu->payload_len + header_len_req;
gcoap_resp_init(pdu, buf, len, ce->response_pkt.hdr->code);
unsigned opt_payload_len = ce->response_len - header_len_cached;

memcpy((buf + header_len_req),
(ce->response_buf + header_len_cached),
opt_payload_len);
gcoap_forward_proxy_dispatch(buf, header_len_req + opt_payload_len, &cep->ep);
}
else {
/* cache entry to be validated cached out while trying to validate */
/* TODO: re-request real response (without ETag) */
}
return;
}
coap_pkt_t req;
if (memo->send_limit == GCOAP_SEND_LIMIT_NON) {
req.hdr = (coap_hdr_t *) &memo->msg.hdr_buf[0];
/* req_tag in cep is pre-processor guarded so we need to as well */
if (cep->req_etag_len > 0) {
uint8_t *resp_etag;

/* check if we can just send 2.03 Valid instead */
if ((cep->req_etag_len == coap_opt_get_opaque(pdu, COAP_OPT_ETAG, &resp_etag)) &&
(memcmp(cep->req_etag, resp_etag, cep->req_etag_len) == 0)) {
gcoap_resp_init(pdu, (uint8_t *)pdu->hdr,
(pdu->payload - (uint8_t *)pdu->hdr + pdu->payload_len),
COAP_CODE_VALID);
coap_opt_add_opaque(pdu, COAP_OPT_ETAG, cep->req_etag, cep->req_etag_len);
coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
}
else {
req.hdr = (coap_hdr_t *) memo->msg.data.pdu_buf;
}
size_t pdu_len = pdu->payload_len +
(pdu->payload - (uint8_t *)pdu->hdr);
nanocoap_cache_process(cep->cache_key, coap_get_code(&req), pdu, pdu_len);
#endif
}
/* we do not need to check if valid came from upstream as this is already automatically
* converted by the client-side to the cached response */
#endif
/* else 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);
}

Expand All @@ -348,38 +245,46 @@ static int _gcoap_forward_proxy_add_uri_path(coap_pkt_t *pkt,
static int _gcoap_forward_proxy_copy_options(coap_pkt_t *pkt,
coap_pkt_t *client_pkt,
client_ep_t *cep,
uri_parser_result_t *urip,
nanocoap_cache_entry_t *ce)
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, etag_added = false;
bool uri_path_added = false;
bool etag_added = false;

for (int i = 0; i < client_pkt->options_len; i++) {
ssize_t optlen = coap_opt_get_next(client_pkt, &opt, &value, !i);
/* wrt to ETag: we always have at least the Proxy-URI option in the client_pkt, so
* we should hit at least once (and it's opt_num is also >= COAP_OPT_ETAG) */
/* wrt to ETag option slack: we always have at least the Proxy-URI option in the client_pkt,
* so * we should hit at least once (and it's opt_num is also >= COAP_OPT_ETAG) */
if (optlen >= 0) {
/* Add ETag before any larger opt num, but skip old ETag if ETag in cache */
if (!etag_added && (opt.opt_num >= COAP_OPT_ETAG)) {
if (IS_USED(MODULE_NANOCOAP_CACHE) && ce) {
uint8_t *etag;
/* Searching for more ETags might become necessary in the future */
ssize_t etag_len = coap_opt_get_opaque(&ce->response_pkt, COAP_OPT_ETAG, &etag);

if (etag_len > 0) {
coap_opt_add_opaque(pkt, COAP_OPT_ETAG, etag, etag_len);
}
if (IS_USED(MODULE_NANOCOAP_CACHE) && !etag_added && (opt.opt_num >= COAP_OPT_ETAG)) {
static const uint8_t tmp[COAP_ETAG_LENGTH_MAX] = { 0 };
/* add slack to maybe add an ETag on stale cache hit later, as is done in gcoap_req_send()
* (which we circumvented in _gcoap_forward_proxy_via_coap()) */
if (coap_opt_add_opaque(pkt, COAP_OPT_ETAG, tmp, sizeof(tmp))) {
etag_added = true;
}
}
/* skip original ETag of request, otherwise we might accidentally fill the cache
* with 2.03 Valid responses which would require additional handling */
#if IS_USED(MODULE_NANOCOAP_CACHE)
/* req_tag in cep is pre-processor guarded so we need to as well */
if (opt.opt_num == COAP_OPT_ETAG) {
cep->validating = 1U;
uint8_t *etag;
ssize_t etag_len = coap_opt_get_opaque(pkt, COAP_OPT_ETAG, &etag);
if (etag_len > 0) {
/* only set request ETag if it can be properly parsed */
cep->req_etag_len = (uint8_t)etag_len;
memcpy(cep->req_etag, etag, etag_len);
}
/* skip original ETag of request, otherwise we might accidentally fill the cache
* with 2.03 Valid responses which would require additional handling.
* For upstream validation, gcoap_req_send() will add an ETag, if the response
* was in cache */
continue;
}
#else
(void)cep;
#endif
/* 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) {
Expand Down Expand Up @@ -410,8 +315,7 @@ static int _gcoap_forward_proxy_copy_options(coap_pkt_t *pkt,

static int _gcoap_forward_proxy_via_coap(coap_pkt_t *client_pkt,
client_ep_t *client_ep,
uri_parser_result_t *urip,
nanocoap_cache_entry_t *ce)
uri_parser_result_t *urip)
{
coap_pkt_t pkt;
sock_udp_ep_t origin_server_ep;
Expand Down Expand Up @@ -447,7 +351,7 @@ static int _gcoap_forward_proxy_via_coap(coap_pkt_t *client_pkt,
}

/* copy all options from client_pkt to pkt */
len = _gcoap_forward_proxy_copy_options(&pkt, client_pkt, client_ep, urip, ce);
len = _gcoap_forward_proxy_copy_options(&pkt, client_pkt, client_ep, urip);

if (len == -EINVAL) {
return -EINVAL;
Expand All @@ -466,27 +370,11 @@ int gcoap_forward_proxy_request_process(coap_pkt_t *pkt,
ssize_t optlen = 0;

client_ep_t *cep = _allocate_client_ep(client);
nanocoap_cache_entry_t *ce = NULL;

if (!cep) {
return -ENOMEM;
}

if (IS_ACTIVE(MODULE_NANOCOAP_CACHE)) {
int pdu_len = _cache_lookup_and_process(pkt,
(uint8_t *)pkt->hdr,
CONFIG_GCOAP_PDU_BUF_SIZE,
cep,
&ce);
/* if a valid cache entry was found, then pdu_len contains the
* length of that response message */
if (pdu_len > 0) {
_free_client_ep(cep);
return pdu_len;
}
/* if there was no cache hit, then we continue forwarding */
}

optlen = coap_get_proxy_uri(pkt, &uri);

if (optlen < 0) {
Expand All @@ -505,7 +393,7 @@ int gcoap_forward_proxy_request_process(coap_pkt_t *pkt,

/* target is using CoAP */
if (!strncmp("coap", urip.scheme, urip.scheme_len)) {
int res = _gcoap_forward_proxy_via_coap(pkt, cep, &urip, ce);
int res = _gcoap_forward_proxy_via_coap(pkt, cep, &urip);
if (res < 0) {
_free_client_ep(cep);
return -EINVAL;
Expand Down
7 changes: 3 additions & 4 deletions sys/net/application_layer/gcoap/gcoap.c
Original file line number Diff line number Diff line change
Expand Up @@ -1283,14 +1283,13 @@ kernel_pid_t gcoap_init(void)
/* randomize initial value */
atomic_init(&_coap_state.next_message_id, (unsigned)random_uint32());

if (IS_USED(MODULE_NANOCOAP_CACHE)) {
nanocoap_cache_init();
}
/* initialize the forward proxy operation, if compiled */
if (IS_ACTIVE(MODULE_GCOAP_FORWARD_PROXY)) {
gcoap_forward_proxy_init();
}
/* gcoap_forward_proxy_init() also initializes nanocoap_cache_init() */
else if (IS_USED(MODULE_NANOCOAP_CACHE)) {
nanocoap_cache_init();
}

return _pid;
}
Expand Down

0 comments on commit 11631a6

Please sign in to comment.