# xlab-gateway networking
# Bond (balance-xor) → VLANs (lan254, wan99, mgmt) → MACVLAN (wan99.0)
# WireGuard tunnels with policy routing ("freedom" tables)
# NAT/masquerade on wan99.0
{ config, ... }:
{
networking = {
useNetworkd = true;
useDHCP = false;
nftables = {
enable = true;
ruleset = ''
table ip filter {
set bogons_v4 {
type ipv4_addr
flags interval
elements = {
0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24,
192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24,
224.0.0.0/4, 240.0.0.0/4
}
}
chain prerouting {
type filter hook prerouting priority raw; policy accept;
iifname "wan99.0" ip saddr @bogons_v4 drop
}
chain forward {
type filter hook forward priority filter; policy drop;
iifname { "bond.lan254", "wg-to-wgnet" } accept
ct state established,related accept
}
}
table inet input_filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
# LAN, management, and WireGuard — trust fully
iifname { "bond.lan254", "bond.mgmt", "wg-to-wgnet", "wg-to-skyworks" } accept
# WAN — DHCP client replies (v4 + v6)
iifname "wan99.0" udp sport 67 udp dport 68 accept
iifname "wan99.0" udp sport 547 udp dport 546 accept
# ICMP/ICMPv6 for path MTU discovery and diagnostics
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
}
}
table ip6 filter {
chain forward {
type filter hook forward priority filter; policy drop;
iifname { "bond.lan254", "wg-to-wgnet" } accept
ct state established,related accept
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "wan99.0" masquerade
oifname "bond.mgmt" masquerade
}
}
table inet mangle {
chain forward_mss {
type filter hook forward priority mangle; policy accept;
# 终极修复:使用位掩码 & (syn) == syn,同时捕获 SYN 和 SYN-ACK。
# 无论数据包是进 WG 还是出 WG,都会根据出口路由(rt mtu)自动调整 MSS。
tcp flags & (syn) == syn tcp option maxseg size set rt mtu
}
chain postrouting {
type filter hook postrouting priority filter; policy accept;
}
}
'';
};
firewall.enable = false; # Using nftables directly
};
# Custom routing table names
environment.etc."iproute2/rt_tables.d/skyworks.conf".text = ''
1000 freedom
1001 freedom-extra
1002 freedom-wgnet
1003 wg-to-skyworks
'';
# ===========================================================================
# NETDEVS
# ===========================================================================
systemd.network.netdevs = {
"10-bond" = {
netdevConfig = {
Name = "bond";
Kind = "bond";
Description = "Main aggregated interface";
};
bondConfig = {
Mode = "balance-xor";
};
};
"20-bond-lan254" = {
netdevConfig = {
Name = "bond.lan254";
Kind = "vlan";
};
vlanConfig.Id = 10;
};
"20-bond-wan99" = {
netdevConfig = {
Name = "bond.wan99";
Kind = "vlan";
};
vlanConfig.Id = 99;
};
"20-bond-mgmt" = {
netdevConfig = {
Name = "bond.mgmt";
Kind = "vlan";
};
vlanConfig.Id = 1;
};
"30-wan99-0" = {
netdevConfig = {
Name = "wan99.0";
Kind = "macvlan";
MACAddress = "90:e2:ba:54:26:90";
};
macvlanConfig.Mode = "bridge";
};
"40-wg-to-wgnet" = {
netdevConfig = {
Name = "wg-to-wgnet";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = config.age.secrets.xlab-wg-wgnet.path;
};
wireguardPeers = [
{
PublicKey = "H+PAPw+1MsE50Of4VMMPzMbzGG731CkNrIgaXbxcFwk=";
PresharedKeyFile = config.age.secrets.xlab-wg-wgnet-psk.path;
AllowedIPs = [ "0.0.0.0/0" "::/0" ];
Endpoint = "166.111.17.108:51820";
PersistentKeepalive = 16;
}
];
};
"40-wg-to-skyworks" = {
netdevConfig = {
Name = "wg-to-skyworks";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = config.age.secrets.xlab-wg-skyworks.path;
};
wireguardPeers = [
{
PublicKey = "yyHQ8fg1riI9BJPUjydh/C2MTiA/p0Pb1f9Hc88BuCk=";
AllowedIPs = [ "0.0.0.0/0" "::/0" ];
Endpoint = "wgep.thu-skyworks.org:16777";
PersistentKeepalive = 20;
}
];
};
};
# ===========================================================================
# NETWORKS
# ===========================================================================
systemd.network.networks = {
# Bond slaves
"10-bond-slaves" = {
matchConfig.Name = "enp3s0f0 enp3s0f1";
networkConfig = {
Bond = "bond";
IPv6AcceptRA = false;
LLDP = false;
};
};
# Bond master
"20-bond" = {
matchConfig.Name = "bond";
linkConfig.RequiredForOnline = "no";
networkConfig = {
VLAN = [ "bond.mgmt" "bond.wan99" "bond.lan254" ];
DHCP = "no";
IPv6AcceptRA = false;
};
};
# LAN - 10.253.254.0/24
"30-bond-lan254" = {
matchConfig.Name = "bond.lan254";
networkConfig = {
DHCP = "no";
Address = [
"10.253.254.1/24"
"fd99:23eb:1682:1::1/64"
];
IPv6AcceptRA = false;
};
routes = [
#{ Destination = "10.0.0.0/16"; Gateway = "10.253.0.1"; }
# Throw routes for Tsinghua ranges → punches holes in WireGuard table
{ Destination = "166.111.0.0/16"; Type = "throw"; Table = 1002; }
{ Destination = "101.5.0.0/16"; Type = "throw"; Table = 1002; }
{ Destination = "101.6.0.0/16"; Type = "throw"; Table = 1002; }
{ Destination = "59.66.0.0/16"; Type = "throw"; Table = 1002; }
{ Destination = "183.172.0.0/16"; Type = "throw"; Table = 1002; }
{ Destination = "183.173.0.0/16"; Type = "throw"; Table = 1002; }
# ========= 新增下面这一行 =========
# 告诉策略路由表:如果是去往管理网段,不要走隧道,跳回 main 表处理
{ Destination = "192.168.1.0/24"; Type = "throw"; Table = 1002; }
# ==================================
];
routingPolicyRules = [
# LAN IPv4 → suppress default route in main table, fall through to freedom-wgnet
{
From = "10.253.254.0/24";
SuppressPrefixLength = 0;
Family = "ipv4";
Priority = 100;
}
# LAN IPv6 → same
{
From = "fd99:23eb:1682:1::/64";
SuppressPrefixLength = 0;
Family = "ipv6";
Priority = 100;
}
# LAN IPv4 → WireGuard routing table
{
From = "10.253.254.0/24";
Table = 1002;
Priority = 20000;
Family = "ipv4";
}
# LAN IPv6 → WireGuard routing table
{
From = "fd99:23eb:1682:1::/64";
Table = 1002;
Priority = 20000;
Family = "ipv6";
}
];
};
# Management - 192.168.1.0/24
"30-bond-mgmt" = {
matchConfig.Name = "bond.mgmt";
networkConfig.DHCP = "no";
addresses = [{ Address = "192.168.1.13/24"; }];
};
# WAN VLAN trunk
"30-bond-wan99" = {
matchConfig.Name = "bond.wan99";
linkConfig.RequiredForOnline = "no";
networkConfig = {
MACVLAN = [ "wan99.0" ];
DHCP = "no";
IPv6AcceptRA = false;
};
};
# WAN uplink (DHCP)
"40-wan99-0" = {
matchConfig.Name = "wan99.0";
networkConfig = {
NTP = [
"ntp.tuna.tsinghua.edu.cn"
"ntp1.aliyun.com"
"ntp.ntsc.ac.cn"
];
DNS = [ "166.111.8.28" "166.111.8.29" ];
DHCP = "yes";
};
dhcpV4Config = {
RouteMTUBytes = "1500";
DUIDType = "link-layer-time";
UseDNS = false;
};
dhcpV6Config = {
DUIDType = "link-layer-time";
UseDNS = false;
};
ipv6AcceptRAConfig = {
UseAutonomousPrefix = false;
UseDNS = false;
};
};
# WireGuard: wg-to-wgnet (main tunnel for "freedom" routing)
"50-wg-to-wgnet" = {
matchConfig.Name = "wg-to-wgnet";
addresses = [
{ Address = "10.253.1.37/32"; }
{ Address = "fd99:23eb:1682:fd::128/128"; }
];
routes = [
{ Destination = "0.0.0.0/0"; Scope = "link"; Table = 1002; }
{ Destination = "::/0"; Scope = "link"; Table = 1002; }
# Internal ULA via tunnel (main table) so gateway + LAN can reach 10.0.0.1's IPv6
{ Destination = "fd99:23eb:1682::/48"; Scope = "link"; }
# ========= 新增下面这两行 =========
{ Destination = "10.0.0.0/16"; Scope = "link"; }
{ Destination = "10.5.0.0/24"; Scope = "link"; }
{ Destination = "10.253.0.0/24"; Scope = "link"; }
{ Destination = "10.254.0.0/24"; Scope = "link"; }
# ==================================
];
};
# WireGuard: wg-to-skyworks
"50-wg-to-skyworks" = {
matchConfig.Name = "wg-to-skyworks";
addresses = [
{ Address = "10.239.0.9/16"; }
{ Address = "fd5b:8249:afe:cb9a::9/64"; }
];
routes = [
{ Destination = "10.0.2.144/32"; }
{ Destination = "10.74.0.0/16"; }
];
};
};
# ===========================================================================
# AGENIX SECRETS
# ===========================================================================
age.secrets = {
xlab-wg-wgnet = {
file = ../../secrets/xlab-wg-wgnet.age;
owner = "systemd-network";
mode = "0400";
};
xlab-wg-wgnet-psk = {
file = ../../secrets/xlab-wg-wgnet-psk.age;
owner = "systemd-network";
mode = "0400";
};
xlab-wg-skyworks = {
file = ../../secrets/xlab-wg-skyworks.age;
owner = "systemd-network";
mode = "0400";
};
};
}