Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gcoap: add nanocoap_cache support for clients #17888

Merged
merged 5 commits into from
May 13, 2022

Conversation

miri64
Copy link
Member

@miri64 miri64 commented Mar 30, 2022

Contribution description

This provides caching support (via nanocoap_cache) for gcoap clients. For the moment this is still a draft, as I am sure there is some cleanup possible now in caching parts of the gcoap_forward_proxy module. However, I invite still everyone to review this PR.

Testing procedure

Use tapsetup to create a TAP bridge

./dist/tools/tapsetup/tapsetup

Patch aiocoap to include the ETag and Max-Age option in the responses of its file server example

Patch
diff --git a/aiocoap/cli/fileserver.py b/aiocoap/cli/fileserver.py
index 10df38e..92ef7d7 100644
--- a/aiocoap/cli/fileserver.py
+++ b/aiocoap/cli/fileserver.py
@@ -20,7 +20,7 @@ import mimetypes
 import aiocoap
 import aiocoap.error as error
 import aiocoap.numbers.codes as codes
-from aiocoap.resource import Resource
+from aiocoap.resource import Resource, hashing_etag
 from aiocoap.util.cli import AsyncCLIDaemon
 from aiocoap.cli.common import (add_server_arguments,
     server_context_from_arguments, extract_server_arguments)
@@ -104,9 +104,12 @@ class FileServer(Resource, aiocoap.interfaces.ObservableResource):
             raise NoSuchFile()
 
         if S_ISDIR(st.st_mode):
-            return await self.render_get_dir(request, path)
+            resp = await self.render_get_dir(request, path)
         elif S_ISREG(st.st_mode):
-            return await self.render_get_file(request, path)
+            resp = await self.render_get_file(request, path)
+        hashing_etag(request, resp)
+        resp.opt.max_age = 10
+        return resp
 
     async def render_get_dir(self, request, path):
         if request.opt.uri_path and request.opt.uri_path[-1] != '':

Start the aiocoap file server example bound to the TAP bridge:

mkdir -p test
echo "test" test/a.txt
./aiocoap-fileserver --bind "[$(ip addr show dev tapbr0 scope link | \
    grep -o 'inet6 [0-9a-f:]\+' | sed 's/inet6 //')%tapbr0]" test/

Start a Wireshark instance and sniffing on the tapbr0 interface.

In another terminal, compile the examples/gcoap example including the nanocoap_cache module and start it in a terminal

USEMODULE=nanocoap_cache make -C examples/gcoap flash -j term

Send a request for /a.txt, then directly after, another, then wait 10 seconds (or whatever value you gave the Max-Age option when patching the file server) and send yet another request. All should show the same response:

> coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 35163, 21 bytes
> --- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 35164, 21 bytes
--- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---
> coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 35165, 21 bytes
> --- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---

However, in Wireshark, you should only see two request/response pairs (the second request was answered from local cache), with the second pair having a 2.03 Valid (rather than a 2.05 Content) response that carries no payload (validating the already present, but stale, cache entry on the third request):

Screenshot of Wireshark with pr17888_test_procedure.pcapng opened

pr17888_test_procedure.pcapng

Issues/PRs references

Depends on #17801 (merged), #17881 (merged), and #18095 (merged) and all their dependencies.

@github-actions github-actions bot added Area: CoAP Area: Constrained Application Protocol implementations Area: Kconfig Area: Kconfig integration Area: network Area: Networking Area: sys Area: System Area: tests Area: tests and testing framework labels Mar 30, 2022
@miri64 miri64 added Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation and removed Area: network Area: Networking Area: tests Area: tests and testing framework Area: CoAP Area: Constrained Application Protocol implementations Area: sys Area: System Area: Kconfig Area: Kconfig integration labels Mar 30, 2022
@miri64 miri64 requested a review from chrysn March 30, 2022 15:46
@miri64 miri64 added Area: network Area: Networking State: waiting for other PR State: The PR requires another PR to be merged first Area: CoAP Area: Constrained Application Protocol implementations Area: sys Area: System labels Mar 30, 2022
@chrysn
Copy link
Member

