FAI VPN gateway: the half that lives on the router
Edit on GitHubThe manual counterpart of
modules/nixos/network/fai-gateway.nix. That file makes the PC
forward and masquerade; this one makes somebody send traffic to it. Without both sides,
nothing happens.
phone/notebook → router → 192.168.1.10 → ppp0 → FAI
(this guide) (the .nix)Why it is manual
Two independent reasons, and neither of them is laziness:
modules/nixos/network/router.nixrefuses to push UCI on purpose: "one wrong network or firewall line locks you out and the way back is failsafe mode with PHYSICAL access". Applying over SSH with no commit-confirm is exactly what that decision avoids.- The router's sudoers does not include
/etc/init.d/network. The NOPASSWD entries are onlyreboot,nft,uci,dnsmasq,firewallandwg-status. You can write the route with no password and you cannot apply it, which would leave/etc/config/networkdiverging from what runs, androuter-sync diffwould report drift that is not even live.
In other words: the uci set is free, the reload asks for your password. Run it yourself.
Before starting
The PC side has to be active (sudo nixos-rebuild switch --flake .) and the VPN up
(vpn connect fai), otherwise there is nothing to test.
Part 1: static routes
The list of ranges belongs to FAI, not to us. It comes down the tunnel on every connection and it can change. Do not trust the list below blindly, pull the current one from the tunnel itself:
ip route show dev ppp0 | grep via | awk '{print $1}'THIS PART IS ALREADY DONE (verified 12/08/2026, reshaped 13/10/2026). The nine routes
exist as NAMED sections fai_r1 through fai_r9, see
hosts/cudy-wr3000/uci/network.conf. They predate this guide and sat
there for years with no effect, because they pointed at a 192.168.1.10 that did not
forward: the missing half was the PC's, not this one.
Because they are named and not anonymous, uci show network | grep '@route' does not
show them: that grep returns empty and it looks like there is no route at all. Look for
=route instead:
sudo uci show network | grep -E '=route|fai_r'If they ever need recreating (a reflash without "keep settings"), this is the pattern, named
and with a netmask instead of CIDR, the way the device already has them:
i=1
for net in 192.168.90.0 192.168.100.0 192.168.110.0 192.168.130.0 192.168.223.0; do
sudo uci set network.fai_r$i=route
sudo uci set network.fai_r$i.interface='lan'
sudo uci set network.fai_r$i.target="$net"
sudo uci set network.fai_r$i.netmask='255.255.255.0'
sudo uci set network.fai_r$i.gateway='192.168.1.10'
i=$((i+1))
done
for host in 200.136.209.229 200.136.209.247 200.136.209.248 200.136.209.252; do
sudo uci set network.fai_r$i=route
sudo uci set network.fai_r$i.interface='lan'
sudo uci set network.fai_r$i.target="$host"
sudo uci set network.fai_r$i.netmask='255.255.255.255'
sudo uci set network.fai_r$i.gateway='192.168.1.10'
i=$((i+1))
done
sudo uci commit network
sudo /etc/init.d/network reload # <- asks for the passwordThe public /25 is NOT routed whole, on purpose (13/10/2026). It also holds
200.136.209.236, the address of every public *.fai.ufscar.br name, and sending the /25 here
took that site down with the VPN for the whole house. The four /32s are faiTunnelHosts in
modules/nixos/network/fai-gateway.nix, the
hosts that only answer through the tunnel. Never fix this with an exception bound to wan: that
was tried and it vanished on the first PPPoE reconnect. The whole story is in
../notes/network/network.md.
Part 2: split-DNS for the FAI zones
APPLIED on 13/08/2026: the three add_list entries below are already on the device
and verified. fai2008.ufscar.br resolves through the router returning
192.168.130.2/.3, and the home DNS plus internet stayed intact across the restart. They
stay here for reflash and rollback.
ONLY THE AD ZONE NEEDS IT. Measured on 12/08/2026, comparing the public answer (1.1.1.1) with the FAI DC's (200.136.209.252):
| zone | public | FAI | split-DNS? |
|---|---|---|---|
fai2008.ufscar.br | nothing | .252, 192.168.130.2/.3 | yes |
sup.fai.ufscar.br | 200.136.209.236 | the same | no |
fai.ufscar.br | 200.136.209.236 | the same | no |
That is why dashboard.sup.fai.ufscar.br worked with none of this: the zone is public, only
the route was missing. The split-DNS below serves the names that only exist inside: a
domain host, a share, an internal service. If a new name does not resolve, test before
touching anything here:
nslookup <name> 200.136.209.252 # does it resolve? then it is an internal zone, add it belowThe DCs 200.136.209.252 and .247 answer on port 53 through the tunnel (tested
12/08/2026).
The trap is rebind_protection, which is set to 1. The FAI DNS returns
192.168.130.2 for fai2008.ufscar.br, an RFC1918 address, and dnsmasq discards private
answers coming from outside as anti-rebind protection. Without allowing the zone, the
split-DNS looks configured and simply does not resolve, with no error anywhere.
sudo uci add_list dhcp.@dnsmasq[0].server='/fai2008.ufscar.br/200.136.209.252'
sudo uci add_list dhcp.@dnsmasq[0].server='/fai2008.ufscar.br/200.136.209.247'
sudo uci add_list dhcp.@dnsmasq[0].rebind_domain='fai2008.ufscar.br'
sudo uci commit dhcp
sudo /etc/init.d/dnsmasq restart # NOPASSWD; it drops the home DNS for ~2sThe noresolv='1' does not get in the way: a server=/zone/ip entry is matched by the more
specific domain and takes precedence over the default forwarding to DoH.
https-dns-proxy owns the server list. Its init script uses uci_add_list_if_new
(additive, it does not delete), but it has a
_dnsmasq_create_server_backup / _dnsmasq_restore_server_backup pair: on stop it
restores the backup, and if the backup was taken before your entry, yours disappears.
The symptom: FAI names stop resolving after touching DoH or rebooting. Check with:
sudo uci show dhcp | grep fai2008If this becomes recurrent, just re-run the add_list commands, since they are idempotent
through uci_add_list_if_new.
Measured 29/08/2026: the backup currently HAS them. The doh_backup_server in
hosts/cudy-wr3000/uci/dhcp.conf lists the four FAI and VPN entries,
because the service re-created the backup after they already existed (a side effect of the
dnsmasq restart in per-client-dns-block.md). So the failure
mode above is disarmed right now, and it re-arms itself the day a new server entry is
added without the service taking a fresh backup afterwards.
Do NOT use serversfile as a plan B. That slot belongs to adblock-fast, which
points it at its own /var/run/adblock-fast/dnsmasq.servers and removes it when it stops
(seen happening in the router-sync pull of 12/08/2026). Overwriting it would take down ad
blocking for the whole house, and it would overwrite you back on the next reload.
Verification
From another device on the network (not from the PC, which reaches it through the tunnel anyway):
ip route get 200.136.209.229 # should go out through 192.168.1.10
ip route get 200.136.209.236 # on the router: dev pppoe-wan, the public face
nslookup fai2008.ufscar.br # should return 192.168.130.2/.3
nc -vz 200.136.209.229 22 # should openIf the route is right and SSH does not open, check on the PC whether the VPN dropped: with
ppp0 gone, traffic dies here and fails silently, with no message at all to the device.
Rollback
# routes: remove them back to front (the indexes shift)
sudo uci show network | grep '@route' | tail -1 # check the index first
sudo uci delete network.@route[-1] # repeat 6x
sudo uci commit network && sudo /etc/init.d/network reload
# dns
sudo uci del_list dhcp.@dnsmasq[0].server='/fai2008.ufscar.br/200.136.209.252'
sudo uci del_list dhcp.@dnsmasq[0].server='/fai2008.ufscar.br/200.136.209.247'
sudo uci del_list dhcp.@dnsmasq[0].rebind_domain='fai2008.ufscar.br'
sudo uci commit dhcp && sudo /etc/init.d/dnsmasq restartAfter applying
router-sync pull && git -C ~/Projects/GitHub/v1cferr/dotfiles diff hosts/cudy-wr3000/Without the pull, the mirror in hosts/cudy-wr3000/uci/ becomes a copy of something that used to be
true, which is exactly what router-sync diff exists to prevent.