IC-7610 Remote Access — System Architecture
This document describes how we built a system to remotely operate an Icom IC-7610 HF transceiver from any web browser, bypassing carrier-grade NAT (CGNAT) with a WireGuard tunnel through an Oracle Cloud free-tier VM.
The browser-based interface is powered by icom-lan, an open-source project by Sergey Morozik (KN4KYD) that provides full radio control, spectrum/waterfall display, and audio streaming directly in the browser — no software installation required on the remote user's end.
This is a real, working system — not a theoretical design. Everything here has been tested and verified.
The Problem
The IC-7610 sits on a home LAN behind a T-Mobile 5G gateway, which uses CGNAT. This means:
- No public IP address at the home location
- No ability to port-forward inbound connections to the radio
- Traditional DDNS solutions don't work behind CGNAT
We need remote operators to control the radio from a web browser with no VPN or special software on their end.
The Solution
Remote operator (any web browser)
|
| HTTPS (port 443)
v
+----------------------------------------------+
| Oracle Cloud VM 129.146.84.173 |
| (Always Free Tier) Ubuntu 22.04 |
| |
| +------------------+ |
| | nginx | HTTPS termination |
| | (port 443) | + reverse proxy |
| +--------+---------+ |
| | |
| | localhost:8080 |
| v |
| +------------------+ |
| | icom-lan | Open-source radio |
| | (by KN4KYD) | control server |
| | | |
| | Svelte frontend | Spectrum, waterfall, |
| | Python backend | audio, CI-V commands |
| +--------+---------+ |
| | |
| | Icom UDP protocol |
| | (via WireGuard tunnel) |
| v |
| +------------------+ |
| | WireGuard wg0 | 10.10.0.1/24 |
| +--------+---------+ |
| | ens3 + MASQUERADE |
+------------+------+--------------------------+
|
| WireGuard tunnel (UDP 51820)
| AES-encrypted, initiated outbound
|
+------------+---------------------------------+
| T-Mobile 5G Home Internet |
| (Gateway / CGNAT) |
| |
| No public IP assigned to home network. |
| Outbound UDP passes through; inbound |
| connections are blocked by CGNAT. |
+------------+---------------------------------+
|
| Home LAN (routed via EdgeRouter)
|
+------------+------+--------------------------+
| EdgeRouter X LAN: 192.168.1.1 |
| (Home Router) WireGuard: 10.10.0.2 |
| |
| eth4 (WAN) ---- wg0 ---- switch0 (LAN) |
| T-Mobile 5G 192.168.1.0/24 |
| (CGNAT) |
+------------+---------------------------------+
|
| LAN (192.168.1.0/24)
|
+------------+---------------------------------+
| Icom IC-7610 192.168.1.40 |
| HF Transceiver |
| |
| UDP 50001 (control/discovery) |
| UDP 50002 (CI-V commands + scope data) |
| UDP 50003 (audio stream) |
+----------------------------------------------+
How It Works
Overview
The system is built around icom-lan, an open-source radio control server created by Sergey Morozik (KN4KYD). icom-lan speaks the Icom radio's native UDP protocol and translates it into a modern web interface with WebSocket-based real-time updates. It provides the full user experience: spectrum/waterfall display, radio controls, metering, and audio streaming — all in the browser.
On our system, the icom-lan backend runs on the Oracle VM and communicates with the IC-7610 over the WireGuard tunnel. nginx sits in front as an HTTPS reverse proxy. Remote users connect via HTTPS to ww0r.org — they never talk to the radio directly, and no radio ports are exposed to the internet.
The WireGuard Tunnel
The key insight is that the EdgeRouter initiates the tunnel outbound to the Oracle VM. Outbound UDP connections work fine through CGNAT — it's only inbound that's blocked. WireGuard's persistent-keepalive (set to 25 seconds) keeps the NAT mapping alive so the Oracle VM can send packets back through the tunnel at any time.
| Parameter | Oracle VM | EdgeRouter |
| WireGuard IP | 10.10.0.1/24 | 10.10.0.2/24 |
| Listen Port | UDP 51820 | UDP 51820 |
| MTU | 1380 | 1380 |
| Role | Listens for connections | Initiates connection outbound |
| AllowedIPs | 10.10.0.2/32, 192.168.1.0/24 | 10.10.0.1/32 |
| Keepalive | 25s (set on both) | 25s |
Packet Flow (Browser → Radio)
- Remote user opens
https://ww0r.org/app in their browser
- nginx terminates HTTPS and proxies the request to icom-lan on
localhost:8080
- The browser establishes three WebSocket connections to the backend:
/api/v1/ws — control commands, status updates, meters
/api/v1/audio — audio RX/TX streaming
/api/v1/scope — spectrum/waterfall data
- When the user tunes the radio, the browser sends a JSON command over the control WebSocket
- icom-lan translates it into a CI-V binary command and sends it to the radio via UDP 50002 through the WireGuard tunnel
- The radio processes the command and sends status/audio/scope data back via UDP
- icom-lan receives the response and pushes it to the browser over the appropriate WebSocket
No radio ports are exposed to the internet. The icom-lan backend communicates with the radio over the WireGuard tunnel's internal network (10.10.0.0/24 → 192.168.1.40). UDP ports 50001-50003 are only accessible from inside the tunnel, not from the public internet. This is significantly more secure than the previous approach of DNAT-forwarding those ports directly.
Packet Flow (Radio → Browser)
- IC-7610 sends audio/scope/status data via UDP to the icom-lan backend (via the tunnel)
- icom-lan processes the data (decodes CI-V, frames audio with headers, parses scope frames)
- Data is pushed to all connected browsers over WebSocket
- The browser renders the spectrum/waterfall, plays audio through Web Audio API, and updates the UI
Access Control
The system uses a two-tier access model:
| Access Level | Authentication | Capabilities |
| Receive-only | None required | Spectrum, waterfall, audio, tuning, all RX controls (mode, filter, DSP, etc.) |
| Full control | Station password | Everything above, plus PTT and TX audio |
Unauthenticated users connect freely and can listen and tune. Transmit commands (PTT) are blocked server-side — the backend checks for a valid auth token before allowing any transmit operation. This lets anyone explore the bands while protecting against unauthorized transmission.
How it works: nginx passes through the client's Authorization header to icom-lan. If the header contains a valid Bearer token, the WebSocket session is flagged as authenticated with full TX privileges. Without a token, the session is receive-only. The frontend stores the token in localStorage after a successful login.
Oracle Cloud VM Configuration
OCI Infrastructure
- Instance: VM.Standard.E2.1.Micro (Always Free tier)
- OS: Ubuntu 22.04
- Public IP: Reserved (static) — survives reboots
- VNIC: Source/destination check disabled (required for tunnel routing)
OCI Security List (Firewall)
| Protocol | Port | Source | Purpose |
| TCP | 22 | 0.0.0.0/0 | SSH |
| TCP | 80 | 0.0.0.0/0 | HTTP (redirects to HTTPS) |
| TCP | 443 | 0.0.0.0/0 | HTTPS (web UI + landing page) |
| UDP | 51820 | 0.0.0.0/0 | WireGuard tunnel |
| ICMP | 3, 4 | 0.0.0.0/0 | Fragmentation needed |
Minimal attack surface. Only SSH, HTTP/HTTPS, and WireGuard are exposed. The radio's UDP ports (50001-50003) are not open to the internet — all radio communication is handled internally by the icom-lan backend through the tunnel.
System Services
| Service | Command | Purpose |
| WireGuard | systemctl status wg-quick@wg0 | VPN tunnel to home network (auto-starts on boot) |
| icom-lan | systemctl status icom-lan | Radio control backend + web UI server |
| nginx | systemctl status nginx | HTTPS reverse proxy + landing page |
| certbot | certbot renew | HTTPS certificate auto-renewal |
WireGuard Config (wg0.conf)
[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
MTU = 1380
PrivateKey = (hidden)
# NAT for traffic leaving to the internet
PostUp = iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
# NAT for traffic entering the tunnel
PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
# Allow forwarding through the tunnel
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
# (matching PostDown rules to clean up)
[Peer]
PublicKey = (EdgeRouter's public key)
AllowedIPs = 10.10.0.2/32, 192.168.1.0/24
PersistentKeepalive = 25
No DNAT rules. Unlike a direct wfview setup, we don't need to forward any ports through to the radio. The icom-lan backend connects to the radio from inside the tunnel. The only iptables rules needed are MASQUERADE (for NAT) and FORWARD (to allow tunnel traffic).
icom-lan Service
[Unit]
Description=icom-lan Web UI
After=network.target wg-quick@wg0.service
[Service]
Type=simple
ExecStart=/opt/icom-lan/.venv/bin/icom-lan \
--host 192.168.1.40 --control-port 50001 \
--user guest --pass (hidden) \
web --host 127.0.0.1 --port 8080 \
--static-dir /opt/icom-lan/frontend/dist \
--auth-token (hidden)
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
The backend listens only on 127.0.0.1:8080 — it is not directly accessible from the internet. nginx handles HTTPS termination and proxies requests to it.
EdgeRouter Configuration
WireGuard (EdgeOS CLI)
interfaces {
wireguard wg0 {
address 10.10.0.2/24
listen-port 51820
mtu 1380
private-key (hidden)
peer (Oracle VM's public key) {
allowed-ips 10.10.0.1/32
endpoint 129.146.84.173:51820
persistent-keepalive 25
}
}
}
protocols {
static {
route 10.10.0.0/24 {
next-hop-interface wg0
}
}
}
Do NOT use route-allowed-ips true on EdgeOS. It conflicts with the default route. Use an explicit static route instead.
Boot Resilience
EdgeRouter's WireGuard interface and static route can silently disappear after reboot if the WAN interface isn't ready during initialization. A task-scheduler script runs every 5 minutes to verify and re-apply if needed:
system {
task-scheduler {
task check-wg {
executable {
path /config/scripts/check-wg.sh
}
interval 5m
}
}
}
The script checks if wg0 exists and the static route is present, re-creating them if missing.
Audio Pipeline
Audio flows through several stages from the radio to the browser speaker:
- IC-7610 sends 16-bit PCM audio at 24kHz over UDP port 50003
- icom-lan backend receives the raw PCM, adds an 8-byte binary header (codec, sample rate, sequence number), and streams it over a dedicated WebSocket (
/api/v1/audio)
- Browser decodes the frame, converts PCM16 samples to Float32 (divide by 32768.0), creates a Web Audio API
AudioBuffer, and schedules gapless playback through a GainNode to the speakers
Why 24kHz instead of 48kHz? The micro VM's 1/8 OCPU can't relay 48kHz PCM (~768kbps) fast enough — the CI-V packet queue overflows. At 24kHz (~384kbps) the queue stays healthy. We chose 24kHz over 16kHz because the browser's Web Audio API resamples to 48kHz internally, and the 2x ratio (24→48) produces cleaner interpolation than the 3x ratio (16→48), which caused audible clipping artifacts on loud signal peaks.
TX Audio (Browser Mic → Radio)
Transmit audio flows in the reverse direction:
- Browser captures microphone audio via
getUserMedia, encodes it as Opus, and sends binary frames over the /api/v1/audio WebSocket
- icom-lan backend decodes Opus to PCM at the radio's negotiated sample rate and pushes it to the radio via UDP port 50003
- IC-7610 modulates the PCM audio and transmits
IC-7610 Radio Settings for TX
The radio must be configured to accept audio from the LAN connection:
| Setting | Path | Value |
| DATA OFF MOD | Menu → SET → Connectors | LAN |
| DATA MOD | Menu → SET → Connectors | LAN |
| LAN MOD Level | Menu → SET → Connectors | 50% (adjust to taste) |
DATA OFF MOD vs DATA MOD: The IC-7610 uses different modulation input settings depending on whether DATA mode is active. Set both to LAN to ensure browser audio works in all modes. If only one is set, TX will appear to key up but produce no RF power in the wrong mode.
Physical microphone still works. The MIC connector and LAN input are independent. You can use the desk microphone when sitting at the radio and the browser microphone when operating remotely — no settings changes needed.
MTU Considerations
WireGuard adds ~60 bytes of overhead per packet. With OCI's networking layer adding its own encapsulation, the default MTU of 1420 caused audio packet fragmentation.
Setting MTU = 1380 on both ends of the tunnel resolved this. The MTU must match — a mismatch between the two ends causes fragmentation on one side.
Symptom of MTU issues: Ping works fine (small packets), but audio is garbled or choppy (large packets get fragmented). If you see this, lower the MTU.
Pitfalls and Lessons Learned
- OCI's three firewalls — Security list, VNIC source/destination check, and iptables must all allow traffic. This is the #1 cause of silent failures on OCI.
- OCI default iptables — Oracle's Ubuntu image ships with REJECT rules in INPUT and FORWARD chains. WireGuard PostUp rules that append (-A) land after the REJECT and never match. Remove the REJECT rules before starting WireGuard.
- MASQUERADE on wg0 — Without this, the icom-lan backend's UDP packets to the radio have a source of 10.10.0.1, but the radio's reply goes out the WAN instead of back through the tunnel. MASQUERADE ensures return traffic flows correctly.
- MTU mismatch — Must be identical on both tunnel endpoints. A mismatch causes fragmentation visible as garbled audio but working pings.
- EdgeOS route-allowed-ips — This setting conflicts with the default route on EdgeRouter. Use an explicit static route instead.
- EdgeOS reboot fragility — WireGuard config can silently disappear after reboot. A scheduled check script is essential.
- Ephemeral IP — Oracle Free Tier VMs get an ephemeral public IP by default. Reserve it (free, limit 1) or it changes on reboot.
- CGNAT keepalive — T-Mobile's CGNAT has an aggressive UDP timeout. 25-second persistent-keepalive works, but if the tunnel drops intermittently, try 15 seconds.
- Browser audio resampling — The Web Audio API resamples to its native rate (usually 48kHz). Aggressive ratios like 3x (16→48kHz) cause intersample peak clipping on loud signals. Use a rate that divides evenly into 48kHz (e.g., 24kHz).
- IC-7610 spectrum mode — The radio's scope must be in Center mode (not Fixed Edge) for the spectrum display to track frequency changes in the web UI.
- icom-lan frontend builds — The Oracle free-tier VM has only 1 GB RAM, which is not enough for
npm run build. Build the frontend on a local machine and upload the dist/ folder.
- Python version — icom-lan requires Python 3.11+. Ubuntu 22.04 ships with 3.10 — install 3.11 via the deadsnakes PPA.
Cost
This entire system runs for free:
- Oracle Cloud VM: Always Free tier (no time limit, no credit card charges)
- HTTPS certificate: Let's Encrypt (free, auto-renewing)
- Domain name: ~$10/year (the only cost)
- WireGuard: Open source, built into Linux kernel and EdgeOS
- icom-lan: Open source
Tools and Credits
Built by WW0R. System designed and implemented with assistance from Claude (Anthropic).
Last updated: March 2026.