chrysn commented Mar 30, 2022

The "blockwise" noise is due to someone having been lazy in optimization on the aiocoap side. If the patch to aiocoap doesn't apply any more, it's likely because I'll pick most of it into aiocoap when I around to it; in that case, things will work with unmodified aiocoap already. (There won't be a Max-Age, but then again that only means testers will have to wait 60 rather than 10 seconds).

@miri64 miri64 force-pushed the gcoap/enh/caching branch from 22a5282 to b646bab Compare March 31, 2022 07:55
@miri64
Copy link
Member Author

miri64 commented Mar 31, 2022

Rebased

@github-actions github-actions bot added Area: Kconfig Area: Kconfig integration Area: tests Area: tests and testing framework labels Mar 31, 2022
@miri64
Copy link
Member Author

miri64 commented Mar 31, 2022

Ok. The proxy definitely needs touching: as it stands, it caches the responses twice at the moment. Once for the server-side request (as the Proxy-URI option is part of the cache key) and once for the client-side request (as the for those the Proxy-URI option is stripped). With the client caching in place, the proxy only needs to do the validating part in place of the server, but none of the other cache handling parts. Will try to work that in.

@miri64 miri64 force-pushed the gcoap/enh/caching branch from f13e814 to 584447d Compare March 31, 2022 14:34
@miri64
Copy link
Member Author

miri64 commented Apr 1, 2022

Will try to work that in.

Done

@miri64 miri64 force-pushed the gcoap/enh/caching branch from 9dbe1a9 to 921ce15 Compare April 1, 2022 08:17
@miri64
Copy link
Member Author

miri64 commented Apr 1, 2022

And rebased and squashed to current master, as #17881 was merged

@miri64 miri64 force-pushed the gcoap/enh/caching branch 2 times, most recently from a1f7acb to 74f4101 Compare April 4, 2022 09:23
@miri64 miri64 mentioned this pull request Apr 13, 2022
@miri64 miri64 force-pushed the gcoap/enh/caching branch from 74f4101 to 6522fb5 Compare May 11, 2022 09:42
@miri64 miri64 force-pushed the gcoap/enh/caching branch 2 times, most recently from 20c8d95 to 93c8585 Compare May 12, 2022 15:37
@miri64
Copy link
Member Author

miri64 commented May 12, 2022

And another rebase, now that #18091 was merged. Will do another round of testing now.

@miri64 miri64 removed the State: waiting for other PR State: The PR requires another PR to be merged first label May 12, 2022
@miri64 miri64 force-pushed the gcoap/enh/caching branch from 93c8585 to 112a821 Compare May 12, 2022 16:19
@miri64
Copy link
Member Author

miri64 commented May 12, 2022

Found some issues while testing. Those should now be fixed with the latest squash.

@miri64
Copy link
Member Author

miri64 commented May 12, 2022

To include the proxy, I also updated the testing procedures somewhat. However, the testing procedures in OP should also still work:

Again I patched aiocoap to speed up the testing

diff --git a/aiocoap/cli/fileserver.py b/aiocoap/cli/fileserver.py
index 1bf2ba4..f6ce129 100644
--- a/aiocoap/cli/fileserver.py
+++ b/aiocoap/cli/fileserver.py
@@ -160,2 +160,3 @@ class FileServer(Resource, aiocoap.interfaces.ObservableResource):
 
+        response.opt.max_age = 10
         response.opt.etag = etag
