Skip to content

fix(hl2): discover on every local interface - #6009

Merged
jensenpat merged 4 commits into
aethersdr:mainfrom
dacacioa:issue-5569-nic-discovery
Oct 7, 2026
Merged

jensenpat merged 4 commits into
aethersdr:mainfrom
dacacioa:issue-5569-nic-discovery

Conversation

@dacacioa

@dacacioa dacacioa commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #5569.

Hermes-Lite 2 discovery and manual-IP connection now walk the active local IPv4 interfaces instead of relying on a wildcard UDP bind. Each interface gets its own probe socket; the interface that receives the valid HL2 response is retained for the Metis stream and automatic reconnects. Directed and global broadcast behavior for normal discovery is preserved.

This addresses the reported case where the HL2 is connected through one Ethernet interface on a multi-homed Windows host, while the route table selects another adapter for AetherSDR.

Constitution principle honored

Principle XI — Fixes Are Demonstrated. The change was built locally, exercised against the Hermes-Lite 2 discovery path, and validated with the relevant HL2 tests before submission.

Scope

The Flex and ANAN protocol/backend implementations are unchanged. The shared ConnectionPanel source-selection and manual-IP handling is intentionally used by all radio families; this preserves explicit source selection consistently and is not an HL2-specific protocol change.

Test plan

  • Local Windows build passes (cmake --build build-5569 --target AetherSDR)
  • Discovery behavior verified with a real Hermes-Lite 2 on the dedicated Ethernet path
  • Relevant existing tests pass: hl2_metis_protocol_test, hl2_hardware_options_test, and hl2_receivers_test
  • Manual-IP Auto path now retains the responding local interface through connect and reconnect
  • Explicit source selection remains unchanged for all radio families
  • Flex and ANAN backend/protocol paths are unchanged
  • Full CI suite — to be run by GitHub Actions

Checklist

  • Commits are SSH-signed and locally verified (Good "git" signature)
  • No new flat-key AppSettings calls — use nested-JSON-under-one-key (Principle V)
  • Code is clean-room — not decompiled, disassembled, or reverse-engineered from a proprietary binary (Principle IV)
  • All meter UI uses MeterSmoother (no meter UI changed)
  • Documentation updated to disclose the shared cross-family source-selection behavior
  • Security-sensitive changes reference a GHSA if applicable — not applicable

@dacacioa
dacacioa requested a review from a team as a code owner September 28, 2026 18:43
@on8st

on8st commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

One gap, for #5569 specifically: this fixes discovery and carries the replying interface into the stream. The manual-IP path the issue is about also goes through ConnectionPanel, though, and at 20d022d5 its HPSDR probe still binds QHostAddress::AnyIPv4 unless the source mode is Explicit (hpsdr.bind(...), in both the connect and the probe paths). sessionBindAddress is only filled for Explicit as well.

That matches the reporter's note that the Explicit-source workaround works. Without a change there, "connect by IP in Auto mode" can still pick the wrong interface on a multi-homed machine.

Happy to send that half separately if you would rather keep this PR to discovery.

ON8ST

🤖 Generated with Claude Code

@dacacioa

Copy link
Copy Markdown
Contributor Author

Thanks for identifying this gap. The manual-IP path is now covered in e0196c2a.

In Auto mode, the HL2 probe binds one UDP socket per active local IPv4 address, sends the directed probe from each socket, and retains the local address of the socket that receives the valid HL2 reply. That session address is passed through the HL2 connect request and automatic reconnect path, so the Metis stream stays on the same interface. Explicit source selection remains unchanged, and Flex/ANAN paths are untouched.

Validation after the change:

  • AetherSDR Windows target builds successfully with MSVC/Qt.
  • hl2_metis_protocol_test, hl2_hardware_options_test, and hl2_receivers_test pass.
  • git diff --check passes.

@dacacioa

Copy link
Copy Markdown
Contributor Author

Follow-up fix in 334b780: manual source selection is now preserved during interactive IP editing and connection. The saved per-radio route is restored only for startup or an explicitly selected recent address, so choosing a specific interface cannot be silently reset to Auto.

Validation: local Windows AetherSDR build succeeds; hl2_metis_protocol_test, hl2_hardware_options_test, and hl2_receivers_test all pass; git diff --check passes. The commit is SSH-signed and pushed to the PR branch.

