wificalling-location-gateway README

The project's README from github.com/smthdagg/wificalling-location-gateway. As one markdown file: README.md.

Wi‑Fi Calling Location Gateway

An all-in-one Wi‑Fi Calling + Apple WLOC gateway for OpenWrt / ImmortalWrt

A standalone Rust service handles exit geolocation, WLOC response rewriting, certificate lifecycle, precise traffic isolation, and LuCI management — all integrated into a single installable package.

CI Release License: MIT Rust 1.90 OpenWrt GitHub stars LINUX.DO

English Guide · 中文完整教程 · Security Policy · Development & Test Plan

[!IMPORTANT] This project is intended for authorized devices, networks, and test environments only. It does not prove that your carrier has enabled Wi‑Fi Calling, and it is not a substitute for real call verification; WLOC target locations must never be treated as emergency-call location. Follow local law, carrier terms, and Apple device-management requirements.

WLOC settings UI


English

Introduction

Wi‑Fi Calling Location Gateway brings two previously separate flows together on one router:

  1. Wi‑Fi Calling Gateway selects a sing-box node for designated LAN devices and keeps the ePDG/IPsec channel (UDP 500/4500) independent.
  2. The WLOC service handles only TCP 443 traffic from the designated test device to the Apple WLOC hosts. In auto mode it resolves the target region from the exit IP of the node bound to that device; in manual mode it uses administrator-chosen coordinates.
  3. The LuCI interface provides nodes, device policies, auto/manual location, certificate installation, runtime status, and a sanitized event log.

The core boundary of the project is "independent, precise, and revertible": WLOC uses its own process, UCI config, Unix socket, nftables table, and logs. It never takes over the Gateway's nftables table and never intercepts UDP 500/4500. When the protocol is unknown, Geo data is invalid, or the service is unhealthy, no default fake coordinates are produced.

Features

How it works

%%{init: {"flowchart": {"toolbar": false}}}%%
flowchart TD
    I["Authorized test iPhone"] -->|"Wi‑Fi Calling · UDP 500/4500"| G["Wi‑Fi Calling Gateway"]
    G --> N["Bound sing-box node"]
    I -->|"Apple WLOC · TCP 443"| D["Precise DNS / nftables isolation"]
    D --> R["wloc-service · Rust"]
    R -->|"TLS + HTTP/2, strict upstream validation"| A["Apple WLOC"]
    R --> P["Exit probe"]
    P --> N
    P --> X["Geo resolution & cache"]
    X --> R
    L["LuCI / rpcd"] -->|"root-only UDS API"| R

A location update roughly goes through these steps:

  1. The router feeds only the assigned test device's Apple WLOC requests into the standalone service.
  2. Auto mode probes the real exit through the device's bound sing-box node; manual mode reads locally stored coordinates.
  3. The Geo layer validates country code, coordinate ranges, timezone, expiry, and provider responses — it never fabricates a result when data is unavailable.
  4. The service rewrites a response only when the authorized protocol structure, resource limits, TLS/ALPN, and safety state all hold; otherwise it passes the original response through or withdraws the redirect.
  5. LuCI shows the target location and network evidence; raw WLOC responses, node credentials, call content, and message content are never logged.

More detail: WLOC Service API, Threat model, and fail-open constraints.

Implementation

Layer Implementation Key constraints
Service runtime Rust 2021, Tokio, static musl ELF Rust 1.90; release LTO, opt-level=z, panic abort
TLS / HTTP rustls, ring, tokio-rustls, h2 TLS 1.2/1.3, ALPN h2, strict upstream cert & hostname validation
WLOC protocol standalone clean-room protocol model with bounded parsing Unknown, malformed, or oversized content is never guessed or partially rewritten
Control plane wloc.service/v1, 4-byte BE framing, JSON, Unix socket 16 KiB max frame, 2s total timeout, socket 0600, no TCP management port
Exit & location sing-box exit probe, Geo primary/fallback + cache, manual coordinates Invalid or stale data never falls back to default coordinates
OpenWrt integration procd, UCI, rpcd, dnsmasq, firewall4/nftables WLOC keeps its own table; never touches the Gateway table or UDP 500/4500
Admin UI LuCI JavaScript Auto/manual switch, certificate, status, and log; sensitive fields sanitized
Build & release OpenWrt SDK / Docker images pinned by digest locked/offline compile, SHA-256, architecture tag cannot masquerade as all

