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_forward_proxy: send empty ACK when response takes too long #18386

Merged

Conversation

miri64
Copy link
Member

@miri64 miri64 commented Aug 1, 2022

Contribution description

This allows a forward proxy to ACK a request early, if the response from upstream takes too long. This is based on the work by @chrysn for https://doi.org/10.1145/3405656.3418718 which is why I gave the main authorship of the commit to him. I merely adopted the state of change to the best practices and changes to forward_proxy in current master and optimized the client_ep_t to keep the increase in RAM-usage minimal (compared to just plane using uint8_ts, I was able to safe 3 bytes per entry, compared to using bitmaps, I was able to safe 16 bytes of ROM with my flag-based approach).

Testing procedure

I used a version of my trusty proxy testing script again:

#! /usr/bin/env python3
import subprocess
import sys
import time

from riotctrl.ctrl import RIOTCtrl

sys.path.append("dist/pythonlibs")
from riotctrl_shell.netif import IfconfigListParser, Ifconfig


def set_proxy(client, proxy):
    netifs = IfconfigListParser().parse(proxy.ifconfig_list())
    proxy_addr = list(netifs.values())[0]["ipv6_addrs"][0]["addr"]
    res = client.cmd(f"coap proxy set {proxy_addr} 5683")
    assert f"coap proxy set {proxy_addr} 5683" in res


def test_proxy(modules=["gcoap_forward_proxy"]):
    RIOTCtrl.MAKE_ARGS = ("-j", "--no-print-directory")
    envs = [
        {"PORT": "tap0", "QUIETER": "1"},
        {
            "USEMODULE": " ".join(modules),
            "PORT": "tap1",
            "BINDIRBASE": "proxy-bin",
            "QUIETER": "1",
        },
        {"PORT": "tap2", "QUIETER": "1"},
    ]
    ctrls = [RIOTCtrl(application_directory="examples/gcoap", env=env) for env in envs]
    ctrls[0].flash(stdout=None, stderr=None)
    ctrls[1].flash(stdout=None, stderr=None)
    with (
        ctrls[0].run_term(reset=False),
        ctrls[1].run_term(reset=False),
        ctrls[2].run_term(reset=False),
    ):
        shells = []
        for ctrl in ctrls:
            ctrl.term.logfile = sys.stdout
            shells.append(Ifconfig(ctrl))
        netifs = IfconfigListParser().parse(shells[0].ifconfig_list())
        server_addr = list(netifs.values())[0]["ipv6_addrs"][0]["addr"]
        set_proxy(shells[2], shells[1])
        shells[2].cmd(f"coap get -c {server_addr} 5683 /.well-known/core")
        # comes in asynchronously, so we need to expect instead of checking result
        shells[2].riotctrl.term.expect_exact(
            '</cli/stats>;ct=0;rt="count";obs,</riot/board>', timeout=10
        )
        for shell in reversed(shells):
            shell.cmd("help")


if __name__ == "__main__":
    test_proxy()
    test_proxy(["gcoap_forward_proxy", "nanocoap_cache"])
    print("")

To induce a delayed response from the server, I patched the examples/gcoap application:

diff --git a/examples/gcoap/server.c b/examples/gcoap/server.c
index bf2315cd01..da5f106747 100644
--- a/examples/gcoap/server.c
+++ b/examples/gcoap/server.c
@@ -30,2 +30,3 @@
 #include "od.h"
+#include "ztimer.h"
 
