From 7da91e401fa47579d560d1b73ad6c5e169ff7598 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 12:48:12 +0000 Subject: [PATCH 1/2] Send a cut-off network's ipctool report to openipc.ru with a 307 From a network the TSPU cuts off, a report with a flash backup sent to openipc.org's port 80 froze at its first kilobytes and sat at "Sending 0%" (OpenIPC/ipctool#232). Port 80 now answers it with `307 http://openipc.ru/api/v1/reports`, whose port 80 proxies it back here, as it already does for ipctool's downloads (#397). 307 keeps the POST and its body. ipctool since OpenIPC/ipctool#233 waits for an answer to the headers before sending the body and follows a plain-HTTP 307, so a plain `ipctool upload` gets through from Russia with no --host. An older one reports the 307 and stops, which is quicker than the freeze. Install after #233 is in the published ipctool. Identify stays: a few kilobytes, which the freeze lets through. --- deploy/nginx/check-config.sh | 18 ++++++++++-------- deploy/nginx/sites-available/org.openipc | 11 +++++++++++ service/deploytest/blocked_nets_test.go | 10 +++++++--- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/deploy/nginx/check-config.sh b/deploy/nginx/check-config.sh index db9f8886..a1f1bfe3 100755 --- a/deploy/nginx/check-config.sh +++ b/deploy/nginx/check-config.sh @@ -721,13 +721,14 @@ for probe in "GET /ru/get-started?a=1" "POST /snapshots" "GET /api/v1/wizard/gk7 fail=1 fi done -# On port 80 only ipctool's downloads go, to the mirror's own port 80; the -# report upload stays, since ipctool follows no redirect. +# On port 80 ipctool's downloads and its report go to the mirror's own port +# 80 -- the report with a 307, so the POST follows as it is. plain_blocked() { curl -sS -o /dev/null -w '%{http_code} %{redirect_url}' --max-time 5 --interface 127.0.0.3 \ --resolve "openipc.org:80:127.0.0.1" "$@" 2>/dev/null } -for probe in "302 GET /ipctool http://openipc.ru/ipctool" "302 GET /ipctool-mips32 http://openipc.ru/ipctool-mips32"; do +for probe in "302 GET /ipctool http://openipc.ru/ipctool" "302 GET /ipctool-mips32 http://openipc.ru/ipctool-mips32" \ + "307 POST /api/v1/reports http://openipc.ru/api/v1/reports"; do set -- $probe got=$(plain_blocked -X "$2" "http://openipc.org$3") if [ "$got" = "$1 $4" ]; then @@ -737,14 +738,15 @@ for probe in "302 GET /ipctool http://openipc.ru/ipctool" "302 GET /ipctool-mips fail=1 fi done -# The report has to reach the service (the stub answers 200, with the -# location's X-Served-By): a redirect, a 5xx or no answer means it does not. +# Identify stays: it is a few kilobytes, which the freeze lets through. It +# has to reach the service (the stub answers 200, with the location's +# X-Served-By): a redirect, a 5xx or no answer means it does not. got=$(curl -sS -o /dev/null -D - --max-time 5 --interface 127.0.0.3 --resolve "openipc.org:80:127.0.0.1" \ - -X POST "http://openipc.org/api/v1/reports" 2>/dev/null | tr -d '\r' | awk 'NR==1{s=$2} tolower($1)=="x-served-by:"{b=$2} END{print s" "b}') + -X POST "http://openipc.org/api/v1/boards/identify" 2>/dev/null | tr -d '\r' | awk 'NR==1{s=$2} tolower($1)=="x-served-by:"{b=$2} END{print s" "b}') if [ "$got" = "200 go" ]; then - printf ' %-32s %-5s (POST over HTTP from a blocked network, reaches the service)\n' /api/v1/reports 200 + printf ' %-32s %-5s (POST over HTTP from a blocked network, reaches the service)\n' /api/v1/boards/identify 200 else - printf ' %-32s %-5s MISMATCH: a report over HTTP from a blocked network does not reach the service\n' /api/v1/reports "$got" + printf ' %-32s %-5s MISMATCH: identify over HTTP from a blocked network does not reach the service\n' /api/v1/boards/identify "$got" fail=1 fi expect $FW 200 go hsts diff --git a/deploy/nginx/sites-available/org.openipc b/deploy/nginx/sites-available/org.openipc index 0516e0e3..f81020d8 100644 --- a/deploy/nginx/sites-available/org.openipc +++ b/deploy/nginx/sites-available/org.openipc @@ -47,7 +47,18 @@ server { # ipctool uploads a report over plain HTTP: stock camera firmware has no # TLS. Only the upload and identify are answered here; everything else # redirects. + # + # A network the TSPU cuts off is sent to openipc.ru's port 80, which + # proxies the report back here: a flash backup never gets through the + # freeze, and the upload sat at "Sending 0%" until it timed out + # (ipctool#232). 307, so the POST and its body go as they are. ipctool + # since that issue waits a moment for an answer to the headers before it + # sends the body, and follows a plain-HTTP 307; an older one reports the + # 307 and stops, which is quicker than the freeze. location = /api/v1/reports { + if ($openipc_blocked_net) { + return 307 http://openipc.ru$request_uri; + } client_max_body_size 300m; client_body_timeout 300; proxy_request_buffering off; diff --git a/service/deploytest/blocked_nets_test.go b/service/deploytest/blocked_nets_test.go index 8b854486..e4b714b6 100644 --- a/service/deploytest/blocked_nets_test.go +++ b/service/deploytest/blocked_nets_test.go @@ -75,7 +75,7 @@ func TestBlockedNetsGoToTheMirror(t *testing.T) { } }) - t.Run("the redirect is the HTTPS vhost's, and on port 80 only ipctool's", func(t *testing.T) { + t.Run("the redirect is the HTTPS vhost's, and on port 80 only ipctool's and its report's", func(t *testing.T) { servers := strings.Split("\n"+directives(v), "\nserver {") if len(servers) != 3 { t.Fatalf("expected two server blocks in org.openipc, found %d", len(servers)-1) @@ -87,8 +87,12 @@ func TestBlockedNetsGoToTheMirror(t *testing.T) { tools := block(plain, "location ~ ^/(ipctool|ipctool-mips32|ipctool-arm64)$ {") mustMatch(t, `if \(\$openipc_blocked_net\) \{\s*return 302 http://openipc\.ru\$request_uri;`, tools, "a cut-off network fetching ipctool is not sent to the mirror's plain HTTP, where it gets through") - if strings.Count(plain, "openipc_blocked_net") != 1 { - t.Error("port 80 redirects more than ipctool: stock firmware sends its report there and follows no redirect") + // A 307, not a 302: the report is a POST and its body has to follow. + report := block(plain, "location = /api/v1/reports {") + mustMatch(t, `if \(\$openipc_blocked_net\) \{\s*return 307 http://openipc\.ru\$request_uri;`, report, + "a cut-off network's report is not sent to the mirror's plain HTTP, and freezes at its first kilobytes") + if strings.Count(plain, "openipc_blocked_net") != 2 { + t.Error("port 80 redirects more than ipctool and its report") } mustNotContain(t, directives(vhost(t, "org.openipc.dev")), "openipc_blocked_net", "dev would send its testers to the production mirror") From fef3a4ae4d63868c8c6973ff116e1a2f02316dbd Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 12:57:48 +0000 Subject: [PATCH 2/2] Blocked nets: say port 80 sends the report with a 307 too Qodo on #422: the geo block's comment still said port 80's report upload was left alone. --- deploy/nginx/conf.d/openipc-blocked-nets.conf | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/deploy/nginx/conf.d/openipc-blocked-nets.conf b/deploy/nginx/conf.d/openipc-blocked-nets.conf index fea4cb8c..3356a646 100644 --- a/deploy/nginx/conf.d/openipc-blocked-nets.conf +++ b/deploy/nginx/conf.d/openipc-blocked-nets.conf @@ -30,11 +30,13 @@ # fails if an address in set_real_ip_from is missing here. # # Applied in sites-available/org.openipc's HTTPS server, and on port 80 to -# the ipctool downloads alone, which go to http://openipc.ru: stock camera -# firmware has no TLS, and openipc.ru's port 80 proxies them from here -# (mirrors/ru.openipc.snippet). Port 80's report upload is left alone, because -# ipctool follows no redirect: on openipc.ru the report page tells it -# `--host openipc.ru`, and a report alone is small enough to get through. +# ipctool's downloads (302) and its report upload (307, so the POST and its +# body follow), which go to http://openipc.ru: stock camera firmware has no +# TLS, and openipc.ru's port 80 proxies them from here +# (mirrors/ru.openipc.snippet). ipctool since OpenIPC/ipctool#233 reads the +# 307 before sending the body and follows it; an older one reports it and +# stops, and on openipc.ru the report page tells it `--host openipc.ru`. +# Identify is not redirected: it is a few kilobytes, which get through. geo $realip_remote_addr $openipc_blocked_net { default 0; include /etc/nginx/conf.d/openipc-blocked-nets.list;