@dacacioa

Copy link
Copy Markdown
Contributor Author

The local test package was also corrected: the Qt runtime dependencies were deployed next to AetherSDR.exe, including Qt6OpenGLWidgets.dll. The rebuilt executable now launches successfully as AetherSDR v26.9.5 and remains responsive. This was a packaging/runtime issue only; no source changes were needed for the deployment fix.

@on8st

on8st commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Thanks. I read e0196c2 and 334b780 at 334b780, and the gap I raised is closed:

  • In Auto, the manual probe now binds one socket per enumerateIpv4Candidates() address.
  • The local address of the socket that gets the valid reply becomes info.sessionBindAddress (ConnectionPanel.cpp ~3286). It is saved with the manual profile (~3303) and carried into the connect request through populateFamilyParams, including the reconnect path.
  • The wildcard fallback when Qt exposes no IPv4 candidate keeps the old behaviour.
  • Explicit is unchanged.

What I have not done is run this on our own HL2, which sits on a routed subnet behind a gateway from this Mac. That is a code reading, not a test.

73, ON8ST

@on8st

on8st commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

@dacacioa, this no longer merges onto main: at head 334b7803 a trial merge with main (8df6db2f, v26.10.1) conflicts in Hl2Backend.cpp, ConnectionPanel.cpp and RadioModel.cpp, and the PR shows no CI run. #5569 is still present in v26.10.1 (discovery and the manual probe still bind AnyIPv4 in Auto), so this PR is still the fix.

Merging main into the branch should be enough to get CI running again. If you would like a hand with the three conflicts, say so and I will propose resolutions here; the branch stays yours.

73, ON8ST

@ten9876 ten9876 self-assigned this Oct 4, 2026
ten9876
ten9876 previously requested changes Oct 4, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed head 334b78036a542f8f30471864a4ad5556190fc405.

Issue fit: The implementation addresses #5569's multi-interface failure: broadcast discovery binds per IPv4 interface, manual Auto probes all candidates, and the observed local address reaches the stream socket and reconnect request. The explicit-source reporter workaround supports the diagnosis. I could not independently reproduce the multi-NIC Windows/hardware exchange; the seam violation below must be addressed before merging.

Scope

File/group Change Claimed? Verdict
Hl2Discovery.{h,cpp} Refresh per-interface sockets and directed/global probes Yes Direct issue fit
MetisClient.{h,cpp}, Hl2Backend.cpp Probe source selection and stream bind Yes Direct issue fit
RadioModel.cpp Pass source through initial/reconnect parameters Yes Needed behavior; new family branch violates standing seam notice
ConnectionPanel.cpp probe Auto candidate fan-out, retain winning source Yes Direct issue fit
ConnectionPanel.cpp address-edit/profile logic Preserve explicit selection while typing, across families Not disclosed as cross-family behavior Issue-related; update scope claim

All three authored commits concern discovery/source selection. No added tests, new dependency, capability flag, or changelog edit.

Blocker

  1. Do not add a family-string branch above the backend seam — src/models/RadioModel.cpp:693–700 adds family.compare("hl2", …) to inject a backend-specific parameter. docs/agents/backends.md, standing notice #5554, explicitly says: “no new usesFlexCommandPlane() / family-string branches” before submission in this territory. Existing neighboring family branches are tracked debt, not authorization to grow it. Preserve the source address through neutral connection metadata, with interpretation in Hl2Backend, or obtain an explicit maintainer-approved seam design. All gates are green because none detects this semantic family check; that does not satisfy the standing notice.

Nits / limits (non-blocking)

  1. src/gui/ConnectionPanel.cpp:2685–2694,2756–2763: the body says Flex and ANAN connection paths are untouched, but these shared address-edit/profile handlers are unconditional. They now preserve an explicit source instead of restoring another address's profile during an interactive edit. Disclose the cross-family scope and verify editing A→B versus selecting B from recents. This is a scope/verification limitation, not an independently reproduced GUI regression.
  2. src/core/backends/hl2/Hl2Discovery.cpp:277–299: broadcast results still do not fill RadioInfo::sessionBindAddress. The current backend can discover a source again during its receiver-count probe, but that is a second discovery, not preservation of the original winning broadcast path as the summary implies. Carry the observed source or narrow the claim; ensure a known boardMaxRx does not skip the only remaining source-discovery opportunity.
  3. src/core/backends/hl2/MetisClient.cpp:264–267: the first idle socket may consume the entire remaining timeout before later sockets are inspected. The manual probe already uses bounded 10 ms waits. Apply equivalent bounded/fair polling here if prompt connection matters on machines with many adapters; this latency concern is reasoned from code, not reproduced on the reporter's host.

