mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-18 13:58:10 -07:00
client mode added
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# WiFi Client Mode for Rayhunter (Orbic RC400L)
|
||||
|
||||
Connect the Orbic to an existing WiFi network while keeping its AP running.
|
||||
This enables internet access (for ntfy notifications, etc.) and allows
|
||||
accessing the Rayhunter web UI from any device on your network.
|
||||
|
||||
## How It Works
|
||||
|
||||
The Orbic's QCA6174 supports concurrent AP + station mode. `wlan0` runs
|
||||
the AP (via hostapd/QCMAP), and `wlan1` is configured as a station using
|
||||
a cross-compiled `wpa_supplicant`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Build wpa_supplicant (one-time):
|
||||
```
|
||||
cd tools/build-wpa-supplicant
|
||||
docker build --platform linux/amd64 --target export --output type=local,dest=./out .
|
||||
```
|
||||
|
||||
2. Push files to device:
|
||||
```
|
||||
sh client-mode/scripts/setup-device.sh
|
||||
```
|
||||
|
||||
3. Set credentials via the Rayhunter web UI (Settings > WiFi Client Mode),
|
||||
or via the installer:
|
||||
```
|
||||
./installer orbic --admin-password YOUR_PASS --wifi-ssid MyNetwork --wifi-password MyPass
|
||||
```
|
||||
|
||||
4. Reboot. WiFi client starts automatically. Check the log:
|
||||
```
|
||||
adb shell cat /tmp/wifi-client.log
|
||||
```
|
||||
|
||||
## File Layout on Device
|
||||
|
||||
```
|
||||
/data/rayhunter/
|
||||
bin/wpa_supplicant # Static ARMv7 binary
|
||||
bin/wpa_cli # Static ARMv7 binary
|
||||
scripts/wifi-client.sh # Main script (start/stop/status)
|
||||
wifi-creds.conf # Credentials (ssid=X / password=Y)
|
||||
```
|
||||
|
||||
## What the Script Does
|
||||
|
||||
1. Waits for wlan1 to appear (up to 30s)
|
||||
2. Sets wlan1 to managed mode, starts wpa_supplicant
|
||||
3. Obtains IP via DHCP
|
||||
4. Fixes routing: replaces bridge0 default route, adds policy routing
|
||||
(table 100) so replies from wlan1's IP always exit via wlan1
|
||||
5. Sets DNS to 8.8.8.8
|
||||
6. Configures iptables: allows inbound on wlan1, blocks outbound except
|
||||
ESTABLISHED/RELATED, DHCP, DNS, and HTTPS (port 443 for ntfy)
|
||||
|
||||
## AT+SYSCMD
|
||||
|
||||
Commands needing `CAP_NET_ADMIN` (iw, iptables, ip rule) cannot run through
|
||||
rootshell -- ADB's capability bounding set is too restrictive. The init
|
||||
script triggers wifi-client.sh which runs with full capabilities.
|
||||
|
||||
Key constraint: AT+SYSCMD via `/dev/smd8` is **one-shot per boot**. The
|
||||
installer uses USB bulk transfers and can send multiple commands.
|
||||
|
||||
## Disabling
|
||||
|
||||
Delete or rename the credentials file, then reboot:
|
||||
```
|
||||
adb shell "mv /data/rayhunter/wifi-creds.conf /data/rayhunter/wifi-creds.conf.disabled"
|
||||
```
|
||||
|
||||
All network changes are runtime-only -- a reboot always restores defaults.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Check the log first: `adb shell cat /tmp/wifi-client.log`
|
||||
|
||||
- **No log file**: wifi-client.sh didn't run. Check that wifi-creds.conf
|
||||
exists and the init script has the PRESTART replacement.
|
||||
- **wpa_supplicant connects but no IP**: Check udhcpc uses
|
||||
`-s /etc/udhcpc.d/50default`.
|
||||
- **Can't reach device from LAN**: Likely a policy routing issue. The
|
||||
script handles this, but if bridge0 and wlan1 share a subnet
|
||||
(both 192.168.1.0/24), check `ip rule show` and `ip route show table 100`.
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
# Pushes all client-mode files to the Orbic device via ADB.
|
||||
# Run from the rayhunter repo root.
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
WPA_DIR="$SCRIPT_DIR/../../tools/build-wpa-supplicant/out"
|
||||
|
||||
if ! adb devices | grep -q device$; then
|
||||
echo "No ADB device found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pushing scripts..."
|
||||
adb shell "mkdir -p /data/rayhunter/scripts /data/rayhunter/bin"
|
||||
adb push "$SCRIPT_DIR/wifi-client.sh" /data/rayhunter/scripts/wifi-client.sh
|
||||
|
||||
if [ -f "$WPA_DIR/wpa_supplicant" ]; then
|
||||
echo "Pushing wpa_supplicant binaries..."
|
||||
adb push "$WPA_DIR/wpa_supplicant" /data/rayhunter/bin/wpa_supplicant
|
||||
adb push "$WPA_DIR/wpa_cli" /data/rayhunter/bin/wpa_cli
|
||||
else
|
||||
echo "wpa_supplicant binaries not found at $WPA_DIR"
|
||||
echo "Build them first: see tools/build-wpa-supplicant/Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Files pushed. Set WiFi credentials via the web UI or installer,"
|
||||
echo "then reboot. WiFi client starts automatically on boot."
|
||||
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/bin/sh
|
||||
# WiFi client mode for Rayhunter - connects wlan1 to an existing network
|
||||
# Reads credentials from /data/rayhunter/wifi-creds.conf
|
||||
# Format:
|
||||
# ssid=YourNetworkName
|
||||
# password=YourPassword
|
||||
|
||||
LOG="/tmp/wifi-client.log"
|
||||
exec > "$LOG" 2>&1
|
||||
|
||||
CRED_FILE="/data/rayhunter/wifi-creds.conf"
|
||||
WPA_BIN="/data/rayhunter/bin/wpa_supplicant"
|
||||
WPA_CONF="/tmp/wpa_sta.conf"
|
||||
WPA_PID="/tmp/wpa_sta.pid"
|
||||
DHCP_PID="/tmp/udhcpc_wlan1.pid"
|
||||
IFACE="wlan1"
|
||||
RT_TABLE=100
|
||||
|
||||
stop() {
|
||||
[ -f "$WPA_PID" ] && kill "$(cat "$WPA_PID")" 2>/dev/null && rm -f "$WPA_PID"
|
||||
[ -f "$DHCP_PID" ] && kill "$(cat "$DHCP_PID")" 2>/dev/null && rm -f "$DHCP_PID"
|
||||
ip link set "$IFACE" down 2>/dev/null
|
||||
}
|
||||
|
||||
start() {
|
||||
if [ ! -f "$CRED_FILE" ]; then
|
||||
echo "No credentials file at $CRED_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSID=$(grep '^ssid=' "$CRED_FILE" | cut -d= -f2-)
|
||||
PSK=$(grep '^password=' "$CRED_FILE" | cut -d= -f2-)
|
||||
|
||||
if [ -z "$SSID" ] || [ -z "$PSK" ]; then
|
||||
echo "Missing ssid or password in $CRED_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for the wireless interface to appear (created asynchronously by QCMAP/hostapd)
|
||||
for i in $(seq 1 30); do
|
||||
[ -d "/sys/class/net/$IFACE" ] && break
|
||||
[ "$i" = "1" ] && echo "Waiting for $IFACE..."
|
||||
sleep 1
|
||||
done
|
||||
if [ ! -d "/sys/class/net/$IFACE" ]; then
|
||||
echo "$IFACE not found after 30s, giving up"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
stop 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
echo "Configuring $IFACE for station mode"
|
||||
iw dev "$IFACE" set type managed
|
||||
ip link set "$IFACE" up
|
||||
|
||||
cat > "$WPA_CONF" <<WPAEOF
|
||||
ctrl_interface=/var/run/wpa_supplicant
|
||||
network={
|
||||
ssid="$SSID"
|
||||
psk="$PSK"
|
||||
key_mgmt=WPA-PSK
|
||||
}
|
||||
WPAEOF
|
||||
|
||||
echo "Starting wpa_supplicant"
|
||||
"$WPA_BIN" -i "$IFACE" -Dnl80211 -c "$WPA_CONF" -B -P "$WPA_PID"
|
||||
sleep 5
|
||||
|
||||
echo "wpa_supplicant status:"
|
||||
iw dev "$IFACE" link
|
||||
|
||||
echo "Starting DHCP"
|
||||
udhcpc -i "$IFACE" -s /etc/udhcpc.d/50default -p "$DHCP_PID" -t 10 -A 3 -b
|
||||
sleep 3
|
||||
|
||||
WLAN1_IP=$(ip addr show "$IFACE" | grep 'inet ' | awk '{print $2}' | cut -d/ -f1)
|
||||
WLAN1_CIDR=$(ip addr show "$IFACE" | grep 'inet ' | awk '{print $2}')
|
||||
WLAN1_SUBNET=$(ip route show dev "$IFACE" | grep 'proto kernel' | awk '{print $1}')
|
||||
WLAN1_GW=$(ip route show dev "$IFACE" | grep 'proto kernel' | awk '{print $1}' | cut -d/ -f1)
|
||||
WLAN1_GW="${WLAN1_GW%.*}.1"
|
||||
|
||||
if [ -z "$WLAN1_IP" ]; then
|
||||
echo "Failed to get IP on $IFACE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "IP: $WLAN1_IP Subnet: $WLAN1_SUBNET CIDR: $WLAN1_CIDR Gateway: $WLAN1_GW"
|
||||
|
||||
# Fix default route: ensure it goes through wlan1, not bridge0
|
||||
GATEWAY=$(ip route show default | grep "dev bridge0" | awk '{print $3}')
|
||||
if [ -n "$GATEWAY" ]; then
|
||||
echo "Fixing default route: bridge0 -> wlan1"
|
||||
ip route del default dev bridge0 2>/dev/null
|
||||
fi
|
||||
ip route replace default via "$WLAN1_GW" dev "$IFACE" metric 10
|
||||
|
||||
# Policy routing: force traffic from our DHCP IP out wlan1
|
||||
# (needed because bridge0 shares the same subnet)
|
||||
ip rule del from "$WLAN1_IP" table $RT_TABLE 2>/dev/null
|
||||
ip route flush table $RT_TABLE 2>/dev/null
|
||||
ip rule add from "$WLAN1_IP" table $RT_TABLE
|
||||
ip route add "$WLAN1_SUBNET" dev "$IFACE" src "$WLAN1_IP" table $RT_TABLE
|
||||
ip route add default via "$WLAN1_GW" dev "$IFACE" table $RT_TABLE
|
||||
|
||||
echo "nameserver 8.8.8.8" > /etc/resolv.conf
|
||||
|
||||
# Allow inbound traffic on wlan1
|
||||
iptables -I INPUT -i "$IFACE" -j ACCEPT
|
||||
iptables -I FORWARD -i "$IFACE" -j ACCEPT
|
||||
|
||||
# Block stock Orbic daemons from phoning home (dmclient, upgrade, etc.)
|
||||
# Allow only: replies to incoming connections, DHCP renewal, DNS, and HTTPS
|
||||
# (needed for ntfy notifications).
|
||||
iptables -A OUTPUT -o "$IFACE" -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -o "$IFACE" -p udp --dport 67:68 -j ACCEPT
|
||||
iptables -A OUTPUT -o "$IFACE" -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -o "$IFACE" -p tcp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -o "$IFACE" -p tcp --dport 443 -j ACCEPT
|
||||
iptables -A OUTPUT -o "$IFACE" -j DROP
|
||||
|
||||
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
|
||||
|
||||
echo "=== iptables OUTPUT ==="
|
||||
iptables -L OUTPUT -v -n 2>&1
|
||||
|
||||
echo "=== policy routing ==="
|
||||
ip rule show
|
||||
echo "--- table $RT_TABLE ---"
|
||||
ip route show table $RT_TABLE
|
||||
|
||||
echo "=== network state ==="
|
||||
ip addr show "$IFACE" | grep 'inet '
|
||||
ip route show
|
||||
|
||||
echo "Internet test:"
|
||||
wget -q -O /dev/null http://detectportal.firefox.com/success.txt && echo "OK" || echo "FAILED"
|
||||
}
|
||||
|
||||
status() {
|
||||
if [ -f "$WPA_PID" ] && kill -0 "$(cat "$WPA_PID")" 2>/dev/null; then
|
||||
ip addr show "$IFACE" | grep 'inet ' | awk '{print $2}'
|
||||
else
|
||||
echo "disconnected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start) start ;;
|
||||
stop) stop ;;
|
||||
status) status ;;
|
||||
*) echo "Usage: $0 {start|stop|status}" >&2; exit 1 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user