Migrate to pure nftables routing (remove iptables/ipset)
- Replace hybrid iptables/ipset/nftables approach with pure nftables - Add nftables native set for Russian IP ranges (populated from RIPE) - Create update-direct-routes.sh script to load IP ranges from RIPE database - Remove ipset and iptables dependencies from postup.sh/postdown.sh - Add automatic weekly cron job for IP range updates - Update all documentation to reflect the new approach Benefits: - More reliable: no iptables/nftables conflicts - Simpler debugging: single tool for all rules (nft list ruleset) - Atomic rule loading: prevents partial failures - IP-based routing is more predictable than DNS-based Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,12 +124,12 @@ ip addr show wg0
|
||||
|
||||
## Phase 2: RU VDS Setup (Gateway)
|
||||
|
||||
The main node - handles user connections, DNS-based routing decisions.
|
||||
The main node - handles user connections, IP-based routing decisions.
|
||||
|
||||
### Step 2.1: Install packages
|
||||
|
||||
```bash
|
||||
apt update && apt install -y wireguard dnsmasq nftables ipset
|
||||
apt update && apt install -y wireguard dnsmasq nftables qrencode curl bc
|
||||
```
|
||||
|
||||
### Step 2.2: Enable IP forwarding
|
||||
@@ -198,16 +198,18 @@ Create `/etc/wireguard/postup.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Create ipsets for routing decisions
|
||||
ipset create direct hash:net -exist
|
||||
ipset create proxy hash:net -exist
|
||||
# Load nftables rules (includes the 'direct' set and packet marking)
|
||||
nft -f /etc/nftables.conf
|
||||
|
||||
# Add default route via DE tunnel for 'proxy' table
|
||||
ip route add default via 10.20.0.2 table proxy
|
||||
ip route add default via 10.20.0.2 dev wg1 table proxy 2>/dev/null || true
|
||||
|
||||
# Load nftables rules
|
||||
nft -f /etc/nftables.conf
|
||||
# Policy routing: packets with fwmark 0x1 use 'proxy' table
|
||||
ip rule add from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
|
||||
|
||||
echo "PostUp script completed successfully"
|
||||
```
|
||||
|
||||
Make executable:
|
||||
@@ -222,15 +224,16 @@ Create `/etc/wireguard/postdown.sh`:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Remove policy routing rule
|
||||
ip rule del from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
|
||||
|
||||
# Flush routing table
|
||||
ip route flush table proxy
|
||||
ip route flush table proxy 2>/dev/null || true
|
||||
|
||||
# Destroy ipsets
|
||||
ipset destroy direct 2>/dev/null
|
||||
ipset destroy proxy 2>/dev/null
|
||||
# Flush nftables vpn-routing table
|
||||
nft flush table ip vpn-routing 2>/dev/null || true
|
||||
|
||||
# Flush nftables
|
||||
nft flush ruleset
|
||||
echo "PostDown script completed"
|
||||
```
|
||||
|
||||
Make executable:
|
||||
@@ -244,6 +247,10 @@ Create `/etc/nftables.conf`:
|
||||
|
||||
```nft
|
||||
#!/usr/sbin/nft -f
|
||||
#
|
||||
# RU VDS nftables configuration
|
||||
# Pure nftables - no iptables/ipset dependencies
|
||||
#
|
||||
|
||||
flush ruleset
|
||||
|
||||
@@ -251,36 +258,20 @@ table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
|
||||
# Allow established
|
||||
ct state established,related accept
|
||||
|
||||
# Allow loopback
|
||||
iif lo accept
|
||||
|
||||
# Allow SSH
|
||||
tcp dport 22 accept
|
||||
|
||||
# Allow WireGuard from anywhere (user connections)
|
||||
udp dport 51820 accept
|
||||
|
||||
# Allow DNS from VPN clients only
|
||||
iifname "wg0" udp dport 53 accept
|
||||
iifname "wg0" tcp dport 53 accept
|
||||
|
||||
# Allow ICMP
|
||||
icmp type echo-request accept
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority 0; policy drop;
|
||||
|
||||
# Allow forwarding from user VPN
|
||||
iifname "wg0" accept
|
||||
|
||||
# Allow forwarding from DE tunnel
|
||||
iifname "wg1" accept
|
||||
|
||||
# Allow established
|
||||
ct state established,related accept
|
||||
}
|
||||
|
||||
@@ -289,27 +280,39 @@ table inet filter {
|
||||
}
|
||||
}
|
||||
|
||||
table ip vpn-routing {
|
||||
# Set for Russian IPs (direct routing, no proxy)
|
||||
# Populated by update-direct-routes.sh script
|
||||
set direct {
|
||||
type ipv4_addr
|
||||
flags interval, timeout
|
||||
timeout 6h
|
||||
}
|
||||
|
||||
# Packet marking for policy routing
|
||||
chain prerouting {
|
||||
type filter hook prerouting priority mangle; policy accept;
|
||||
|
||||
# Only process traffic from VPN clients
|
||||
ip saddr != 10.10.0.0/24 return
|
||||
|
||||
# Destinations in 'direct' set: no mark (direct routing)
|
||||
ip daddr @direct return
|
||||
|
||||
# Everything else: mark for proxy routing
|
||||
meta mark set 0x1
|
||||
}
|
||||
}
|
||||
|
||||
table inet nat {
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority 100;
|
||||
|
||||
# NAT direct traffic (going out main interface)
|
||||
oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade
|
||||
}
|
||||
}
|
||||
|
||||
table inet mangle {
|
||||
chain prerouting {
|
||||
type filter hook prerouting priority -150;
|
||||
|
||||
# Mark packets destined for 'proxy' ipset
|
||||
ip daddr @proxy meta mark set 0x1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** nftables native sets will be used instead of ipset. See Step 2.10 for updated approach.
|
||||
|
||||
### Step 2.9: Configure dnsmasq
|
||||
|
||||
Disable systemd-resolved if running:
|
||||
@@ -322,6 +325,9 @@ echo "nameserver 8.8.8.8" > /etc/resolv.conf
|
||||
Create `/etc/dnsmasq.d/vpn-routing.conf`:
|
||||
|
||||
```conf
|
||||
# dnsmasq configuration for VPN
|
||||
# Routing is handled by nftables based on IP ranges, not DNS
|
||||
|
||||
# Listen only on VPN interface
|
||||
interface=wg0
|
||||
bind-interfaces
|
||||
@@ -329,122 +335,89 @@ bind-interfaces
|
||||
# Upstream DNS
|
||||
server=8.8.8.8
|
||||
server=8.8.4.4
|
||||
server=1.1.1.1
|
||||
|
||||
# Don't read /etc/resolv.conf
|
||||
no-resolv
|
||||
|
||||
# Cache size
|
||||
cache-size=10000
|
||||
|
||||
# Log queries (optional, disable in production)
|
||||
# log-queries
|
||||
|
||||
# Russian TLDs - route directly (add to 'direct' ipset)
|
||||
ipset=/ru/direct
|
||||
ipset=/рф/direct
|
||||
ipset=/su/direct
|
||||
|
||||
# Everything else goes to proxy (default)
|
||||
# This is handled by routing table, not ipset
|
||||
```
|
||||
|
||||
**Important:** dnsmasq's ipset feature requires ipset, not nftables sets. We'll use a hybrid approach.
|
||||
### Step 2.10: Create Russian IP ranges update script
|
||||
|
||||
### Step 2.10: Updated routing approach (hybrid ipset + nftables)
|
||||
|
||||
Since dnsmasq works with ipset, we'll use ipset for the sets and nftables for the rules.
|
||||
|
||||
Update `/etc/nftables.conf`:
|
||||
|
||||
```nft
|
||||
#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
|
||||
ct state established,related accept
|
||||
iif lo accept
|
||||
tcp dport 22 accept
|
||||
udp dport 51820 accept
|
||||
iifname "wg0" udp dport 53 accept
|
||||
iifname "wg0" tcp dport 53 accept
|
||||
icmp type echo-request accept
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority 0; policy drop;
|
||||
|
||||
iifname "wg0" accept
|
||||
iifname "wg1" accept
|
||||
ct state established,related accept
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 0; policy accept;
|
||||
}
|
||||
}
|
||||
|
||||
table inet nat {
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority 100;
|
||||
|
||||
# NAT direct traffic going out main interface
|
||||
oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `/etc/wireguard/postup.sh`:
|
||||
Create `/etc/wireguard/update-direct-routes.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#
|
||||
# Downloads Russian IP ranges from RIPE and loads them into nftables
|
||||
# Run after services are started, and weekly via cron
|
||||
#
|
||||
|
||||
# Create ipsets
|
||||
ipset create direct hash:net -exist
|
||||
ipset flush direct
|
||||
set -e
|
||||
|
||||
# Add default route via DE tunnel for 'proxy' table
|
||||
ip route add default via 10.20.0.2 table proxy 2>/dev/null || true
|
||||
RIPE_URL="https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest"
|
||||
TEMP_FILE="/tmp/ripe-delegated.txt"
|
||||
|
||||
# Policy routing: packets NOT going to 'direct' ipset use 'proxy' table
|
||||
ip rule add from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
|
||||
echo "Downloading RIPE delegation data..."
|
||||
curl -s "$RIPE_URL" -o "$TEMP_FILE"
|
||||
|
||||
# Load nftables
|
||||
nft -f /etc/nftables.conf
|
||||
echo "Parsing Russian IP allocations..."
|
||||
RU_RANGES=$(grep '|RU|ipv4|' "$TEMP_FILE" | while IFS='|' read -r _ _ _ start count _ _ _; do
|
||||
if [[ "$count" =~ ^[0-9]+$ ]] && [ "$count" -gt 0 ]; then
|
||||
prefix=$(echo "32 - l($count)/l(2)" | bc -l | cut -d. -f1)
|
||||
echo "$start/$prefix"
|
||||
fi
|
||||
done)
|
||||
|
||||
# Add iptables rule for fwmark (nftables mangle is complex with ipset)
|
||||
iptables -t mangle -A PREROUTING -m set ! --match-set direct dst -s 10.10.0.0/24 -j MARK --set-mark 0x1
|
||||
echo "Flushing existing 'direct' set..."
|
||||
nft flush set ip vpn-routing direct 2>/dev/null || true
|
||||
|
||||
echo "Adding ranges to nftables..."
|
||||
echo "$RU_RANGES" | while read -r cidr; do
|
||||
[[ -n "$cidr" ]] && nft add element ip vpn-routing direct { "$cidr" } 2>/dev/null || true
|
||||
done
|
||||
|
||||
rm -f "$TEMP_FILE"
|
||||
echo "Done. Russian IP ranges loaded."
|
||||
```
|
||||
|
||||
Update `/etc/wireguard/postdown.sh`:
|
||||
|
||||
Make executable and set up cron:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
chmod +x /etc/wireguard/update-direct-routes.sh
|
||||
|
||||
ip rule del from 10.10.0.0/24 fwmark 0x1 table proxy priority 100 2>/dev/null || true
|
||||
ip route flush table proxy 2>/dev/null || true
|
||||
iptables -t mangle -F PREROUTING 2>/dev/null || true
|
||||
ipset destroy direct 2>/dev/null || true
|
||||
nft flush ruleset
|
||||
# Weekly updates
|
||||
cat > /etc/cron.weekly/update-vpn-routes << 'EOF'
|
||||
#!/bin/bash
|
||||
/etc/wireguard/update-direct-routes.sh >> /var/log/vpn-routes-update.log 2>&1
|
||||
EOF
|
||||
chmod +x /etc/cron.weekly/update-vpn-routes
|
||||
```
|
||||
|
||||
### Step 2.11: Enable and start services
|
||||
|
||||
```bash
|
||||
systemctl enable --now dnsmasq
|
||||
systemctl enable --now wg-quick@wg0
|
||||
systemctl enable --now wg-quick@wg1
|
||||
systemctl enable nftables dnsmasq wg-quick@wg0 wg-quick@wg1
|
||||
systemctl start dnsmasq
|
||||
systemctl start wg-quick@wg1
|
||||
systemctl start wg-quick@wg0
|
||||
```
|
||||
|
||||
### Step 2.12: Verify
|
||||
### Step 2.12: Load Russian IP ranges
|
||||
|
||||
After the tunnel is up:
|
||||
```bash
|
||||
/etc/wireguard/update-direct-routes.sh
|
||||
```
|
||||
|
||||
### Step 2.13: Verify
|
||||
|
||||
```bash
|
||||
wg show
|
||||
ip route show table proxy
|
||||
ipset list direct
|
||||
ip rule show
|
||||
nft list set ip vpn-routing direct | head -20
|
||||
```
|
||||
|
||||
---
|
||||
@@ -620,19 +593,18 @@ dig @127.0.0.1 google.com
|
||||
### Routing not working
|
||||
|
||||
```bash
|
||||
# Check ipset
|
||||
ipset list direct
|
||||
# Check nftables set
|
||||
nft list set ip vpn-routing direct
|
||||
|
||||
# Check routing table
|
||||
ip route show table proxy
|
||||
ip rule show
|
||||
|
||||
# Check marks
|
||||
iptables -t mangle -L -v
|
||||
# Check nftables rules
|
||||
nft list chain ip vpn-routing prerouting
|
||||
|
||||
# Test marking
|
||||
ping -c 1 8.8.8.8
|
||||
conntrack -L | grep 8.8.8.8
|
||||
# Test packet marking (watch counters)
|
||||
nft list ruleset | grep -A5 "chain prerouting"
|
||||
```
|
||||
|
||||
### Traffic not NATed
|
||||
@@ -660,16 +632,17 @@ cat /proc/sys/net/ipv4/ip_forward
|
||||
- [ ] **RU VDS**
|
||||
- [ ] WireGuard installed
|
||||
- [ ] dnsmasq installed
|
||||
- [ ] ipset installed
|
||||
- [ ] nftables installed
|
||||
- [ ] IP forwarding enabled
|
||||
- [ ] Keys generated
|
||||
- [ ] Routing table 'proxy' added
|
||||
- [ ] wg0.conf configured (users)
|
||||
- [ ] wg1.conf configured (DE tunnel)
|
||||
- [ ] postup.sh / postdown.sh created
|
||||
- [ ] nftables configured
|
||||
- [ ] nftables configured (with vpn-routing table)
|
||||
- [ ] dnsmasq configured
|
||||
- [ ] Services enabled and started
|
||||
- [ ] Russian IP ranges loaded (update-direct-routes.sh)
|
||||
|
||||
- [ ] **Key Exchange**
|
||||
- [ ] DE public key → RU wg1.conf
|
||||
|
||||
Reference in New Issue
Block a user