Support and validation status

"Installable" is not the same as "verified with a real iPhone / Wi‑Fi Calling". The table separates the evidence levels:

Platform Arch Package manager Current evidence Status
Redmi AX6S · ImmortalWrt 24.10.6 MediaTek MT7622 / AArch64 opkg r13 service installed; one WIFICalling sing-box remained running, WLOC was intercepting, crash-recovery re-interception was observed, and post-test available memory remained above 32 MiB Docker + router + iPhone WLOC passed
OpenWrt 24.10.8 x86_64 opkg / IPK Docker boot of init/ubus, integrated package install, service start, socket and v1 status checks Install matrix passed
iStoreOS 24.10.5 x86_64 opkg / IPK Same as above Install matrix passed
OpenWrt 25.12.3 x86_64 apk / APK v3 Same, using native APK v3, not a renamed IPK Install matrix passed
Other OpenWrt / ImmortalWrt versions or CPUs — — No device/SDK evidence yet Not verified

The runtime packages contain architecture-specific ELF files and must match the router CPU architecture. x86_64 packages are built with pinned SDKs; AX6S uses a separate AArch64 cortex-a53 toolchain. The formal Docker matrix installs both runtime variants for all three targets (six assets, eight runtime cases). Docker verifies install and boot; the AX6S row adds real router and iPhone WLOC evidence.

Installation

Prerequisites

1. Choose the right package

Release v1.3.0-r13 provides two installation variants for each of three targets. Both belong to this project and contain the same WCG, WLOC, control tools, LuCI and saved UCI schema:

r13 changes

Choose Standard when the firmware already supplies a suitable /usr/bin/sing-box. Choose Lite for constrained gateways such as AX6S: it stores a hash-pinned compressed sing-box in flash, transparently expands one shared copy into /tmp, and keeps only the single-worker/moderate-GC runtime profile without an artificial heap ceiling. Standard and Lite conflict intentionally and must not be installed together. Both preserve /etc/config/wificalling-gateway and /etc/config/wloc-service.

The variant suffix changes runtime ownership only; it does not create a separate product or restore split component packages.

Two ways to install:

Method A — package feed (recommended): add the signed feed and opkg install directly:

# Import the feed signing key (one-time)
wget -O /etc/opkg/keys/f7050198aa77cf15 \
  https://raw.githubusercontent.com/smthdagg/Smthdagg-Repo-feeds/main/wloc.pub
# Add the feed and install
echo "src/gz wloc https://smthdagg.github.io/Smthdagg-Repo-feeds/wificalling-location-gateway" \
  >> /etc/opkg/customfeeds.conf
opkg update && opkg install wificalling-location-gateway

Method B — manual download: grab the matching file from Releases and verify it against SHA256SUMS from the same release directory first.

Full instructions for both methods live in the feed repository (including the manual .apk install commands for OpenWrt 25.x).

opkg install /tmp/wificalling-location-gateway-lite_1.3.0-r13_aarch64_cortex-a53.ipk

Back up both UCI files first. On storage-constrained AX6S units, stop the services and remove the old integrated and sing-box packages before installing Lite; do not delete the saved UCI files. The r13 Lite package replaces the separate sing-box package and owns its transparent wrapper. /tmp is RAM: prefer the signed-feed upgrade (opkg update && opkg upgrade wificalling-location-gateway-lite), and when installing a local IPK, delete it right after installation (opkg install /tmp/x.ipk && rm -f /tmp/x.ipk) — leftover files in /tmp can push available memory below the cold-start memory gate.

3. OpenWrt 24.10 / iStoreOS 24.10 (IPK)

opkg install /tmp/wificalling-location-gateway_1.3.0-r13_x86_64.ipk
# Or use the corresponding Lite asset when a bundled, bounded runtime is preferred.

4. OpenWrt 25.12 (native APK v3)

apk add --allow-untrusted /tmp/wificalling-location-gateway-1.3.0-r13.apk

--allow-untrusted applies only to locally built packages that are not yet signed in a repository. Formal releases use repository signing; never rename an IPK into an APK.

5. Verify the services

test -S /var/run/wloc-service/control.sock
/usr/sbin/wloc-ctl status
/etc/init.d/wificalling-gateway status
logread -e wloc-service

