Skip to content

VTesse 2.0.0: userspace only — inbound forwards, ip=learn, default-route wait, TAP backend removed - #74

Merged
compot-dev merged 5 commits into
mainfrom
feat/route-wait-73
Oct 22, 2026
Merged

compot-dev merged 5 commits into
mainfrom
feat/route-wait-73

Conversation

@compot-dev

@compot-dev compot-dev commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

VTesse 2.0.0: the userspace stack only. Closes #67, closes #68, closes #72, closes #73.

Commits

  1. Always wait for a default route before starting (Always wait for a default route before starting (2.0.0) #73): every start mode, no time limit, a log line per minute, SIGINT/SIGTERM end the wait (exit 0); --show-config and --install do not wait. Bench: route-wait-test.
  2. Userspace only (lwIP: inbound port forwards (accept=PORT[:IP:PORT]) #67, lwIP: learn the address from the first inbound packet on type tun (ip=learn) #68, Remove the TAP backend: userspace proxy only (2.0.0) #72):
    • Inbound forwards (lwIP: inbound port forwards (accept=PORT[:IP:PORT]) #67): accept=PORT[:IP:PORT] / --accept (up to 4). An lwIP listener per port on the tunnel address; each connection is relayed to a host socket, by default the same port on the address of the default-route interface (looked up per connection, netcore/hostaddr.c) — the device's own LAN address, so its web interface asks for the password as from the LAN (owner decision; the issue said 127.0.0.1, which stays available explicitly). The host connect is non-blocking; data from the tunnel waits unacknowledged until it completes; a refused or silent local port aborts the PCB (RST). Shares the proxy's connection limit. MEMP_NUM_TCP_PCB_LISTEN 1 → 4.
    • ip=learn (lwIP: learn the address from the first inbound packet on type tun (ip=learn) #68) in the lwIP stack: the destination of the first unicast packet of a type tun session is the address (/32), its source the peer, set before the packet enters lwIP. After a reconnect the next packet is checked again: a changed address replaces the old one, the same address changes nothing (rather than a reset on every reconnect, which would flap the address). type ether with ip=learn stops the client with exit code 2.
    • TAP backend removed (Remove the TAP backend: userspace proxy only (2.0.0) #72): netcore/tapdev.c, --tap/--ifname/--hook (refused with the reason), LWIP=0/TAP=0, the hook and udhcpc scripts (built-in copies and release files), --install --scripts, the 1.13 hook migration. CI: the TAP-only build and tap-test replaced by accept-test.
    • The service keeps working (owner decision): it runs the userspace stack; --install adds accept=80, accept=22, accept=554 to a configuration without accept lines, and a client started from the U-Boot environment without accept lines opens the same ports — the camera's web interface, ssh and RTSP, as the TAP backend gave them. Updating a 1.x installation removes the old hook scripts and reports --tap in /etc/default/vtesse (left for the user to clean).
    • Docs: README (en, ru, zh-CN), docs/usage.md (new "Inbound forwards", ip=learn), architecture.md, security.md, testing.md, bench/README.md.
  3. Version 2.0.0 and its CHANGELOG section (incompatible: options removed, default behaviour changed).

Checked locally (Docker, as CI): unit tests (+ ASan/UBSan), the minimal build, all five musl targets with the unit tests under qemu, the Android debug APK, the version check, and the bench: selftest, cli-test, accept-test, tun-test, udp-test, fault-test, route-wait-test. proxy-test could not run locally (its host ports 18080/18554 were taken by an unrelated process on the build machine); CI runs it.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

VTesse 2.0.0: userspace-only networking and inbound access

✨ Enhancement ⚙️ Configuration changes 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add inbound forwards and ip=learn so servers can reach device services without TAP.
• Wait for a default route before startup and migrate services to userspace-only operation.
• Remove TAP support, expand acceptance coverage, and document the incompatible 2.0.0 release.
Diagram

graph TD
  R["Host default route"] --> C["CLI startup"] --> V["VTun client"] --> S["lwIP stack"] --> P["Inbound proxy"] --> A["LAN address lookup"] --> D["Device services"]
  X["VTun server"] --> V
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Retain TAP alongside lwIP
  • ➕ Preserves kernel-interface access for arbitrary device processes and existing installations.
  • ➖ Retains root, tun-driver, DHCP-hook, and dual-backend maintenance requirements; conflicts with the userspace-only goal.

Recommendation: The userspace-only approach fits the no-kernel-interface goal, and inbound forwards replace the service access needed by existing deployments. The trade-off is that only configured TCP ports are exposed, while installation now exposes three ports by default.

Files changed (31) +1084 / -1026

Enhancement (8) +539 / -537
install.cMigrate installed services to userspace forwarding +74/-171

Migrate installed services to userspace forwarding

• Adds ports 80, 22, and 554 when an installed configuration has no accept lines. Removes old hook scripts, warns about retained TAP arguments, and refuses --scripts.

cli/install.c

vtesse_cli.cStart a userspace-only client with inbound and learned-IP options +181/-358

Start a userspace-only client with inbound and learned-IP options

• Waits indefinitely for a default route before normal starts, with periodic logging and signal exit. Parses accept forwards and ip=learn, supplies helpdesk port defaults, rejects TAP options, and removes backend branching.

cli/vtesse_cli.c

hostaddr.hDeclare default-route interface address lookup +14/-0

Declare default-route interface address lookup

• Introduces the host-address API used to choose the implicit inbound-forward target.

include/netcore/hostaddr.h

proxy.hExpose inbound-forward proxy configuration +22/-4

Expose inbound-forward proxy configuration

• Adds accept parsing and configuration for up to four tunnel-side listeners sharing the proxy connection limit.

include/netcore/proxy.h

stack.hAdd learned-IP stack configuration +3/-1

Add learned-IP stack configuration

• Allows type tun sessions to derive an address and peer from incoming traffic while applying configured routes.

include/netcore/stack.h

hostaddr.cResolve the host default-route interface address +44/-0

Resolve the host default-route interface address

• Reads the first default route from /proc/net/route and retrieves its interface IPv4 address for implicit inbound targets.

netcore/hostaddr.c

proxy.cRelay tunnel-side TCP connections to host services +164/-3

Relay tunnel-side TCP connections to host services

• Adds lwIP accept listeners, nonblocking host connects, receive buffering until connection, and reset behavior on failure or timeout. Shares connection accounting and relay flow control with existing proxy traffic.

netcore/proxy.c

stack.cLearn type tun addresses before processing inbound packets +37/-0

Learn type tun addresses before processing inbound packets

• Derives a /32 address and peer from the first eligible IPv4 packet, before lwIP receives it. Rechecks after reconnect without resetting an unchanged address.

netcore/stack.c

Refactor (1) +1 / -9
install.hRemove obsolete embedded-hook interfaces +1/-9

Remove obsolete embedded-hook interfaces

• Drops declarations for locating and running TAP hook scripts; installation retains embedded service scripts.

cli/install.h

Documentation (11) +369 / -313
CHANGELOG.mdDocument the incompatible 2.0.0 release +31/-0

Document the incompatible 2.0.0 release

• Records inbound forwards, learned tunnel IPs, route waiting, service defaults, removed TAP options, and upgrade steps.

CHANGELOG.md

README.mdPresent userspace-only installation and inbound access +43/-43

Present userspace-only installation and inbound access

• Explains learned IPs, default service forwards, TAP removal, upgrade implications, and revised release contents in English.

README.md

README.ru.mdUpdate Russian overview for 2.0.0 +43/-43

Update Russian overview for 2.0.0

• Documents userspace-only networking, inbound service defaults, learned IPs, TAP removal, and installation migration.

README.ru.md

README.zh-CN.mdUpdate Chinese overview for 2.0.0 +43/-43

Update Chinese overview for 2.0.0

• Documents userspace-only networking, inbound service defaults, learned IPs, TAP removal, and installation migration.

README.zh-CN.md

README.mdDescribe new userspace acceptance scenarios +15/-9

Describe new userspace acceptance scenarios

• Replaces TAP test instructions with inbound-forward, learned-IP, and default-route-wait scenarios.

bench/README.md

architecture.mdDescribe the single-backend network architecture +43/-42

Describe the single-backend network architecture

• Replaces TAP comparisons with userspace-only architecture and explains learned addressing and inbound proxy flow.

docs/architecture.md

protocol.mdRemove obsolete TAP reconnect explanation +1/-2

Remove obsolete TAP reconnect explanation

• Describes ARP-cache cleanup without referring to kernel-interface carrier handling.

docs/protocol.md

security.mdExplain inbound exposure from service defaults +7/-3

Explain inbound exposure from service defaults

• Clarifies that each accept line exposes a TCP port to the VTun server side and that installed services open three ports by default.

docs/security.md

testing.mdAlign test and release guidance with userspace-only CI +14/-13

Align test and release guidance with userspace-only CI

• Documents new acceptance scenarios and minimal-build coverage while removing TAP build and release references.

docs/testing.md

usage.mdDocument inbound forwards and learned tunnel addressing +128/-114

Document inbound forwards and learned tunnel addressing

• Adds option syntax, forwarding targets and limits, route-wait behavior, ip=learn rules, and service migration guidance. Removes TAP usage and obsolete build switches.

docs/usage.md

embed-scripts.shClarify service-only script embedding +1/-1

Clarify service-only script embedding

• Removes the obsolete built-in hook reference from the script generator's comments.

tools/embed-scripts.sh

Other (11) +175 / -167
build.ymlReplace TAP CI coverage with userspace acceptance +9/-9

Replace TAP CI coverage with userspace acceptance

• Drops the TAP-only build and tap-test, adds inbound-forward and route-wait acceptance, and stops packaging hook scripts.

.github/workflows/build.yml

MakefileBuild only the userspace networking backend +7/-21

Build only the userspace networking backend

• Removes LWIP and TAP build switches and TAP sources, adds host address lookup, and embeds only service scripts.

Makefile

CMakeLists.txtInclude host address lookup in Android native build +1/-0

Include host address lookup in Android native build

• Adds the new host address source required by the shared proxy implementation.

android/app/src/main/cpp/CMakeLists.txt

bench.shExercise inbound forwarding, IP learning, and route waiting +104/-84

Exercise inbound forwarding, IP learning, and route waiting

• Replaces TAP acceptance with tunnel-to-local HTTP, parallel integrity, refusal, reconnect, and invalid-session checks. Adds tests for startup waiting and signal handling.

bench/bench.sh

build-musl.shRemove obsolete LWIP build selection +3/-3

Remove obsolete LWIP build selection

• Cross-builds the userspace-only binary without passing LWIP=1 and revises artifact comments.

ci/build-musl.sh

version.hSet the client version to 2.0.0 +1/-1

Set the client version to 2.0.0

• Bumps the version reported by the client and release pipeline for the incompatible release.

include/vtesse/version.h

lwipopts.hReserve four lwIP TCP listener PCBs +1/-1

Reserve four lwIP TCP listener PCBs

• Raises listener capacity to match the maximum number of inbound accept forwards.

port/lwip/lwipopts.h

S99vtesseRun OpenIPC service without TAP +4/-5

Run OpenIPC service without TAP

• Clears default TAP arguments and removes tun-module loading while retaining service overrides.

scripts/S99vtesse

vtesse.initRun OpenWrt service on the userspace stack +3/-5

Run OpenWrt service on the userspace stack

• Removes TAP arguments and tun-module loading from the procd service.

scripts/vtesse.init

vtesse.serviceRun systemd service without a kernel interface +3/-5

Run systemd service without a kernel interface

• Removes the tun-module prerequisite and TAP arguments from ExecStart.

scripts/vtesse.service

test_install.shTest service defaults and legacy TAP cleanup +39/-33

Test service defaults and legacy TAP cleanup

• Checks default and custom accept lines, removal of legacy hook files, warnings for old TAP arguments, and refusal of removed options.

tests/test_install.sh

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. The learning test omits its broadcast case 📎 Requirement gap ⚙ Maintainability
Description
cmd_accept_test waits for the first-packet message and then immediately uses a unicast HTTP SYN to
teach the address, without injecting a broadcast, multicast or zero-destination packet first. A
regression that incorrectly learns from excluded traffic would therefore pass the acceptance suite,
leaving the address-learning lifecycle requirement unverified.
Code

bench/bench.sh[R177-184]

+    until logs | grep -q "waiting for the first packet" || [ $i -gt 20 ]; do sleep 1; i=$((i + 1)); done
+    # shellcheck disable=SC2034  # sha, t0: used inside the eval strings of check
+    sha=$(docker exec "$c" cat /tmp/www/big.sha256)
+    check "ip=learn: no address before the first packet" '! logs | grep -q "address 10.76"'
+    check "accept: the server reaches the client's web server (first SYN teaches the address)" \
+        'srv "curl -fsS --max-time 10 -o /dev/null http://10.76.0.2:8080/big.sha256"'
+    check "ip=learn: address 10.76.0.2/32, peer 10.76.0.1" \
+        'logs | grep -q "address 10.76.0.2 mask 255.255.255.255 (learnt), peer 10.76.0.1"'
Evidence
PR Compliance ID 7 requires the acceptance test to verify that the initial address is absent,
excluded broadcast traffic is ignored, the first SYN learns the address, and the lifecycle behavior
remains correct. The added test waits for the unlearned state and then proceeds directly to a valid
unicast HTTP request, with no excluded-destination or pre-learning outbound scenario.

Enforce address-learning configuration and test lifecycle
bench/bench.sh[177-184]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The inbound acceptance test does not verify that broadcast, multicast, or 0.0.0.0 destinations are ignored before the first valid unicast packet teaches the address. It also should exercise the required pre-learning outbound failure behavior so regressions in the address-learning lifecycle are caught.
## Fix Focus Areas
- bench/bench.sh[177-184]
## Recommended Fix
Before the first valid HTTP SYN, send or trigger representative broadcast, multicast, and 0.0.0.0-destination traffic through the type-tun session and assert that no address is learned. Add checks that outbound SOCKS5 and static-forward attempts fail before learning, then retain the existing unicast SYN check to confirm the valid packet teaches the address.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Legacy TAP settings are silently ignored ✓ Resolved
Description
The load_config parser handles supported keys but has no rejection path for legacy tap= or
hook= settings. When an old configuration contains either key, parsing continues into userspace
startup without identifying the TAP removal, so the installation is not stopped loudly.
Code

cli/vtesse_cli.c[R205-211]

+        else if (!strcmp(k, "accept")) {
+            if (c->accept_count >= NC_PROXY_MAX_ACCEPTS) {
+                fprintf(stderr, "%s: at most %d accept lines\n", full, NC_PROXY_MAX_ACCEPTS);
+                fclose(f);
+                return -1;
+            }
+            snprintf(c->accept[c->accept_count++], sizeof(c->accept[0]), "%s", v);
Evidence
The configuration parser adds handling for supported keys such as accept but provides no error
branch for legacy tap or hook keys, allowing them to fall through silently. This violates the
requirement that legacy TAP configuration fail explicitly.

Reject legacy TAP configuration explicitly
cli/vtesse_cli.c[167-225]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Legacy `tap=` and `hook=` settings in `/etc/vtesse.conf` are silently ignored instead of producing an explicit TAP-removal error.
## Fix Focus Areas
- cli/vtesse_cli.c[167-225]
## Recommended Fix
Recognize `tap` and `hook` while parsing configuration and return a clear error naming the TAP backend removal; ensure parsing does not continue into userspace startup when either key is present.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Excess inbound connections go untested ✓ Resolved
Description
cmd_accept_test exercises eight parallel downloads and a closed local port but never opens more
than the shared connection limit at once. The required reset behavior for an inbound
connection-limit excess therefore remains unverified by the acceptance bench.
Code

bench/bench.sh[R184-186]

+    check "accept: 8 parallel 20 MiB downloads, sha256 intact" '
+        srv "for n in 1 2 3 4 5 6 7 8; do (curl -fsS --max-time 120 http://10.76.0.2:8080/big.bin | sha256sum > /tmp/acc.\$n) & done; wait"
+        [ "$(srv "cat /tmp/acc.*" | cut -d" " -f1 | sort -u)" = "$sha" ]'
Evidence
The acceptance test only launches eight parallel downloads, while the implementation uses a larger
shared connection limit and contains a separate reset path for excess inbound connections. No
scenario in the changed test invokes or checks that required limit behavior.

Verify inbound accept behavior across load and reconnect
bench/bench.sh[184-191]
netcore/proxy.c[395-408]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The inbound acceptance test does not verify that connections exceeding the shared limit are reset.
## Fix Focus Areas
- bench/bench.sh[184-191]
## Recommended Fix
Add a scenario that holds the configured maximum number of inbound connections open, attempts an additional connection, and asserts that the excess connection is reset before cleanup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View action required (1)
4. Manual routes with ip=learn are dropped on first address ✓ Resolved
Description
learn() calls apply_routes(&st.cfg.routes, routes.count == 0) and then netif_set_addr(). On
the first bind (!st.bound), netif_set_addr() runs status_cb, and because static_ip is 0 in
learn mode, status_cb calls apply_dhcp_routes(NULL) → apply_routes(&none, 1). That wipes the
configured route= lines and turns on a default route through the tunnel. The CLI accepts route=
together with ip=learn and nc_stack_cfg_t says those routes are honoured, but they only take
effect after a later address *change*, so the first session sends every destination into the tunnel.
Code

netcore/stack.c[R307-309]

+    /* like the manual address: explicit routes, or a default route via the peer */
+    apply_routes(&st.cfg.routes, st.cfg.routes.count == 0);
+    netif_set_addr(&st.netif, &a, &m, &g);
Evidence
status_cb treats any non-static_ip bind as a DHCP bind and calls apply_dhcp_routes. With no
DHCP data, that is apply_routes(&none, 1), which overwrites st.routes/use_default right after
learn() set them. The CLI passes sc.routes = st_routes in learn mode and only rejects gw, not
route.

netcore/stack.c[252-264]
netcore/stack.c[217-228]
netcore/stack.c[291-312]
cli/vtesse_cli.c[802-806]
cli/vtesse_cli.c[912-915]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
With `ip=learn`, the first `netif_set_addr()` in `learn()` triggers `status_cb`. Because `static_ip` is 0, `status_cb` calls `apply_dhcp_routes(NULL)`, which replaces the configured routes with 'default route only'.
## Fix Focus Areas
- netcore/stack.c[252-264]
- netcore/stack.c[304-311]
## Recommended Fix
In `status_cb`, call `apply_dhcp_routes` only when the address came from DHCP: `if (!st.cfg.static_ip && !st.cfg.learn_ip)`. Alternatively, in `learn()`, call `apply_routes(&st.cfg.routes, st.cfg.routes.count == 0)` after `netif_set_addr()`. Add a unit/bench check that `route=` with `ip=learn` leaves `default_route` off.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Malformed packets can lock the wrong address ✓ Resolved
Description
learn() marks the address as learned and installs the packet destination before lwIP validates the
IPv4 header. If the first packet of a tun session has a malformed length or checksum, lwIP drops it,
but subsequent valid packets cannot correct the address until the session reconnects.
Code

netcore/stack.c[R303-305]

+    if ((pkt[0] & 0x0f) < 5 || !unicast(dst) || !unicast(src) || dst == src)
+        return;
+    st.learnt = 1;
Evidence
The new learning path runs before lwIP input and prevents further learning once st.learnt is set.
lwIP performs length and checksum checks afterward and drops packets that fail them.

netcore/stack.c[296-327]
third_party/lwip/src/core/ipv4/ip4.c[495-544]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The first IPv4-looking packet can set `st.learnt` and the interface address even when lwIP will reject that packet. Later valid packets cannot teach the correct address during the same session.
## Fix Focus Areas
- netcore/stack.c[296-327]
## Recommended Fix
Before calling `learn()`, validate that the complete IPv4 header fits in the packet, the declared total length is valid, and the header checksum is correct. Leave `st.learnt` unset for invalid packets so the next valid packet can supply the address.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Reconnected sessions can send from an old address ✓ Resolved
Description
on_tunnel_event() retains the previous learned address and brings the link up while the next
session’s address remains unknown. If the server assigns a different address, proxy traffic or
existing connections can transmit with the old source address before an inbound packet triggers
learn().
Code

netcore/stack.c[347]

+        st.learnt = 0;                  /* learn_ip: the next packet tells the address again */
Evidence
The reconnect path resets only the learning flag after raising the link; the bound address remains
in place. Route approval uses the bound address, and L3 output sends packets without checking
whether this session has learned its address.

netcore/stack.c[340-357]
netcore/stack.c[142-151]
netcore/stack.c[177-184]
netcore/stack.c[291-322]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
After a learned-address session reconnects, outbound traffic can use the previous session’s address before the first inbound packet supplies the new one.
## Fix Focus Areas
- netcore/stack.c[340-357]
- netcore/stack.c[142-151]
- netcore/stack.c[177-184]
## Recommended Fix
Mark learned addressing unavailable across reconnect and prevent outbound transmission or route approval until a valid packet confirms the new session address. Preserve the existing address without unnecessary reconfiguration when that packet confirms the same address.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Invalid settings can wait forever to fail ✓ Resolved
Description
main() calls wait_default_route() before validating credentials, IP settings, and configured
inbound forwards. On a device without a default route, those errors cannot be reported until a route
appears, even though the wait has no time limit.
Code

cli/vtesse_cli.c[R742-746]

+    if (!show_config) {
+        signal(SIGINT, on_signal);
+        signal(SIGTERM, on_signal);
+        if (wait_default_route() < 0)
+            return 0;               /* stopped while waiting */
Evidence
The new wait precedes check_config() and the IP and accept parsers; its loop ends only when a
default route appears or a signal stops it.

cli/vtesse_cli.c[100-119]
cli/vtesse_cli.c[723-747]
cli/vtesse_cli.c[780-837]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Malformed startup settings are hidden behind an indefinite default-route wait.
## Fix Focus Areas
- cli/vtesse_cli.c[738-747]
- cli/vtesse_cli.c[748-837]
## Recommended Fix
Perform configuration checks that do not require the route-selected interface before waiting. Keep MAC-dependent device identity and password derivation after the wait.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (2)
8. Some inbound forwards reset despite a usable route ✓ Resolved
Description
nc_default_route_addr() stops at the first default-route row and queries only that interface for
an IPv4 address. When it has no IPv4 address but a later default-route interface does, an inbound
forward with no explicit target gets zero and aborts rather than trying the usable interface.
Code

netcore/hostaddr.c[R28-30]

+    while (!found && fgets(line, sizeof(line), f))
+        found = sscanf(line, "%15s %lx %lx %x %d %d %d %lx", ifname, &dst, &gw, &flags, &ref, &use,
+                       &metric, &mask) == 8 && dst == 0 && mask == 0;
Evidence
The route scan ends at the first matching row, an unsuccessful address query leaves the return value
zero, and the inbound caller treats zero as a connection failure.

netcore/hostaddr.c[16-43]
netcore/proxy.c[353-360]
netcore/proxy.c[395-425]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The implicit inbound-forward target fails if the first default-route interface has no IPv4 address, even when another default route is usable.
## Fix Focus Areas
- netcore/hostaddr.c[26-43]
- netcore/proxy.c[353-360]
## Recommended Fix
Continue scanning eligible default-route entries when an interface has no IPv4 address, and return zero only after no usable interface remains.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Inbound exposure of ssh/web/RTSP cannot be turned off ✓ Resolved
Description
build_config appends accept=80, accept=22 and accept=554 whenever the file has no non-empty
accept line. main() likewise opens the same three listeners in U-Boot environment mode when
pc.accept_count is 0. An empty accept= is not counted by has_value and is rejected by
nc_accept_parse, so a user who deleted the lines to avoid exposing sshd to the VTun server gets
them back on every --install. The env-mode service always relays these ports to the device's own
LAN address, including on generic systemd Linux hosts.
Code

cli/install.c[R578-582]

+    if (!has_accept && (!env || old)) {
+        if (out_kv(&o, "accept", "80") < 0 || out_kv(&o, "accept", "22") < 0 || out_kv(&o, "accept", "554") < 0)
+            return -1;
+        printf("accept=80, 22, 554: the server reaches the camera's web, ssh and RTSP (#67)\n");
+    }
Evidence
has_accept |= v uses has_value, so only non-empty accept lines count. The CLI env-mode default
fills ports 80/22/554 whenever no accept was given. nc_accept_parse rejects an empty or 0 value,
so there is no value that means 'none'.

cli/install.c[545-546]
cli/vtesse_cli.c[838-849]
netcore/proxy.c[428-447]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The default inbound forwards (80, 22, 554) are re-added by every `--install` and forced in U-Boot env mode. A user cannot disable them.
## Fix Focus Areas
- cli/install.c[575-582]
- cli/vtesse_cli.c[828-849]
- netcore/proxy.c[428-447]
## Recommended Fix
Support `accept=none` (in the file and as `--accept none`). In `load_config`/`main`, record that the user opted out and skip the env-mode defaults. In `build_config`, treat any `accept` key, including `none`, as present so nothing is appended. Consider adding the defaults only on a fresh install or a 1.x upgrade, not on every re-run. Document the opt-out in docs/usage.md and security.md.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread cli/vtesse_cli.c
Comment thread bench/bench.sh
Comment thread netcore/stack.c
Comment thread cli/vtesse_cli.c Outdated
Comment thread netcore/hostaddr.c Outdated
Comment thread netcore/stack.c
Comment thread cli/install.c
@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

Comment thread netcore/stack.c Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fe5b656

@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

Comment thread bench/bench.sh
Comment on lines +177 to +184
until logs | grep -q "waiting for the first packet" || [ $i -gt 20 ]; do sleep 1; i=$((i + 1)); done
# shellcheck disable=SC2034 # sha, t0: used inside the eval strings of check
sha=$(docker exec "$c" cat /tmp/www/big.sha256)
check "ip=learn: no address before the first packet" '! logs | grep -q "address 10.76"'
check "accept: the server reaches the client's web server (first SYN teaches the address)" \
'srv "curl -fsS --max-time 10 -o /dev/null http://10.76.0.2:8080/big.sha256"'
check "ip=learn: address 10.76.0.2/32, peer 10.76.0.1" \
'logs | grep -q "address 10.76.0.2 mask 255.255.255.255 (learnt), peer 10.76.0.1"'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. The learning test omits its broadcast case 📎 Requirement gap ⚙ Maintainability

cmd_accept_test waits for the first-packet message and then immediately uses a unicast HTTP SYN to
teach the address, without injecting a broadcast, multicast or zero-destination packet first. A
regression that incorrectly learns from excluded traffic would therefore pass the acceptance suite,
leaving the address-learning lifecycle requirement unverified.
Agent Prompt
## Issue description
The inbound acceptance test does not verify that broadcast, multicast, or 0.0.0.0 destinations are ignored before the first valid unicast packet teaches the address. It also should exercise the required pre-learning outbound failure behavior so regressions in the address-learning lifecycle are caught.

## Fix Focus Areas
- bench/bench.sh[177-184]

## Recommended Fix
Before the first valid HTTP SYN, send or trigger representative broadcast, multicast, and 0.0.0.0-destination traffic through the type-tun session and assert that no address is learned. Add checks that outbound SOCKS5 and static-forward attempts fail before learning, then retain the existing unicast SYN check to confirm the valid packet teaches the address.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 06fdd8e

@compot-dev
compot-dev merged commit 54ded64 into main Oct 22, 2026
10 checks passed
@compot-dev
compot-dev deleted the feat/route-wait-73 branch October 22, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant