156 lines
14 KiB
Markdown
156 lines
14 KiB
Markdown
# ADB через Wi-Fi (TCP/IP)
|
||
|
||
ARC умеет работать с устройствами не только по USB-кабелю, но и по Wi-Fi
|
||
(ADB-over-TCP). Включается автоматически, **без участия пользователя**: USB остаётся
|
||
основным транспортом, Wi-Fi — горячий резерв, который подхватывается при отключении
|
||
кабеля.
|
||
|
||
## Поведение
|
||
|
||
1. Пользователь, как и раньше, подключает телефон по USB.
|
||
2. Фоновый `DeviceScreener`, обнаружив **простаивающее** USB-устройство без живого
|
||
Wi-Fi-эндпоинта, автоматически:
|
||
- читает IP телефона в сети Wi-Fi (`wlan0`) и его MAC;
|
||
- переводит adbd в TCP-режим (`adb tcpip <port>`);
|
||
- подключается по сети (`adb connect ip:port`);
|
||
- сохраняет эндпоинт в `settings` (ключ `wifi_endpoints`, JSON
|
||
`{ android_id: { "endpoint": "ip:port", "mac": "aa:bb:.." } }`; старый формат —
|
||
значение-строка `"ip:port"` — читается прозрачно).
|
||
3. После этого кабель можно отключить — устройство **остаётся онлайн** (статус
|
||
привязан к `android_id`, а не к ADB-serial; меняется только `adbSerial` на
|
||
`ip:port`).
|
||
4. При старте ARC и периодически воркер переподключает сохранённые эндпоинты
|
||
(`adb connect`), так что устройство, у которого кабель уже отключён, само
|
||
появляется онлайн.
|
||
5. Если беспроводной эндпоинт «завис», воркер чинит его пересозданием TCP
|
||
(`adb disconnect` → `adb connect`): голый повторный `connect` вернул бы
|
||
`already connected` и ничего бы не починил. `disconnect` ещё и расклинивает это
|
||
«залипшее» состояние — после него последующие `connect` (в т.ч. из периодического
|
||
reconnect) становятся реальными попытками. Срабатывает по двум сигналам:
|
||
- эндпоинт виден в `adb devices` со статусом `offline`;
|
||
- устройство ушло в debounced-offline (30 c не отвечает), хотя `adb devices` всё ещё
|
||
держит его как `device` — частый реальный случай «полумёртвого» соединения при
|
||
слабом Wi-Fi / уходе телефона в doze, когда команды виснут, а статус не меняется
|
||
(принудительный `disconnect→connect`, «kick», на переходе в debounced-offline).
|
||
Починка **пропускается** для устройств с активной/ожидающей задачей (см. инварианты).
|
||
6. Если `connect` по сохранённому IP не удался (адрес сменился по DHCP), воркер ищет
|
||
устройство в сети по сохранённому **MAC** (ARP-таблица ОС, при включённой опции —
|
||
с прогревом через ping-скан /24) и переподключается на новый IP, обновляя эндпоинт.
|
||
|
||
## Инварианты
|
||
|
||
- **USB приоритетнее.** Если один `android_id` виден одновременно по USB-serial и по
|
||
`ip:port`, каноническим `adbSerial` (по нему ходят банк-скрипты) выбирается USB.
|
||
Wi-Fi-эндпоинт остаётся резервом и используется только когда USB пропадёт.
|
||
- **Разрывающие операции — только на простаивающих устройствах.** `adb tcpip`
|
||
перезапускает adbd, а принудительный `disconnect→connect` (kick) рвёт текущее
|
||
соединение — поэтому ни bootstrap, ни kick не выполняются, если на устройстве есть
|
||
активная/ожидающая задача (тот же сигнал занятости
|
||
`EventDAO::getDevicesWithPendingEvents()`, по которому `EventHandler` не назначает
|
||
задачи). Иначе разрыв оборвал бы выполняющийся bank-скрипт.
|
||
- **Бесшовное отключение кабеля.** К моменту отключения USB Wi-Fi-эндпоинт уже поднят
|
||
и виден в `adb devices`, поэтому перехода в офлайн не происходит.
|
||
|
||
## Конфигурация (`config.ini`, секция `[wifi]`)
|
||
|
||
| Ключ | Назначение | Дефолт |
|
||
|---|---|---|
|
||
| `enabled` | Полностью вкл/выкл функционал | `true` |
|
||
| `port` | TCP-порт adbd для tcpip-режима | `5555` |
|
||
| `reconnectIntervalSecs` | Как часто переподключать сохранённые эндпоинты | `30` |
|
||
| `bootstrapRetryIntervalSecs` | Мин. интервал между попытками bootstrap одного устройства | `120` |
|
||
| `macRediscoverEnabled` | Искать устройство по MAC при смене IP (DHCP) | `true` |
|
||
| `macRediscoverIntervalSecs` | Мин. интервал между попытками поиска по MAC | `300` |
|
||
| `subnetSweepEnabled` | Прогрев ARP ping-сканом /24 (медленно/шумно) | `false` |
|
||
|
||
## Ограничения
|
||
|
||
- **Перезагрузка телефона** сбрасывает tcpip-режим (adbd возвращается в USB-only).
|
||
Wi-Fi-резерв поднимется заново при следующем подключении кабеля.
|
||
- **Смена IP** (новый адрес по DHCP) обрабатывается поиском по MAC: при неуспешном
|
||
`adb connect` воркер находит устройство в ARP-таблице по сохранённому MAC и
|
||
переподключается на новый IP. Если устройства нет в ARP, помогает либо включённый
|
||
`subnetSweepEnabled` (ping-скан /24 — медленно, шумно), либо повторный bootstrap по
|
||
USB. Поиск по MAC требует, чтобы ARC и устройство были в одной L2-подсети (ARP не
|
||
маршрутизируется), и работает только когда MAC был снят при bootstrap.
|
||
- Требуется, чтобы телефон и десктоп были в одной сети Wi-Fi (маршрутизируемой до
|
||
`ip:port` устройства).
|
||
- **Чтение MAC** идёт через `ip addr show wlan0` (та же команда, что и для IP), фолбэк —
|
||
`cmd wifi status`. Оба проходят без root, включая MIUI/HyperOS (где `cat /sys/...` и
|
||
`ip link show` отдают Permission denied). Обычно это рандомизированный per-SSID MAC, но
|
||
он стабилен для сети и совпадает с тем, что видит ARP-таблица ПК. Если MAC снять не
|
||
удалось — эндпоинт хранится без него: теряется только восстановление при смене IP,
|
||
остальное (bootstrap, reconnect, kick) работает.
|
||
- **Скриншоты по Wi-Fi медленнее.** Основной путь захвата — http через APK arc-shot (байты
|
||
идут мимо adb, см. `[screenshot]` в config.ini); при его недоступности — fallback на
|
||
`exec-out screencap`, который отдаёт полноразмерный PNG через adb и по сети тяжелее USB.
|
||
Для wireless-serial таймаут fallback-захвата поднят (12 c против 10 c по USB). Интервал
|
||
превью задаётся `[screenshot]` (`previewIntervalHttpSecs` для http, `previewIntervalSecs`
|
||
для exec-out) и от транспорта Wi-Fi не зависит напрямую. На детект online/offline не
|
||
влияет; на слабом линке отдельные кадры всё равно могут не успевать.
|
||
- **Залипшее соединение vs. статус `offline`.** На практике мёртвый Wi-Fi часто остаётся
|
||
в `adb devices` как `device` (команды виснут), а не переходит в `offline` — поэтому
|
||
основной триггер починки именно debounced-offline, а не статус `offline` (см. Поведение).
|
||
|
||
## Требования к сети
|
||
|
||
Не требуют доработок ARC — это настройки сети/развёртывания.
|
||
|
||
**Обязательно (иначе Wi-Fi-ADB не работает):**
|
||
- ПК с ARC и устройства — в одной L2-подсети (или маршрутизируемой до `ip:5555`).
|
||
- На роутере **отключить AP isolation (изоляцию клиентов Wi-Fi)** — иначе порт `5555`
|
||
между узлами недоступен.
|
||
- Единый `adb` (бандл `tools/adb/...`), один adb-сервер — чтобы списки устройств у ARC и
|
||
фоновых операций не расходились.
|
||
|
||
**Опционально (предсказуемый адрес устройства — снижает число переподключений):**
|
||
смену IP по DHCP ARC и так переживает поиском по MAC (см. Ограничения), поэтому ниже —
|
||
не требования, а способы сделать адрес стабильным:
|
||
|
||
| Способ | Где настраивать | Работает на Android <11 |
|
||
|---|---|---|
|
||
| DHCP-резервация по MAC | роутер | да |
|
||
| Статический IP на самом устройстве | каждое устройство | да |
|
||
| mDNS-имя (`adb connect <имя>.local`) | устройство + сеть | нет (только 11+; **в ARC не используется**) |
|
||
| Поиск по MAC силами ARC (`macRediscoverEnabled`) | — (уже в ARC) | да |
|
||
|
||
## Безопасность
|
||
|
||
`adb tcpip` открывает порт **5555 на самом телефоне** (на его Wi-Fi-IP), а не на ПК.
|
||
Парк из N устройств = N эндпоинтов `ip:5555`, различаются по IP. Это отладочный доступ к
|
||
устройству, достижимый для **любого узла той же L2-подсети**. Доступ всё ещё проходит
|
||
RSA-авторизацию adb (чужой ключ упрётся в запрос «Allow debugging?» на экране), но сама
|
||
открытая поверхность + контекст (это **банковские приложения**) требуют сетевой гигиены.
|
||
На уровне приложения порт закрыть нельзя — на нерутованном устройстве это свойство adbd,
|
||
поэтому безопасность здесь **почти целиком инфраструктурная**:
|
||
|
||
- **Выделенная/изолированная Wi-Fi-сеть для фермы** — без выхода в недоверенный сегмент.
|
||
На роутере **отключить AP isolation** (иначе узлы не видят порт 5555 друг друга — это и
|
||
про работоспособность), но саму сеть держать сегментированной (VLAN/гостевую-наоборот).
|
||
- **Защита `adbkey` управляющего ПК.** RSA-ключ этой машины — фактический «пароль» ко
|
||
**всему** парку (особенно при «всегда разрешать с этого компьютера»). Хранить только на
|
||
ней, доступ к машине ограничить.
|
||
- **Ограничение на уровне роутера/файрвола** — сузить, кто вообще достаёт до `:5555`
|
||
устройств (по возможности — только управляющий ПК).
|
||
- **Поиск по MAC / `subnetSweepEnabled`** сканируют подсеть — ещё одна причина держать
|
||
ферму в выделённой сети (в чужой/общей сети скан нежелателен).
|
||
|
||
> Главный практический долг по безопасности — организационный: выделенная сеть, AP
|
||
> isolation off + сегментация, защита `adbkey`. В коде ARC тут рычагов нет.
|
||
|
||
## Где в коде
|
||
|
||
- `android/adb/AdbUtils` — низкоуровневые обёртки (`isWirelessSerial`,
|
||
`getDeviceWifiIp`, `getDeviceMac`, `enableTcpip`, `connectWireless`,
|
||
`disconnectWireless`).
|
||
- `android/adb/WifiAdbManager` — авто-bootstrap (`tryBootstrap`), переподключение
|
||
(`reconnectKnown`: починка `offline`-эндпоинтов через disconnect→connect и поиск по MAC
|
||
при смене IP), принудительный kick залипшего эндпоинта (`kickEndpoint`), хранение
|
||
эндпоинтов (`loadEndpoints`/`forget`).
|
||
- `services/DeviceScreener` — интеграция в цикл опроса (reconnect c починкой stale →
|
||
dedup USB/Wi-Fi → kick на debounced-offline (с guard по занятости) → bootstrap
|
||
простаивающих; увеличенный таймаут/интервал screencap для wireless).
|
||
- `views/device/DeviceWidget` — пассивный бейдж «Wi-Fi» на карточке устройства;
|
||
показывается **только когда устройство онлайн** по Wi-Fi (у офлайн-карточки не горит,
|
||
чтобы не вводить оператора в заблуждение).
|