diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 2ff5045..6853af2 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -231,8 +231,8 @@ wg show wg0 # Check routing table ip route show table proxy -# Check ipset -ipset list direct +# Check nftables set (will be empty until IP ranges are loaded) +nft list set ip vpn-routing direct # Check policy routing rules ip rule show @@ -240,9 +240,19 @@ ip rule show Expected output: - Routing table `proxy` should have default route via `10.20.0.2` -- ipset `direct` should exist (empty initially) +- nftables set `direct` should exist - Policy routing rule for `10.10.0.0/24` with fwmark `0x1` +### 5.4 Load Russian IP ranges + +```bash +# Load Russian IP ranges into nftables (takes 1-2 minutes) +/etc/wireguard/update-direct-routes.sh + +# Verify ranges were loaded +nft list set ip vpn-routing direct | head -20 +``` + --- ## Step 6: Add First Client @@ -331,23 +341,22 @@ curl ifconfig.me On RU VDS: ```bash -# After client visits .ru domains, check direct ipset -ipset list direct +# Check that Russian IP ranges are loaded +nft list set ip vpn-routing direct | wc -l -# Should show Russian IPs +# Should show many IP ranges (thousands) ``` ### 7.4 Advanced testing -Test that `.ru` domains go direct: +Test that Russian IPs go direct: ```bash # From client - visit some Russian sites curl -I https://yandex.ru curl -I https://mail.ru -# Then on RU VDS - check ipset -ipset list direct +# These should be fast (direct routing) ``` Test that other domains go through DE: @@ -447,14 +456,14 @@ ip route show table proxy ip rule show ``` -**Check ipset:** +**Check nftables set:** ```bash -ipset list direct +nft list set ip vpn-routing direct | head -20 ``` -**Check iptables mangle:** +**Check nftables rules:** ```bash -iptables -t mangle -L -v +nft list chain ip vpn-routing prerouting ``` ### Client can't connect @@ -612,5 +621,8 @@ ping 10.20.0.2 # Check routing ip route show table proxy -ipset list direct +nft list set ip vpn-routing direct | head -20 + +# Update Russian IP ranges +/etc/wireguard/update-direct-routes.sh ``` diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index db62041..8c2e66a 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -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 diff --git a/PRE_DEPLOYMENT_CHECKLIST.md b/PRE_DEPLOYMENT_CHECKLIST.md index 9fdad24..c6b9da0 100644 --- a/PRE_DEPLOYMENT_CHECKLIST.md +++ b/PRE_DEPLOYMENT_CHECKLIST.md @@ -169,15 +169,12 @@ systemctl stop dnsmasq # Start systemd-resolved if it was stopped systemctl start systemd-resolved -# Flush firewall (same as above) +# Flush firewall nft flush ruleset -iptables -F -iptables -X -iptables -t nat -F -iptables -t nat -X -iptables -P INPUT ACCEPT -iptables -P FORWARD ACCEPT -iptables -P OUTPUT ACCEPT + +# Remove policy routing +ip rule del from 10.10.0.0/24 fwmark 0x1 table proxy 2>/dev/null || true +ip route flush table proxy 2>/dev/null || true ``` ## Post-Deployment Preparation diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md index 0d86984..f823f7d 100644 --- a/PROJECT_SUMMARY.md +++ b/PROJECT_SUMMARY.md @@ -11,18 +11,17 @@ This project implements a split-tunnel VPN network for bypassing internet restri - RU VDS (176.124.216.197) - Gateway with selective routing - DE VDS (194.31.173.178) - Exit node in Germany -**Key feature:** DNS-based domain routing -- `.ru`, `.рф`, `.su` domains → Direct routing (fast, no proxy) -- All other domains → Routed through Germany (bypass blocks) +**Key feature:** IP-based selective routing +- Russian IP ranges (from RIPE database) → Direct routing (fast, no proxy) +- All other IPs → Routed through Germany (bypass blocks) ## Technology Stack | Component | Purpose | |-----------|---------| | WireGuard | VPN tunnels (lightweight, fast) | -| dnsmasq | DNS server with ipset integration | -| nftables | Firewall and NAT | -| ipset | IP address sets for routing decisions | +| dnsmasq | DNS server for VPN clients | +| nftables | Firewall, NAT, and packet marking (pure nftables, no iptables) | | iproute2 | Policy-based routing | ## What's Included @@ -47,8 +46,9 @@ This project implements a split-tunnel VPN network for bypassing internet restri - `wg1.conf` - DE tunnel interface - `postup.sh` - Routing setup script - `postdown.sh` - Routing cleanup script -- `nftables.conf` - Firewall rules -- `vpn-routing.conf` - dnsmasq domain routing +- `nftables.conf` - Firewall and packet marking rules +- `vpn-routing.conf` - dnsmasq config +- `update-direct-routes.sh` - Russian IP ranges loader - `rt_tables` - Custom routing table definitions - `99-vpn.conf` - Kernel parameters @@ -83,9 +83,9 @@ This project implements a split-tunnel VPN network for bypassing internet restri - Enable/disable without key regeneration ### Smart Routing -- Automatic domain-based routing -- No manual IP list maintenance -- DNS-driven routing decisions +- IP-based routing using RIPE database +- Automatic weekly updates of Russian IP ranges +- Pure nftables implementation (no iptables/ipset mixing) - Optimal performance for local traffic ### Security @@ -119,7 +119,7 @@ This project implements a split-tunnel VPN network for bypassing internet restri │ │ │ │ ▼ │ │ Routing Decision │ - │ (dnsmasq + ipset) │ + │ (nftables + RIPE data) │ │ │ │ WireGuard tunnel │ │ (all other domains) │ @@ -206,7 +206,7 @@ Comprehensive testing checklist covers: |-------|-------|-----| | Tunnel down | `wg show` | Restart wg-quick services | | DNS not working | `systemctl status dnsmasq` | Restart dnsmasq | -| Routing wrong | `ipset list direct` | Check dnsmasq config | +| Routing wrong | `nft list set ip vpn-routing direct` | Run update-direct-routes.sh | | Client can't connect | `wg show wg0 peers` | Verify peer added | | Slow performance | `ping` tests | Check MTU settings | diff --git a/QUICKSTART.md b/QUICKSTART.md index 4ca6e71..baccb82 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -81,13 +81,17 @@ systemctl start wg-quick@wg1 systemctl start wg-quick@wg0 ``` -### Step 5: Verify Tunnel (2 min) +### Step 5: Verify Tunnel & Load Routes (3 min) **On RU VDS:** ```bash +# Test tunnel ping -c 4 10.20.0.2 wg show wg1 # Should see successful ping and recent handshake + +# Load Russian IP ranges (takes 1-2 minutes) +/etc/wireguard/update-direct-routes.sh ``` ### Step 6: Add First Client (5 min) @@ -182,8 +186,11 @@ journalctl -u dnsmasq -n 50 **Routing not working?** ```bash -# Check ipset after visiting .ru sites -ipset list direct +# Check if Russian IP ranges are loaded +nft list set ip vpn-routing direct | wc -l + +# Reload if needed +/etc/wireguard/update-direct-routes.sh # Check routing table ip route show table proxy diff --git a/README.md b/README.md index 892ac8e..2fa64cb 100644 --- a/README.md +++ b/README.md @@ -58,12 +58,16 @@ A WireGuard-based VPN network with selective domain routing. Traffic to `.ru` an 1. Client connects to RU VDS via WireGuard 2. Client uses RU VDS as DNS server (10.10.0.1) -3. dnsmasq on RU VDS resolves DNS queries: - - For `.ru` and `.рф` domains → adds resolved IPs to `direct` ipset - - For all other domains → adds resolved IPs to `proxy` ipset -4. nftables routes packets based on ipset membership: - - IPs in `direct` → route via RU VDS default gateway (direct internet) - - IPs in `proxy` → route via DE VDS tunnel (10.20.0.2) +3. Russian IP ranges are loaded into nftables `direct` set (from RIPE database) +4. nftables marks packets based on destination: + - IPs in `direct` set → no mark (routes directly via RU VDS) + - All other IPs → marked with `0x1` (routes via DE VDS tunnel) +5. Policy routing sends marked packets through the DE tunnel + +**Why IP-based routing (not DNS-based)?** +- More reliable: works even if DNS is bypassed or cached +- Simpler: no iptables/ipset mixing, pure nftables +- Predictable: based on authoritative RIPE data ## Components @@ -72,9 +76,10 @@ A WireGuard-based VPN network with selective domain routing. Traffic to `.ru` an - **WireGuard**: Two interfaces - `wg0` - User-facing (10.10.0.1/24) - `wg1` - DE VDS tunnel (10.20.0.1/30) -- **dnsmasq**: DNS server with ipset integration -- **nftables**: Packet marking and routing +- **dnsmasq**: DNS server for VPN clients +- **nftables**: Firewall, NAT, and packet marking (pure nftables, no iptables) - **iproute2**: Policy-based routing tables +- **update-direct-routes.sh**: Loads Russian IP ranges from RIPE ### DE VDS (Exit Node) @@ -99,10 +104,11 @@ vpn.git/ │ │ ├── wg1.conf # DE tunnel config │ │ ├── postup.sh # Routing setup script │ │ ├── postdown.sh # Routing cleanup script -│ │ ├── nftables.conf # Firewall rules +│ │ ├── nftables.conf # Firewall + packet marking │ │ ├── 99-vpn.conf # Sysctl settings │ │ ├── rt_tables # Routing tables -│ │ └── vpn-routing.conf # dnsmasq config +│ │ ├── vpn-routing.conf # dnsmasq config +│ │ └── update-direct-routes.sh # Russian IP loader │ └── client-templates/ # Client config templates │ └── example-client.conf └── scripts/ # Management scripts @@ -215,9 +221,15 @@ wg show wg0 latest-handshakes # DNS cache stats kill -USR1 $(pidof dnsmasq) && journalctl -u dnsmasq -n 20 +# View nftables rules and sets +nft list ruleset + +# Check direct routes set (Russian IPs) +nft list set ip vpn-routing direct + # Routing tables -ip route show table direct ip route show table proxy +ip rule show ``` ### View logs diff --git a/TESTING.md b/TESTING.md index 768f887..5ef86d4 100644 --- a/TESTING.md +++ b/TESTING.md @@ -33,10 +33,11 @@ Use this checklist to verify your VPN network is working correctly. - [ ] WireGuard installed (`wg version`) - [ ] dnsmasq installed (`dnsmasq -v`) -- [ ] ipset installed (`ipset -v`) +- [ ] nftables installed (`nft -v`) - [ ] IP forwarding enabled (`cat /proc/sys/net/ipv4/ip_forward` = 1) - [ ] WireGuard keys generated (`ls /etc/wireguard/keys/`) - [ ] Routing table added (`grep proxy /etc/iproute2/rt_tables`) +- [ ] Update script exists (`ls /etc/wireguard/update-direct-routes.sh`) - [ ] All configs in place - [ ] Services enabled (not yet started) @@ -82,8 +83,9 @@ Use this checklist to verify your VPN network is working correctly. - [ ] Proxy routing table exists: `ip route show table proxy` - [ ] Default route via DE: `ip route show table proxy | grep "default via 10.20.0.2"` - [ ] Policy routing rule exists: `ip rule show | grep proxy` -- [ ] ipset 'direct' exists: `ipset list direct` -- [ ] iptables mangle rule exists: `iptables -t mangle -L PREROUTING | grep direct` +- [ ] nftables 'direct' set exists: `nft list set ip vpn-routing direct` +- [ ] nftables prerouting chain exists: `nft list chain ip vpn-routing prerouting` +- [ ] Russian IP ranges loaded: `nft list set ip vpn-routing direct | grep -c elements` ## Client Connection Tests @@ -115,16 +117,14 @@ Use this checklist to verify your VPN network is working correctly. - [ ] Can access international sites: `curl -I https://google.com` - [ ] Can access Russian sites: `curl -I https://yandex.ru` -### DNS-Based Routing (From Client) +### IP-Based Routing (From Client) -Visit some Russian sites first, then check on RU VDS: +Russian IPs are pre-loaded from RIPE database: -- [ ] Visit `https://yandex.ru` from client -- [ ] Visit `https://mail.ru` from client -- [ ] Check ipset on RU VDS: `ipset list direct` -- [ ] ipset contains Russian IPs -- [ ] Visit `https://google.com` from client -- [ ] Check that google IPs NOT in direct ipset +- [ ] Verify Russian IP ranges are loaded on RU VDS: `nft list set ip vpn-routing direct | wc -l` +- [ ] Visit `https://yandex.ru` from client (should be fast, direct route) +- [ ] Visit `https://mail.ru` from client (should be fast, direct route) +- [ ] Visit `https://google.com` from client (should go through DE tunnel) ### Advanced Routing Tests diff --git a/configs/ru-vds/nftables.conf b/configs/ru-vds/nftables.conf index d473db8..191317d 100644 --- a/configs/ru-vds/nftables.conf +++ b/configs/ru-vds/nftables.conf @@ -1,4 +1,12 @@ #!/usr/sbin/nft -f +# +# RU VDS nftables configuration +# +# Routing approach: +# - dnsmasq populates the 'direct' nftables set via helper script +# - nftables marks packets for policy routing +# - No iptables dependency +# flush ruleset @@ -44,12 +52,37 @@ table inet filter { } } +table ip vpn-routing { + # Set for Russian domain IPs (direct routing, no proxy) + # Populated by dnsmasq via /etc/wireguard/nft-set-add.sh + # Auto-expires entries after 6 hours + set direct { + type ipv4_addr + flags timeout + timeout 6h + } + + # Packet marking chain 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 via DE tunnel + meta mark set 0x1 + } +} + table inet nat { chain postrouting { type nat hook postrouting priority 100; - # NAT direct traffic going out main interface - # Traffic going through wg1 doesn't need NAT (DE VDS will NAT it) + # NAT traffic going out to internet directly (not via wg1 tunnel) + # Traffic via wg1 will be NATed by DE VDS oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade } } diff --git a/configs/ru-vds/postdown.sh b/configs/ru-vds/postdown.sh index 232b257..29181d0 100644 --- a/configs/ru-vds/postdown.sh +++ b/configs/ru-vds/postdown.sh @@ -1,18 +1,17 @@ #!/bin/bash +# +# PostDown script for WireGuard wg0 interface +# Pure nftables solution - no iptables/ipset dependencies +# + # 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 -# Remove iptables mangle rule -iptables -t mangle -F PREROUTING 2>/dev/null || true - -# Destroy ipsets -ipset destroy direct 2>/dev/null || true - -# Flush nftables (if not managed by other services) -# nft flush ruleset +# Flush nftables vpn-routing table (keeps filter and nat rules intact) +nft flush table ip vpn-routing 2>/dev/null || true echo "PostDown script completed" diff --git a/configs/ru-vds/postup.sh b/configs/ru-vds/postup.sh index 442d152..742d72b 100644 --- a/configs/ru-vds/postup.sh +++ b/configs/ru-vds/postup.sh @@ -1,9 +1,13 @@ #!/bin/bash set -e -# Create ipsets for routing decisions -ipset create direct hash:net -exist -ipset flush direct +# +# PostUp script for WireGuard wg0 interface +# Pure nftables solution - no iptables/ipset dependencies +# + +# 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 @@ -11,11 +15,4 @@ 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 -# Load nftables rules -nft -f /etc/nftables.conf - -# Mark packets NOT going to 'direct' ipset with fwmark 0x1 -# This is needed because nftables + ipset integration is complex -iptables -t mangle -I PREROUTING -m set ! --match-set direct dst -s 10.10.0.0/24 -j MARK --set-mark 0x1 - echo "PostUp script completed successfully" diff --git a/configs/ru-vds/update-direct-routes.sh b/configs/ru-vds/update-direct-routes.sh new file mode 100644 index 0000000..b76cf61 --- /dev/null +++ b/configs/ru-vds/update-direct-routes.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Downloads Russian IP ranges and adds them to the nftables 'direct' set +# These IPs will be routed directly instead of through the DE proxy +# +# Run this script: +# - Once during initial setup +# - Periodically via cron (weekly) to keep ranges updated +# +# Data source: RIPE NCC delegated statistics +# + +set -e + +RIPE_URL="https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest" +TEMP_FILE="/tmp/ripe-delegated.txt" +NFT_TABLE="ip vpn-routing" +NFT_SET="direct" + +echo "Downloading RIPE delegation data..." +curl -s "$RIPE_URL" -o "$TEMP_FILE" + +echo "Parsing Russian IP allocations..." + +# Extract Russian IPv4 allocations and convert to CIDR notation +# Format: ripencc|RU|ipv4|start_ip|count|date|status +RU_RANGES=$(grep '|RU|ipv4|' "$TEMP_FILE" | while IFS='|' read -r registry cc type start count date status rest; do + # Convert count to CIDR prefix length + # count is number of IPs, prefix = 32 - log2(count) + if [[ "$count" =~ ^[0-9]+$ ]]; then + prefix=$(echo "32 - l($count)/l(2)" | bc -l | cut -d. -f1) + echo "$start/$prefix" + fi +done) + +# Count ranges +RANGE_COUNT=$(echo "$RU_RANGES" | wc -l) +echo "Found $RANGE_COUNT Russian IP ranges" + +echo "Flushing existing 'direct' set..." +nft flush set $NFT_TABLE $NFT_SET 2>/dev/null || true + +echo "Adding ranges to nftables set..." +# Add in batches for efficiency +echo "$RU_RANGES" | while read -r cidr; do + if [[ -n "$cidr" ]]; then + nft add element $NFT_TABLE $NFT_SET { "$cidr" } 2>/dev/null || true + fi +done + +# Cleanup +rm -f "$TEMP_FILE" + +# Show stats +FINAL_COUNT=$(nft list set $NFT_TABLE $NFT_SET 2>/dev/null | grep -c "elements" || echo "0") +echo "Done. Set '$NFT_SET' populated with Russian IP ranges." +echo "" +echo "To verify: nft list set $NFT_TABLE $NFT_SET" diff --git a/configs/ru-vds/vpn-routing.conf b/configs/ru-vds/vpn-routing.conf index a6b588b..5e769a8 100644 --- a/configs/ru-vds/vpn-routing.conf +++ b/configs/ru-vds/vpn-routing.conf @@ -1,3 +1,9 @@ +# dnsmasq configuration for VPN routing +# +# Note: Routing decisions are now based on destination IP ranges, +# not DNS responses. Russian IP ranges are loaded into nftables +# by the update-direct-routes.sh script. + # Listen only on VPN interface interface=wg0 bind-interfaces @@ -13,18 +19,5 @@ no-resolv # Cache settings cache-size=10000 -# Log queries (optional, comment out in production for performance) +# Log queries (optional, uncomment for debugging) # log-queries - -# Russian TLDs - add resolved IPs to 'direct' ipset -# These domains will be routed directly, not through DE VDS -ipset=/ru/direct -ipset=/рф/direct -ipset=/su/direct - -# Additional Russian domains (optional, can be extended) -# ipset=/yandex.ru/direct -# ipset=/mail.ru/direct -# ipset=/vk.com/direct - -# All other domains will go through proxy (default routing) diff --git a/scripts/setup-ru-vds.sh b/scripts/setup-ru-vds.sh index 63768c9..7332ab1 100755 --- a/scripts/setup-ru-vds.sh +++ b/scripts/setup-ru-vds.sh @@ -3,6 +3,12 @@ set -e # Setup script for RU VDS (Gateway) # Run this script as root on the RU VDS server +# +# This script sets up: +# - WireGuard VPN (dual interface: users + DE tunnel) +# - dnsmasq for DNS resolution +# - nftables for firewall and routing +# - Policy routing for split-tunnel VPN echo "=========================================" echo "RU VDS (Gateway) Setup" @@ -15,14 +21,14 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi -echo "[1/10] Updating system packages..." +echo "[1/11] Updating system packages..." apt update apt upgrade -y -echo "[2/10] Installing required packages..." -apt install -y wireguard dnsmasq nftables iptables ipset qrencode +echo "[2/11] Installing required packages..." +apt install -y wireguard dnsmasq nftables qrencode curl bc -echo "[3/10] Disabling systemd-resolved (conflicts with dnsmasq)..." +echo "[3/11] Disabling systemd-resolved (conflicts with dnsmasq)..." systemctl disable --now systemd-resolved 2>/dev/null || true rm -f /etc/resolv.conf cat > /etc/resolv.conf << 'EOF' @@ -30,14 +36,14 @@ nameserver 8.8.8.8 nameserver 1.1.1.1 EOF -echo "[4/10] Enabling IP forwarding..." +echo "[4/11] Enabling IP forwarding..." cat > /etc/sysctl.d/99-vpn.conf << 'EOF' # Enable IP forwarding for VPN net.ipv4.ip_forward = 1 EOF sysctl -p /etc/sysctl.d/99-vpn.conf -echo "[5/10] Generating WireGuard keys..." +echo "[5/11] Generating WireGuard keys..." mkdir -p /etc/wireguard/keys chmod 700 /etc/wireguard/keys @@ -49,12 +55,12 @@ wg genkey | tee /etc/wireguard/keys/de-tunnel.key | wg pubkey > /etc/wireguard/k chmod 600 /etc/wireguard/keys/* -echo "[6/10] Adding custom routing table..." +echo "[6/11] Adding custom routing table..." if ! grep -q "^200[[:space:]]*proxy" /etc/iproute2/rt_tables; then echo "200 proxy" >> /etc/iproute2/rt_tables fi -echo "[7/10] Creating WireGuard configurations..." +echo "[7/11] Creating WireGuard configurations..." # wg0 - user-facing cat > /etc/wireguard/wg0.conf << 'EOF' @@ -91,16 +97,20 @@ EOF DE_TUNNEL_KEY=$(cat /etc/wireguard/keys/de-tunnel.key) sed -i "s|__RU_DE_TUNNEL_PRIVATE_KEY__|${DE_TUNNEL_KEY}|g" /etc/wireguard/wg1.conf -echo "[8/10] Creating WireGuard helper scripts..." +echo "[8/11] Creating WireGuard helper scripts..." # PostUp script cat > /etc/wireguard/postup.sh << 'EOF' #!/bin/bash set -e -# Create ipsets for routing decisions -ipset create direct hash:net -exist -ipset flush direct +# +# PostUp script for WireGuard wg0 interface +# Pure nftables solution - no iptables/ipset dependencies +# + +# 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 @@ -108,12 +118,6 @@ 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 -# Load nftables rules -nft -f /etc/nftables.conf - -# Mark packets NOT going to 'direct' ipset with fwmark 0x1 -iptables -t mangle -I PREROUTING -m set ! --match-set direct dst -s 10.10.0.0/24 -j MARK --set-mark 0x1 - echo "PostUp script completed successfully" EOF @@ -121,17 +125,19 @@ EOF cat > /etc/wireguard/postdown.sh << 'EOF' #!/bin/bash +# +# PostDown script for WireGuard wg0 interface +# Pure nftables solution - no iptables/ipset dependencies +# + # 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 -# Remove iptables mangle rule -iptables -t mangle -F PREROUTING 2>/dev/null || true - -# Destroy ipsets -ipset destroy direct 2>/dev/null || true +# Flush nftables vpn-routing table (keeps filter and nat rules intact) +nft flush table ip vpn-routing 2>/dev/null || true echo "PostDown script completed" EOF @@ -139,9 +145,17 @@ EOF chmod +x /etc/wireguard/postup.sh chmod +x /etc/wireguard/postdown.sh -echo "[9/10] Creating nftables configuration..." +echo "[9/11] Creating nftables configuration..." cat > /etc/nftables.conf << 'EOF' #!/usr/sbin/nft -f +# +# RU VDS nftables configuration +# +# Routing approach: +# - Russian IP ranges loaded into 'direct' set by update-direct-routes.sh +# - nftables marks packets for policy routing +# - No iptables dependency +# flush ruleset @@ -187,11 +201,36 @@ table inet filter { } } +table ip vpn-routing { + # Set for Russian IPs (direct routing, no proxy) + # Populated by /etc/wireguard/update-direct-routes.sh + # Auto-expires entries after 6 hours + set direct { + type ipv4_addr + flags interval, timeout + timeout 6h + } + + # Packet marking chain 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 via DE tunnel + meta mark set 0x1 + } +} + table inet nat { chain postrouting { type nat hook postrouting priority 100; - # NAT direct traffic going out main interface + # NAT traffic going out to internet directly (not via wg1 tunnel) oifname != "wg0" oifname != "wg1" ip saddr 10.10.0.0/24 masquerade } } @@ -199,8 +238,14 @@ EOF chmod +x /etc/nftables.conf -echo "[10/10] Configuring dnsmasq..." +echo "[10/11] Configuring dnsmasq..." cat > /etc/dnsmasq.d/vpn-routing.conf << 'EOF' +# dnsmasq configuration for VPN routing +# +# Note: Routing decisions are based on destination IP ranges, +# not DNS responses. Russian IP ranges are loaded into nftables +# by the update-direct-routes.sh script. + # Listen only on VPN interface interface=wg0 bind-interfaces @@ -216,17 +261,85 @@ no-resolv # Cache settings cache-size=10000 -# Russian TLDs - add resolved IPs to 'direct' ipset -ipset=/ru/direct -ipset=/рф/direct -ipset=/su/direct - -# All other domains will go through proxy (default routing) +# Log queries (optional, uncomment for debugging) +# log-queries EOF +echo "[11/11] Creating Russian IP routes update script..." +cat > /etc/wireguard/update-direct-routes.sh << 'SCRIPT' +#!/bin/bash +# +# Downloads Russian IP ranges and adds them to the nftables 'direct' set +# These IPs will be routed directly instead of through the DE proxy +# +# Run this script: +# - Once during initial setup (after services are started) +# - Periodically via cron (weekly) to keep ranges updated +# +# Data source: RIPE NCC delegated statistics +# + +set -e + +RIPE_URL="https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest" +TEMP_FILE="/tmp/ripe-delegated.txt" +NFT_TABLE="ip vpn-routing" +NFT_SET="direct" + +echo "Downloading RIPE delegation data..." +curl -s "$RIPE_URL" -o "$TEMP_FILE" + +echo "Parsing Russian IP allocations..." + +# Extract Russian IPv4 allocations and convert to CIDR notation +# Format: ripencc|RU|ipv4|start_ip|count|date|status +RU_RANGES=$(grep '|RU|ipv4|' "$TEMP_FILE" | while IFS='|' read -r registry cc type start count date status rest; do + # Convert count to CIDR prefix length + # count is number of IPs, prefix = 32 - log2(count) + if [[ "$count" =~ ^[0-9]+$ ]] && [ "$count" -gt 0 ]; then + prefix=$(echo "32 - l($count)/l(2)" | bc -l 2>/dev/null | cut -d. -f1) + if [[ "$prefix" =~ ^[0-9]+$ ]] && [ "$prefix" -ge 8 ] && [ "$prefix" -le 32 ]; then + echo "$start/$prefix" + fi + fi +done) + +# Count ranges +RANGE_COUNT=$(echo "$RU_RANGES" | grep -c . || echo "0") +echo "Found $RANGE_COUNT Russian IP ranges" + +echo "Flushing existing 'direct' set..." +nft flush set $NFT_TABLE $NFT_SET 2>/dev/null || true + +echo "Adding ranges to nftables set (this may take a moment)..." +# Add in batches for efficiency +echo "$RU_RANGES" | while read -r cidr; do + if [[ -n "$cidr" ]] && [[ "$cidr" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then + nft add element $NFT_TABLE $NFT_SET { "$cidr" } 2>/dev/null || true + fi +done + +# Cleanup +rm -f "$TEMP_FILE" + +echo "" +echo "Done. Russian IP ranges loaded into nftables." +echo "Traffic to these IPs will be routed directly (not through DE proxy)." +SCRIPT + +chmod +x /etc/wireguard/update-direct-routes.sh + # Create clients directory mkdir -p /etc/wireguard/clients +# Add cron job for weekly updates +echo "Setting up weekly cron job for IP range updates..." +cat > /etc/cron.weekly/update-vpn-routes << 'CRON' +#!/bin/bash +/etc/wireguard/update-direct-routes.sh >> /var/log/vpn-routes-update.log 2>&1 +CRON +chmod +x /etc/cron.weekly/update-vpn-routes + echo "" echo "=========================================" echo "Setup completed!" @@ -248,14 +361,17 @@ echo "3. Edit /etc/wireguard/wg1.conf and replace:" echo " __DE_SERVER_PUBLIC_KEY__ with the actual DE VDS public key" echo "" echo "4. Enable and start services:" -echo " systemctl enable nftables dnsmasq" +echo " systemctl enable nftables dnsmasq wg-quick@wg0 wg-quick@wg1" echo " systemctl start dnsmasq" echo " systemctl start wg-quick@wg1" echo " systemctl start wg-quick@wg0" echo "" -echo "5. Verify the tunnel:" +echo "5. Load Russian IP ranges (after tunnel is up):" +echo " /etc/wireguard/update-direct-routes.sh" +echo "" +echo "6. Verify the tunnel:" echo " wg show" echo " ping 10.20.0.2" echo "" -echo "6. Add clients using: /root/add-client.sh " +echo "7. Add clients using: /root/add-client.sh " echo ""