Verification and attacks

  • No added/modified test owns sockets; no synthetic radio peer was created. Read explicit-bind failure, no candidate, bind failure, malformed/non-HL2 reply, in-use reply, socket replacement/stop, and reconnect paths. The manual in-use refusal and explicit bind-error reporting survive.
  • Built and ran the head's actual socket-free hl2_metis_protocol_test with GCC 16.2.1/C++20. It passes; it checks codec compatibility, not interface routing. The existing tests listed in the body do not exercise the changed multi-interface lifecycle.
  • All four engine boundary, touchpoint, capability-record and command-plane gates pass on merge base and head with byte-identical output. No manifest/baseline was weakened; the family-check blocker is invisible to these gates. Read the backend thread contract; the new local-address discovery still uses the existing blocking I/O-thread invocation.
  • No full-app/bridge run: the key claims require HL2 replies and a multi-homed host; SimBackend cannot establish that routing behavior. Windows/real-radio results remain author/reporter evidence. An isolated refusal/routing-policy test would be appropriate; a fake firmware peer would not.
  • Automated code-review skill unavailable; independent manual audit and focused codec execution performed.

CI and recommendation

Request changes for the new above-seam family branch. Current check rollup is empty. Earliest historical run is 2026-09-29T17:46:37Z, before main's 2026-10-04T18:26:40Z; those runs do not establish today's merge compatibility, and non-strict branch protection does not force a refresh. Resolve the seam routing and correct the scope claims, then obtain required checks and reporter confirmation on the dedicated Ethernet path.

— Jeremy KK7GWY + Codex

@ten9876 ten9876 removed their assignment Oct 4, 2026
ten9876
ten9876 previously requested changes Oct 4, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Independent re-review at head 334b78036a542f8f30471864a4ad5556190fc405. Same head as the earlier ten9876 + Codex review. I re-checked each of its findings: one is confirmed, two of its nits don't hold, and there is a new merge blocker.

Issue fit: yes, for the mechanism. #5569's root cause is a wildcard-bound probe that leaves via the route table's adapter rather than the HL2's link-local NIC. The reporter confirmed it in the thread: picking Ethernet 3 explicitly in Advanced fixes it. This PR does in Auto what that workaround does by hand, in three places:

  • the broadcast sweep, with one socket per local IPv4 address;
  • the manual-IP probe;
  • the connect-time unicast probe, which also carries the winning local address into the stream socket's bind (MetisClient.cpp:369-374).

Scope

File/group Change Claimed? Verdict
Hl2Discovery.{h,cpp} Per-interface sockets rebuilt every sweep; directed and global broadcast Yes In scope
MetisClient.{h,cpp}, Hl2Backend.cpp Per-interface discover(), Params::localAddress, stream bind Yes In scope
RadioModel.cpp Passes sessionBindAddress as hl2.localAddress on connect, reconnect and wake Yes Needed, but the shape is a blocker (below)
ConnectionPanel.cpp HL2 probe Auto probes from every candidate and keeps the winning socket's address Yes In scope
ConnectionPanel.cpp onManualIpChanged / probeRadio Keeps an explicit source while typing; stops re-applying the saved profile on interactive connects Partly: "explicit source selection remains unchanged" Shared by every family's manual connect, and the body says Flex/ANAN are untouched. Disclose it.

All three commits are on-topic and signed. No tests were added, and there's no CHANGELOG entry.

