Push Tracker
ria-ran--sim-drop/README.md
A ashkan@beigi.net fd5f650861 Write operational README
Replace the placeholder README with a full operational guide: architecture,
user flow, quick start, config table, API reference (token profile URLs,
admin-token-gated export), profile-package-v2 format, Pi/Android deployment,
core sync, security notes, testing, and project layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 02:16:49 +00:00

187 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ria-ran--sim-drop
**Closed-loop eSIM provisioning for emergency / NTN 5G deployments.**
A self-contained appliance (Raspberry Pi or Android) that connects to a 5G gNB
for backhaul, broadcasts an open WiFi AP, and hands out eSIM credentials to
connecting devices through a captive portal. Users go from "no signal" to
"connected to the ad-hoc 5G network" in under a minute, with no operator in the
loop.
> Deep design rationale lives in [`plan/design.md`](plan/design.md). This README
> is the operational guide. Notable changes are tracked in
> [`CHANGELOG.md`](CHANGELOG.md).
## Architecture
```
┌─────────────────────────────────────────┐
│ Server Host (srsRAN gNB) │
│ - srsRAN gNB │
│ - Open5GS Core (or free5gc) │
│ - Subscriber database │
└─────────────┬───────────────────────────┘
│ 5G NR
┌─────────────────────────────────────────┐
│ UE WiFi AP Drop │
│ (Android device or Raspberry Pi) │
│ - WiFi AP (open network) │
│ - eSIM Provisioning Server (Flask) │
│ - Captive Portal (nginx + dnsmasq) │
└─────────────┬───────────────────────────┘
│ WiFi
End User Devices
```
## User flow
1. User connects to the open WiFi network `Emergency-5G`.
2. The captive portal auto-redirects to the activation page.
3. User taps **Activate eSIM**.
4. The server generates a unique IMSI / Ki / OPc / ICCID.
5. An eSIM profile is generated and offered as a one-time download.
6. User installs the profile via device Settings.
7. The device connects to the 5G network automatically.
## Quick start (local dev)
```bash
git clone https://riahub.ai/qoherent/ria-ran--sim-drop.git
cd ria-ran--sim-drop
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp config.yaml.example config.yaml # edit MCC/MNC/op_key as needed
python scripts/init_db.py
python -m server.app # → http://localhost:5000
```
Open <http://localhost:5000/activate> and click **Activate eSIM**.
## Configuration
All configuration is in `config.yaml` (start from `config.yaml.example`).
| Field | Description | Example |
|-------|-------------|---------|
| `network.mcc` | Mobile Country Code (3 digits) | `"302"` |
| `network.mnc` | Mobile Network Code (23 digits) | `"720"` |
| `network.op_key` | Operator key, 16-byte hex (32 chars) | `"63bfa50e…"` |
| `wifi.ip` | WiFi AP IP address | `"192.168.4.1"` |
| `server.host` / `server.port` | Flask bind address | `"0.0.0.0"` / `5000` |
| `server.admin_token` | Token guarding `/api/export`; empty = export disabled | `""` |
| `database.path` | SQLite database path | `"/opt/…/subscribers.db"` |
| `rate_limiting.window_hours` | One profile per MAC per window | `1` |
| `profile.apn` | APN / DNN used in the profile and core session | `"internet"` |
| `profile.smdp_address` | SM-DP+ address embedded in the LPA activation code | `"esim.local"` |
| `core.type` | Core adapter: `open5gs` / `free5gc` / `none` | `"open5gs"` |
| `core.mongodb_uri` | MongoDB connection for the core | `"mongodb://localhost:27017"` |
`op_key` is validated at startup (must be 16-byte hex) and the config file is
rejected if it is empty or missing a required section.
## API reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/activate` | GET | Captive portal page |
| `/api/provision` | POST | Generate credentials + profile; returns a one-time `profile_url` |
| `/api/profile/<token>` | GET | Download the profile via its unguessable capability token |
| `/api/export` | GET | Export all subscribers (JSON). Requires `X-Admin-Token`; disabled unless `server.admin_token` is set |
| `/api/status` | GET | Health check (subscriber count, uptime, core adapter) |
`POST /api/provision` accepts `{"mac_address": "aa:bb:cc:dd:ee:ff"}`; if omitted,
the server resolves the client MAC from the ARP table. Rate limiting is per MAC.
## Profile format
`create_esim_profile` produces a `profile-package-v2` bundle containing the raw
credentials plus **real 3GPP field encodings**`EF_ICCID` and `EF_IMSI`
(nibble-swapped BCD, 3GPP TS 31.102) — and an SGP.22 **LPA activation code**
(`LPA:1$<smdp>$<matching-id>`).
Full SGP.22 SIMalliance Interoperable Profile (ASN.1 UPP) generation via osmocom
pySim is the planned follow-up; consumer over-the-air install additionally
requires an SM-DP+ / LPA. The current package targets programmable SIMs and
core-network sync. See [`CHANGELOG.md`](CHANGELOG.md).
## Deployment
### Raspberry Pi
Requirements: Pi 4 (4GB+), a compatible 5G modem (e.g. Quectel RM500Q), Raspberry
Pi OS Lite 64-bit, Python 3.11+, and a pre-provisioned SIM for gNB backhaul.
```bash
sudo bash scripts/setup_wifi_ap.sh
```
This installs hostapd/dnsmasq/nginx, configures the AP and captive-portal
redirect, deploys the server under `/opt/esim-provisioning`, creates the `esim`
service user, restricts `config.yaml` to `600`, initialises the DB, and enables
the systemd services. Then edit `/etc/esim-provisioning/config.yaml` and
`systemctl restart esim-provisioning`.
### Android / Termux
```bash
bash android/termux_setup.sh
~/esim-provisioning/start.sh
```
See [`android/README.md`](android/README.md) for details and limitations.
## Core network sync
With `core.type: none`, credentials are stored locally only. To push them to a
core network:
```bash
python scripts/sync_subscribers.py # sync unsynced subscribers to Open5GS/free5gc
python scripts/export_subscribers.py # dump all subscribers as JSON (reads the DB directly)
```
Adding a new core is a single class in `server/core_adapters/` implementing the
`CoreAdapter` interface.
## Security notes
- Profiles are served at an unguessable one-time token URL, never an enumerable
id — the profile body contains Ki/OPc.
- `/api/export` returns all key material and is **disabled by default**; set
`server.admin_token` and send it as `X-Admin-Token` to enable.
- The deployed `config.yaml` (holds `op_key`) is `chmod 600`, owned by the
service user; the systemd unit runs with `NoNewPrivileges`, `PrivateTmp`, and
`ProtectSystem=strict`.
- The WiFi network is intentionally open (emergency access); there is no
transport encryption on the local link.
## Testing
```bash
pip install -r requirements.txt
python -m pytest tests/ -q
```
The suite covers credential generation and 3GPP encodings, the database and rate
limiting, the core adapters, config/utility validation, and the Flask API
(provisioning, token download, rate-limit and export auth).
## Project layout
```
server/ Flask app, profile generator, database, core adapters
portal/ Captive-portal frontend (HTML/CSS/JS)
scripts/ init_db, sync/export subscribers, Pi AP setup
configs/ nginx / dnsmasq / hostapd reference configs
systemd/ Service units
android/ Termux deployment
tests/ Test suite
plan/ Design doc and per-unit implementation notes
```
## License
MIT (or as appropriate for the deployment context).