fix(eye-remote): Use usb1 (ECM) instead of usb0 for Linux hosts

The USB composite gadget creates two network interfaces:
- usb0: RNDIS function (for Windows hosts)
- usb1: ECM function (for Linux/Mac hosts via cdc_ether driver)

Previously, both interfaces were configured with the same IP (10.55.0.2/30),
causing asymmetric routing issues where packets received on usb1 could be
replied via usb0.

Fix: Configure only usb1 (ECM) since that's what Linux hosts use.
Falls back to usb0 if usb1 is not present (e.g., single-function gadget).

Changes:
- secubox-otg-gadget.sh: Wait for and configure usb1 instead of usb0
- gadget-setup.sh: Same fix for eye-remote variant
- agent/main.py: Update ensure_usb_network() to prefer usb1
- agent/network_debug.py: New debug script for USB network troubleshooting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-04-23 19:30:17 +02:00
parent 980e0076c4
commit 48de2440b8
4 changed files with 157 additions and 15 deletions

View File

@ -26,6 +26,52 @@ from touch_handler import TouchHandler, create_touch_handler, Gesture
log = logging.getLogger(__name__)
def ensure_usb_network():
"""Ensure USB network interface is configured for OTG connection.
The composite gadget creates two interfaces:
- usb0: RNDIS (Windows compatible)
- usb1: ECM (Linux/Mac compatible via cdc_ether driver)
Linux hosts use cdc_ether which maps to usb1.
We configure only usb1 to avoid asymmetric routing issues.
"""
import subprocess
import time
for attempt in range(30):
# Prefer usb1 (ECM) for Linux hosts
if Path("/sys/class/net/usb1").exists():
try:
subprocess.run(["/sbin/ip", "addr", "flush", "dev", "usb1"],
capture_output=True, timeout=5)
subprocess.run(["/sbin/ip", "addr", "add", "10.55.0.2/30", "dev", "usb1"],
capture_output=True, timeout=5)
subprocess.run(["/sbin/ip", "link", "set", "usb1", "up"],
capture_output=True, timeout=5)
log.info(f"usb1 (ECM) configured: 10.55.0.2/30 (attempt {attempt + 1})")
return True
except Exception as e:
log.warning(f"Failed to configure usb1: {e}")
# Fallback to usb0 (RNDIS) if usb1 not present
elif Path("/sys/class/net/usb0").exists():
try:
subprocess.run(["/sbin/ip", "addr", "flush", "dev", "usb0"],
capture_output=True, timeout=5)
subprocess.run(["/sbin/ip", "addr", "add", "10.55.0.2/30", "dev", "usb0"],
capture_output=True, timeout=5)
subprocess.run(["/sbin/ip", "link", "set", "usb0", "up"],
capture_output=True, timeout=5)
log.info(f"usb0 (RNDIS) configured: 10.55.0.2/30 (attempt {attempt + 1})")
return True
except Exception as e:
log.warning(f"Failed to configure usb0: {e}")
time.sleep(1)
log.warning("No USB network interface found after 30 attempts")
return False
# Default paths
SOCKET_PATH = Path("/run/secubox-eye/metrics.sock")
PID_FILE = Path("/run/secubox-eye/agent.pid")
@ -359,6 +405,9 @@ async def main():
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
# Ensure USB OTG network is configured
ensure_usb_network()
# Parse args
config_path = DEFAULT_CONFIG_PATH
if len(sys.argv) > 1:

