2026-08-10 22:00:00
家里的主路由用爱快(iKuai)用了挺久。最近我把它换成了跑在 PVE 里的 RouterOS——一半是嫌爱快的界面高仿 UniFi 又不精致、还被抓包爆出后台偷偷往外传数据;一半是想把路由器也虚拟化进 PVE,和别的服务一起管。折腾下来最值得记的,是「兜了一圈、分流还是回归 Surge VM Gateway」这个结论,以及顺手把家里各种零碎都收进同一台 PVE 的一点心得。这篇一起记下来。
先说结论:不是爱快不能用,而是我越用越不喜欢、也越用越不放心。
一是观感。爱快 4.0 那套界面明显是照着 UniFi 抄的,但又抄得没 UniFi 那么精致,用起来总有种「高仿」的别扭——形似神不似,细节一比就露怯。
二是更要命的隐私。有人把爱快挂在主路由下抓包分析,发现不管是原厂硬件还是免费版,后台都有相当可观的、不该有的对外上传行为(参见这篇抓包分析)。这事我还是从上一篇博客的评论区才知道的——一台蹲在全家流量咽喉位置的路由器,在背地里往外传数据,这是我完全没法接受的。
正好又有个顺水推舟的理由:我整套家用基础设施都在 PVE 上(一堆 VM、随手快照、想装什么装什么),我更希望连路由器本身也变成一台 VM,和别的服务一起被同一台 hypervisor 管理。于是换成了 RouterOS CHR,冲着它这几点:
/ip cloud、甚至以后想玩 BGP,全在一台设备里捏;对喜欢自己摆弄路由的人比封闭界面爽太多。更重要的是——它就是一台老老实实的路由器,不会背着我偷偷干别的事。物理拓扑很简单:PVE 上四个 Intel I226-V,两个直通给 RouterOS 当双线 WAN,两个桥进 vmbr0 当 LAN;RouterOS 这台 CHR 就是内网网关 10.10.10.1。
迁过来之后,这些是 RouterOS 帮我搞定、当初在爱快上要么没有、要么不顺手的东西:
per-connection-classifier 把连接分到两条线。这里有个关键细节:both-addresses 分类器会把「同一台设备到同一个目标」的所有连接哈希到同一条线,根本不叠加;要让单机到单个目标的多连接真正分摊到两条线,得用 both-addresses-and-ports(把端口也算进哈希)。address-list 的那几个境外 VPS 才双线叠加。/ip dns 本身就是带缓存的解析器,allow-remote-requests=yes 之后全家可以直接拿它当 DNS。/ip cloud set ddns-enabled=yes,白得一个跟着公网 IP 走的域名。我家公网 IP 一天能变好几次,这个是刚需。配完这些,路由这层是舒服了。真正的乐子在后面。
迁过来之后,在端口转发上补了两个 RouterOS 的小知识,尤其第二个解决了我折腾这套网络里困扰最久的问题:
:A 去这台、:B 去那台」这种按端口分流,DNS 层压根做不了。所以结论是:这件事只能在 NAT 层做——局域网设备访问「公网 IP:端口」时,路由器按端口把它 DNAT 回对应内网主机(再补一条 masquerade 让回包对称)。配好之后同一个域名在家在外一套配置、完全无感,困扰了我很久的问题算是彻底解决了。RouterOS 能干的事很多,我一度也想过把「国内外分流」也塞进路由器:GEOIP、address-list、mangle 打标、按目的地分流……理论上都做得到。但真上手你会发现,在路由器上做「好用的」域名级分流是件很累的事:规则要自己维护、GEOIP 库要更新、很多按域名/SNI 才判得准的场景在纯 IP 层很别扭。
而这套东西,Surge 已经做到极致了:fake-IP + 规则集 + 域名级判定 + 各种策略组,机场订阅一挂、规则一套,国内直连、国外走代理,几乎零维护。
所以我最后的架构是一个很舒服的分工:
10.10.10.88),然后——关键一步——让 DHCP 把默认网关下发成这个 Surge 地址而不是路由器。这样全家设备一拿到 IP,默认网关就是 Surge,流量天然经过 Surge 做国内外分流,路由器只在 Surge 背后老老实实转发。普通设备 → 网关 10.10.10.88 (Surge, on Mac) → 国内直连 / 国外走代理
│
└→ RouterOS (10.10.10.1) → 双线 PPPoE 出口
有个容易忽略的细节:DHCP 下发的 DNS 要故意保持成公网 DNS(而不是路由器)。正因为 DNS 在「别的网段」,客户端的查询才会被送去经过 Surge、让它做 fake-IP 劫持和分流;要是把 DNS 指回路由器,反而绕过了 Surge,分流就破了。
绕这一圈我才想明白:「主路由用什么」和「分流用什么」是两个问题。 我换 RouterOS,换的是那台负责链路、拨号、转发的「路由器」;而「分流」这件最需要域名级智能、又最不想维护的事,Surge VM Gateway 依然是我的最优解。把两者拆开,各干各最擅长的,整套网络反而最清爽。
既然路由器都进 PVE 了,索性把家里各种零碎需求也一起收进同一台机器。其中我自己最得意的一个,是门禁自动确认。
我在 PVE 上单独开了一台 VM,专门伺候一个 DJI 的 4G 模块:模块用 USB 插在 PVE 宿主机上,再在 PVE 里把它直通给这台 VM。VM 里跑的是 VoHive。
它解决的是一个特别琐碎、又特别烦的日常:以前外卖或快递员到楼下,按我的房间号,门禁系统会自动打电话到我手机,我接通、按一个「#」,楼下的门才开。人在忙、在开会、或者手机不在手边的时候,这通电话很容易误事。
现在这套流程被彻底自动化了:VoHive 一旦接到门禁系统打来的电话,就自动接通并确认(等于替我按下那个「#」)。快递外卖照样顺利送上楼,我完全不用管。折腾归折腾,但这种「省掉一件天天要做的小事」的自动化,用起来是真香。
both-addresses-and-ports;both-addresses 会把同一对 src/dst 钉死在一条线上,不叠加。2026-07-08 01:00:00
Here's a fact that sounds wrong the first time you hear it: the great-circle route — the provably shortest path between two airports on a sphere — is almost never what your flight actually flies.
Every flight tracker, every mileage program, every "distance" you've ever been shown for a flight is a great-circle number. It's the aviation industry's official fiction. The real path is longer. Usually much longer.
I can put numbers on this, because I recently rebuilt my flights page to compute distances from actual ADS-B position data instead of airport coordinates. For 107 of my 113 lifetime flights I now have the real flown track. The result:
Here are my ten biggest offenders — every flight ranked by how far its actual ADS-B track exceeded the great-circle distance:
| # | Flight | Date | Route | Great circle | Flown (ADS-B) | Over |
|---|---|---|---|---|---|---|
| 1 | NH32 | 2025-06-24 | ITM → HND | 251 mi | 362 mi | +44.2% |
| 2 | CX961 | 2025-07-24 | HGH → HKG | 680 mi | 940 mi | +38.2% |
| 3 | AA4568 | 2025-03-02 | PIT → JFK | 339 mi | 458 mi | +35.1% |
| 4 | GJ8738 | 2023-04-15 | SZX → HGH | 665 mi | 879 mi | +32.2% |
| 5 | CX302 | 2026-07-06 | HKG → PVG | 780 mi | 1,027 mi | +31.7% |
| 6 | DL5794 | 2024-01-12 | JFK → PIT | 339 mi | 440 mi | +29.8% |
| 7 | 9C8921 | 2023-06-07 | PVG → HKG | 780 mi | 1,008 mi | +29.2% |
| 8 | UA408 | 2025-05-22 | EWR → IAD | 212 mi | 273 mi | +28.8% |
| 9 | AA4734 | 2025-03-07 | JFK → PIT | 339 mi | 436 mi | +28.6% |
| 10 | KE882 | 2026-03-31 | PVG → ICN | 511 mi | 656 mi | +28.4% |
Two patterns jump out immediately: short hops (Osaka–Tokyo, the New York–Pittsburgh cluster, Newark–Washington) and Chinese coastal routes (Hong Kong, Shanghai, Hangzhou, Shenzhen in every combination). Both are explained below.
The record: NH32, Osaka Itami → Tokyo Haneda, +44.2%. Great circle: 251 mi. Actually flown: 362 mi. This is a 250-mile hop, and that's exactly why the percentage explodes — departure and arrival procedures are a roughly fixed overhead, and on a short flight they are the flight. You don't take off from Itami and point at Haneda; you fly a published departure, get vectored around Osaka's terminal airspace, cruise for twenty minutes, then join an arrival stream that snakes across Tokyo Bay. The overhead that disappears into rounding error on a long-haul is nearly half the trip here.
The runners-up are all Chinese coastal routes. Hangzhou → Hong Kong at +38%, my Hong Kong → Shanghai flights clustered between +27% and +32%. These aren't weather anomalies — the same routes come in long every single time, because in Chinese airspace you fly published airways between fixed waypoints, full stop. The direct line from Hong Kong to Shanghai crosses airspace you simply don't get to use. Every HKG–PVG flight dutifully tracks up the coast, and every one of them logs roughly 1,000 miles for a 780-mile city pair.
The exception that proves the rule: TN102, Auckland → Papeete, +0.4%. Over the empty South Pacific there are no airways to follow, no military zones to dodge, no arrival queues. The 787 simply pointed at Tahiti and flew the sphere's geodesic, 2,543 miles of it, four miles over the theoretical minimum. When nothing stops a plane from flying the great circle, it does.
1. The sky is a road network, not an open field. Most of the world's airspace is organized into airways — published corridors between named waypoints. Your flight plan is a sequence of these, filed and approved before departure. Some regions (the North Atlantic, parts of the US) have loosened into "free route" airspace; much of Asia has not. If the airway bends, you bend.
2. Wind beats distance. The shortest path is not the fastest or cheapest flight. A jet stream tailwind of 100+ knots is worth far more than a few dozen miles of track. Eastbound trans-Pacific flights deliberately swing south or north to ride the jet; westbound flights detour to avoid it. Dispatchers optimize a cost function of time and fuel — distance is just one input. A longer route that lands earlier wins.
3. Procedures bracket every flight. SIDs and STARs — standard instrument departures and arrivals — exist so that hundreds of flights an hour don't converge on an airport from random directions. They add miles by design, and runway direction adds more: land to the south on a field north of the city and you'll overfly, turn back, and line up. Fixed overhead, every flight, both ends.
4. Weather, the day-of tax. Thunderstorm cells, typhoons, turbulence reports — all bought with track miles. My own record-setting CX302 into Shanghai carried a remark about bad weather at Chek Lap Kok and paid +31.7% that day.
5. Politics and money. Overflight permissions, country-by-country navigation fees, and sanctions redraw routes wholesale — the closure of Russian airspace added hours to Europe–Asia flights, no meteorology involved. Airlines also route around expensive airspace when fuel is cheaper than fees.
Of my 113 flights, six have no usable track at all and are counted at their great-circle minimum. All six are mainland-China domestic flights, flown between May 2021 and August 2023 — four Shanghai–Chongqing legs, one Shenzhen–Hangzhou, one Guangzhou–Hangzhou. That clustering is not a coincidence.
Community trackers like ADS-B Exchange depend on volunteers running ground receivers, and through 2020 mainland coverage was genuinely good — both of my July 2020 domestic flights came back with essentially complete tracks (one at 100% coverage, one at 91%). Then, around 2021, Chinese authorities cracked down on hobbyist ADS-B receivers on national-security grounds, and crowd-sourced coverage over the mainland collapsed. My 2021 and 2022 flights exist in the archives only as scraps: a distant receiver catching a few minutes of cruise, then nothing. One flight I checked appears as 58 position reports covering thirteen minutes of a two-hour trip.
I tried hard to salvage these. For a couple of them I could stitch fragments into a partial reconstruction, but they failed my own sanity checks — one came out 48% over the great circle, which might even be true (a typhoon was active in East China that week), but "might" isn't a number I'll put in the dataset. The alternatives dried up too: Flightradar24 does have these flights — it never depended on the volunteer receivers — but its history is subscription-tiered and my plan reaches back one year, not five. adsb.lol's open archive only begins around 2024. Coverage over the mainland has since partially recovered through satellite ADS-B and new receivers, which is why my recent Chinese flights track fine; the 2021–2023 gap, though, is simply lost history.
So those six are honest floors: the plane certainly flew farther than the number I'm using.
Every frequent-flyer program on earth credits you great-circle miles. Flighty, Umetrip, your airline's app — all display great-circle distance (I checked Umetrip against my own tracks: its kilometers match the great circle almost exactly). By the industry's own bookkeeping, I've flown 155,589 miles. My actual tracks say 167,015 and counting, and that number is a floor — the six flights I couldn't recover tracks for are counted at their great-circle minimum, and coverage gaps inside tracked flights are bridged with straight lines. Eleven thousand miles, genuinely flown, that no ledger will ever credit.
So the shortest-path number everyone quotes is really a lower bound nobody achieves — except, apparently, one 787 alone over the South Pacific with nowhere to be vectored and nothing in the way.
The great circle isn't a route. It's a promise the sky can't keep.
2026-07-07 18:30:00
This blog ran on Hugo with the PaperMod theme for four or five years. On June 15 I threw the whole thing away and rewrote it in Astro.
The interesting part isn't the framework choice. It's how the rewrite happened: I typed /goal in Claude Code, described what I wanted, and went to bed. The next morning, a working Astro site was sitting in a new repository. The migration commit tells the story in one line: 184 files changed, 95,175 insertions — every published post carried over, every URL intact, every comment thread still attached.
Hugo itself was never the problem — it's fast and it's stable. The problem was living on top of PaperMod. Updates slowed to a crawl, known bugs sat unfixed for months, and every release I did pull was a small gamble on what my overridden partials would collide with. And fixing things myself meant fighting Hugo's template DSL — which isn't really Go, even if you write Go: logic lives inside markup, there's no component model, composition happens through partials and dict gymnastics, and when something breaks the error points into the theme's internals instead of your code. Over the years my workarounds accumulated as a pile of overridden partials I was afraid to touch.
The honest reason I stayed so long: a full rewrite was a multi-weekend project, and the blog worked. That math changed.
I started from AstroPaper (v6 at the time, since upgraded to Astro 7) rather than a blank page — a solid, minimal base to mutate. The overnight /goal run handled the tedious parts that used to make these projects die in a branch:
date → pubDatetime, AI summaries → description, tags and drafts preserved/posts/<slug>/ — nothing 301s, nothing breaksI reviewed the result over coffee and filed nitpicks. That was the migration.
I started on Astro 6 and upgraded to 7 mid-rewrite — a single commit. It's worth a section, because the toolchain got noticeably better underneath me:
@astrojs/compiler-rs, a Rust rewrite of the old Go-to-WASM compiler, with Rolldown in the dependency tree. A full production build of this site — astro check type-checking every component, building 80+ posts, generating OG images, and indexing everything for Pagefind search — finishes in about half a minute on my laptop.astro dev is a daemon now. The dev server detaches and keeps running with astro dev stop / status / logs to manage it, instead of holding a terminal hostage.{" "} at those boundaries.Deployment matches the pace. The repo pushes to GitHub, and Cloudflare Workers picks up every commit, builds it with bun, and has it live on the edge about a minute later. My publishing flow is literally /ship — Claude writes the conventional commit, pushes, and by the time I've switched tabs to check, the deploy is usually already green. The old Hugo site deployed fast too, to be fair; the difference is that now everything else is this fast.
The real payoff came after. The git log since June 15 is ~50 small commits, and almost every one of them was a sentence to Claude rather than an evening of my own:
Typography took four iterations to get right — and that's the point: iterating was cheap. System fonts first, then Berkeley Mono as the Latin face, then Space Grotesk for everything, and finally where it landed today: Inter for body text, Space Grotesk for headings, Berkeley Mono for code — with CJK always falling through to system fonts so Chinese readers never download a webfont.
The palette went through the same loop. I started with AstroPaper's colors, tried a blue/teal pair that never felt right, studied a design I admire, and ended with warm stone neutrals and pure ink accents — black on paper in light mode, white on warm black in dark. No borrowed blues.
Code blocks became macOS windows via a custom Shiki transformer — traffic-light dots, the filename in the title bar, click a line to copy that line, click the header to copy the block.
Posts got infrastructure Hugo never had:
.md endpoint with a copy button, because half of us are pasting articles into an LLM anywayThe flights page became a real data product. It started as a static table; now a pipeline in the repo computes my actual flown distances from ADS-B position data — 107 of my 113 flights show genuine track miles instead of great-circle estimates, each labeled with its data source, with rankings and an AI-written analysis of my flying habits.
Even the chores automate themselves: a GitHub Action refreshes my U.S. visa wait-times post from travel.state.gov every three hours. It has made more commits than I have.
My comment system is now entirely my own implementation. The backend is Cloudflare Workers + D1 with Telegram moderation — I wrote about building it in March. During this rewrite the frontend became a native Astro component in my own codebase: no embedded widget, no iframe, no third-party JavaScript. The git log shows what that ownership buys — four styling passes on reply indentation and the input area until it felt right, each a ten-minute conversation instead of a fork of someone else's plugin.
None of the individual features here are remarkable. What's remarkable is the cost curve. A bespoke blog used to be a tax paid in weekends; now the expensive part is knowing what you want. I spent three weeks deciding — fonts, colors, how a code block should feel — and almost no time implementing. The implementation happened while I slept, or while I was doing something else, one sentence at a time.
Hugo is still a great tool, and I'd still recommend it to anyone who wants a blog that just works. But if you've been putting off the rewrite that makes the site truly yours — the excuse is gone.
2026-06-17 22:30:00
我一直有个执念:希望每一张照片的 EXIF 里都带上准确的 GPS 位置。这样以后在地图上回看,哪年哪月在哪儿按下的快门一目了然。可惜大部分相机并不内置 GPS,为了这件小事我折腾了一圈方案,从手表轨迹回填、低功耗 App,到自己写了一款 App,最后才找到一个几乎完美的硬件解法。这篇就把整个过程和最终方案完整记录一下。
大部分数码相机,尤其是无反,还是没有内置 GPS。照片导出来以后,EXIF 里少的就是经纬度。
厂商一般会把这个问题丢给手机 App:手机通过蓝牙连相机,把当前位置同步过去,相机按下快门时顺手写进 EXIF。这个设计在纸面上没什么毛病,但一到旅行场景就很烦。相机不可能一直开着,走几步拍几张,关机、休眠、再唤醒,蓝牙连接很容易就断了。等它慢吞吞重新连上时,刚才那几张已经拍完了。
手机这边也不省心。App 放到后台久了以后,系统随时可能把它收掉,iOS 尤其明显。定位一停,后面拍出来的照片就又变成没有坐标的状态。最难受的是,拍的时候你通常还以为它在正常工作,回家导图才发现 GPS 信息有一段没一段。
我一开始想的是,不走相机厂商这套蓝牙同步,改成后期补。
一种办法是用 Garmin 手表全程记录 GPS 轨迹,导出 GPX。回来以后写个脚本,按照片的拍摄时间去轨迹里查坐标,再写回 EXIF。
另一种是 Lakr233 之前推荐过的「一生足迹」。让手机低功耗记录轨迹,最后再用他写的程序回填照片位置。
这两条路本质上都是先录轨迹,再按时间对齐。技术上当然可行,精度也够。但我后来发现,这种方案很吃「自觉」。
出门前要记得开记录。忘了开,这一天就没轨迹可用。回来以后还要导轨迹、跑脚本、检查时区。刚开始觉得还好,多来几次就懒了。它解决的是「照片里最终能不能有 GPS」的问题,没有解决「我拍完就希望它已经有 GPS」的问题。
后来我在 GitHub 上看到 gkoh/furble。这个项目很厉害,作者把不少相机厂商的蓝牙协议都逆向出来了。也就是说,开发者可以绕过官方 App,直接给相机发送 GPS 坐标。
我顺着这个项目写了一款 iOS App,叫 Koko。名字来自日语「ここ(koko)」,意思是「这里」。它做的事情也很直接:配对相机以后,在按下快门那一刻,把手机当前的位置写进 RAF / JPEG 的 EXIF。
Koko 集成了 furble 里已有的协议,理论上支持富士 X / GFX 系列、索尼 Alpha / ZV 系列、佳能 EOS / PowerShot 系列等机型。
另外,Ricoh GR 这边也可以稍微期待一下。furble 的 Issue #267 里已经有人在做 GR 的蓝牙支持,后面还提交了 PR #268。目前这个 PR 主要是连接、配对、重连和基础快门控制,作者说已经在 Ricoh GR III HDF 真机上测过。GPS 写入还没一起做进去,只是先留了 stub,等快门这部分稳定以后再单独补。所以它还不是「今天刷完就能给 GR 写 GPS」的状态,但至少这条路已经有人在推了,GR 用户可以先关注一下。
顺便说一句:Koko 支持的相机型号是按协议推的,但我手头只有富士 X-S20 一台机器,所以只在 X-S20 上实测过。其他型号理论上都能用,但因为没有设备,我没办法逐一验证,介意的话请谨慎尝试。
Koko 比官方 App 清爽很多,后台常驻也更稳一些。但它最后还是没能把问题彻底解决掉。原因很简单:不管 App 怎么写,它仍然走的是「手机 ↔ 相机」这条蓝牙链路。相机休眠会断,唤醒后不一定马上重连,手机系统也可能杀后台。这些不是写几行代码就能绕开的。
真正让我觉得可以不用再折腾的,是 furble README 里提到的一套硬件玩法。
思路反而很朴素:既然手机不适合当中间人,那就换一个只做这件事的小设备。这个设备自己接 GPS 模块定位,再通过蓝牙直接连相机。它不需要联网,也不需要手机参与。配对好以后,开机就连,相机醒着的时候它就一直给坐标。
有意思的是,这套东西不是需要自己画板焊接,而是可以直接买 M5Stack 的现成模块。我已经在他们官方店下单了。
| 硬件 | 作用 | 美元价 | 国内价(淘宝官方旗舰店) |
|---|---|---|---|
| M5StickS3(ESP32-S3 Mini IoT Dev Kit) | 主控 + 屏幕 + 电池 + 蓝牙 | US$21.50 | ¥149 |
| M5Stack GPS Unit v1.1(AT6668) | GPS / 北斗定位模块 | US$9.95 | ¥65 |
两样加起来三十多美元,国内官方店两百出头人民币。比起为了 GPS 去换一台带定位的机身,这个成本基本可以忽略。GPS 模块尤其便宜,AT6668 还是 GPS + 北斗双模。
GPS Unit 用 HY2.0-4P 线接到 StickS3 上就行。组装完大概就是这么一小坨,揣兜里没什么压力:
furble 的 README 里推荐了好几款主控,比如 M5StickC Plus、M5StickC Plus2、M5Core2、M5Core Basic 等。我最后选 M5StickS3,主要是两个原因。
第一,它是新款,芯片是 ESP32-S3,内置电池 250mAh,比常被推荐的 StickC Plus2(200mAh)还大一点。第二,README 里那些旧型号现在有些已经停产,或者不太好买。与其到处淘库存,不如买一个还在正常销售的新款。
StickS3 这块小东西其实性能挺夸张,双核 LX7 @ 240MHz、8MB Flash、1.14 寸彩屏、USB-C。拿来只当相机 GPS 模块,确实有点大材小用。
这是我下单前最关心的一点。
furble 作者给过一个参考:ESP32 保持蓝牙连接时功耗大约 30mA。单看这个数字:
StickS3 是 250mAh,如果只是纯蓝牙遥控,不挂 GPS,按比例估算大概能到 7 到 8 小时。但这套方案还要外接 GPS Unit,AT6668 会从 StickS3 的电池里取电,屏幕如果常亮也会继续耗电,所以不能按纯蓝牙的数字算。
综合现有反馈,带 GPS 连续工作大概按 4 到 5 小时预期比较稳。小半天够用,拍一整天就得补电。
好在 StickS3 是 USB-C,可以边充边用。真要从早拍到晚,带个小充电宝就行。再把屏幕超时、亮度这些设置调一下,还能再省一点。
整套东西到手以后,刷机和配置都不复杂:
设置好以后,每次开机,StickS3 + GPS Unit 都会自动连相机并开始发送坐标。相机休眠以后再唤醒,它也会自己重连。至少从机制上看,这比手机 App 靠谱太多。
小提示:furble 的 GPS 地理标记目前主要支持富士和索尼机型(快门遥控、对焦、间隔拍摄等功能覆盖更广)。下单前最好先去 README 的支持列表里确认下自己的机器。
现在这套东西还是有点散装。StickS3 和 GPS Unit 靠一根线连着,拍的时候只能揣兜里或者拿在手上,能用,但不够舒服。
我后面打算给它做一个 3D 打印支架,把这两个小东西固定成一体,然后直接卡到相机热靴口上。这样 GPS 模块朝天,信号应该更好;线也能收进支架里,看起来不会那么狼狈。等做完了再单开一篇,把结构和打印文件一起放出来。
为了让每张照片都带 GPS,我从 Garmin 轨迹回填、低功耗记录 App,一路折腾到自己写 Koko。绕了一圈之后,结论反而很简单:只要还让手机夹在相机和 GPS 中间,这件事就很难稳定。
后期回填能用,但每次出门和回来都要多做一步。Koko 可以把官方 App 的体验改善一些,但它还是逃不出手机后台和蓝牙重连的问题。furble + M5StickS3 这套方案最吸引我的地方,就是把手机从链路里拿掉了。
三十多美元,一个小盒子,自己定位,自己连相机,拍完照片坐标已经在 EXIF 里。对我这种相机 GPS 强迫症来说,这大概就是目前最接近「不用管它」的解法。
2026-06-17 20:00:00
最近在折腾日本落地节点,用的是 WebArena Indigo(下文简称 Indigo)。这家日本 VPS 便宜、走 NTT 线路,拿来做落地很合适。但我在「中转机 + Indigo 落地」这套架构上踩了个不小的坑,折腾了大半天才定位清楚,这篇就记录一下整个过程和最后的解法。
需求很简单:用 Indigo 当日本落地,但 Indigo 的直连 IP 从国内访问的路由一般,所以前面再挂一台中转机(线路对国内友好),客户端连中转机、中转机把流量送到 Indigo 落地。
客户端 → 中转机 → (隧道) → Indigo 落地(snell)
落地这台 Indigo 上跑的是 snell,中转机负责把入口端口的流量转发过去。问题就出在「中转机 → Indigo」这一段该怎么走。
最省事的做法当然是 realm(或 gost 之类)做个普通的公网 TCP 转发,中转机监听一个端口,直接转到 Indigo 的公网 IP。
但在 Indigo 上,走公网转发是百分之百会被限速的——把流量从中转机公网直接灌到落地的公网 IP,必然撞限速。所以 realm 这条路直接 pass,我给两台机器之间建了一条 GRE 隧道,想着把流量「包」进点对点的隧道里、看起来像内网互联,应该能躲过针对公网中转的限速。
剧透一下:隧道这个方向是对的,但只对了一半——IPv4 GRE 照样会被限速,一上量就崩;最后只有 IPv6 的 GRE 才真正躲过去。
隧道建好后,节点能连、能上网,平时刷网页也没问题。但只要一测速,现象非常稳定:
Socket closed by remote peer,测速被打断,把 snell 摘掉、直接在隧道内裸跑 iperf3,问题原形毕露:
| 方向 | IPv4 GRE |
|---|---|
| 落地 → 中转(即下行/测速方向) | ~198 Mbps,一路下滑,重传 2.8 万+ |
| 中转 → 落地 | ~475 Mbps,重传几千 |
下行方向 8 秒重传两万八,吞吐从 235 一路掉到 154——典型的「一上量就被狠丢包、TCP 雪崩、连接被 reset」。这就是测速「第一下能跑、马上断」的根源。
为了不冤枉好人,我把可能的原因逐个排掉:
curl 直连测速源,5MB 秒下,出口带宽、源站、解析全都好的。cake,但它 bandwidth unlimited,整个生命周期只丢了几百个包,根本没在限速。也就是说:包不是在落地机的队列里丢的,是出了落地机之后、在到中转机的这段公网链路上丢的。 落地机本机直连测速源又是满速的(出网和源站都没问题),所以矛头指向:这条路径对 GRE(协议 47)这种隧道流量,在高速率下做了限速 / 丢包。
更有意思的是,后来我拿另一台 Indigo(同机房、邻近子网)裸测不带任何 GRE 的 IPv6 直连,重传也高达四万多。这进一步说明:底层路径本身对大流量就一般,而 IPv4 + GRE 是被额外「针对」的那一层,叠起来就直接雪崩了。
两台机器都有 global IPv6,而且 v6 之间互 ping 延迟低、空载零丢。运营商的 QoS / policing 规则又常常只配了 IPv4,IPv6 那条路往往更干净、没人管。于是我把隧道的外层从 IPv4 GRE 换成 IPv6 GRE(ip6gre)——内层地址(10.0.0.x)和上面的转发规则一个字都不用动,只换「管子外面」。
换完立刻见效:
| 方向 | IPv4 GRE | IPv6 ip6gre |
|---|---|---|
| 落地 → 中转(测速方向) | ~198 Mbps,持续塌 | ~470 Mbps,稳定还上扬 |
| 中转 → 落地 | ~475 Mbps | ~460 Mbps |
最关键的是:不再雪崩了。测速方向稳稳跑到带宽上限附近,Surge 那边也不再 closed by peer。残余还是有一些重传(这条路底子就一般),但 TCP 能正常「骑」过去,不影响使用。
所以这篇的核心结论就一句话:在 Indigo 上做隧道中转,只有 IPv6 的 GRE 才能保证不被限速。
建隧道(两端对称,把 local / remote 和内层地址对调即可):
# 中转机一端
ip link add tun-gre type ip6gre \
local <中转机 IPv6> remote <Indigo IPv6> \
ttl 255 encaplimit none
ip addr add 10.0.0.2/30 dev tun-gre # 落地机用 10.0.0.1/30
ip link set tun-gre mtu 1400 up
几个容易忽略的点:
encaplimit none。ip6gre 默认会塞一个 IPv6 封装限制扩展头,多 8 字节还可能被某些路径丢,关掉更干净。tcp option maxseg size set rt mtu),不然某些方向的大包还是会顶破隧道。systemd-networkd(.netdev 里 Kind=ip6gre),一台用 ifupdown(/etc/network/interfaces.d/),改完都要落盘,不然重启就回去了。10.0.0.x,所以日常验证隧道通不通还是 ping 10.0.0.1(穿隧道、外层用 v6 封装);ping 那串 v6 地址只是测两台公网底层,跟隧道本身没关系。顺带说下套餐。我手上两台 Indigo:
原因有两个:一是那 1000M 是共享带宽,不保证;二是这套架构真正的瓶颈在中转那段链路和路径质量,不在落地机的标称带宽——换上 B 之后单条隧道也跑不出比 A 更高的有效吞吐。所以为了这个用途,500M 四核那档就够了,没必要上 1000M 共享,省下的钱多开一台或者升级中转机更划算。
顺手也跑了下这几个 IP 的质量测试,贴出来给个参考:


