Scenario. One Linux box acts as a router / firewall / DHCP / DNS. It trunks VLANs to a managed switch: USERS (VLAN10), SERVERS (VLAN20), MGMT (VLAN99). We use nftables for firewall/NAT, dnsmasq for DHCP/DNS, and systemd-networkd (with Netplan and NetworkManager alternatives shown). Commands are Debian/Ubuntu-oriented but portable.
10.0.0.0/8172.16.0.0/12 (172.16.0.0–172.31.255.255)192.168.0.0/16These are non-routed on the public Internet by design (use one range and subnet it).
Choose a ULA /48 from fd00::/8 (e.g., fd12:3456:789a::/48). Use one /64 per VLAN for SLAAC.
fd12:3456:789a:10::/64fd12:3456:789a:20::/64fd12:3456:789a:99::/64192.168.10.0/24, GW 192.168.10.1192.168.20.0/24, GW 192.168.20.1192.168.99.0/24, GW 192.168.99.1 Internet / ISP
|
[ WAN: eth0 ]
Linux Router / Firewall
[ LAN: eth1 trunk ]
/ | \
VLAN10 VLAN20 VLAN99
USERS SERVERS MGMT
192.168.10.0/24 192.168.20.0/24 192.168.99.0/24
fd12:...:10::/64 fd12:...:20::/64 fd12:...:99::/64
sudo apt update
sudo apt install -y nftables dnsmasq iproute2 bridge-utils vlan net-tools
sudo tee /etc/sysctl.d/99-router.conf <<'EOF'
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
EOF
sudo sysctl --system
ip -br link
ip -br addr
ip route && ip -6 route
nft list ruleset || true
Assume eth1 is a trunk to your managed switch. Define VLAN subinterfaces with systemd-networkd:
# /etc/systemd/network/10-eth1.network
[Match]
Name=eth1
[Network]
VLAN=eth1.10
VLAN=eth1.20
VLAN=eth1.99
# /etc/systemd/network/20-eth1.10.netdev
[NetDev]
Name=eth1.10
Kind=vlan
[VLAN]
Id=10
# /etc/systemd/network/21-eth1.20.netdev
[NetDev]
Name=eth1.20
Kind=vlan
[VLAN]
Id=20
# /etc/systemd/network/22-eth1.99.netdev
[NetDev]
Name=eth1.99
Kind=vlan
[VLAN]
Id=99
# /etc/systemd/network/30-vlan10.network
[Match] Name=eth1.10
[Network]
Address=192.168.10.1/24
Address=fd12:3456:789a:10::1/64
# /etc/systemd/network/31-vlan20.network
[Match] Name=eth1.20
[Network]
Address=192.168.20.1/24
Address=fd12:3456:789a:20::1/64
# /etc/systemd/network/32-vlan99.network
[Match] Name=eth1.99
[Network]
Address=192.168.99.1/24
Address=fd12:3456:789a:99::1/64
Apply: sudo systemctl restart systemd-networkd
# /etc/netplan/01-router.yaml
network:
version: 2
renderer: networkd
ethernets: { eth1: {} }
vlans:
vlan10:
id: 10
link: eth1
addresses: [192.168.10.1/24, fd12:3456:789a:10::1/64]
vlan20:
id: 20
link: eth1
addresses: [192.168.20.1/24, fd12:3456:789a:20::1/64]
vlan99:
id: 99
link: eth1
addresses: [192.168.99.1/24, fd12:3456:789a:99::1/64]
# Then:
sudo netplan apply
# /etc/dnsmasq.d/smb.conf
# GENERAL
domain-needed
bogus-priv
no-resolv
server=1.1.1.1
server=9.9.9.9
# VLAN10 (USERS) DHCPv4
interface=eth1.10
dhcp-range=eth1.10,192.168.10.50,192.168.10.250,255.255.255.0,12h
dhcp-option=tag:eth1.10,option:router,192.168.10.1
dhcp-option=tag:eth1.10,option:dns-server,192.168.10.1
# VLAN20 (SERVERS) DHCPv4 (optional; servers may be static)
interface=eth1.20
dhcp-range=eth1.20,192.168.20.50,192.168.20.200,255.255.255.0,12h
dhcp-option=tag:eth1.20,option:router,192.168.20.1
dhcp-option=tag:eth1.20,option:dns-server,192.168.20.1
# IPv6 RA + DHCPv6 (stateless) for both VLANs
interface=eth1.10
dhcp-range=fd12:3456:789a:10::,ra-stateless,ra-names,12h
interface=eth1.20
dhcp-range=fd12:3456:789a:20::,ra-stateless,ra-names,12h
# Optional static leases (example)
# dhcp-host=AA:BB:CC:DD:EE:FF,host123,192.168.20.10
Enable and reload:
sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq
# /etc/nftables.conf
flush ruleset
table inet filter {
sets {
lan_ifaces { type ifname; elements = { "eth1.10", "eth1.20", "eth1.99" } }
}
chains {
input {
type filter hook input priority 0;
policy drop;
ct state established,related accept
iif lo accept
# Allow DHCP/DNS from LANs
iifname @lan_ifaces udp dport {67, 53} accept
iifname @lan_ifaces tcp dport 53 accept
# Allow SSH from MGMT only
iif "eth1.99" tcp dport 22 accept
}
forward {
type filter hook forward priority 0;
policy drop;
ct state established,related accept
# Inter-VLAN policy (example: USERS <-> SERVERS allowed to 80/443/22 only)
iif "eth1.10" oif "eth1.20" tcp dport {22,80,443} accept
iif "eth1.20" oif "eth1.10" tcp dport {80,443} accept
# LANs to WAN allowed
iifname @lan_ifaces oif "eth0" accept
}
output {
type filter hook output priority 0;
policy accept;
}
}
}
table ip nat {
chains {
prerouting { type nat hook prerouting priority -100; }
postrouting {
type nat hook postrouting priority 100;
# Masquerade IPv4 from LANs to WAN
oif "eth0" ip saddr { 192.168.10.0/24, 192.168.20.0/24, 192.168.99.0/24 } masquerade
}
}
}
Apply & persist:
sudo systemctl enable nftables
sudo systemctl restart nftables
eth1 to the managed switch; tag VLANs 10/20/99.# /etc/netplan/01-ensX.yaml
network:
version: 2
renderer: networkd
ethernets:
ensX:
addresses: [192.168.20.10/24, fd12:3456:789a:20::10/64]
nameservers:
addresses: [192.168.20.1, fd12:3456:789a:20::1]
routes:
- to: 0.0.0.0/0
via: 192.168.20.1
- to: ::/0
via: fd12:3456:789a:20::1
# then
sudo netplan apply
# Replace ethX with actual device name
nmcli con add type ethernet ifname ethX con-name srv-static ipv4.addresses 192.168.20.10/24 \
ipv4.gateway 192.168.20.1 ipv4.dns "192.168.20.1" ipv4.method manual \
ipv6.addresses fd12:3456:789a:20::10/64 ipv6.gateway fd12:3456:789a:20::1 \
ipv6.dns fd12:3456:789a:20::1 ipv6.method manual
nmcli con up srv-static
ip (temporary)sudo ip addr add 192.168.20.10/24 dev ethX
sudo ip -6 addr add fd12:3456:789a:20::10/64 dev ethX
sudo ip route replace default via 192.168.20.1
sudo ip -6 route replace default via fd12:3456:789a:20::1
nmcli dev status # find device (e.g., eth0 or wlan0)
nmcli con add type ethernet ifname eth0 con-name dhcp-auto ipv4.method auto ipv6.method auto
nmcli con up dhcp-auto
# /etc/systemd/network/10-dhcp.network
[Match]
Name=eth0
[Network]
DHCP=yes
IPv6AcceptRA=yes
# then:
sudo systemctl restart systemd-networkd
# From a client on VLAN10
ip addr
ip route
ping -c3 192.168.10.1
ping -c3 1.1.1.1
ping -c3 google.com
dig @192.168.10.1 example.com
curl -I https://example.com
# From the router
ip -br addr
ip route && ip -6 route
nft list ruleset
journalctl -u dnsmasq --since "10 min ago"
journalctl -u dnsmasq; ensure the correct interface= lines and no conflicting DHCP service.tcpdump -i eth1.10 -n to see ARP/DHCP.rdisc6 eth1.10 (package: ndisc6), ensure a /64 per VLAN.input/forward; allow only necessary east-west (inter-VLAN) ports./etc/nftables.conf, /etc/dnsmasq.d/*.conf, /etc/netplan or /etc/systemd/network.