[in aiocoap repo]
$ mkdir -p test
$ echo "foobar" > test/a.txt
$ cp aiocoap-fileserver test/
$ ./aiocoap-fileserver --bind "[$(ip addr show dev tapbr0 scope link | grep -o 'inet6 [0-9a-f:]\+' | sed 's/inet6 //')%tapbr0]" test/
...
[in another terminal in the RIOT repo]
$ ./dist/tools/tapsetup/tapsetup
$ USEMODULE="gcoap_forward_proxy nanocoap_cache" PORT=tap1 make -C examples/gcoap -j flash term
[...]
main(): This is RIOT! (Version: 2022.07-devel-406-g112a82-gcoap/enh/caching)
gcoap example app
All up, running the shell now
> ifconfig
ifconfig
Iface  6  HWaddr: DA:27:1D:A8:64:24 
          L2-PDU:1500  MTU:1500  HL:64  Source address length: 6
          Link type: wired
          inet6 addr: fe80::d827:1dff:fea8:6424  scope: link  VAL
          inet6 group: ff02::1
          inet6 group: ff02::1:ffa8:6424
          
> 
[yet another terminal in the RIOT repo]
$ make -C examples/gcoap term
[…]
main(): This is RIOT! (Version: 2022.07-devel-406-g112a82-gcoap/enh/caching)
gcoap example app
All up, running the shell now
> coap proxy set fe80::d827:1dff:fea8:6424 5683
coap proxy set fe80::d827:1dff:fea8:6424 5683
> coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
gcoap_cli: sending msg ID 22700, 76 bytes
> gcoap: response Error, code 5.00, empty payload
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
gcoap_cli: sending msg ID 22701, 76 bytes
> gcoap: response Error, code 5.00, empty payload
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
gcoap_cli: sending msg ID 22702, 76 bytes
> gcoap: response Error, code 5.00, empty payload
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /aiocoap-fileserver
gcoap_cli: sending msg ID 22703, 76 bytes
> gcoap: response Error, code 5.00, empty payload
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 22704, 63 bytes
> --- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 22705, 63 bytes
--- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---
> coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
coap get fe80::dc1a:a8ff:fe09:45b3 5683 /a.txt
gcoap_cli: sending msg ID 22706, 63 bytes
> --- blockwise start ---
gcoap: response Success, code 2.05, 7 bytes
foobar

--- blockwise complete ---

Here is what Wireshark saw during the execution of commands

image

First a large file aiocoap-fileserver is requested. As the proxy receives a truncated response, it reports a 5.00 to the client. This will even happen when the proxy validated that truncated response with the upstream file server. After that we request a small file, which in the second request is not seen in wireshark, as its response is taken from the cache of the client. On the third request the Max-Age was exceeded, so both client and proxy try to validate the expired cache entry, which succeeds, as can be seen by the 2.03 Valid responses.

@miri64 miri64 force-pushed the gcoap/enh/caching branch 2 times, most recently from 8ac9385 to e54f9ef Compare May 12, 2022 16:37
@miri64 miri64 added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label May 12, 2022
@github-actions github-actions bot added the Area: tests Area: tests and testing framework label May 13, 2022
@cgundogan
Copy link
Member

Changes look good so far. Squash Please.

miri64 added 5 commits May 13, 2022 12:14
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).
@miri64 miri64 force-pushed the gcoap/enh/caching branch from edec966 to dbed2b4 Compare May 13, 2022 10:16
@miri64 miri64 added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels May 13, 2022
@cgundogan cgundogan self-requested a review May 13, 2022 12:22
Copy link
Member

@cgundogan cgundogan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested the caching functionality with and without a proxy. Works. Code also looks good. ACK

@cgundogan cgundogan enabled auto-merge May 13, 2022 12:23
@cgundogan cgundogan merged commit fb3f1a2 into RIOT-OS:master May 13, 2022
@miri64 miri64 deleted the gcoap/enh/caching branch May 13, 2022 15:00
@chrysn chrysn added this to the Release 2022.07 milestone Aug 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: CoAP Area: Constrained Application Protocol implementations Area: network Area: Networking Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants