<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>networking &amp;mdash; th.oughts</title>
    <link>https://th.oughts.org/tag:networking</link>
    <description>Living the fake American dream, 3 years at a time</description>
    <pubDate>Sun, 19 Apr 2026 08:48:06 +0000</pubDate>
    <image>
      <url>https://i.snap.as/ixKmaGoG.jpg</url>
      <title>networking &amp;mdash; th.oughts</title>
      <link>https://th.oughts.org/tag:networking</link>
    </image>
    <item>
      <title>Backup VPN tunnel using a MVNO data plan</title>
      <link>https://th.oughts.org/backup-vpn-tunnel-using-a-mvno-data-plan?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Problem Statement&#xA;&#xA;I wanted a cheap backup VPN tunnel to reinforce my main tunnel in case things go wrong. The backup tunnel is on standby and is utilized only when I need an alternate path to investigate why my main network is down or isn&#39;t functioning when I am physically away from my systems.&#xA;&#xA;Challenges&#xA;&#xA;CG-NAT has ruined cellular network based data plans. Cellular networks have kept their eccentric design choices throughout their evolution even though they have leveraged a lot from regular internet based data networks. CG-NAT is one of those inconvenient design choices that has stayed on. To summarize, a lot of the cheap MVNO data plans operate like a NATed LAN and incoming connections aren&#39;t really possible because you do not get gifted with a public IP.  Setting up a VPN tunnel using one of these data networks becomes a bendy road to success.&#xA;&#xA;Design choices&#xA;&#xA;Using a cheap data plan is quite appealing because I would ideally want the cost to be as low as possible without sacrificing much on the minimum reliability that you would expect from a backup network. &#xA;&#xA;Owing to the design restrictions mentioned above, you cannot simply dial in to your backup VPN endpoint. The connection has to be initiated in the opposite direction. This post details how I achieved an usable setup without sacrificing too much on cost or reliability.&#xA;&#xA;Network topology&#xA;&#xA;img src=&#34;https://i.snap.as/Z5RAwYjC.jpg&#34; alt=&#34;Network Topology&#34; style=&#34;width:800px;height:600px;&#34;&#xA;&#xA;Click here for a somewhat legible image.&#xA;&#xA;Main LAN (1)&#xA;The main network gated by a OpenBSD based firewall and gateway. &#xA;&#xA;BMC (2)&#xA;The base management controller to control the gateway.&#xA;&#xA;CRS 326 (3)&#xA;This is the backup gateway using a Mikrotik CRS326. It&#39;s probably overkill for what I am trying to achieve. It provides three different functions in our setup:&#xA;&#xA;Backup firewall/gateway&#xA;Creates another smaller LAN composed of BMCs for systems that provide services. This LAN is also accessible from the main LAN. This is simple using masquerade rules.&#xA; &#xA;On Mikrotik:&#xA;chain=srcnat action=masquerade out-interface=backuplanbridge log=yes log-prefix=&#34;BackupLANBridge  &#34;&#xA;Note that, for this to work, one of the interfaces of backup gateway&#39;s bridged network should be a dhcp client on the main LAN. &#xA;&#xA;The backup firewall is connected to the internet via the LM1200 (4).&#xA;&#xA;Scheduler&#xA;We also use the scheduler function of the Mikrotik box. It checks a cookie at regular intervals in case user has requested VPN to be on. While you have a always-on tunnel, I would like to minimize data usage on the data only SIM (the backup network).&#xA;&#xA;A simple script to check a variable on remote web server(RouterOS):&#xA;Check if user has enabled cookie&#xA;&#x9;:local result [/tool fetch url=&#34;path to remote web server/radar.txt&#34; mode=https as-value output=user ]&#xA;&#x9;:delay 5&#xA;        :local dat ($result-  &#34;data&#34;)&#xA;        :log info (&#34;Wireguard, Cookie is:$dat&#34;)&#xA;&#xA;check if user wants to run tunnel&#xA;       :if ( [: pick $dat 0 1]  != &#34;0&#34;) do={&#xA;            :local status [/interface get wireguard interface disabled];&#xA;             :if ($status=true) do={&#xA;                  :log info &#34;Trying to enable wireguard interface&#34;;&#xA;                  /interface/wireguard/enable wireguard interface;&#xA;                  :delay 5;&#xA;            } else={&#xA;                         :log info &#34;Wireguard interface already enabled&#34;;&#xA;            }&#xA;             # For debugging&#xA;            /ping count=5 10.8.0.1; &#xA;        } else={&#xA;                     :log info &#34;Trying to disable wireguard interface&#34;;&#xA;                     /interface/wireguard/disable wireguard interface;       &#xA;      }&#xA;To set this to run every 5 minutes:&#xA;add disabled=no interval=5m name=myscript&#xA; &#xA;Wireguard endpoint&#xA;This is the configured interface we have referred above in the script.&#xA;&#xA;LM1200 (4)&#xA;This is most convenient device I could find for my needs. It takes in a data sim and gives you a bridged interface. &#xA;&#xA;CG-NAT (5)&#xA;The cellular network.&#xA;&#xA;AWS S3 Cookie (6)&#xA;I use a publicly accessible S3 bucket for my cookie. All it needs is writing a 0 or 1 to a text file.&#xA;&#xA;Wireguard Server (7)&#xA;This is another critical piece of the setup. It serves as a bridge between the roaming endpoint and the local site. Besides the necessary firewall rules, some masquerade and postrouting rules are required:&#xA;&#xA;iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE&#xA;iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -o wg0 -j TCPMSS --set-mss 1280&#xA;The mangle is essential since we are accessing the tunnel indirectly via another system. Note that instead of setting a --set-mss value explicitly, you can just use --clamp-mss-to-pmtu that automatically sets an appropriate value.&#xA;&#xA;Roaming system configuration&#xA;Before we get to our script that makes life a bit easier, here&#39;s the typical workflow:&#xA;&#xA;User detects that the main network is down and primary VPN does not work.&#xA;User sets cookie in the S3 bucket to 1.&#xA;User waits for the remote backup system to initiate a wireguard tunnel.&#xA;Once done, user uses a ssh tunnel or a wireguard tunnel to the wireguard server.&#xA;&#xA;Here&#39;s a script that does something similar.&#xA;&#xA;#networking #diy&#xA;&#xA;a href=&#34;https://remark.as/p/th.oughts.org/backup-vpn-tunnel-using-a-mvno-data-plan&#34;Discuss.../a]]&gt;</description>
      <content:encoded><![CDATA[<h2 id="problem-statement" id="problem-statement">Problem Statement</h2>

<p>I wanted a <em>cheap</em> backup VPN tunnel to reinforce my main tunnel in case things go wrong. The backup tunnel is on standby and is utilized only when I need an alternate path to investigate why my main network is down or isn&#39;t functioning when I am physically away from my systems.</p>

<h2 id="challenges" id="challenges">Challenges</h2>

<p>CG-NAT has ruined cellular network based data plans. Cellular networks have kept their eccentric design choices throughout their evolution even though they have leveraged a lot from regular internet based data networks. CG-NAT is one of those <em>inconvenient design choices</em> that has stayed on. To summarize, a lot of the cheap MVNO data plans operate like a NATed LAN and incoming connections aren&#39;t really possible because you do not get gifted with a public IP.  Setting up a VPN tunnel using one of these data networks becomes a bendy road to success.</p>

<h2 id="design-choices" id="design-choices">Design choices</h2>

<p>Using a cheap data plan is quite appealing because I would ideally want the cost to be as low as possible without sacrificing much on the <em>minimum reliability</em> that you would expect from a backup network.</p>

<p>Owing to the design restrictions mentioned above, you cannot simply <em>dial in</em> to your backup VPN endpoint. The connection has to be initiated in the opposite direction. This post details how I achieved an usable setup without sacrificing too much on cost or reliability.</p>

<h2 id="network-topology" id="network-topology">Network topology</h2>

<p><img src="https://i.snap.as/Z5RAwYjC.jpg" alt="Network Topology" style="width:800px;height:600px;"></p>

<p>Click <a href="https://i.snap.as/Z5RAwYjC.jpg">here</a> for a somewhat legible image.</p>

<h3 id="main-lan-1" id="main-lan-1"><em>Main LAN</em> (1)</h3>

<p>The main network gated by a OpenBSD based firewall and gateway.</p>

<h3 id="bmc-2" id="bmc-2"><em>BMC</em> (2)</h3>

<p>The base management controller to control the gateway.</p>

<h3 id="crs-326-3" id="crs-326-3"><em>CRS 326</em> (3)</h3>

<p>This is the backup gateway using a Mikrotik CRS326. It&#39;s probably overkill for what I am trying to achieve. It provides three different functions in our setup:</p>

<h4 id="backup-firewall-gateway" id="backup-firewall-gateway">Backup firewall/gateway</h4>

<p>Creates another smaller LAN composed of BMCs for systems that provide services. This LAN is also accessible from the main LAN. This is simple using masquerade rules.</p>

<p>On Mikrotik:</p>

<pre><code>chain=srcnat action=masquerade out-interface=&lt;backuplanbridge&gt; log=yes log-prefix=&#34;BackupLANBridge&gt;&#34;
</code></pre>

<p>Note that, for this to work, one of the interfaces of backup gateway&#39;s bridged network should be a dhcp client on the main LAN.</p>

<p>The backup firewall is connected to the internet via the LM1200 (4).</p>

<h4 id="scheduler" id="scheduler">Scheduler</h4>

<p>We also use the scheduler function of the Mikrotik box. It checks a <em>cookie</em> at regular intervals in case user has requested VPN to be on. While you have a always-on tunnel, I would like to minimize data usage on the data only SIM (the backup network).</p>

<p>A simple script to check a variable on remote web server(RouterOS):</p>

<pre><code># Check if user has enabled cookie
	:local result [/tool fetch url=&#34;&lt;path to remote web server&gt;/radar.txt&#34; mode=https as-value output=user ]
	:delay 5
        :local dat ($result-&gt;&#34;data&#34;)
        :log info (&#34;Wireguard, Cookie is:$dat&#34;)

# check if user wants to run tunnel
       :if ( [: pick $dat 0 1]  != &#34;0&#34;) do={
            :local status [/interface get &lt;wireguard interface&gt; disabled];
             :if ($status=true) do={
                  :log info &#34;Trying to enable wireguard interface&#34;;
                  /interface/wireguard/enable &lt;wireguard interface&gt;;
                  :delay 5;
            } else={
                         :log info &#34;Wireguard interface already enabled&#34;;
            }
             # For debugging
            /ping count=5 10.8.0.1; 
        } else={
                     :log info &#34;Trying to disable wireguard interface&#34;;
                     /interface/wireguard/disable &lt;wireguard interface&gt;;       
      }
</code></pre>

<p>To set this to run every 5 minutes:</p>

<pre><code>add disabled=no interval=5m name=&lt;myscript&gt;
</code></pre>

<h4 id="wireguard-endpoint" id="wireguard-endpoint">Wireguard endpoint</h4>

<p>This is the configured interface we have referred above in the script.</p>

<h3 id="lm1200-4" id="lm1200-4"><em>LM1200</em> (4)</h3>

<p>This is most convenient device I could find for my needs. It takes in a data sim and gives you a bridged interface.</p>

<h3 id="cg-nat-5" id="cg-nat-5"><em>CG-NAT</em> (5)</h3>

<p>The cellular network.</p>

<h3 id="aws-s3-cookie-6" id="aws-s3-cookie-6"><em>AWS S3 Cookie</em> (6)</h3>

<p>I use a publicly accessible S3 bucket for my cookie. All it needs is writing a 0 or 1 to a text file.</p>

<h3 id="wireguard-server-7" id="wireguard-server-7"><em>Wireguard Server</em> (7)</h3>

<p>This is another critical piece of the setup. It serves as a bridge between the roaming endpoint and the local site. Besides the necessary firewall rules, some masquerade and postrouting rules are required:</p>

<pre><code>iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN -o wg0 -j TCPMSS --set-mss 1280
</code></pre>

<p>The <em>mangle</em> is essential since we are accessing the tunnel indirectly via another system. Note that instead of setting a —set-mss value explicitly, you can just use —clamp-mss-to-pmtu that automatically sets an appropriate value.</p>

<h2 id="roaming-system-configuration" id="roaming-system-configuration">Roaming system configuration</h2>

<p>Before we get to our script that makes life a bit easier, here&#39;s the typical workflow:</p>
<ol><li>User detects that the main network is down and primary VPN does not work.</li>
<li>User sets cookie in the S3 bucket to 1.</li>
<li>User waits for the remote backup system to initiate a wireguard tunnel.</li>
<li>Once done, user uses a ssh tunnel or a wireguard tunnel to the wireguard server.</li></ol>

<p>Here&#39;s a <a href="https://gist.github.com/whitebrandy/52ffa91abb2232ed0e541dbd386b1bd3">script</a> that does something similar.</p>

<p><a href="https://th.oughts.org/tag:networking" class="hashtag"><span>#</span><span class="p-category">networking</span></a> <a href="https://th.oughts.org/tag:diy" class="hashtag"><span>#</span><span class="p-category">diy</span></a></p>

<p><a href="https://remark.as/p/th.oughts.org/backup-vpn-tunnel-using-a-mvno-data-plan">Discuss...</a></p>
]]></content:encoded>
      <guid>https://th.oughts.org/backup-vpn-tunnel-using-a-mvno-data-plan</guid>
      <pubDate>Mon, 26 Dec 2022 06:50:59 +0000</pubDate>
    </item>
  </channel>
</rss>