mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-02 18:29:59 -07:00
client mode added (#888)
* client mode added
* Prevent OTA daemons dmclient and upgrade from running and phoning home to Verizon
* Fix workflow
* WIFI changes to support moxee. May need to rebase as delivering refactoring under other PR.
* code changes for rust based wifi client mode docs next
* Doc changes & security fixes
* Added watchdog and recover if crash occurs for wifi.
* Remove changes which were from device UI work (seperate feature which snuck into this branch)
* Add missing wifi and firewall module declarations
* cleaning up the code a bit
* Gate wpa_suplicant in installer and workflow to avoid building binary every push
* fix to check diskspace
* Improved support for subnet colisions, and attempts to rejoin network.
* Add WiFi client support and S01iptables to T-Mobile and Wingtech installers
Both installers now deploy wpa_supplicant, wpa_cli, udhcpc-hook.sh, and
the S01iptables boot-time firewall script. Config generation uses the
shared install_config/install_wifi_creds helpers instead of manual string
replacement.
* Revert "Add WiFi client support and S01iptables to T-Mobile and Wingtech installers"
This reverts commit 944b369c4f.
* Fix build: ignore unused wifi_ssid/wifi_password fields in T-Mobile and Wingtech installers
* Moved to a wifi crate
* Add host route and arp_filter to prevent subnet collisions
* add wakelock so kernel doesn't shut down wifi on battery when wifi is enabled
* Move wifi to external wifi-station crate, remove wifi from installer, extract OTA blocking
* fixed outdated info, moved udhcpc hook to wifi-station crate.
* Update to new version of wifi-station
* Address PR review feedback: replace Docker wpa build, add iw, remove OTA, revert unrelated changes
- Replace Docker-based wpa_supplicant build with shell script (scripts/build-wpa-supplicant.sh)
- Add iw cross-compilation and deployment to Orbic installer
- Skip wifi tool install if binary already exists on device
- Remove OTA daemon blocker (extracted for separate PR)
- Revert unrelated UZ801 and T-Mobile installer changes
- Remove connection.rs test scaffolding
- Rewrite S01iptables init script to read config.toml directly
- Pin url crate to 2.5.4 to fix MSRV
* Fix build script: use bash for parameter substitution
The ${VAR//pattern/replacement} syntax is a bash extension that
doesn't work in dash (Ubuntu's /bin/sh).
* Fix iw build: export PKG_CONFIG_LIBDIR as env var
Passing PKG_CONFIG_LIBDIR as a make variable doesn't export it to
$(shell pkg-config ...) calls. Set it as an environment variable
so pkg-config finds the cross-compiled libnl.
* Point wifi-station to GitHub rev 97c579a
* add comment
* Update daemon/src/config.rs
Add decorators
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update daemon/src/server.rs
add utopia doc support
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update daemon/src/server.rs
add utopia doc support
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
* Update to wifi-station with utoipa doc strings
* add utoipa to wifi-station
* added WPA3 support
* fix firewall port detection, update wifi-station to c267d37
fix ntfy port_or_known_default, comment out ntfy_url in config
template, update wifi-station with resolv.conf bind mount
fallback, udhcpc_bin config, and module path fix for UZ801
* show wifi UI for tmobile and wingtech, add udhcpc_bin config
both devices have wifi hardware and backend support. wingtech
verified on hardware (QCA6174 via PCIe). uz801 excluded for now
due to driver scan limitations with hostapd active.
* install wifi tools from orbic-usb installer, fix DNS default to Quad9, bump wifi-station rev
* fix Modal scroll listener leak, correct file transfer timeout math, document firewall fail-open, clarify UZ801 wifi status
* build-dev.sh: build wifi tools so install-dev works for orbic-family devices
* update Cargo.lock for wifi-station e8ec5b4
* fix setup_timeout_server crypto provider install, apply rustfmt
* Update installer/src/connection.rs
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
* Update installer/src/orbic.rs
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
* apply rustfmt to AdbConnection::run_command
---------
Co-authored-by: Andrej Walilko <walilkoa@gmail.com>
Co-authored-by: Cooper Quintin <cooperq@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { get_config, set_config, test_notification, type Config } from '../utils.svelte';
|
||||
import {
|
||||
get_config,
|
||||
set_config,
|
||||
test_notification,
|
||||
get_wifi_status,
|
||||
scan_wifi_networks,
|
||||
type Config,
|
||||
type WifiStatus,
|
||||
type WifiNetwork,
|
||||
} from '../utils.svelte';
|
||||
import Modal from './Modal.svelte';
|
||||
|
||||
let { shown = $bindable() }: { shown: boolean } = $props();
|
||||
@@ -12,13 +21,20 @@
|
||||
let messageType = $state<'success' | 'error' | null>(null);
|
||||
let testMessage = $state('');
|
||||
let testMessageType = $state<'success' | 'error' | null>(null);
|
||||
let wifiStatus = $state<WifiStatus | null>(null);
|
||||
let wifiStatusTimer = $state<ReturnType<typeof setInterval> | null>(null);
|
||||
let scanning = $state(false);
|
||||
let scanResults = $state<WifiNetwork[]>([]);
|
||||
let dnsServersInput = $state('');
|
||||
|
||||
async function load_config() {
|
||||
try {
|
||||
loading = true;
|
||||
config = await get_config();
|
||||
dnsServersInput = config.dns_servers ? config.dns_servers.join(', ') : '';
|
||||
message = '';
|
||||
messageType = null;
|
||||
poll_wifi_status();
|
||||
} catch (error) {
|
||||
message = `Failed to load config: ${error}`;
|
||||
messageType = 'error';
|
||||
@@ -30,6 +46,15 @@
|
||||
async function save_config() {
|
||||
if (!config) return;
|
||||
|
||||
const trimmed = dnsServersInput.trim();
|
||||
config.dns_servers =
|
||||
trimmed.length > 0
|
||||
? trimmed
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0)
|
||||
: null;
|
||||
|
||||
try {
|
||||
saving = true;
|
||||
await set_config(config);
|
||||
@@ -44,6 +69,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function poll_wifi_status() {
|
||||
if (wifiStatusTimer) clearInterval(wifiStatusTimer);
|
||||
try {
|
||||
wifiStatus = await get_wifi_status();
|
||||
} catch {
|
||||
wifiStatus = null;
|
||||
}
|
||||
wifiStatusTimer = setInterval(async () => {
|
||||
try {
|
||||
wifiStatus = await get_wifi_status();
|
||||
} catch {
|
||||
wifiStatus = null;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
let scanError = $state('');
|
||||
|
||||
async function do_scan() {
|
||||
scanning = true;
|
||||
scanError = '';
|
||||
try {
|
||||
scanResults = await scan_wifi_networks();
|
||||
} catch (error) {
|
||||
scanResults = [];
|
||||
scanError = `Scan failed: ${error}`;
|
||||
} finally {
|
||||
scanning = false;
|
||||
}
|
||||
}
|
||||
|
||||
function select_network(network: WifiNetwork) {
|
||||
if (config) {
|
||||
config.wifi_ssid = network.ssid;
|
||||
config.wifi_password = '';
|
||||
config.wifi_security =
|
||||
network.security === 'WPA3' || network.security === 'WPA3 (transition)'
|
||||
? 'sae'
|
||||
: 'wpa_psk';
|
||||
scanResults = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function send_test_notification() {
|
||||
try {
|
||||
testingNotification = true;
|
||||
@@ -64,6 +132,16 @@
|
||||
if (shown && !config) {
|
||||
load_config();
|
||||
}
|
||||
if (!shown && wifiStatusTimer) {
|
||||
clearInterval(wifiStatusTimer);
|
||||
wifiStatusTimer = null;
|
||||
}
|
||||
return () => {
|
||||
if (wifiStatusTimer) {
|
||||
clearInterval(wifiStatusTimer);
|
||||
wifiStatusTimer = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -267,6 +345,218 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if config.device === 'orbic' || config.device === 'moxee' || config.device === 'tmobile' || config.device === 'wingtech'}
|
||||
<div class="border-t pt-4 mt-6 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">WiFi Client Mode</h3>
|
||||
<p class="text-xs text-gray-500">
|
||||
Connect the device to an existing WiFi network for internet access (e.g.
|
||||
notifications, remote access). The hotspot AP stays running alongside
|
||||
WiFi client mode.
|
||||
</p>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="wifi_enabled"
|
||||
type="checkbox"
|
||||
bind:checked={config.wifi_enabled}
|
||||
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||
/>
|
||||
<label for="wifi_enabled" class="ml-2 block text-sm text-gray-700">
|
||||
Enable WiFi
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
Unchecking stops WiFi without clearing saved credentials.
|
||||
</p>
|
||||
|
||||
{#if wifiStatus && config.wifi_enabled}
|
||||
{#if wifiStatus.state === 'connected'}
|
||||
<p class="text-xs text-green-600">
|
||||
Connected to "{wifiStatus.ssid}" ({wifiStatus.ip})
|
||||
</p>
|
||||
{:else if wifiStatus.state === 'connecting'}
|
||||
<p class="text-xs text-amber-600">Connecting...</p>
|
||||
{:else if wifiStatus.state === 'recovering'}
|
||||
<p class="text-xs text-amber-600">Recovering connection...</p>
|
||||
{:else if wifiStatus.state === 'dataPathDead'}
|
||||
<p class="text-xs text-amber-600">
|
||||
Data path stalled, attempting recovery...
|
||||
</p>
|
||||
{:else if wifiStatus.state === 'failed'}
|
||||
<p class="text-xs text-red-600">
|
||||
Failed: {wifiStatus.error}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="wifi_ssid"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
WiFi Network Name (SSID)
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
id="wifi_ssid"
|
||||
type="text"
|
||||
bind:value={config.wifi_ssid}
|
||||
placeholder="MyWiFiNetwork"
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick={do_scan}
|
||||
disabled={scanning}
|
||||
class="px-3 py-2 text-sm bg-gray-100 hover:bg-gray-200 disabled:opacity-50 border border-gray-300 rounded-md"
|
||||
>
|
||||
{scanning ? 'Scanning...' : 'Scan'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if scanError}
|
||||
<p class="text-xs text-red-600">{scanError}</p>
|
||||
{/if}
|
||||
|
||||
{#if scanResults.length > 0}
|
||||
<div
|
||||
class="border border-gray-200 rounded-md max-h-40 overflow-y-auto divide-y"
|
||||
>
|
||||
{#each scanResults as network}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-gray-50 flex justify-between"
|
||||
onclick={() => select_network(network)}
|
||||
>
|
||||
<span>{network.ssid}</span>
|
||||
<span class="text-gray-400"
|
||||
>{network.signal_dbm} dBm · {network.security}</span
|
||||
>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if config.wifi_ssid}
|
||||
<div>
|
||||
<label
|
||||
for="wifi_security"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Security Type
|
||||
</label>
|
||||
<select
|
||||
id="wifi_security"
|
||||
bind:value={config.wifi_security}
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
>
|
||||
<option value="wpa_psk">WPA2 (WPA-PSK)</option>
|
||||
<option value="sae">WPA3 (SAE)</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="wifi_password"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
WiFi Password
|
||||
</label>
|
||||
<input
|
||||
id="wifi_password"
|
||||
type="password"
|
||||
bind:value={config.wifi_password}
|
||||
placeholder="Enter password"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Changing the network requires re-entering the password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if config.wifi_ssid}
|
||||
<div>
|
||||
<label
|
||||
for="dns_servers"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
DNS Servers
|
||||
</label>
|
||||
<input
|
||||
id="dns_servers"
|
||||
type="text"
|
||||
bind:value={dnsServersInput}
|
||||
placeholder="9.9.9.9, 149.112.112.112"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Comma-separated. Used when WiFi is active. Defaults to 9.9.9.9,
|
||||
149.112.112.112 (Quad9).
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="border-t pt-4 mt-6 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">Device Security</h3>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="firewall_restrict_outbound"
|
||||
type="checkbox"
|
||||
bind:checked={config.firewall_restrict_outbound}
|
||||
class="h-4 w-4 text-rayhunter-blue focus:ring-rayhunter-blue border-gray-300 rounded"
|
||||
/>
|
||||
<label
|
||||
for="firewall_restrict_outbound"
|
||||
class="ml-2 block text-sm text-gray-700"
|
||||
>
|
||||
Restrict outbound traffic
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">
|
||||
Only allows DNS, DHCP, and HTTPS (port 443) outbound. Blocks all other
|
||||
outbound connections on every interface (WiFi and cellular). Loopback and
|
||||
hotspot traffic are always allowed. Changes take effect immediately.
|
||||
</p>
|
||||
|
||||
{#if config.firewall_restrict_outbound}
|
||||
<div>
|
||||
<label
|
||||
for="firewall_allowed_ports"
|
||||
class="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Additional Allowed Ports
|
||||
</label>
|
||||
<input
|
||||
id="firewall_allowed_ports"
|
||||
type="text"
|
||||
value={config.firewall_allowed_ports
|
||||
? config.firewall_allowed_ports.join(', ')
|
||||
: ''}
|
||||
oninput={(e) => {
|
||||
const val = (e.target as HTMLInputElement).value.trim();
|
||||
config!.firewall_allowed_ports =
|
||||
val.length > 0
|
||||
? val
|
||||
.split(',')
|
||||
.map((s) => parseInt(s.trim()))
|
||||
.filter((n) => !isNaN(n) && n >= 1 && n <= 65535)
|
||||
: null;
|
||||
}}
|
||||
placeholder="22, 80"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-rayhunter-blue"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Comma-separated TCP ports, e.g. 22, 80
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="border-t pt-4 mt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-4">
|
||||
Analyzer Heuristic Settings
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
}: { shown: boolean; title: string; children: Snippet } = $props();
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener('scroll', () => {
|
||||
const handler = () => {
|
||||
document.documentElement.style.setProperty('--scroll-y', `${window.scrollY}px`);
|
||||
});
|
||||
};
|
||||
window.addEventListener('scroll', handler);
|
||||
return () => window.removeEventListener('scroll', handler);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -19,6 +19,7 @@ export enum enabled_notifications {
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
device: string;
|
||||
ui_level: number;
|
||||
colorblind_mode: boolean;
|
||||
key_input_mode: number;
|
||||
@@ -27,6 +28,34 @@ export interface Config {
|
||||
analyzers: AnalyzerConfig;
|
||||
min_space_to_start_recording_mb: number;
|
||||
min_space_to_continue_recording_mb: number;
|
||||
wifi_ssid: string | null;
|
||||
wifi_password: string | null;
|
||||
wifi_security: 'wpa_psk' | 'sae' | null;
|
||||
wifi_enabled: boolean;
|
||||
dns_servers: string[] | null;
|
||||
firewall_restrict_outbound: boolean;
|
||||
firewall_allowed_ports: number[] | null;
|
||||
}
|
||||
|
||||
export interface WifiStatus {
|
||||
state: string;
|
||||
ssid?: string;
|
||||
ip?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface WifiNetwork {
|
||||
ssid: string;
|
||||
signal_dbm: number;
|
||||
security: string;
|
||||
}
|
||||
|
||||
export async function get_wifi_status(): Promise<WifiStatus> {
|
||||
return JSON.parse(await req('GET', '/api/wifi-status'));
|
||||
}
|
||||
|
||||
export async function scan_wifi_networks(): Promise<WifiNetwork[]> {
|
||||
return JSON.parse(await req('POST', '/api/wifi-scan'));
|
||||
}
|
||||
|
||||
export async function req(method: string, url: string, json_body?: unknown): Promise<string> {
|
||||
|
||||
Reference in New Issue
Block a user