The status response must contain "api_version":"wloc.service/v1". If the LuCI menu did not refresh, clear the browser cache and log back in instead of reinstalling packages for other architectures.

Usage order

Configure in this order to avoid mixing network and location problems:

  1. Import or add nodes in Wi‑Fi Calling Settings, then Save & Apply.
  2. In Device Policies, add the test iPhone with a fixed LAN IP, routing mode, and bound node; Save & Apply again.
  3. Enable Wi‑Fi Calling on the iPhone and watch for UDP 4500 ASSURED in Wi‑Fi Calling Monitor & Log; always confirm with a real call in/out.
  4. In WLOC Settings, copy the router-generated profile link and install it from Safari on the iPhone.
  5. On the iPhone, enable full trust for wloc-service root CA under Settings → General → About → Certificate Trust Settings, and verify the fingerprint.
  6. Turn on WLOC interception and choose Auto (follow node) or a manual location; Save & Apply.
  7. Toggle airplane mode / Wi‑Fi or reopen Maps/Weather to trigger a location request.
  8. Check the mode, country, city, timezone, coordinates, Geo state, and update time in WLOC Monitor & Log.

Step-by-step guides:

Building and verifying from source

Rust quality gate

./scripts/ci/verify.sh

This entry runs formatting, Clippy, unit/integration tests, Rust line coverage (minimum 80%), dependency audit, license policy, secret scan, release size, and repository contract checks. The formal 1.0 baseline is 69 Python tests passing, Rust line coverage ≥ 80%, and a release verification binary of about 0.97 MB.

AX6S / AArch64 cross build

OPENWRT_BIN_NAME=wloc-service \
OPENWRT_CROSS_CACHE_DIR=/tmp/wloc-rust-openwrt \
./scripts/ci/verify-rust-openwrt.sh

This pins the OpenWrt 24.10.8 mediatek/mt7622 toolchain, Rust version, and SHA-256, and verifies the AArch64 ELF, static linking, and size. See Rust OpenWrt cross-build notes.

x86_64 dual-format packaging

./scripts/openwrt/build-x86_64-runtime.sh \
  --out-dir "$PWD/dist/runtime/x86_64"

./scripts/openwrt/build-release-packages.sh \
  --version 1.3.0 \
  --release 9 \
  --arch x86_64 \
  --service-bin "$PWD/dist/runtime/x86_64/wloc-service" \
  --ctl-bin "$PWD/dist/runtime/x86_64/wloc-ctl" \
  --gateway-ipk /absolute/path/wificalling-location-gateway_1.3.0-r1_aarch64_cortex-a53.ipk \
  --gateway-sha256 <verified-sha256> \
  --out-dir "$PWD/dist/openwrt-release"

Four-environment Docker install & start matrix for all release packages

./scripts/openwrt/verify-docker-matrix.sh \
  --dist-dir "$PWD/dist/wloc-openwrt-release-r13"

Builds use the official OpenWrt SDK pinned by digest; after dependency preparation, product compilation runs locked/offline with read-only sources in a network-disabled container. Full boundaries and results: OpenWrt packaging and Docker matrix.

Language composition

A GitHub Linguist byte snapshot of the current main branch (2026-08-19). The router product runtime is mainly Rust, with JavaScript driving the LuCI admin UI and Shell handling OpenWrt lifecycle and network integration; Python is mostly used for reproducible builds, fixture governance, and CI.

pie showData
    title GitHub Linguist language snapshot
    "Rust · 46.36%" : 435146
    "JavaScript · 26.18%" : 245706
    "Shell · 17.28%" : 162237
    "Python · 9.93%" : 93223

The numbers drift as main updates; whether LuCI JavaScript, docs, and generated/excluded files count depends on GitHub Linguist rules. Do not judge the project's primary language by helper-tool bytes alone.

Project structure

src/                         Rust service, protocol, TLS/H2, exit & Geo modules
openwrt/                     procd/UCI, LuCI/rpcd, and OpenWrt package definitions
scripts/openwrt/             cross builds, dual-format packaging, Docker matrix
scripts/ci/                  coverage, security, dependency, and repository gates
tests/                       Rust, Python, JavaScript, and network-model tests
fixtures/                    synthetic/sanitized fixture contracts and validators
docs/                        API, security, deployment, testing, bilingual guides
.handoffs/                   reproducible multi-agent handoff records

