Push Tracker
Files 17 items
|
A
c33843d1ec
Provisioning bodies are tiny; set MAX_CONTENT_LENGTH=16 KiB so oversized POSTs are rejected with 413 rather than buffered. Tests: +1; 99 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
|||
|---|---|---|---|
| android | |||
| configs | |||
| plan | |||
| portal | |||
| scripts | |||
| server | |||
| systemd | |||
| tests | |||
| .gitattributes | |||
| .gitignore | |||
| CHANGELOG.md | |||
| claude.md | |||
| config.yaml.example | |||
| pytest.ini | |||
| README.md | |||
| requirements-saip.txt | |||
| requirements.txt | |||
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. This README is the operational guide. Notable changes are tracked inCHANGELOG.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
- User connects to the open WiFi network
Emergency-5G. - The captive portal auto-redirects to the activation page.
- User taps Activate eSIM.
- The server generates a unique IMSI / Ki / OPc / ICCID.
- An eSIM profile is generated and offered as a one-time download.
- User installs the profile via device Settings.
- The device connects to the 5G network automatically.
Quick start (local dev)
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 (2–3 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
Two formats are selectable via profile.format:
v2(default, always available) — a JSONprofile-package-v2bundle with the raw credentials plus real 3GPP field encodings (EF_ICCID,EF_IMSI, nibble-swapped BCD per 3GPP TS 31.102) and an SGP.22 LPA activation code (LPA:1$<smdp>$<matching-id>).saip— a real DER-encoded SGP.22 SAIPProfileElementsequence: ProfileHeader (ICCID), PE-MF personalizing EF.ICCID, PE-USIM personalizing EF.IMSI (both against the standard "created by default" templates), and Milenage AKA parameters (Ki/OPc). Built withasn1toolsagainst the officialPE_DefinitionsASN.1 schema. Enable by installingrequirements-saip.txt(see that file for the schema source) and settingprofile.format: saip. If prerequisites are missing the server logs a warning and falls back tov2.saip_profile.decode_saip_profile()parses a package back into its ProfileElements for inspection.
The SAIP package carries ICCID, IMSI, and the AKA credentials; a fully
installable profile additionally needs the remaining mandatory PEs (MF, PIN/PUK,
full EF set) and card-level validation. Consumer over-the-air install also
requires an SM-DP+ / LPA; these packages target programmable SIMs and
core-network sync. See 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.
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 android/termux_setup.sh
~/esim-provisioning/start.sh
See 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:
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)
To generate or inspect a profile outside the server (dev/ops):
python scripts/make_profile.py --format saip --out profile.der # generate
python scripts/make_profile.py --inspect profile.der # decode + summarize
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/exportreturns all key material and is disabled by default; setserver.admin_tokenand send it asX-Admin-Tokento enable.- The deployed
config.yaml(holdsop_key) ischmod 600, owned by the service user; the systemd unit runs withNoNewPrivileges,PrivateTmp, andProtectSystem=strict. - The WiFi network is intentionally open (emergency access); there is no transport encryption on the local link.
Testing
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).