Skip to content

feat: WiFi hotspot port forwarding#8

Open
steffen-heil-secforge wants to merge 11 commits into
mainfrom
feat/wifi-hotspot-forwarding
Open

feat: WiFi hotspot port forwarding#8
steffen-heil-secforge wants to merge 11 commits into
mainfrom
feat/wifi-hotspot-forwarding

Conversation

@steffen-heil-secforge

Copy link
Copy Markdown
Owner

Summary

  • Adds bind address support to LOCAL/DYNAMIC port forwards via PortForward.sourceAddr (sentinel value wifi_hotspot)
  • New NetworkUtils object detects active AP interfaces (ap0, wlan1, swlan*, etc.) and resolves the bind IP
  • AccessPointReceiver BroadcastReceiver catches WIFI_AP_STATE_CHANGED for instant response; 10 s polling timer in TerminalManager covers cases where no broadcast fires
  • Port forward editor shows a bind address RadioGroup (Localhost / All interfaces / WiFi hotspot) for LOCAL and DYNAMIC forwards with a security warning for non-localhost choices
  • Dysfunctional hotspot forwards (hotspot unavailable) are shown with strikethrough in the port forward list
  • Running notification shows the hotspot IP and retries failed forwards when the AP comes up
  • ACCESS_WIFI_STATE permission added to manifest

Test plan

  • ./gradlew testOssDebugUnitTest — 34 NetworkUtilsTest cases cover AP detection, bind address resolution, interface name matching
  • Create a LOCAL forward with bind address = WiFi hotspot, enable mobile hotspot → forward binds to AP IP
  • Disable hotspot → forward entry shows strikethrough; re-enable → forward retries automatically
  • Running notification shows "Hotspot IP: 192.168.43.1" when AP is active

🤖 Generated with Claude Code

steffen-heil-secforge and others added 11 commits June 16, 2026 15:22
Pure-Kotlin utility object providing WiFi hotspot interface detection,
private IPv4 validation, bind address resolution and display name helpers.
All 23 unit tests pass (TDD, no Android framework dependencies).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 6 unit tests for the pure-logic NetworkInterface overload of
getHotspotInterfaceIP, covering: hotspot interface with private IPv4,
empty list, cellular interface rejection, loopback rejection, IPv6
rejection, and first-match-wins with multiple interfaces.

Uses the package-private JDK constructor (via reflection + --add-opens
java.base/java.net=ALL-UNNAMED) to construct real NetworkInterface
stubs without requiring mockito-inline or production-code changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…x matching

- Move getAccessPointIP() call inside synchronized block in hasAccessPointStateChanged
  to eliminate the race where two threads could both detect the same state change
- Remove now-redundant @volatile annotation from lastKnownApIP
- Fix isHotspotInterfaceName to treat "wlan1" as an exact match or non-numeric suffix
  only, preventing false positives on wlan10/wlan11 (multi-radio STA interfaces)
- Add tests: wlan10/wlan11 rejection, wlan1 acceptance, hasAccessPointStateChanged
  change detection and no-change stability (using reflection + Mockito mock Context)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in SSH

Add resolveLocalBindAddress() helper that delegates to NetworkUtils to
resolve the portForward.sourceAddr field (including the "wifi_hotspot"
sentinel) into a concrete InetAddress. Replace hardcoded
InetAddress.getLocalHost() with this resolver in both PORTFORWARD_LOCAL
and PORTFORWARD_DYNAMIC5 branches of enablePortForward(). Returns false
(with a Timber warning) when the hotspot bind is requested but the AP
is currently unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add AccessPointReceiver + 10-second Timer fields to TerminalManager
- Add updateAccessPointMonitoring() to start/stop polling when AP-bound
  forwards are present in active bridges
- Add checkAccessPointStateChange() called by timer and receiver
- Add retryFailedAccessPointForwards() to re-enable forwards on hotspot up
- Add hasActiveAccessPointForwards() and updateAccessPointNotification() stub
- Wire monitoring into openConnection() and onDisconnected()
- Replace TODO comments in SSH.kt with real manager?.updateAccessPointNotification()
  calls for LOCAL and DYNAMIC5 forwards (enable and disable paths)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…urces

- ConnectionNotifier: add 3-arg showRunningNotification(service, apIP, hasApForwards)
  that builds a BigTextStyle multiline notification when AP forwards are active;
  existing 1-arg overload now delegates to the new one with (null, false)
- TerminalManager: updateAccessPointNotification() now passes apIP and
  hasActiveAccessPointForwards() to the new 3-arg overload
- strings.xml: add notification_access_point_text, notification_ap_disabled_text,
  bind_localhost, bind_all_interfaces, bind_hotspot,
  security_warning_network_exposure, prompt_bind_address

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add BindAddressOption enum and radio-button UI to PortForwardEditorDialog
so users can choose localhost, all-interfaces, or WiFi hotspot as the bind
address when adding/editing LOCAL or DYNAMIC port forwards. Shows a security
warning when a network-exposed option is selected. The chosen value is passed
through the existing onSave callback as sourceAddr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A LOCAL/DYNAMIC port forward bound to the WiFi hotspot (sourceAddr == "wifi_hotspot")
that is not enabled and whose hotspot is currently down is now rendered with
strikethrough text in the port forward list, distinguishing it from a simply-disabled
forward. The ViewModel resolves the current hotspot IP via NetworkUtils and exposes it
in PortForwardListUiState.hotspotIp; the Screen uses that value to compute isDysfunctional
and applies TextDecoration.LineThrough to the nickname Text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added missing Android permission required for accessing WiFi state in hotspot detection logic. Updated test to provide applicationContext parameter to PortForwardListViewModel constructor.

Co-Authored-By: Claude Sonnet 4.6 <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.

1 participant