纯净度改为只查自己:锁定 IPv4 / IPv6 连接分别请求 cleanip.io/cli?json=1

cleanip.io 自 09-15 起不再接受 /cli?ip=,带参数一律 400,网络详情的纯净度因此一直停在“正在查询”。

- 新增 AddressFamilyRequest:用 Network.framework 建连接并锁定 IP 版本,走 HTTP/1.1 over TLS 请求,
  处理 chunked 与 Content-Length;这一族没有路由时直接失败,不干等。URLSession 无法指定地址族,
  而 cleanip.io 在 Cloudflare 后面 A / AAAA 都有,锁定地址族才能两族各查到自己的结果
- 锁定地址族的连接建不起来时退回 URLSession,只接受与所查同一族的结果
- 分流代理下 cleanip.io 看到的出口与 Cloudflare 不同时,显示 cleanip.io 看到的地址;缓存按 Cloudflare 的地址比对
- 查询失败的空结果不再写进 7 天缓存,也不进内存缓存,服务恢复后下次打开详情即重查
- 单元测试覆盖响应解析;真实网络下 IPv4 在本机验证、IPv6 在服务器上用 curl -6 验证 cleanip.io 返回对应地址的纯净度
This commit is contained in:
gentpan committed 2026-09-16 01:47:21 +05:00
1 parent 504b6eaeab
commit d305e5e3e1
11 files changed
+255 -66

No files matched your search

+8 -3
View File
@@ -158,9 +158,14 @@ is open and back to accessory when all are closed.
- **Interface and addresses** — `SCDynamicStore` / `SCPreferences` for the primary and physical
service, `getifaddrs` for addresses, CoreWLAN for signal and rate.
- **Public IP** — Cloudflare trace (ipify as fallback) for the address only. Location, ASN, network
type, native / broadcast and the cleanliness score come from `cleanip.io`, queried directly from each
Mac by `PublicAddressLookup` (one request per address family; results are cached per IP for an hour
in memory and for a week on disk, and a 429 stops further calls until the next UTC day). Country
type, native / broadcast and the cleanliness score come from `cleanip.io/cli?json=1`, queried directly
from each Mac by `PublicAddressLookup`. The endpoint only reports the caller's own address, so
`AddressFamilyRequest` opens one Network.framework connection pinned to IPv4 and one pinned to IPv6
(URLSession cannot choose the family) and speaks plain HTTP/1.1 over TLS; when a pinned connection
cannot be set up it falls back to URLSession and keeps the answer only if it is for the same family.
If cleanip.io sees a different exit than Cloudflare (split-routing proxies), its address is shown.
Results are cached per IP for an hour in memory and for a week on disk (failed lookups are not
cached), and a 429 stops further calls until the next UTC day. Country
codes are validated before being used as flag file names. `server/geoip/` still holds the systemd
timer that syncs MaxMind GeoLite2 onto `getopenstats.com/geoip/` (account and key in
`/etc/openstats/maxmind.env` on the server); the app no longer downloads those files, they are kept