RIOT icon indicating copy to clipboard operation
RIOT copied to clipboard

gcoap_forward_proxy: add capability to add upstream proxy

Open miri64 opened this issue 3 years ago • 1 comments

Contribution description

"Good luck, I'm Behind 7 Proxies" meme

This allows to set an upstream proxy for the local forward proxy. I had a look at what @cgundogan did for https://doi.org/10.1145/3405656.3418718, however, his approach is very app-centric and I think this solution is much more streamlined (albeit, probably to @chrysn's horror, URI-based).

Testing procedure

I patched examples/gcoap/client.c to be able to set the proxy for the local forward proxy (if one is present) and capabilities to get some feedback from the gcoap_forward_proxy_upstream pseudo-module.

Patch
diff --git a/examples/gcoap/client.c b/examples/gcoap/client.c
index d22c62eac0..8ef6895f30 100644
--- a/examples/gcoap/client.c
+++ b/examples/gcoap/client.c
@@ -28,2 +28,3 @@
 #include "net/gcoap.h"
+#include "net/gcoap/forward_proxy.h"
 #include "net/utils.h"
@@ -238,3 +239,5 @@ int gcoap_cli_cmd(int argc, char **argv)
     else if (strcmp(argv[1], "proxy") == 0) {
+        static char proxy[CONFIG_GCOAP_FORWARD_PROXY_UPSTREAM_URI_MAX];
         if ((argc == 5) && (strcmp(argv[2], "set") == 0)) {
+            int res;
             if (!_parse_endpoint(&_proxy_remote, argv[3], argv[4])) {
@@ -243,2 +246,7 @@ int gcoap_cli_cmd(int argc, char **argv)
             }
+            sprintf(proxy, "coap://[%s]:%s/", argv[3], argv[4]);
+            if ((res = gcoap_forward_proxy_upstream_set(proxy)) < 0) {
+                printf("Could not set upstream proxy %s: %s\n", proxy, strerror(-res));
+                return 1;
+            }
             _proxied = true;
@@ -249,4 +257,11 @@ int gcoap_cli_cmd(int argc, char **argv)
             _proxied = false;
+            gcoap_forward_proxy_upstream_set(NULL);
             return 0;
         }
+        if (gcoap_forward_proxy_upstream_get(proxy, sizeof(proxy)) > 0) {
+            printf("proxy: %s\n", proxy);
+        }
+        else {
+            puts("proxy: not set");
+        }
         printf("usage: %s proxy set <addr>[%%iface] <port>\n", argv[0]);
Creating 4 bridged TAP interfaces (`dist/tools/tapsetup/tapsetup -c 4`) and sniffing on `tapbr0` with Wireshark, I ran the following test script.
Test script
#! /usr/bin/env python3
import sys

from riotctrl.ctrl import RIOTCtrl

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


def set_proxy(client, proxy):
    res = client.cmd(f"coap proxy")
    assert f"proxy: not set" in res
    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
    res = client.cmd(f"coap proxy")
    assert f"proxy: coap://[{proxy_addr}]:5683" in res


if __name__ == "__main__":
    RIOTCtrl.MAKE_ARGS = ("-j",)
    env = {"USEMODULE": "gcoap_forward_proxy_upstream", "PORT": "tap0"}
    ctrl0 = RIOTCtrl(application_directory="examples/gcoap", env=env)
    env = {"USEMODULE": "gcoap_forward_proxy_upstream", "PORT": "tap1"}
    ctrl1 = RIOTCtrl(application_directory="examples/gcoap", env=env)
    env = {"USEMODULE": "gcoap_forward_proxy_upstream", "PORT": "tap2"}
    ctrl2 = RIOTCtrl(application_directory="examples/gcoap", env=env)
    env = {"USEMODULE": "gcoap_forward_proxy_upstream", "PORT": "tap3"}
    ctrl3 = RIOTCtrl(application_directory="examples/gcoap", env=env)
    ctrl0.make_run(["flash"])
    with (
        ctrl0.run_term(reset=False),
        ctrl1.run_term(reset=False),
        ctrl2.run_term(reset=False),
        ctrl3.run_term(reset=False),
    ):
        ctrl0.term.logfile = sys.stdout
        ctrl1.term.logfile = sys.stdout
        ctrl2.term.logfile = sys.stdout
        ctrl3.term.logfile = sys.stdout
        shell0 = Ifconfig(ctrl0)
        shell1 = Ifconfig(ctrl1)
        shell2 = Ifconfig(ctrl2)
        shell3 = Ifconfig(ctrl3)
        netifs = IfconfigListParser().parse(shell0.ifconfig_list())
        server_addr = list(netifs.values())[0]["ipv6_addrs"][0]["addr"]
        shell3.cmd(f"coap get {server_addr} 5683 /.well-known/core")
        # comes in asynchronously, so we need to expect instead of checking result
        shell3.riotctrl.term.expect_exact(
            '</cli/stats>;ct=0;rt="count";obs,</riot/board>,</>'
        )
        set_proxy(shell2, shell1)
        set_proxy(shell3, shell2)
        shell3.cmd(f"coap get {server_addr} 5683 /.well-known/core")
        # comes in asynchronously, so we need to expect instead of checking result
        shell3.riotctrl.term.expect_exact(
            '</cli/stats>;ct=0;rt="count";obs,</riot/board>,</>'
        )
        shell2.cmd("coap proxy unset")
        res = shell2.cmd(f"coap proxy")
        assert f"proxy: not set" in res
        shell3.cmd(f"coap get {server_addr} 5683 /.well-known/core")
        # comes in asynchronously, so we need to expect instead of checking result
        shell3.riotctrl.term.expect_exact(
            '</cli/stats>;ct=0;rt="count";obs,</riot/board>,</>'
        )

With that Wireshark showed the following 12 CoAP packets:

The first two are a request-response pair for the unproxied GET request (destination is the link local of ctrl0, fe80::e0bc:7dff:fecb:f550, and there is no Proxy-URI option present).

The next 6 are 3 GET requests, the first two of them are proxied (with Proxy-URI option set to coap://[fe80::e0bc:7dff:fecb:f550]:5683/.well-known/core), while the last goes to actual server. Then are following their 3 responses.

For the last 4, the upstream most proxy got its proxy removed (resetting the behavior of the downstream most proxy to a proxy without an upstream). We see first 1 request going to the downstream most proxy, which then forwards it to the server. That request is followed by the 2 corresponding responses.

Issues/PRs references

None

miri64 avatar Jul 28 '22 15:07 miri64

Hi! :bird:

Could you rebase? There are not tests for this, do you intend to write some? Are there difficulties to it?

Teufelchen1 avatar Jan 30 '24 14:01 Teufelchen1