Blockers

  1. This no longer merges with main. git merge-tree origin/main <head> reports conflicts in src/core/backends/hl2/Hl2Backend.cpp, src/gui/ConnectionPanel.cpp and src/models/RadioModel.cpp. RadioModel::populateFamilyParams in particular has moved under #5919 and the ANAN work. Rebase. The fix for blocker 2 changes that same function anyway.

  2. New family-string branch above the seam. This is the earlier review's blocker, and I confirm it. src/models/RadioModel.cpp:693-702 adds family.compare("hl2", …) to decide whether to forward a bind address. docs/agents/backends.md (standing notice #5554) says: "no new usesFlexCommandPlane() / family-string branches". The existing neighbouring branches are tracked debt, not permission to add more.

    A "local interface that reached the radio" is not HL2-specific: RadioInfo::sessionBindAddress is already family-neutral and the Flex probe fills it. So forward it without asking which family. For example, add a typed field to RadioConnectRequest (IRadioBackend.h:43) and set it unconditionally:

    struct RadioConnectRequest {
        QString host;
        quint16 port = 0;
        QString serial;         // when a family identifies radios by serial
        QVariantMap params;     // family-specific extras (namespaced by the backend)
        RadioSerialIdentity serialIdentity;
        // Local IPv4 address the probe reached the radio from; null = let the OS route.
        QHostAddress localBindAddress;
    };

    Then req.localBindAddress = info.sessionBindAddress; (and m_lastInfo.… / selectedRadio.… at the other two call sites). Hl2Backend::connectRadio reads request.localBindAddress instead of params["hl2.localAddress"]. Adding a field to the seam struct is itself a maintainer call. Keeping a params key but inserting it unconditionally would also clear the branch.