Security, privacy, and rollback

wificalling-location-gateway (smthdagg/wificalling-location-gateway)

Searching Database…

这是什么?一个面向 OpenWrt/ImmortalWrt 路由器的一体化网关:一个 Rust 守护进程把指定测试设备的 Wi-Fi Calling(无线通话)流量经由选定的 sing-box 节点转发,并改写 Apple WLOC 的定位响应,使其与该节点所在地区一致,全部操作都可以在 LuCI 网页界面里完成。
它是装在我的 iPhone 上,还是装在路由器上?装在路由器上。它以 OpenWrt 软件包的形式安装——包含一个静态链接的 Rust 守护进程和 LuCI 界面。iPhone 只是指定的测试设备,通过 .mobileconfig 描述文件信任网关本地生成的 WLOC 根证书。
我怎么把它装到路由器上?Release v1.3.0-r13 提供 OpenWrt/iStoreOS 24.10 的 IPK 软件包和 OpenWrt 25.12 的原生 APK v3 软件包,均有 x86_64 和 AArch64 版本。推荐做法:添加已签名的软件源后用 opkg install 安装;否则请从 Releases 页面下载,并先对照 SHA256SUMS 校验。
路由器需要满足什么条件才能用?路由器需要 firewall4/nftables、LuCI 和 rpcd。测试用的 iPhone 必须有固定的 DHCP 地址,测试期间手机上的任何 VPN(WARP、Shadowrocket)都必须关闭,安装前还应该备份两份 UCI 配置文件。
到底在哪些路由器上验证过?完整的路由器加 iPhone 测试只在 Redmi AX6S 搭配 ImmortalWrt 24.10.6 上有记录;面向 OpenWrt 24.10、iStoreOS 24.10 和 OpenWrt 25.12 的 x86_64 版本仅通过了安装和启动检查。其他版本和 CPU 均未验证,软件包的架构必须与路由器的 CPU 匹配。
直接在家庭网络上试一试安全吗?它只面向获得授权的设备、网络和测试环境。它不能证明你的运营商已开通 Wi-Fi Calling,不能代替真实的拨号验证,而且 WLOC 的目标位置绝不能当作紧急呼叫的位置信息。
它会记录我手机上的操作吗?它的拦截范围仅限于一台指定测试设备发往 Apple WLOC 服务器的 TCP 443 流量,而且事件日志经过脱敏处理:原始 WLOC 响应、节点凭据、通话内容和消息内容一律不会记录。
它是怎么决定我的 iPhone 看到什么位置的?自动模式下,它会通过绑定到该设备的 sing-box 节点探测真实的出口,并遵循该节点的国家、城市、时区和坐标,只在缓存的探测结果过期时才重新探测。手动模式则使用管理员设置的坐标、地点搜索或保存的预设,并且 Geo 层绝不会用无效数据编造结果。
如果服务出问题或者我把它关掉会怎样?它的设计是故障放行(fail-open)的:WLOC 拥有自己独立的进程、UCI 配置、Unix socket 和 nftables 表,并且从不拦截 ePDG/IPsec 的 UDP 500/4500 隧道。遇到未知协议、无效的地理数据或健康状态异常时,它会放行原始响应或撤回重定向;禁用服务则会移除 DNS 劫持和 TPROXY 规则。
在 LuCI 界面里我能管理哪些东西?节点导入(sing-box 节点以及带预共享密钥的标准 WireGuard [Interface]/[Peer] 配置)、每台设备的策略、自动/手动定位切换(含地点搜索和保存的预设)、带指纹校验的证书安装、逐节点连通性测试、每 10 秒刷新一次的服务状态页,以及脱敏后的事件日志。
Standard 版和 Lite 版,我该选哪个?如果你的固件已经自带合适的 /usr/bin/sing-box,选 Standard;像 AX6S 这类资源受限的路由器选 Lite——它把经过哈希校验的压缩版 sing-box 存在闪存里,并在 /tmp 中展开一份使用。两个版本有意互相冲突,只能安装其中一个;两者都会保留你已保存的 UCI 配置。
Creator
smthdagg
Size
4 MB
Stars
245
Age
2mo
Commits · 90 Days