<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>vpn on Antonio Space</title>
    <link>/tags/vpn/</link>
    <description>Recent content in vpn on Antonio Space</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Tue, 07 Jul 2026 20:45:00 +0800</lastBuildDate><atom:link href="/tags/vpn/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Building a China Exit Node in My Homelab</title>
      <link>/posts/china-exit-node-homelab/</link>
      <pubDate>Tue, 07 Jul 2026 20:45:00 +0800</pubDate>
      
      <guid>/posts/china-exit-node-homelab/</guid>
      <description>I ended up building a China-facing VPN path in my homelab for a very unglamorous reason: some devices simply behave better when they think they are on a China network.
Not faster. Not more elegant. Just better.
This was one of those projects that started with a minor annoyance and slowly turned into a proper piece of infrastructure. A few smart devices and Android-based appliances in my lab were technically online from Singapore, but the experience was inconsistent enough to be irritating:</description>
      <content>&lt;p&gt;I ended up building a China-facing VPN path in my homelab for a very unglamorous reason: some devices simply behave better when they think they are on a China network.&lt;/p&gt;
&lt;p&gt;Not faster. Not more elegant. Just better.&lt;/p&gt;
&lt;p&gt;This was one of those projects that started with a minor annoyance and slowly turned into a proper piece of infrastructure. A few smart devices and Android-based appliances in my lab were technically online from Singapore, but the experience was inconsistent enough to be irritating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;app sessions behaved strangely&lt;/li&gt;
&lt;li&gt;local control broke and then came back&lt;/li&gt;
&lt;li&gt;firmware updates changed behaviour in ways that did not make much sense&lt;/li&gt;
&lt;li&gt;some cloud-backed features were clearly happier when traffic exited from China&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That was enough for me to stop treating it as random consumer device weirdness and build a proper path for it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-real-motivation&#34;&gt;The Real Motivation&lt;/h2&gt;
&lt;p&gt;I did not want a full-network VPN.&lt;/p&gt;
&lt;p&gt;That would have been lazy and honestly a bit reckless. The whole point of a homelab is to make systems more understandable, not to put everything behind an opaque tunnel and hope for the best.&lt;/p&gt;
&lt;p&gt;What I wanted was much narrower:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;only the problematic devices should use the China path&lt;/li&gt;
&lt;li&gt;the rest of the lab should stay exactly as it was&lt;/li&gt;
&lt;li&gt;I should be able to observe every hop&lt;/li&gt;
&lt;li&gt;I should be able to disable the whole thing quickly if the experience was bad&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point ended up being more important than I expected.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-basic-idea&#34;&gt;The Basic Idea&lt;/h2&gt;
&lt;p&gt;At a high level, the path looked like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/china-exit-node-overview.svg&#34; alt=&#34;Sanitized overview of the selective China exit node path&#34;&gt;&lt;/p&gt;
&lt;p&gt;I am intentionally keeping this generic. No public IPs, no public hostnames, and no details that would help anybody map the external edge of the setup.&lt;/p&gt;
&lt;p&gt;The useful part of the story is the shape of the design, not the sensitive values.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-this-needed-to-exist&#34;&gt;Why This Needed to Exist&lt;/h2&gt;
&lt;p&gt;I have learned, sometimes the hard way, that many consumer devices are regional even when vendors do not clearly say so.&lt;/p&gt;
&lt;p&gt;On paper they are internet-connected devices. In reality they are often tightly coupled to where their backend expects them to be. You notice it when something feels &amp;ldquo;almost working&amp;rdquo; but never completely stable.&lt;/p&gt;
&lt;p&gt;I saw this with a mix of devices in the lab, and one case in particular made the pattern obvious: a device that was perfectly alive on the local network, but much happier when its traffic path looked Chinese.&lt;/p&gt;
&lt;p&gt;Once I saw that, the solution stopped being &amp;ldquo;let&amp;rsquo;s keep trying random app settings&amp;rdquo; and became &amp;ldquo;let&amp;rsquo;s build a controlled network path and test it properly.&amp;rdquo;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-nodes-in-the-chain&#34;&gt;The Nodes in the Chain&lt;/h2&gt;
&lt;p&gt;Each piece in the design had a small job and nothing more.&lt;/p&gt;
&lt;h3 id=&#34;main-gateway&#34;&gt;Main gateway&lt;/h3&gt;
&lt;p&gt;My main OpenWrt router stayed as the normal gateway for the house and the lab.&lt;/p&gt;
&lt;p&gt;Its special role here was only policy routing. For selected source IPs, it could choose a different next hop instead of the normal WAN path. That meant I did not have to trust a consumer device to run a VPN client properly.&lt;/p&gt;
&lt;h3 id=&#34;singapore-relay&#34;&gt;Singapore relay&lt;/h3&gt;
&lt;p&gt;The Singapore-side relay was a small Linux box that already had WireGuard in the lab.&lt;/p&gt;
&lt;p&gt;It became the transit hop for selected traffic. This was a very practical place to do it because I could inspect it, route around it, and instrument it without much overhead.&lt;/p&gt;
&lt;h3 id=&#34;china-router&#34;&gt;China router&lt;/h3&gt;
&lt;p&gt;The China-side node was the real exit.&lt;/p&gt;
&lt;p&gt;It terminated the WireGuard path and NATed traffic out locally. This was the point where traffic stopped being &amp;ldquo;just tunneled&amp;rdquo; and became regionally meaningful.&lt;/p&gt;
&lt;p&gt;In practical terms, this node is a Raspberry Pi connected to China Unicom at an intentionally undisclosed spot in Hangzhou. That was enough realism for the experiment without turning the post into a map of the setup.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;sanitized-architecture-diagram&#34;&gt;Sanitized Architecture Diagram&lt;/h2&gt;
&lt;p&gt;This is the version I am comfortable publishing:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/china-exit-node-overview.svg&#34; alt=&#34;Sanitized architecture diagram for the China exit node&#34;&gt;&lt;/p&gt;
&lt;p&gt;It captures what matters without exposing anything that should stay private.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-wireguard&#34;&gt;Why WireGuard&lt;/h2&gt;
&lt;p&gt;There was never much doubt here.&lt;/p&gt;
&lt;p&gt;WireGuard is simple, lightweight, and fits small ARM hardware very well. I already use it elsewhere in the lab, so it did not introduce a new operational model. More importantly, it is easy to reason about when something goes wrong.&lt;/p&gt;
&lt;p&gt;That mattered because I did not want a feature-rich VPN box. I wanted a narrow, inspectable transport layer.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-i-did-not-rely-on-client-side-vpn&#34;&gt;Why I Did Not Rely on Client-Side VPN&lt;/h2&gt;
&lt;p&gt;I tried thinking from the client side first, especially with embedded Android devices.&lt;/p&gt;
&lt;p&gt;That turned out to be a bad use of time.&lt;/p&gt;
&lt;p&gt;Some of these devices will let you install or configure a VPN app, but they do not behave like a normal phone or laptop. They may show a profile, suggest they are connected, or partially integrate with the system UI while never actually emitting the expected tunnel traffic.&lt;/p&gt;
&lt;p&gt;That is the kind of thing that can waste hours if you do not verify it from the network.&lt;/p&gt;
&lt;p&gt;So I pushed the logic back into the infrastructure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the main router decides who should use the path&lt;/li&gt;
&lt;li&gt;the relay decides how traffic enters the tunnel&lt;/li&gt;
&lt;li&gt;the exit node decides how traffic leaves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is much more reliable than hoping a consumer device understands its own VPN state.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-part-i-enjoyed-most-observability&#34;&gt;The Part I Enjoyed Most: Observability&lt;/h2&gt;
&lt;p&gt;The VPN itself was not the fun part.&lt;/p&gt;
&lt;p&gt;The fun part was making the whole chain visible enough that I could answer real questions instead of guessing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is the device actually sending traffic?&lt;/li&gt;
&lt;li&gt;did the gateway match the policy route?&lt;/li&gt;
&lt;li&gt;did the relay forward packets into WireGuard?&lt;/li&gt;
&lt;li&gt;did the China node receive them?&lt;/li&gt;
&lt;li&gt;did replies come back?&lt;/li&gt;
&lt;li&gt;is the failure in routing, in the app, or on the remote side?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That visibility came from a mix of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;WireGuard counters&lt;/li&gt;
&lt;li&gt;packet captures&lt;/li&gt;
&lt;li&gt;syslog&lt;/li&gt;
&lt;li&gt;Loki&lt;/li&gt;
&lt;li&gt;blackbox probes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is where the project stopped being &amp;ldquo;a VPN hack&amp;rdquo; and became proper infrastructure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-the-path-actually-did&#34;&gt;What the Path Actually Did&lt;/h2&gt;
&lt;p&gt;The packet flow was simple enough:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/images/china-exit-node-flow.svg&#34; alt=&#34;Sanitized step-by-step packet flow for the China exit node&#34;&gt;&lt;/p&gt;
&lt;p&gt;That logic was easy to document and, more importantly, easy to test.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-i-learned-the-hard-way&#34;&gt;What I Learned the Hard Way&lt;/h2&gt;
&lt;p&gt;One of the more useful lessons from this project was that a path can be technically correct while still being operationally bad.&lt;/p&gt;
&lt;p&gt;I had moments where I could prove the traffic was taking the expected route. Packet captures lined up. Counters moved. Replies came back. From a routing perspective, it was real.&lt;/p&gt;
&lt;p&gt;And still, the actual user experience on some devices was disappointing.&lt;/p&gt;
&lt;p&gt;That matters.&lt;/p&gt;
&lt;p&gt;It is very easy to declare victory too early in network experiments. &amp;ldquo;The packets pass&amp;rdquo; is not the same as &amp;ldquo;the device works well.&amp;rdquo; Regional backend behaviour, resets, protocol assumptions, and application quirks can still make the experience poor even when the routing is exactly right.&lt;/p&gt;
&lt;p&gt;That is one reason I made reversibility a hard requirement.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;reversibility-was-not-optional&#34;&gt;Reversibility Was Not Optional&lt;/h2&gt;
&lt;p&gt;I wanted a setup that I could shut down without drama.&lt;/p&gt;
&lt;p&gt;That meant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;named rules instead of random manual state&lt;/li&gt;
&lt;li&gt;clear enable and disable points&lt;/li&gt;
&lt;li&gt;no mystery dependency hiding in the middle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In hindsight, this was probably the most important design choice.&lt;/p&gt;
&lt;p&gt;If a special path cannot be turned off easily, it becomes dangerous. Not because it is malicious, but because it quietly becomes part of the environment before you fully trust it.&lt;/p&gt;
&lt;p&gt;I did not want that.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;why-i-still-like-this-project&#34;&gt;Why I Still Like This Project&lt;/h2&gt;
&lt;p&gt;Even if a specific device does not end up using the path permanently, the China exit node is still useful.&lt;/p&gt;
&lt;p&gt;It gives me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a real China-origin test path&lt;/li&gt;
&lt;li&gt;a way to validate how regional devices behave&lt;/li&gt;
&lt;li&gt;a better understanding of policy routing in my own lab&lt;/li&gt;
&lt;li&gt;a reusable building block for future experiments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is enough for me to consider it worthwhile.&lt;/p&gt;
&lt;p&gt;It also reminded me of something I keep relearning in homelab work: sometimes the most interesting infrastructure is built not for scale, but for explanation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-xgimi-case&#34;&gt;The XGIMI Case&lt;/h2&gt;
&lt;p&gt;One of the best examples in this project was an XGIMI projector running Android.&lt;/p&gt;
&lt;p&gt;It was a perfect candidate for the China path, at least on paper. The device is clearly tied to a China-centric ecosystem, and I wanted to see whether giving it China egress at the network level would make it behave more naturally.&lt;/p&gt;
&lt;p&gt;My first thought was the obvious one: install or configure a VPN client on the projector itself.&lt;/p&gt;
&lt;p&gt;That went nowhere.&lt;/p&gt;
&lt;p&gt;The device would accept some configuration, but from the network side it was not behaving like a proper client. I could not see the expected tunnel traffic leaving it, which made the whole thing suspect. This is a good example of why I do not trust embedded Android UIs when it comes to networking. If a device claims to be connected but never emits the expected packets, the network wins the argument.&lt;/p&gt;
&lt;p&gt;So I moved the logic back into the infrastructure and routed the projector at the gateway layer instead.&lt;/p&gt;
&lt;p&gt;The policy-routing chain itself worked. I could verify that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;traffic from the projector was matched by the main gateway&lt;/li&gt;
&lt;li&gt;the selected flows were forwarded to the Singapore relay&lt;/li&gt;
&lt;li&gt;the relay sent them into WireGuard&lt;/li&gt;
&lt;li&gt;the China-side node received them&lt;/li&gt;
&lt;li&gt;return traffic came back through the same path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, the routing was real. This was not a fake success caused by a bad assumption or a stale route.&lt;/p&gt;
&lt;p&gt;But the user experience was still poor.&lt;/p&gt;
&lt;p&gt;That was the important observation.&lt;/p&gt;
&lt;p&gt;Some traffic patterns behaved well enough to prove the path was active, but the projector itself did not become reliably pleasant to use through that route. Certain flows were slow, some connections were reset, and the overall experience was not good enough to justify forcing the device through the China exit all the time.&lt;/p&gt;
&lt;p&gt;From the traffic side, the pattern was quite revealing. I could see outbound connections taking the expected path, but some of them were answered almost immediately with resets from the remote side. A simplified example looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;projector -&amp;gt; 203.0.113.10:443   SYN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;remote    -&amp;gt; projector          RST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;projector -&amp;gt; 203.0.113.11:443   SYN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;remote    -&amp;gt; projector          RST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;projector -&amp;gt; 198.51.100.20:443  SYN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;remote    -&amp;gt; projector          RST&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These are not the real addresses, just representative examples, but the behaviour was real enough: the routing path was alive, replies were coming back, and yet the application experience was still poor. That was an important distinction. The problem was no longer &amp;ldquo;is the tunnel working?&amp;rdquo; but &amp;ldquo;is this regional egress actually good enough for this device?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;That was a useful outcome, even if it was not the outcome I originally wanted.&lt;/p&gt;
&lt;p&gt;It showed that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the routing design was sound&lt;/li&gt;
&lt;li&gt;the observability stack was doing its job&lt;/li&gt;
&lt;li&gt;regional egress can help some devices&lt;/li&gt;
&lt;li&gt;but not every China-market device benefits equally from the same path&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is probably the most practical lesson from the whole setup: routing works for some devices, and for others it does not improve the experience enough to be worth keeping. The only way to know which is which is to test them properly and watch the traffic end to end.&lt;/p&gt;
&lt;p&gt;In the case of the projector, I rolled the route back cleanly. That was not failure. That was exactly the kind of decision the design was supposed to make easy.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;lessons-learned&#34;&gt;Lessons Learned&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A route can be correct and still not be the right answer for the device using it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is probably the most useful takeaway from the whole experiment.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;regional routing is worth testing when device behaviour clearly depends on backend geography&lt;/li&gt;
&lt;li&gt;policy routing is much more reliable than trusting embedded devices to run their own VPN clients&lt;/li&gt;
&lt;li&gt;observability is what makes the difference between debugging and guessing&lt;/li&gt;
&lt;li&gt;a technically valid tunnel does not guarantee a good user experience&lt;/li&gt;
&lt;li&gt;rollback paths are not a nice-to-have, they are part of the design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short: routing works for some devices, and for others it does not improve the experience enough to justify keeping it. The job of the lab is to make that visible quickly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;final-thoughts&#34;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Homelabs are often described as miniature datacenters, but sometimes they are really miniature negotiation layers between reality and badly behaved consumer hardware.&lt;/p&gt;
&lt;p&gt;This project was one of those cases.&lt;/p&gt;
&lt;p&gt;I did not build a China exit node because it looked cool in a network diagram. I built it because a few devices only behave properly when they feel closer to home, and in modern networking &amp;ldquo;home&amp;rdquo; is often just a routing decision.&lt;/p&gt;
&lt;p&gt;If there is one idea I would keep from this whole exercise, it is this:&lt;/p&gt;
&lt;p&gt;Make the special path visible.
Make it selective.
And make sure you can turn it off.&lt;/p&gt;
</content>
    </item>
    
  </channel>
</rss>