Nits (non-blocking)

  • Two of the earlier review's nits don't hold.
    • "Broadcast results don't carry the interface." True of Hl2Discovery, but it doesn't matter: production never supplies boardMaxRx. Only three tests set it (hl2_gain_restore_test.cpp:173, hl2_gain_split_test.cpp:113, hl2_slice_meter_lifecycle_test.cpp:78). So every real connect runs the per-interface unicast discover() (Hl2Backend.cpp:2796-2810), and that fills mp.localAddress from the socket that got the reply. A broadcast-found radio is bound correctly too.
    • "The first idle socket eats the timeout." The total doesn't change: the old single-socket loop also ran to timeoutMs collecting replies, and later sockets' datagrams are buffered and read on the next 1 ms pass. Connect latency is the same 400 ms as before.
  • Two families now sweep subnets two different ways. #6146 (ANAN, ruled acceptable as-is) uses one AnyIPv4 socket plus a directed broadcast per subnet, with the target list as a pure function and a socket-free test of it. This PR rebuilds a bound socket per address on every sweep (Hl2Discovery.cpp:179,212). Per-address binding is what makes the unicast probe and the stream work on the reporter's host, so keep it there. For the broadcast sweep, #6146's shape would be simpler and testable. Aligning the two is worth a follow-up.
  • Repeated interface walks. The NIC walk is hand-rolled in Hl2Discovery::refreshSockets() and MetisClient::discover() (MetisClient.cpp:226) with the same filter as NetworkPathResolver::enumerateIpv4Candidates() in src/core, which the ConnectionPanel half already uses (ConnectionPanel.cpp:3119). sweepNow() also calls QNetworkInterface::allInterfaces() once per socket per sweep to find that socket's broadcast address. That's cheap on Linux and noticeably slower on Windows. Carry the QNetworkAddressEntry from refreshSockets() instead.
  • Global broadcast from every socket. It goes out once per local address per sweep (Hl2Discovery.cpp:207). On Linux a limited broadcast follows the routing table regardless of the source bind, so N copies leave the default interface with N different source addresses. That's harmless noise, but noise on every sweep.
  • Lost diagnostic. ConnectionPanel.cpp:3164 dropped errorString() from the Auto bind-failure message. Keep the last socket's error, as the send path does.
  • No tests were added. The per-interface target derivation can be tested without a socket (see #6146's pattern). A fake HL2 peer would not be acceptable.

Verification and what I tried to break

  • Socket preflight: no test was added or modified. hl2_metis_protocol_test opens no sockets, and it passes at this head (it checks the codec, not routing).

  • Built and ran the head offscreen, with isolated settings and TX disabled, and never connected to anything. Sampled the process's UDP sockets with ss -uanp every 4 s for 32 s:

    • one discovery socket per local IPv4 address (192.168.50.236:*, 100.86.176.80:* on the tailscale /32);
    • a fresh ephemeral port on each sweep;
    • the count stayed at two the whole time, with no wildcard HL2 socket.

    The per-sweep rebuild doesn't leak. Flex discovery on the shared 0.0.0.0:4992 was unaffected: connect list still showed the LAN FLEX and the demo.

  • Couldn't verify: a positive HL2 discovery or connect, because there's no HL2 on this LAN and canon forbids a synthetic peer. The multi-NIC Windows behaviour remains the reporter's evidence.

  • Read for regressions: the explicit-bind failure path, the in-use (0x03) refusal, the non-HL2 board-id gate, and the reconnect path. All are preserved in the restructured probe loop.

  • CI: the earliest run on this head (2026-09-29) predates main's current tip, and the conflicts above confirm the green is stale.

Recommendation: request changes. Rebase, and route the bind address through a family-neutral field instead of the hl2 branch. The core mechanism is right, and it's exactly what the reporter's manual workaround proved.

— Jeremy KK7GWY + Claude

@ten9876

ten9876 commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Maintainer ruling: pass the discovered interface address to the connect through a new family-neutral field on RadioConnectRequest, and remove the "hl2" family-string check in RadioModel.cpp. The rebase blocker still stands.

— Jeremy KK7GWY + Claude

@dacacioa
dacacioa force-pushed the issue-5569-nic-discovery branch from 334b780 to bec8b27 Compare October 5, 2026 18:17

dacacioa commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest upstream/main (3342761) and resolved the merge conflicts.

The connection route is now carried through the family-neutral RadioConnectRequest::localBindAddress. RadioModel no longer contains an hl2 family-string branch, and Hl2Backend consumes the neutral transport metadata directly. The manual multi-interface discovery behavior is preserved.

Validation:

  • Built locally on Windows with Qt 6.12.0 and MSVC.
  • hl2_metis_protocol_test, hl2_hardware_options_test, and hl2_receivers_test all pass.
  • Deployed and launched the resulting AetherSDR.exe; the application is responsive.
  • The rebased commits remain SSH-signed.

dacacioa commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-up completed.

  • The maintainer-requested rebase and family-neutral RadioConnectRequest::localBindAddress change are already on the branch.
  • The shared ConnectionPanel source-selection behavior is now explicitly disclosed in the PR scope; Flex and ANAN backend/protocol implementations remain unchanged.
  • The existing bind-error diagnostic is preserved.
  • Local Windows build and the relevant HL2 tests pass, and the resulting executable was launched successfully.

There are no unresolved inline review threads remaining. GitHub reports the PR as mergeable.

@dacacioa
dacacioa requested a review from ten9876 October 5, 2026 20:40
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Thanks for sticking with this one, @dacacioa — the rebase onto the family-neutral RadioConnectRequest::localBindAddress landed cleanly and CI is finally running on the branch. Two jobs are red, and the pattern of which two is the most useful clue here.

What's red at bec8b27

Job What it builds Result
build (Linux) cmake --build build — whole tree ❌ failed at Build
check-macos cmake --build build — whole tree ❌ failed at Build
check-windows --target AetherSDR + 4 named test exes ✅ passed
Static checks — ✅ passed
Sanitizer option configures — ✅ passed

I could not read the Build step's log text from here, so I won't quote you a compiler error I haven't seen — the step log on either job page has the file and line. But the shape of the failure narrows it a lot.

The useful part: it's almost certainly not in the app target

Look at what each job actually builds (.github/workflows/ci.yml):

  • build (Linux) and check-macos both run plain cmake --build build — the whole tree, app plus every test target (a couple of hundred executables).
  • check-windows runs cmake --build build -j … --target AetherSDR aether-dv-waveform thumbdv_queue_test dstar_model_test digital_voice_waveform_process_test cross_needle_meter_test — deliberately narrow.

Both full-tree jobs fail; the only narrow-target job passes. Two different toolchains (GCC on the Linux CI image, Apple Clang on macOS) fail the same way, so this isn't a GCC quirk or a platform API difference — it's a translation unit that the AetherSDR target doesn't include and your local build therefore never compiled. Your validation was cmake --build build-5569 --target AetherSDR, which is exactly the blind spot.

To reproduce, drop --target:

cmake --build build        # no --target: this is what both red jobs run
ctest --test-dir build --output-on-failure

(The two red jobs also pass -DENABLE_NVIDIA_AFX=ON -DAETHER_GPU_SPECTRUM=ON; the missing --target is the discriminator, so don't fight those flags on Windows if they don't configure for you.)

The test targets that compile your changed sources directly — only ever built by the full tree — are the first places to look:

  • connection_panel_size_test — compiles src/gui/ConnectionPanel.cpp (tests/tests.cmake:6443)
  • startup_autoconnect_lockout_test — also compiles src/gui/ConnectionPanel.cpp (tests/tests.cmake:6479)
  • app_settings_safety_test — compiles src/core/backends/hl2/Hl2Discovery.cpp (tests/tests.cmake:2043)

Beyond those, anything that includes MetisClient.h, Hl2Discovery.h or IRadioBackend.h now sees your new struct members, so a test that builds one of those types positionally would break there.

Dead ends you can skip

Checked at your head commit so you don't spend time on them:

  • Not a broken lane. Linux build is green on current main (035a8583).
  • Not base drift. Merge base is 3342761; main hasn't touched any of your eight files since, so the merged tree compiles the same sources you're reading.
  • Not a missing include in the changed files. All five changed .cpp files syntax-check clean at bec8b27 against Clang/Qt 6.11, as do the ConnectionPanel and backend-seam test TUs. (std::vector without <vector> in ConnectionPanel.cpp is fine — qmetatype.h/qdebug.h pull it in.)
  • #include <QHostAddress> in src/core/backends/IRadioBackend.h is safe. aethercore links Qt6::Network PUBLIC (CMakeLists.txt:2386 at your head), so every consumer of that header already has QtNetwork's include path.
  • The generated seam-probe table is still in sync — python3 tools/gen_seam_probe_table.py --check reports "up to date (51 signals)" on your branch, so adding the field to RadioConnectRequest didn't drift it (consistent with Static checks passing).

Reviewer comments

The task surfaced no GitHub Copilot findings on this PR. @on8st's manual-IP gap looks closed by the per-interface probe in probeHermesLite2(), and the maintainer ruling (family-neutral field on RadioConnectRequest, no "hl2" family-string branch in RadioModel.cpp) is implemented as asked. CI is the only thing in the way.

One non-blocking observation

Hl2Discovery::refreshSockets() closes and deleteLater()s every socket at the top of each sweep, then rebinds. The rebind gets fresh ephemeral ports, so a reply that arrives after the next sweep has started comes back to a port nobody is listening on and is dropped. kMissedSweepsBeforeLost = 3 absorbs most of that, but a radio on a slow or loaded path could flap present/lost. Keeping a socket whose local address hasn't changed, and adding/removing only on real interface changes, would avoid it. Separate from the CI failure — just cheaper to fix now than after merge.

(For the record, the new &mp reference capture in Hl2Backend.cpp is fine: that invokeMethod is Qt::BlockingQueuedConnection, so the lambda completes before mp goes out of scope.)

Paste the compiler error here if it isn't obvious from the log and I'll dig in with you. Thanks again for the real-hardware validation — the multi-homed case only ever gets fixed by someone who has the setup to see it.

— AetherClaude


🤖 aethersdr-agent · cost: $10.3870 · model: claude-opus-5

dacacioa commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

CI follow-up on c3e3442:

The previous Linux and macOS failures were caused by hl2_telemetry_source_test including QHostAddress through the shared connection contract while linking only Qt Core. The test target now links Qt6::Network; no discovery or runtime behavior was changed.

Local validation after the fix:

  • Windows Release target hl2_telemetry_source_test builds successfully with MSVC 2022 and Qt 6.12.0.
  • hl2_telemetry_source_test passes.
  • hl2_metis_protocol_test, hl2_hardware_options_test, and hl2_receivers_test pass.
  • The commit is SSH-signed.

The new GitHub Actions runs are currently marked action_required with no jobs exposed, so they appear to be awaiting workflow approval rather than reporting a new compilation failure.

@jensenpat jensenpat self-assigned this Oct 7, 2026

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed head c3e344275d4c882f9eae8dd5b109ea55b05f91de.

Issue fit: Yes. #5569 is a multi-homed Windows host whose Hermes-Lite 2 sits on a link-local adapter the route table does not select. The reporter confirmed that choosing Ethernet 3 explicitly connects. This PR does that selection in Auto: one discovery socket per up IPv4 address, a manual probe from every enumerateIpv4Candidates() address (link-local included), and the winning local address carried on RadioConnectRequest::localBindAddress into the Metis stream bind and reconnect.

Maintainer ruling: The 2026-10-05 ruling asked for a family-neutral field on RadioConnectRequest and removal of the new hl2 family-string check. That is what landed. populateFamilyParams sets localBindAddress for every family; Hl2Backend::connectRadio is the only consumer. The pre-existing makeBackend, ANAN, and Icom family branches are unchanged.

Scope

File/group Change Claimed? Verdict
Hl2Discovery Per-interface sockets, directed and global broadcast, rebuilt each sweep Yes In scope
MetisClient, Hl2Backend Per-interface unicast discover; stream bind to localAddress Yes In scope
IRadioBackend.h, RadioModel Neutral localBindAddress; no new family branch Yes, maintainer-ruled In scope
ConnectionPanel probe Auto fan-out; keep the winning source Yes In scope
ConnectionPanel edit/profile Preserve an explicit source while typing, all families Disclosed in the updated body In scope, shared UI
tests/tests.cmake Qt6::Network on hl2_telemetry_source_test Yes, the CI compile fix In scope

No CHANGELOG entry. No new test. Flex, ANAN, Icom, RTL, and sim backend sources are untouched. ANAN's manual probe is still a wildcard bind; that is outside #5569.

Approve. No code blocker. Merge waits on required checks: the runs for this SHA are action_required and have not executed. The prior CHANGES_REQUESTED reviews are on 334b78036, before the rebase and the neutral field.

Nits (non-blocking, left in the body so they do not open threads)

  1. Broadcast RadioInfo still leaves sessionBindAddress empty. Production connect does not pass boardMaxRx, so Hl2Backend still runs the per-interface unicast discover() and binds the stream to the socket that answered. A later caller that supplies boardMaxRx would skip that probe and fall back to AnyIPv4.
  2. MetisClient::discover waits the remaining timeout on the first idle socket before reading the others. The reply is still collected; connect latency can be the full 400 ms when the winning adapter is not first. The manual probe already polls at 10 ms.
  3. The Auto "could not open a UDP socket" message no longer includes errorString(). The Explicit path still does.
  4. MetisClient::start still comments that the bind is a wildcard.

Verification

  • Diff matches head c3e34427 against base 33427611 (9 files). git merge-tree with current origin/main (121340c0, 20 commits ahead) reports no conflicts; GitHub reports MERGEABLE.
  • The Linux and macOS failure at bec8b27 is hl2_telemetry_source_test not finding QHostAddress after IRadioBackend.h started including it, while that target linked only Qt Core. This head adds Qt6::Network. An include-closure check of current origin/main test targets found no other target that compiles the seam header without Qt6::Network, aethercore, or aetherdesktop_support.
  • No socket-owning test was added or modified. hl2_telemetry_source_test stays a pure function. I did not build or run it on this SHA; the previous full CI run is the evidence for the compile break, and this SHA has no completed checks.
  • I did not reproduce the multi-NIC Windows exchange. The author reports a real HL2 on the dedicated Ethernet path. No synthetic firmware peer.

Follow-up (not a merge prerequisite)

A socket-free test of the per-interface target list, in the style of #6146, would pin directed-broadcast selection without a fake HL2. The Windows link-local path still needs the reporter's host.

@jensenpat
jensenpat dismissed stale reviews from ten9876 and ten9876 October 7, 2026 17:17

Superseded by c3e3442: rebased onto main and the bind address now rides the family-neutral RadioConnectRequest::localBindAddress, which is the maintainer ruling from 2026-10-05. The hl2 family-string branch is gone.

@jensenpat
jensenpat merged commit 43e2633 into aethersdr:main Oct 7, 2026
5 checks passed
ten9876 added a commit that referenced this pull request Oct 12, 2026
… and re-take the Stable snapshot. Principle VIII. (#6396)

## Summary

This brings the user guide (docs.aethersdr.com) up to date with
everything in v26.10.2 and re-takes the Stable snapshot so the default
site shows it. Docs only, apart from the TCI page prose in
`tools/docs/gen_reference.py`; no application source, test or CI
changes.

Every page was audited against the 106 PRs in the release. Every fact
that was added or kept was checked against the source at `f753dbfc4`,
the v26.10.2 release commit, not taken from the PR bodies alone.

## What changed

- **Menu paths.**
- AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on
**Tools**. They are created on View and then moved to Tools
(`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's
example says **Tools → AetherMap...**.
  - **Show live CW contacts** is on **View**.
  - Waveforms is on **Tools**, not File.
  - The VUDU applet button was called TXDSP.
  - Labels now match the source's `...` vs `…`.
- **Gaps from this cycle.**
- **Windows and Network Diagnostics:** Windows Firewall rules and the
Network Diagnostics Windows Firewall page (#6289); the `aether.network`
and `aether.nvafx` log categories (#6289, #6308).
  - **Remote access:** Tailscale shim revocation semantics (#6325).
  - **Sleep, App Nap and power throttling (#6195):** per-OS notes.
- **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level,
multi-interface discovery and a discovery troubleshooting entry (#6259,
#6254, #5975, #6009, #6293).
- **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply
voltage and link status (#6282, #6129, #6137, #6211).
  - **NB2** on the HL2 and ANAN-G2 (#5966).
- **DVK:** WAV import requirements, the slot menu without Delete, status
text and licensing (#6247, #6262, #6251).
  - **Copy Assist** from unprocessed audio (#6216).
  - **Live CW contacts** replace the decoder's contact card (#6268).
  - **Status-bar indicators** work from the keyboard (#6267).
- **Volume:** controllers, hotkeys and TCI `volume` follow the active
output (#6129, #6260).
- **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the
AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes
(#6031, #6038).
  - **RTL-SDR:** live PPM (#6082).
  - **Icom:** COMP and the MK2 squelch scale (#6174, #6184).
- **Known issues.** Entries whose GitHub issues are closed are removed
(each checked with `gh issue view`). As decided, this includes three
closed today without a demonstrated fix: #5107, #4824 and #5665.
- **US spelling.** `docs-style-guide.md` asked for British spelling; it
now asks for US, by the maintainer's decision, and every page is
converted. The generated TCI pages are converted at their source, the
prose strings in `tools/docs/gen_reference.py`. Code spans, links and
URLs were left alone. UI labels were checked against the source strings,
and none of them are British. Two in-page anchors and two Log Analyzer
rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings.
- **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the
release tree with these corrections, before `main` was merged in, so
Stable carries no post-release docs. `docs/` also has `main`'s
post-release pages, in US spelling. Stable alone keeps the IC-9700
waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so
26.10.2 still has it. The PDF manual already attached to the v26.10.2
release keeps the tag's docs.

## Validation

- `npm ci && npm run build` in `docs/user`: success. This is the
broken-link and broken-anchor check.
- `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules.
- `python3 tools/docs/gen_reference.py --check`: up to date (8 pages).
- `git diff --check`: clean.
- Menu paths on a build of this branch (offscreen, isolated settings, no
radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign
Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools
→ Waveforms...`, `View → Show live CW contacts`.

## Screenshots now out of date (not recaptured)

These need a recapture pass on the radio:
- **Old panadapter title strip (#6386):** `panadapter`, `main-window`,
`first-session-*`, `multi-slice-two-slices`, `split-pair`,
`tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`,
`default-light-theme`.
- **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`,
`aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`,
`connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`,
`rx-agc-calibration`.
- **Other:**
  - `title-bar` (lowercase tab states, #6281);
  - `menu-help` (#6294);
  - `display-panel` (#6371);
  - `support-diagnostics-dialog`;
  - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0);
  - `about-window` (v26.10.1);
  - `radio-setup-qrz`.

## Found during the audit, not fixed here

- **App string bug:** `TailnetShimDialog.cpp:628` tells the user to
"Connect remotely with Connect → Manual"; the real path is **File →
Connect to Radio... → Connect by IP**.
- **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the
slider position as dB, which is wrong on ANAN. #6282 lists this as a
follow-up.
- **MIDI Master Volume on non-Flex radios:** it is refused while PC
Audio is off, whereas hotkeys, FlexControl and HID drive the line out.
The docs describe the code as it is.
- **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the
source strings are `FMN` and `CWR`. Left as is until someone confirms
what the mode menu displays.
- **IC-705 COMP readback:** inferred from the MK2 (#6174) and not
verified on an IC-705.
- **Not yet documented:** the #6230 Radio Health row "Forward responses
skipped", and the Store/MSIX package.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HL2 cannot connect by manual IP until another client performs discovery

4 participants