View File

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Configure USB network interface for OTG connection.
For Linux/Mac hosts: Only configure usb1 (ECM) to avoid routing issues.
The composite gadget creates:
- usb0: RNDIS (Windows compatible)
- usb1: ECM (Linux/Mac compatible via cdc_ether driver)
Linux hosts use the cdc_ether driver which maps to usb1.
Configuring both interfaces with the same IP causes asymmetric routing.
CyberMind - https://cybermind.fr
Author: Gerald Kerma <gandalf@gk2.net>
"""
import subprocess
import time
from pathlib import Path
LOG = "/var/log/usb_network_debug.log"
def log(msg: str) -> None:
"""Log message to file and stdout."""
with open(LOG, "a") as f:
f.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
print(msg)
def configure_interface(iface: str, ip: str) -> bool:
"""Configure a USB network interface with IP address."""
if not Path(f"/sys/class/net/{iface}").exists():
return False
log(f"Configuring {iface} with {ip}")
# Flush existing addresses
r = subprocess.run(["/sbin/ip", "addr", "flush", "dev", iface],
capture_output=True, text=True)
log(f" flush: rc={r.returncode}")
# Add IP address
r = subprocess.run(["/sbin/ip", "addr", "add", ip, "dev", iface],
capture_output=True, text=True)
log(f" addr add: rc={r.returncode}")
# Bring interface up
r = subprocess.run(["/sbin/ip", "link", "set", iface, "up"],
capture_output=True, text=True)
log(f" link up: rc={r.returncode}")
# Show final state
r = subprocess.run(["ip", "addr", "show", iface], capture_output=True, text=True)
log(f" state:\n{r.stdout}")
return r.returncode == 0
def main() -> bool:
"""Configure USB network interface."""
log("=== USB Network Config Started ===")
log("Linux/Mac hosts use ECM (usb1), Windows uses RNDIS (usb0)")
# Wait for usb1 (ECM) interface - this is what Linux hosts use
for attempt in range(30):
if Path("/sys/class/net/usb1").exists():
log(f"usb1 (ECM) found at attempt {attempt + 1}")
configure_interface("usb1", "10.55.0.2/30")
log("=== USB Network Config Complete ===")
return True
time.sleep(1)
# Fallback to usb0 if usb1 doesn't appear (Windows host or single-function gadget)
if Path("/sys/class/net/usb0").exists():
log("usb1 not found, falling back to usb0 (RNDIS)")
configure_interface("usb0", "10.55.0.2/30")
return True
log("ERROR: No USB interfaces found after 30 seconds")
return False
if __name__ == "__main__":
main()

View File

@ -214,17 +214,25 @@ gadget_up() {
debug "Binding to UDC: ${udc}"
echo "$udc" > UDC
# Wait for usb0 interface to appear
# Wait for usb1 (ECM) interface to appear
# Note: Composite gadget creates usb0 (RNDIS/Windows) and usb1 (ECM/Linux-Mac)
# Linux hosts use cdc_ether driver which maps to usb1
local retry=0
while [[ ! -d /sys/class/net/usb0 ]] && [[ $retry -lt 10 ]]; do
while [[ ! -d /sys/class/net/usb1 ]] && [[ $retry -lt 10 ]]; do
sleep 0.5
((retry++))
done
if [[ -d /sys/class/net/usb0 ]]; then
debug "Interface usb0 created"
if [[ -d /sys/class/net/usb1 ]]; then
debug "Interface usb1 (ECM) created"
# Configure IP on usb1 only (avoids asymmetric routing with same IP on both)
ip addr flush dev usb1 2>/dev/null || true
ip addr add 10.55.0.2/30 dev usb1
ip link set usb1 up
debug "usb1 configured: 10.55.0.2/30"
else
err "Interface usb0 did not appear after 5s"
err "Interface usb1 did not appear after 5s"
return 1
fi

View File

@ -266,24 +266,26 @@ gadget_start() {
log "Activation sur UDC: ${udc}"
echo "$udc" > UDC
# Attendre que l'interface usb0 apparaisse
# Attendre que l'interface usb1 (ECM) apparaisse
# Note: Le gadget composite crée usb0 (RNDIS/Windows) et usb1 (ECM/Linux-Mac)
# On configure usb1 car les hôtes Linux utilisent le driver cdc_ether (ECM)
local retry=0
while [[ ! -d /sys/class/net/usb0 ]] && [[ $retry -lt 10 ]]; do
while [[ ! -d /sys/class/net/usb1 ]] && [[ $retry -lt 10 ]]; do
sleep 0.5
((retry++))
done
if [[ -d /sys/class/net/usb0 ]]; then
log "Interface usb0 créée"
if [[ -d /sys/class/net/usb1 ]]; then
log "Interface usb1 (ECM) créée"
# Configurer l'IP
ip addr flush dev usb0 2>/dev/null || true
ip addr add "${OTG_NETWORK_DEV}/30" dev usb0
ip link set usb0 up
# Configurer l'IP sur usb1 uniquement (évite le routage asymétrique)
ip addr flush dev usb1 2>/dev/null || true
ip addr add "${OTG_NETWORK_DEV}/30" dev usb1
ip link set usb1 up
log "usb0 configuré: ${OTG_NETWORK_DEV}/30"
log "usb1 configuré: ${OTG_NETWORK_DEV}/30"
else
err "Interface usb0 non créée après 5s"
err "Interface usb1 non créée après 5s"
return 1
fi