- 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>
658 lines
13 KiB
Markdown
658 lines
13 KiB
Markdown
# Implementation Plan
|
|
|
|
## Prerequisites
|
|
|
|
- SSH access to both VDS servers (RU: 176.124.216.197, DE: 194.31.173.178)
|
|
- Root or sudo privileges on both servers
|
|
- Basic firewall rules allowing SSH access
|
|
|
|
---
|
|
|
|
## Phase 1: DE VDS Setup (Exit Node)
|
|
|
|
The simpler node - just accepts traffic from RU VDS and NATs it to the internet.
|
|
|
|
### Step 1.1: Install packages
|
|
|
|
```bash
|
|
apt update && apt install -y wireguard nftables
|
|
```
|
|
|
|
### Step 1.2: Enable IP forwarding
|
|
|
|
```bash
|
|
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-vpn.conf
|
|
sysctl -p /etc/sysctl.d/99-vpn.conf
|
|
```
|
|
|
|
### Step 1.3: Generate WireGuard keys
|
|
|
|
```bash
|
|
mkdir -p /etc/wireguard/keys
|
|
chmod 700 /etc/wireguard/keys
|
|
wg genkey | tee /etc/wireguard/keys/server.key | wg pubkey > /etc/wireguard/keys/server.pub
|
|
chmod 600 /etc/wireguard/keys/*
|
|
```
|
|
|
|
### Step 1.4: Create WireGuard config
|
|
|
|
Create `/etc/wireguard/wg0.conf`:
|
|
|
|
```ini
|
|
[Interface]
|
|
Address = 10.20.0.2/30
|
|
ListenPort = 51821
|
|
PrivateKey = <DE_SERVER_PRIVATE_KEY>
|
|
PostUp = nft -f /etc/nftables.conf
|
|
PostDown = nft flush ruleset
|
|
|
|
[Peer]
|
|
# RU VDS
|
|
PublicKey = <RU_SERVER_PUBLIC_KEY>
|
|
AllowedIPs = 10.20.0.1/32, 10.10.0.0/24
|
|
```
|
|
|
|
### Step 1.5: Configure nftables
|
|
|
|
Create `/etc/nftables.conf`:
|
|
|
|
```nft
|
|
#!/usr/sbin/nft -f
|
|
|
|
flush ruleset
|
|
|
|
table inet filter {
|
|
chain input {
|
|
type filter hook input priority 0; policy drop;
|
|
|
|
# Allow established connections
|
|
ct state established,related accept
|
|
|
|
# Allow loopback
|
|
iif lo accept
|
|
|
|
# Allow SSH (adjust port if needed)
|
|
tcp dport 22 accept
|
|
|
|
# Allow WireGuard from RU VDS only
|
|
ip saddr 176.124.216.197 udp dport 51821 accept
|
|
|
|
# Allow ICMP
|
|
icmp type echo-request accept
|
|
}
|
|
|
|
chain forward {
|
|
type filter hook forward priority 0; policy drop;
|
|
|
|
# Allow forwarding from VPN
|
|
iifname "wg0" accept
|
|
|
|
# Allow established connections back
|
|
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 traffic from VPN to internet
|
|
oifname != "wg0" ip saddr { 10.10.0.0/24, 10.20.0.0/30 } masquerade
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 1.6: Enable and start services
|
|
|
|
```bash
|
|
systemctl enable --now nftables
|
|
systemctl enable --now wg-quick@wg0
|
|
```
|
|
|
|
### Step 1.7: Verify
|
|
|
|
```bash
|
|
wg show
|
|
ip addr show wg0
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: RU VDS Setup (Gateway)
|
|
|
|
The main node - handles user connections, IP-based routing decisions.
|
|
|
|
### Step 2.1: Install packages
|
|
|
|
```bash
|
|
apt update && apt install -y wireguard dnsmasq nftables qrencode curl bc
|
|
```
|
|
|
|
### Step 2.2: Enable IP forwarding
|
|
|
|
```bash
|
|
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.d/99-vpn.conf
|
|
sysctl -p /etc/sysctl.d/99-vpn.conf
|
|
```
|
|
|
|
### Step 2.3: Generate WireGuard keys
|
|
|
|
```bash
|
|
mkdir -p /etc/wireguard/keys
|
|
chmod 700 /etc/wireguard/keys
|
|
|
|
# Server key for user-facing interface
|
|
wg genkey | tee /etc/wireguard/keys/server.key | wg pubkey > /etc/wireguard/keys/server.pub
|
|
|
|
# Key for DE tunnel
|
|
wg genkey | tee /etc/wireguard/keys/de-tunnel.key | wg pubkey > /etc/wireguard/keys/de-tunnel.pub
|
|
|
|
chmod 600 /etc/wireguard/keys/*
|
|
```
|
|
|
|
### Step 2.4: Create routing tables
|
|
|
|
Add to `/etc/iproute2/rt_tables`:
|
|
|
|
```
|
|
200 proxy
|
|
```
|
|
|
|
### Step 2.5: Create WireGuard configs
|
|
|
|
Create `/etc/wireguard/wg0.conf` (user-facing):
|
|
|
|
```ini
|
|
[Interface]
|
|
Address = 10.10.0.1/24
|
|
ListenPort = 51820
|
|
PrivateKey = <RU_SERVER_PRIVATE_KEY>
|
|
PostUp = /etc/wireguard/postup.sh
|
|
PostDown = /etc/wireguard/postdown.sh
|
|
|
|
# Users will be added here as [Peer] sections
|
|
```
|
|
|
|
Create `/etc/wireguard/wg1.conf` (DE tunnel):
|
|
|
|
```ini
|
|
[Interface]
|
|
Address = 10.20.0.1/30
|
|
PrivateKey = <RU_DE_TUNNEL_PRIVATE_KEY>
|
|
|
|
[Peer]
|
|
# DE VDS
|
|
PublicKey = <DE_SERVER_PUBLIC_KEY>
|
|
Endpoint = 194.31.173.178:51821
|
|
AllowedIPs = 0.0.0.0/0
|
|
PersistentKeepalive = 25
|
|
```
|
|
|
|
### Step 2.6: Create PostUp script
|
|
|
|
Create `/etc/wireguard/postup.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
# 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 dev wg1 table proxy 2>/dev/null || true
|
|
|
|
# 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:
|
|
```bash
|
|
chmod +x /etc/wireguard/postup.sh
|
|
```
|
|
|
|
### Step 2.7: Create PostDown script
|
|
|
|
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 2>/dev/null || true
|
|
|
|
# Flush nftables vpn-routing table
|
|
nft flush table ip vpn-routing 2>/dev/null || true
|
|
|
|
echo "PostDown script completed"
|
|
```
|
|
|
|
Make executable:
|
|
```bash
|
|
chmod +x /etc/wireguard/postdown.sh
|
|
```
|
|
|
|
### Step 2.8: Configure nftables
|
|
|
|
Create `/etc/nftables.conf`:
|
|
|
|
```nft
|
|
#!/usr/sbin/nft -f
|
|
#
|
|
# RU VDS nftables configuration
|
|
# Pure nftables - no iptables/ipset dependencies
|
|
#
|
|
|
|
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 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;
|
|
|
|
oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 2.9: Configure dnsmasq
|
|
|
|
Disable systemd-resolved if running:
|
|
```bash
|
|
systemctl disable --now systemd-resolved
|
|
rm /etc/resolv.conf
|
|
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
|
|
|
|
# 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
|
|
```
|
|
|
|
### Step 2.10: Create Russian IP ranges update script
|
|
|
|
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
|
|
#
|
|
|
|
set -e
|
|
|
|
RIPE_URL="https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest"
|
|
TEMP_FILE="/tmp/ripe-delegated.txt"
|
|
|
|
echo "Downloading RIPE delegation data..."
|
|
curl -s "$RIPE_URL" -o "$TEMP_FILE"
|
|
|
|
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)
|
|
|
|
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."
|
|
```
|
|
|
|
Make executable and set up cron:
|
|
```bash
|
|
chmod +x /etc/wireguard/update-direct-routes.sh
|
|
|
|
# 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 nftables dnsmasq wg-quick@wg0 wg-quick@wg1
|
|
systemctl start dnsmasq
|
|
systemctl start wg-quick@wg1
|
|
systemctl start wg-quick@wg0
|
|
```
|
|
|
|
### 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
|
|
ip rule show
|
|
nft list set ip vpn-routing direct | head -20
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 3: Key Exchange
|
|
|
|
After generating keys on both servers, exchange public keys:
|
|
|
|
### On DE VDS:
|
|
```bash
|
|
cat /etc/wireguard/keys/server.pub
|
|
# Copy this value
|
|
```
|
|
|
|
### On RU VDS:
|
|
```bash
|
|
cat /etc/wireguard/keys/server.pub
|
|
cat /etc/wireguard/keys/de-tunnel.pub
|
|
# Copy these values
|
|
```
|
|
|
|
### Update configs:
|
|
|
|
1. **DE VDS** `/etc/wireguard/wg0.conf`: Replace `<RU_SERVER_PUBLIC_KEY>` with RU's `de-tunnel.pub`
|
|
2. **RU VDS** `/etc/wireguard/wg1.conf`: Replace `<DE_SERVER_PUBLIC_KEY>` with DE's `server.pub`
|
|
|
|
### Restart WireGuard:
|
|
|
|
```bash
|
|
# On DE VDS
|
|
systemctl restart wg-quick@wg0
|
|
|
|
# On RU VDS
|
|
systemctl restart wg-quick@wg1
|
|
```
|
|
|
|
### Verify tunnel:
|
|
|
|
```bash
|
|
# On RU VDS
|
|
ping 10.20.0.2
|
|
wg show wg1
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 4: Add First Client
|
|
|
|
### Step 4.1: Generate client keys (on RU VDS)
|
|
|
|
```bash
|
|
CLIENT_NAME="phone"
|
|
wg genkey | tee /etc/wireguard/keys/client_${CLIENT_NAME}.key | wg pubkey > /etc/wireguard/keys/client_${CLIENT_NAME}.pub
|
|
chmod 600 /etc/wireguard/keys/client_${CLIENT_NAME}.*
|
|
```
|
|
|
|
### Step 4.2: Add peer to server
|
|
|
|
```bash
|
|
CLIENT_IP="10.10.0.2"
|
|
CLIENT_PUBKEY=$(cat /etc/wireguard/keys/client_${CLIENT_NAME}.pub)
|
|
|
|
wg set wg0 peer ${CLIENT_PUBKEY} allowed-ips ${CLIENT_IP}/32
|
|
wg-quick save wg0
|
|
```
|
|
|
|
### Step 4.3: Create client config
|
|
|
|
```bash
|
|
CLIENT_NAME="phone"
|
|
CLIENT_IP="10.10.0.2"
|
|
SERVER_PUBKEY=$(cat /etc/wireguard/keys/server.pub)
|
|
CLIENT_PRIVKEY=$(cat /etc/wireguard/keys/client_${CLIENT_NAME}.key)
|
|
|
|
cat > /etc/wireguard/clients/${CLIENT_NAME}.conf << EOF
|
|
[Interface]
|
|
PrivateKey = ${CLIENT_PRIVKEY}
|
|
Address = ${CLIENT_IP}/32
|
|
DNS = 10.10.0.1
|
|
|
|
[Peer]
|
|
PublicKey = ${SERVER_PUBKEY}
|
|
Endpoint = 176.124.216.197:51820
|
|
AllowedIPs = 0.0.0.0/0
|
|
PersistentKeepalive = 25
|
|
EOF
|
|
|
|
mkdir -p /etc/wireguard/clients
|
|
chmod 600 /etc/wireguard/clients/${CLIENT_NAME}.conf
|
|
```
|
|
|
|
### Step 4.4: Transfer to client
|
|
|
|
Display as QR code (for mobile):
|
|
```bash
|
|
apt install -y qrencode
|
|
qrencode -t ansiutf8 < /etc/wireguard/clients/${CLIENT_NAME}.conf
|
|
```
|
|
|
|
Or copy the file contents manually.
|
|
|
|
---
|
|
|
|
## Phase 5: Testing
|
|
|
|
### Test 1: Basic connectivity
|
|
|
|
```bash
|
|
# From client
|
|
ping 10.10.0.1 # Should work - RU VDS
|
|
ping 10.20.0.2 # Should work - DE VDS
|
|
```
|
|
|
|
### Test 2: DNS resolution
|
|
|
|
```bash
|
|
# From client
|
|
nslookup google.com 10.10.0.1
|
|
nslookup yandex.ru 10.10.0.1
|
|
```
|
|
|
|
### Test 3: Routing verification
|
|
|
|
```bash
|
|
# On RU VDS - check ipset after client visits some .ru sites
|
|
ipset list direct
|
|
|
|
# From client - check external IP
|
|
curl ifconfig.me # Should show DE VDS IP (194.31.173.178)
|
|
curl ifconfig.me --resolve ifconfig.me:80:$(dig +short yandex.ru | head -1) # Won't work, but...
|
|
|
|
# Better test - check where traffic goes
|
|
curl https://2ip.ru # Russian service, should go direct, show RU VDS IP
|
|
curl https://ifconfig.me # Should show DE VDS IP
|
|
```
|
|
|
|
### Test 4: Check that .ru domains go direct
|
|
|
|
```bash
|
|
# From client
|
|
traceroute yandex.ru # Should not go through DE
|
|
traceroute google.com # Should go through DE (you'll see 10.20.0.x hop)
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### WireGuard not connecting
|
|
|
|
```bash
|
|
# Check if service is running
|
|
systemctl status wg-quick@wg0
|
|
|
|
# Check for errors
|
|
journalctl -u wg-quick@wg0 -e
|
|
|
|
# Verify port is open
|
|
ss -ulnp | grep 51820
|
|
```
|
|
|
|
### DNS not working
|
|
|
|
```bash
|
|
# Check dnsmasq
|
|
systemctl status dnsmasq
|
|
journalctl -u dnsmasq -e
|
|
|
|
# Test locally
|
|
dig @127.0.0.1 google.com
|
|
```
|
|
|
|
### Routing not working
|
|
|
|
```bash
|
|
# Check nftables set
|
|
nft list set ip vpn-routing direct
|
|
|
|
# Check routing table
|
|
ip route show table proxy
|
|
ip rule show
|
|
|
|
# Check nftables rules
|
|
nft list chain ip vpn-routing prerouting
|
|
|
|
# Test packet marking (watch counters)
|
|
nft list ruleset | grep -A5 "chain prerouting"
|
|
```
|
|
|
|
### Traffic not NATed
|
|
|
|
```bash
|
|
# Check nftables
|
|
nft list ruleset
|
|
|
|
# Check forwarding
|
|
cat /proc/sys/net/ipv4/ip_forward
|
|
```
|
|
|
|
---
|
|
|
|
## Summary Checklist
|
|
|
|
- [ ] **DE VDS**
|
|
- [ ] WireGuard installed
|
|
- [ ] IP forwarding enabled
|
|
- [ ] Keys generated
|
|
- [ ] wg0.conf configured
|
|
- [ ] nftables configured
|
|
- [ ] Services enabled and started
|
|
|
|
- [ ] **RU VDS**
|
|
- [ ] WireGuard installed
|
|
- [ ] dnsmasq 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 (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
|
|
- [ ] RU de-tunnel public key → DE wg0.conf
|
|
- [ ] Tunnel verified (ping 10.20.0.2 from RU)
|
|
|
|
- [ ] **First Client**
|
|
- [ ] Keys generated
|
|
- [ ] Peer added to wg0
|
|
- [ ] Client config created
|
|
- [ ] Connection tested
|
|
- [ ] Routing verified
|