@@ -88,2 +89,3 @@ static ssize_t _encode_link(const coap_resource_t *resource, char *buf,
     ssize_t res = gcoap_encode_link(resource, buf, maxlen, context);
+    ztimer_sleep(ZTIMER_MSEC, 2100);
     if (res > 0) {
When running with 3 TAP interfaces, the output is as follows
Building application "gcoap_example" for "native" with MCU "native".

   text	   data	    bss	    dec	    hex	filename
 196251	   2020	  90280	 288551	  46727	/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf
Building application "gcoap_example" for "native" with MCU "native".

   text	   data	    bss	    dec	    hex	filename
 204263	   2052	  90536	 296851	  48793	/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/proxy-bin/native/gcoap_example.elf
/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf tap0 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
gcoap example app
All up, running the shell now
> 

> ifconfig
ifconfig
Iface  6  HWaddr: E2:BC:7D:CB:F5:50 
          L2-PDU:1500  MTU:1500  HL:64  Source address length: 6
          Link type: wired
          inet6 addr: fe80::e0bc:7dff:fecb:f550  scope: link  VAL
          inet6 group: ff02::1
          inet6 group: ff02::1:ffcb:f550
          
> /home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/proxy-bin/native/gcoap_example.elf tap1 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
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
          
> /home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf tap2 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
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 -c fe80::e0bc:7dff:fecb:f550 5683 /.well-known/core
coap get -c fe80::e0bc:7dff:fecb:f550 5683 /.well-known/core
gcoap_cli: sending msg ID 58505, 65 bytes
> gcoap: response Success, code 2.05, 46 bytes
</cli/stats>;ct=0;rt="count";obs,</riot/board>
help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> Building application "gcoap_example" for "native" with MCU "native".

   text	   data	    bss	    dec	    hex	filename
 196251	   2020	  90280	 288551	  46727	/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf
Building application "gcoap_example" for "native" with MCU "native".

   text	   data	    bss	    dec	    hex	filename
 213361	   2156	  92680	 308197	  4b3e5	/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/proxy-bin/native/gcoap_example.elf
/home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf tap0 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
gcoap example app
All up, running the shell now
> 

> ifconfig
ifconfig
Iface  6  HWaddr: E2:BC:7D:CB:F5:50 
          L2-PDU:1500  MTU:1500  HL:64  Source address length: 6
          Link type: wired
          inet6 addr: fe80::e0bc:7dff:fecb:f550  scope: link  VAL
          inet6 group: ff02::1
          inet6 group: ff02::1:ffcb:f550
          
> /home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/proxy-bin/native/gcoap_example.elf tap1 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
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
          
> /home/mlenders/Repositories/RIOT-OS/RIOT/examples/gcoap/bin/native/gcoap_example.elf tap2 
RIOT native interrupts/signals initialized.
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2022.10-devel-147-gda5cd-gcoap_forward_proxy/enh/empty-ack)
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 -c fe80::e0bc:7dff:fecb:f550 5683 /.well-known/core
coap get -c fe80::e0bc:7dff:fecb:f550 5683 /.well-known/core
gcoap_cli: sending msg ID 48175, 65 bytes
> gcoap: response Success, code 2.05, 46 bytes
</cli/stats>;ct=0;rt="count";obs,</riot/board>
help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> help
help
Command              Description
---------------------------------------
coap                 CoAP example
ifconfig             Configure network interfaces
nib                  Configure neighbor information base
ping                 Alias for ping6
ping6                Ping via ICMPv6
pm                   interact with layered PM subsystem
ps                   Prints information about running threads.
reboot               Reboot the node
version              Prints current RIOT_VERSION
> 

With this PR there will be empty ACKs sent by both the proxy and the client (the latter sends it in response to the CON response by the proxy):
A via-proxy exchange with empty ACKs

On master, there are no empty ACKs, but retransmissions by both the client and the proxy, even though the request should have reached the proxy already (illustrated by the fact, that in the second run, the proxy sends its retransmission before the client):
A via-proxy exchange where the client also retransmits

Issues/PRs references

@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 labels Aug 1, 2022
@miri64 miri64 requested a review from benpicco August 1, 2022 12:04
@miri64 miri64 added Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Aug 1, 2022
@miri64 miri64 force-pushed the gcoap_forward_proxy/enh/empty-ack branch from 46d9f50 to 8d57ffd Compare August 11, 2022 13:54
@benpicco benpicco requested a review from chrysn August 11, 2022 13:58
Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

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

Looks good, needs a rebase

sys/net/application_layer/gcoap/forward_proxy.c Outdated Show resolved Hide resolved
sys/net/application_layer/gcoap/forward_proxy.c Outdated Show resolved Hide resolved
sys/net/application_layer/gcoap/forward_proxy.c Outdated Show resolved Hide resolved
sys/net/application_layer/gcoap/gcoap.c Outdated Show resolved Hide resolved
@miri64 miri64 force-pushed the gcoap_forward_proxy/enh/empty-ack branch from ad67ddc to cf58776 Compare October 12, 2022 14:16
@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

Rebased and squashed

@riot-ci
Copy link

riot-ci commented Oct 12, 2022

Murdock results

✔️ PASSED

63c4fe5 gcoap: fix "line is longer than 100 characters" pointed out by Vera++

Success Failures Total Runtime
1980 0 1980 06m:50s

Artifacts

This only reflects a subset of all builds from https://ci-prod.riot-os.org. Please refer to https://ci.riot-os.org for a complete build for now.

@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

Test fail seems unrelated..

Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

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

Code looks good, I trust your testing.

btw: That test script of yours could also be run by Murdock if you replace netdev_tap with socket_zep - see e.g. tests/gcoap_fileserver

sys/net/application_layer/gcoap/forward_proxy.c Outdated Show resolved Hide resolved
sys/net/application_layer/gcoap/forward_proxy.c Outdated Show resolved Hide resolved
@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

btw: That test script of yours could also be run by Murdock if you replace netdev_tap with socket_zep - see e.g. tests/gcoap_fileserver

Mhhh it somewhat relies on someone™ checking if the proxy actually sent an empty ACK. Since this requires a sniffer i.e. root permissions, I don't think having thing run on Murdock (beyond simply checking forward proxy functionality) for now, does not make sense in the scope of this PR.

Off-topic BTW: there are some quality defects with the way you defined the native rules with these kinds of tests, which

  1. make the test fail when trying to run make test with any other board other than native (e.g. when running compile_and_test_for_board.py but also any unsuspecting user wanting to run the test on their board) and
  2. mess up the dependency resolution for that application. Any board dependent build config should go into the application's Makefile.board.dep, so it will only be executed during the dependency resolution phase.

See also this discussion: #18722 (comment)

chrysn and others added 2 commits October 12, 2022 17:33
Co-Authored-By: Martine S. Lenders <m.lenders@fu-berlin.de>
Signed-off-by: Martine Lenders <m.lenders@fu-berlin.de>
@miri64 miri64 force-pushed the gcoap_forward_proxy/enh/empty-ack branch from 915684f to 63c4fe5 Compare October 12, 2022 15:34
@benpicco
Copy link
Contributor

arg, just squash directly

Since this requires a sniffer i.e. root permissions, I don't think having thing run on Murdock

Since with socket_zep all traffic is encapsulated in UDP and send via the ZEP dispatcher, you can just ask the dispatcher to also forward you all the traffic. No need for root permissions.

make the test fail when trying to run make test with any other board other than native (e.g. when running compile_and_test_for_board.py but also any unsuspecting user wanting to run the test on their board)

Maybe compile_and_test_for_board.py should learn about TEST_ON_CI_WHITELIST 😉
But tbh this script is questionable at best. Most of the tests that can be run without user interaction are software only, so not board dependent at all. While all the 'interesting tests' require to bridge some pins or inspect some LEDs to verify the configuration is correct.

@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

Maybe compile_and_test_for_board.py should learn about TEST_ON_CI_WHITELIST wink

The problem is not the script, the problem is that the build system is not aware about the TEST_ON_CI_WHITELIST variable. Just try make test on a non-native board for your tests.

But tbh this script is questionable at best.

Because that's exactly what this script is doing: test the apps and boards like a user would use them, without any CI sugaring.

@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

But again: off-topic.

@miri64
Copy link
Member Author

miri64 commented Oct 12, 2022

arg, just squash directly

Huh? I did just that... but I had to pull your suggestions first... Or am I missing a Github feature where you can squash suggestions directly into your own commits?

@benpicco
Copy link
Contributor

Nvm, wrote the comment before you squashed the two fixups

@miri64
Copy link
Member Author

miri64 commented Oct 14, 2022

@maribu @leandrolanzieri is it okay to merge this still?

@leandrolanzieri leandrolanzieri merged commit 70acefa into RIOT-OS:master Oct 14, 2022
@leandrolanzieri leandrolanzieri added this to the Release 2022.10 milestone Oct 14, 2022
@miri64 miri64 deleted the gcoap_forward_proxy/enh/empty-ack branch October 14, 2022 09:24
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: Kconfig Area: Kconfig integration Area: network Area: Networking Area: sys Area: System 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.

6 participants