├── openwrt ├── crontab.example ├── network │ └── interfaces.example ├── amneziawg │ ├── awg-watchdog.init │ ├── servers.conf.example │ ├── mullvad-awg0.conf.example │ ├── awg0.conf.example │ ├── 99-awg.hotplug │ └── awg-watchdog.sh ├── dhcp │ └── dhcp.example ├── firewall │ └── zones.example ├── firewall-vpn-zone.example ├── banip │ └── banip.example ├── rc.local.example └── firewall-bypass-rules.example ├── .gitignore ├── LICENSE ├── docker ├── config │ ├── predown.sh │ ├── AdGuardHome.yaml.example │ ├── awg0.conf.example │ └── postup.sh ├── scripts │ ├── quick-test.sh │ ├── healthcheck.sh │ ├── watchdog.sh │ ├── awg-profiles.sh │ ├── test-suite.sh │ └── entrypoint.sh ├── Dockerfile ├── .env.example ├── docker-compose.yml └── README.md ├── scripts ├── auto-backup.sh ├── rotate-watchdog-log.sh ├── awg-watchdog.service ├── adguardhome.service ├── setup-firewall.sh ├── disable-ipv6.sh └── awg-profiles.sh ├── secrets.env.example ├── adguard ├── mullvad-AdGuardHome.yaml.example └── AdGuardHome.yaml.example └── docs ├── OPTIONAL_ADDONS.md ├── ARCHITECTURE.md └── DEPLOYMENT.md /openwrt/crontab.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # OpenWrt Crontab - Scheduled Maintenance Tasks 3 | # ============================================================================= 4 | # 5 | # Installation: 6 | # Edit with: crontab -e 7 | # Or add to: /etc/crontabs/root 8 | # 9 | # Cron format: minute hour day month weekday command 10 | # * = every 11 | # */5 = every 5 12 | # 13 | # ============================================================================= 14 | 15 | # Daily config backup at 3am 16 | 0 3 * * * /etc/backup/auto-backup.sh 17 | 18 | # Daily log rotation check at 4am 19 | 0 4 * * * /etc/backup/rotate-watchdog-log.sh 20 | 21 | # Optional: Weekly system reboot (Sunday 4:30am) - uncomment if desired 22 | # 30 4 * * 0 /sbin/reboot 23 | 24 | # Optional: Hourly DNS cache flush - uncomment if experiencing DNS issues 25 | # 0 * * * * /etc/init.d/dnsmasq restart 26 | 27 | # ============================================================================= 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Privacy Router Stack - Git Ignore 3 | # ============================================================================= 4 | 5 | # Environment files with secrets 6 | .env 7 | secrets.env 8 | *.env.local 9 | *.env.production 10 | 11 | # Actual configuration files (use .example templates) 12 | *.conf 13 | !*.conf.example 14 | *.yaml 15 | !*.yaml.example 16 | !docker-compose.yml 17 | 18 | # WireGuard/AmneziaWG keys 19 | privatekey 20 | publickey 21 | presharedkey 22 | *.key 23 | 24 | # AdGuard Home runtime 25 | adguard/work/ 26 | adguard/conf/ 27 | !adguard/*.example 28 | 29 | # Docker runtime 30 | wireguard/config/ 31 | docker/wireguard/ 32 | docker/adguard/ 33 | 34 | # Logs 35 | *.log 36 | logs/ 37 | 38 | # OS files 39 | .DS_Store 40 | Thumbs.db 41 | *.swp 42 | *.swo 43 | *~ 44 | 45 | # IDE 46 | .idea/ 47 | .vscode/ 48 | *.sublime-* 49 | 50 | # Backups 51 | *.bak 52 | *.backup 53 | *.old 54 | 55 | # Temporary files 56 | tmp/ 57 | temp/ 58 | *.tmp 59 | 60 | # Local progress/context (never push) 61 | LOCAL_PROGRESS.md 62 | LOCAL_*.md 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker/config/predown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Kill Switch - PreDown Script 4 | # ============================================================================= 5 | # Runs when VPN tunnel goes down 6 | # 7 | # IMPORTANT: Kill switch rules STAY ACTIVE 8 | # DROP policies remain in place - no traffic leaks 9 | # ============================================================================= 10 | 11 | log() { 12 | echo "[$(date '+%Y-%m-%d %H:%M:%S')] PREDOWN: $1" 13 | } 14 | 15 | log "VPN tunnel going down..." 16 | 17 | # Clear connection tracking to prevent stale connections 18 | # This ensures old connections don't bypass the kill switch 19 | conntrack -F 2>/dev/null || true 20 | 21 | log "Connection tracking flushed" 22 | 23 | # Log kill switch engagement 24 | log "KILL SWITCH ENGAGED - All internet traffic blocked" 25 | log "VPN must reconnect before traffic will flow" 26 | 27 | # Note: We do NOT remove iptables rules here 28 | # The DROP policies remain active, blocking all traffic 29 | # This is intentional - it's the kill switch protecting you 30 | 31 | # The watchdog or Docker restart will bring the tunnel back up 32 | # When it does, postup.sh will re-apply the rules 33 | 34 | exit 0 35 | -------------------------------------------------------------------------------- /scripts/auto-backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # OpenWrt Auto-Backup Script 4 | # ============================================================================= 5 | # 6 | # Creates daily compressed backups of /etc/config with rotation. 7 | # 8 | # Installation: 9 | # 1. Copy to /etc/backup/auto-backup.sh 10 | # 2. chmod +x /etc/backup/auto-backup.sh 11 | # 3. mkdir -p /etc/backup 12 | # 4. Add to crontab: 0 3 * * * /etc/backup/auto-backup.sh 13 | # 14 | # ============================================================================= 15 | 16 | BACKUP_DIR=/etc/backup 17 | MAX_BACKUPS=7 18 | 19 | # Ensure backup directory exists 20 | mkdir -p "$BACKUP_DIR" 21 | 22 | # Create new backup 23 | BACKUP_FILE="$BACKUP_DIR/config-$(date +%Y%m%d).tar.gz" 24 | tar czf "$BACKUP_FILE" /etc/config/ 2>/dev/null 25 | 26 | if [ $? -eq 0 ]; then 27 | logger -t auto-backup "Config backup completed: $BACKUP_FILE" 28 | else 29 | logger -t auto-backup "ERROR: Config backup failed" 30 | exit 1 31 | fi 32 | 33 | # Remove backups older than MAX_BACKUPS days 34 | find "$BACKUP_DIR" -name 'config-*.tar.gz' -mtime +$MAX_BACKUPS -delete 35 | 36 | # Log retention status 37 | BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/config-*.tar.gz 2>/dev/null | wc -l) 38 | logger -t auto-backup "Retention: $BACKUP_COUNT backups (max: $MAX_BACKUPS days)" 39 | -------------------------------------------------------------------------------- /scripts/rotate-watchdog-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # Watchdog Log Rotation Script 4 | # ============================================================================= 5 | # 6 | # Rotates watchdog log when it exceeds MAX_SIZE. 7 | # Keeps compressed historical logs with rotation. 8 | # 9 | # Installation: 10 | # 1. Copy to /etc/backup/rotate-watchdog-log.sh 11 | # 2. chmod +x /etc/backup/rotate-watchdog-log.sh 12 | # 3. Add to crontab: 0 4 * * * /etc/backup/rotate-watchdog-log.sh 13 | # 14 | # ============================================================================= 15 | 16 | LOG="/var/log/awg-watchdog.log" 17 | MAX_SIZE=1048576 # 1MB in bytes 18 | MAX_ROTATIONS=4 19 | 20 | if [ ! -f "$LOG" ]; then 21 | exit 0 22 | fi 23 | 24 | # Get current log size 25 | SIZE=$(stat -c%s "$LOG" 2>/dev/null || wc -c < "$LOG") 26 | 27 | if [ "$SIZE" -gt "$MAX_SIZE" ]; then 28 | # Remove oldest rotation 29 | rm -f "$LOG.$MAX_ROTATIONS.gz" 2>/dev/null 30 | 31 | # Shift existing rotations 32 | i=$((MAX_ROTATIONS - 1)) 33 | while [ $i -ge 1 ]; do 34 | [ -f "$LOG.$i.gz" ] && mv "$LOG.$i.gz" "$LOG.$((i + 1)).gz" 35 | i=$((i - 1)) 36 | done 37 | 38 | # Rotate current log 39 | mv "$LOG" "$LOG.1" 40 | gzip -f "$LOG.1" 2>/dev/null 41 | 42 | # Create fresh log 43 | touch "$LOG" 44 | 45 | logger -t log-rotate "Rotated watchdog log (was ${SIZE} bytes)" 46 | fi 47 | -------------------------------------------------------------------------------- /openwrt/network/interfaces.example: -------------------------------------------------------------------------------- 1 | # OpenWrt Network Configuration 2 | # /etc/config/network 3 | # 4 | # This is a template - adjust IPs and interface names for your setup 5 | 6 | config interface 'loopback' 7 | option device 'lo' 8 | option proto 'static' 9 | option ipaddr '127.0.0.1' 10 | option netmask '255.0.0.0' 11 | 12 | config globals 'globals' 13 | option ula_prefix 'auto' 14 | 15 | # LAN Bridge Device 16 | config device 17 | option name 'br-lan' 18 | option type 'bridge' 19 | # Add your LAN interface(s) here 20 | list ports 'eth1' 21 | 22 | # LAN Interface - Your internal network 23 | config interface 'lan' 24 | option device 'br-lan' 25 | option proto 'static' 26 | # Gateway IP - this will be the default gateway for all LAN devices 27 | option ipaddr '192.168.1.1' 28 | option netmask '255.255.255.0' 29 | # DNS for the router itself (use your VPN provider's DNS) 30 | # Mullvad: 10.64.0.1 (standard), 100.64.0.1-4 (with blocking) 31 | # 100.64.0.4 = Ad + Tracker + Malware + Adult (recommended) 32 | # IVPN: 10.0.254.1 | Proton: 10.2.0.1 33 | option dns 'VPN_PROVIDER_DNS_IP' 34 | # Disable IPv6 to prevent leaks 35 | option ip6assign '' 36 | 37 | # WAN Interface - Connection to modem/ISP 38 | config interface 'wan' 39 | # Your WAN interface name (check with: ip link) 40 | option device 'eth0' 41 | option proto 'dhcp' 42 | # Disable IPv6 to prevent leaks 43 | option ipv6 '0' 44 | option peerdns '0' 45 | 46 | # Do NOT create a wan6 interface - IPv6 disabled for leak prevention 47 | -------------------------------------------------------------------------------- /scripts/awg-watchdog.service: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AmneziaWG Watchdog Systemd Service 3 | # ============================================================================= 4 | # 5 | # For non-OpenWrt Linux systems (Raspberry Pi, Debian, Ubuntu, etc.) 6 | # 7 | # Installation: 8 | # 1. Copy awg-watchdog.sh to /etc/awg-watchdog.sh 9 | # 2. chmod +x /etc/awg-watchdog.sh 10 | # 3. Edit /etc/awg-watchdog.sh with your VPN settings 11 | # 4. Copy this file to /etc/systemd/system/awg-watchdog.service 12 | # 5. systemctl daemon-reload 13 | # 6. systemctl enable awg-watchdog 14 | # 7. systemctl start awg-watchdog 15 | # 16 | # Commands: 17 | # systemctl status awg-watchdog # Check status 18 | # journalctl -u awg-watchdog -f # Follow logs 19 | # systemctl restart awg-watchdog # Restart 20 | # 21 | # ============================================================================= 22 | 23 | [Unit] 24 | Description=AmneziaWG Watchdog - VPN Tunnel Monitor and Auto-Recovery 25 | Documentation=https://github.com/yoloshii/net-freedom-privacy 26 | After=network-online.target 27 | Wants=network-online.target 28 | 29 | [Service] 30 | Type=simple 31 | ExecStart=/etc/awg-watchdog.sh 32 | Restart=always 33 | RestartSec=10 34 | 35 | # Security hardening (optional but recommended) 36 | NoNewPrivileges=no 37 | ProtectSystem=strict 38 | ProtectHome=yes 39 | ReadWritePaths=/var/log /var/run /etc/amneziawg 40 | PrivateTmp=yes 41 | 42 | # Logging 43 | StandardOutput=journal 44 | StandardError=journal 45 | SyslogIdentifier=awg-watchdog 46 | 47 | [Install] 48 | WantedBy=multi-user.target 49 | -------------------------------------------------------------------------------- /docker/scripts/quick-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Privacy Router - Quick Test 4 | # ============================================================================= 5 | # Fast validation for daily use (~10 seconds) 6 | # For full validation, use test-suite.sh 7 | # 8 | # Usage: docker exec privacy-router /opt/scripts/quick-test.sh 9 | # ============================================================================= 10 | 11 | set -e 12 | 13 | # Colors 14 | RED='\033[0;31m' 15 | GREEN='\033[0;32m' 16 | NC='\033[0m' 17 | 18 | pass() { echo -e "[${GREEN}OK${NC}] $1"; } 19 | fail() { echo -e "[${RED}FAIL${NC}] $1"; exit 1; } 20 | 21 | echo "Privacy Router - Quick Test" 22 | echo "============================" 23 | 24 | # 1. VPN Interface UP 25 | ip link show awg0 2>/dev/null | grep -q "state UP\|state UNKNOWN" || fail "VPN interface down" 26 | pass "VPN interface UP" 27 | 28 | # 2. Recent Handshake 29 | amneziawg show awg0 2>/dev/null | grep -q "latest handshake" || fail "No VPN handshake" 30 | pass "VPN handshake active" 31 | 32 | # 3. Tunnel HTTP (more reliable than ping through VPN) 33 | curl -s --max-time 5 --interface awg0 http://1.1.1.1 &>/dev/null || fail "Cannot reach internet through tunnel" 34 | pass "Tunnel connectivity" 35 | 36 | # 4. Exit IP 37 | EXIT_IP=$(curl -s --max-time 8 --interface awg0 https://ipinfo.io/ip 2>/dev/null || echo "") 38 | [[ -n "$EXIT_IP" ]] || fail "Could not get exit IP" 39 | pass "Exit IP: $EXIT_IP" 40 | 41 | # 5. Kill Switch Rules Present 42 | iptables -L OUTPUT -n 2>/dev/null | grep -q "DROP" || fail "Kill switch not active" 43 | pass "Kill switch active" 44 | 45 | echo "" 46 | echo -e "${GREEN}All checks passed${NC}" 47 | -------------------------------------------------------------------------------- /openwrt/amneziawg/awg-watchdog.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # ============================================================================= 3 | # AmneziaWG Watchdog - procd init script 4 | # ============================================================================= 5 | # 6 | # Installation: 7 | # 1. Copy to /etc/init.d/awg-watchdog 8 | # 2. chmod +x /etc/init.d/awg-watchdog 9 | # 3. /etc/init.d/awg-watchdog enable 10 | # 4. /etc/init.d/awg-watchdog start 11 | # 12 | # Management: 13 | # /etc/init.d/awg-watchdog start # Start watchdog 14 | # /etc/init.d/awg-watchdog stop # Stop watchdog 15 | # /etc/init.d/awg-watchdog restart # Restart watchdog 16 | # /etc/init.d/awg-watchdog status # Check if running 17 | # 18 | # Logs: 19 | # logread -e awg-watchdog # View syslog entries 20 | # tail -f /var/log/awg-watchdog.log # View detailed log 21 | # 22 | # ============================================================================= 23 | 24 | START=99 25 | STOP=10 26 | 27 | USE_PROCD=1 28 | 29 | WATCHDOG_SCRIPT="/etc/awg-watchdog.sh" 30 | 31 | start_service() { 32 | # Verify watchdog script exists and is executable 33 | if [ ! -x "$WATCHDOG_SCRIPT" ]; then 34 | logger -t awg-init "ERROR: $WATCHDOG_SCRIPT not found or not executable" 35 | return 1 36 | fi 37 | 38 | # Check for unconfigured placeholders (actual assignments, not validation code) 39 | if grep -E '^VPN_IP="CHANGE_ME"|^ENDPOINT_IP="CHANGE_ME"' "$WATCHDOG_SCRIPT" >/dev/null 2>&1; then 40 | logger -t awg-init "ERROR: $WATCHDOG_SCRIPT not configured (VPN_IP or ENDPOINT_IP is CHANGE_ME)" 41 | return 1 42 | fi 43 | 44 | procd_open_instance 45 | procd_set_param command "$WATCHDOG_SCRIPT" 46 | procd_set_param respawn 47 | procd_set_param stdout 1 48 | procd_set_param stderr 1 49 | procd_close_instance 50 | 51 | logger -t awg-init "AWG watchdog started" 52 | } 53 | 54 | stop_service() { 55 | logger -t awg-init "AWG watchdog stopped" 56 | } 57 | -------------------------------------------------------------------------------- /openwrt/dhcp/dhcp.example: -------------------------------------------------------------------------------- 1 | # OpenWrt DHCP Configuration 2 | # /etc/config/dhcp 3 | # 4 | # Pushes AdGuard Home as DNS server to all clients 5 | 6 | config dnsmasq 7 | option domainneeded '1' 8 | option boguspriv '1' 9 | option filterwin2k '0' 10 | option localise_queries '1' 11 | option rebind_protection '1' 12 | option rebind_localhost '1' 13 | option local '/lan/' 14 | option domain 'lan' 15 | option expandhosts '1' 16 | option nonegcache '0' 17 | option cachesize '1000' 18 | option authoritative '1' 19 | option readethers '1' 20 | option leasefile '/tmp/dhcp.leases' 21 | option resolvfile '/tmp/resolv.conf.d/resolv.conf.auto' 22 | option nonwildcard '1' 23 | option localservice '1' 24 | option ednspacket_max '1232' 25 | # Disable AAAA record filtering (AdGuard handles this) 26 | option filter_aaaa '0' 27 | option filter_a '0' 28 | 29 | config dhcp 'lan' 30 | option interface 'lan' 31 | # DHCP range: .100 to .249 32 | option start '100' 33 | option limit '150' 34 | option leasetime '12h' 35 | option dhcpv4 'server' 36 | # Push AdGuard Home as DNS server (Option 6) 37 | # Replace with your AdGuard IP 38 | list dhcp_option '6,192.168.1.5' 39 | # Disable DHCPv6 (IPv6 leak prevention) 40 | option dhcpv6 'disabled' 41 | option ra 'disabled' 42 | 43 | config dhcp 'wan' 44 | option interface 'wan' 45 | option ignore '1' 46 | 47 | # ============================================================================= 48 | # STATIC LEASES (Optional) 49 | # ============================================================================= 50 | 51 | # Example: Reserve IP for a specific device 52 | #config host 53 | # option name 'my-server' 54 | # option mac '00:11:22:33:44:55' 55 | # option ip '192.168.1.50' 56 | 57 | # ============================================================================= 58 | # DNS OVERRIDES (Optional) 59 | # ============================================================================= 60 | 61 | # Example: Local DNS entry 62 | #config domain 63 | # option name 'nas.lan' 64 | # option ip '192.168.1.50' 65 | -------------------------------------------------------------------------------- /secrets.env.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Privacy Router - Secrets Configuration (Optional Convenience File) 3 | # ============================================================================= 4 | # This file is ONE option for providing secrets to the agent. 5 | # When the agent needs a secret, it will ask for your consent first. 6 | # 7 | # You can then choose to: 8 | # A) Point agent to this file (if you've prepared it) 9 | # B) Paste the secret directly in chat 10 | # C) Decline agent handling and enter secrets manually into config files 11 | # 12 | # If using this file: 13 | # 1. Copy secrets.env.example to secrets.env 14 | # 2. Fill in your values below 15 | # 3. Tell the agent "I've put it in secrets.env" when asked 16 | # 17 | # NOTE: This is NOT a traditional .env file loaded into app environment. 18 | # It's a secrets reference file - the agent reads values during deployment 19 | # and injects them into configuration files (e.g., awg0.conf). 20 | # 21 | # This file is gitignored - your secrets stay local. 22 | # ============================================================================= 23 | 24 | # ----------------------------------------------------------------------------- 25 | # VPN Private Key (REQUIRED) 26 | # ----------------------------------------------------------------------------- 27 | # Get this from your VPN provider's account page or WireGuard config download. 28 | # 29 | # Mullvad: Account page → WireGuard configuration → Download/Show key 30 | # IVPN: Client Area → WireGuard → Generate Key 31 | # 32 | # Format: Base64 string, 44 characters ending in = 33 | # Example: kHxE5xh8kL9fR4A2bC3dE5fG6hI7jK8lM9nO0pQ1rS4= 34 | # 35 | VPN_PRIVATE_KEY= 36 | 37 | # ----------------------------------------------------------------------------- 38 | # Optional: Admin Passwords 39 | # ----------------------------------------------------------------------------- 40 | # Leave blank to be prompted during setup, or set here for automated deployment. 41 | # 42 | # Router admin password (OpenWrt LuCI web interface) 43 | # ROUTER_ADMIN_PASSWORD= 44 | 45 | # AdGuard Home admin password (if deploying AdGuard) 46 | # ADGUARD_ADMIN_PASSWORD= 47 | -------------------------------------------------------------------------------- /scripts/adguardhome.service: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AdGuard Home Systemd Service 3 | # ============================================================================= 4 | # 5 | # For running AdGuard Home as a system service on Linux 6 | # 7 | # Installation: 8 | # 1. Download AdGuard Home: 9 | # curl -s -S -L https://static.adguard.com/adguardhome/release/AdGuardHome_linux_arm64.tar.gz | tar xz 10 | # # Use linux_amd64 for x86 systems, linux_arm64 for Pi5/arm64 11 | # 2. Move to /opt: 12 | # sudo mv AdGuardHome /opt/ 13 | # 3. Run initial setup: 14 | # /opt/AdGuardHome/AdGuardHome --web-addr 0.0.0.0:3000 15 | # # Visit http://[IP]:3000 to complete wizard 16 | # 4. Copy this file to /etc/systemd/system/adguardhome.service 17 | # 5. systemctl daemon-reload 18 | # 6. systemctl enable adguardhome 19 | # 7. systemctl start adguardhome 20 | # 21 | # Commands: 22 | # systemctl status adguardhome # Check status 23 | # journalctl -u adguardhome -f # Follow logs 24 | # systemctl restart adguardhome # Restart 25 | # 26 | # Configuration: /opt/AdGuardHome/AdGuardHome.yaml 27 | # Web UI: http://[IP]:3000 28 | # 29 | # ============================================================================= 30 | 31 | [Unit] 32 | Description=AdGuard Home - Network-wide ads & trackers blocking DNS server 33 | Documentation=https://github.com/AdguardTeam/AdGuardHome 34 | After=network-online.target 35 | Wants=network-online.target 36 | 37 | [Service] 38 | Type=simple 39 | User=root 40 | Group=root 41 | WorkingDirectory=/opt/AdGuardHome 42 | ExecStart=/opt/AdGuardHome/AdGuardHome -c /opt/AdGuardHome/AdGuardHome.yaml --no-check-update 43 | Restart=always 44 | RestartSec=10 45 | 46 | # Capabilities for binding to low ports (53) 47 | AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_RAW 48 | CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_RAW 49 | 50 | # Security hardening 51 | NoNewPrivileges=yes 52 | ProtectSystem=strict 53 | ProtectHome=yes 54 | ReadWritePaths=/opt/AdGuardHome 55 | PrivateTmp=yes 56 | 57 | # Logging 58 | StandardOutput=journal 59 | StandardError=journal 60 | SyslogIdentifier=adguardhome 61 | 62 | [Install] 63 | WantedBy=multi-user.target 64 | -------------------------------------------------------------------------------- /openwrt/amneziawg/servers.conf.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AmneziaWG Server List for Failover 3 | # ============================================================================= 4 | # 5 | # Format: NAME ENDPOINT_IP PORT PUBLIC_KEY 6 | # 7 | # - NAME: Friendly name for logging 8 | # - ENDPOINT_IP: Server IP address (not hostname) 9 | # - PORT: WireGuard port (usually 51820) 10 | # - PUBLIC_KEY: Server's public key (use "-" if same as base config) 11 | # 12 | # First server is PRIMARY - watchdog will failback to it when available 13 | # Order matters - servers are tried in sequence during failover 14 | # 15 | # Get server list from your VPN provider: 16 | # - Mullvad: https://mullvad.net/en/servers 17 | # - IVPN: https://www.ivpn.net/status 18 | # 19 | # ============================================================================= 20 | # IMPORTANT: PUBLIC KEY GOTCHA 21 | # ============================================================================= 22 | # 23 | # Different cities/regions typically have DIFFERENT public keys! 24 | # Using "-" only works for servers that share the SAME key as your base config. 25 | # 26 | # SAFE: All servers from same city cluster (e.g., all us-lax-wg-xxx) 27 | # UNSAFE: Mixing cities (us-lax + us-sjc) with "-" - different keys will fail! 28 | # 29 | # If mixing cities, get each city's public key from your provider and specify it. 30 | # 31 | # ============================================================================= 32 | 33 | # Primary server (preferred) 34 | us-lax-wg-001 CHANGE_ME_IP 51820 CHANGE_ME_PUBKEY 35 | 36 | # Failover servers (SAME CITY = same public key, can use "-") 37 | us-lax-wg-002 CHANGE_ME_IP 51820 - 38 | us-lax-wg-003 CHANGE_ME_IP 51820 - 39 | us-lax-wg-004 CHANGE_ME_IP 51820 - 40 | us-lax-wg-005 CHANGE_ME_IP 51820 - 41 | 42 | # ============================================================================= 43 | # Example with explicit keys (for mixing cities): 44 | # ============================================================================= 45 | # 46 | # # Same city cluster - can use "-" after first key 47 | # us-lax-wg-001 203.0.113.10 51820 LAX_PUBLIC_KEY_HERE 48 | # us-lax-wg-002 203.0.113.11 51820 - 49 | # us-lax-wg-003 203.0.113.12 51820 - 50 | # 51 | # # Different city - MUST specify its own key 52 | # us-sjc-wg-001 198.51.100.10 51820 SJC_PUBLIC_KEY_HERE 53 | # us-sea-wg-001 198.51.100.20 51820 SEA_PUBLIC_KEY_HERE 54 | # 55 | # IPs above are RFC 5737 documentation ranges - replace with real server IPs 56 | # ============================================================================= 57 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Privacy Router - AmneziaWG VPN Client 3 | # ============================================================================= 4 | # Multi-arch Docker image for AmneziaWG VPN gateway 5 | # Supports: amd64, arm64 (Raspberry Pi 4/5) 6 | # 7 | # Based on amneziawg-go (userspace implementation - no kernel module required) 8 | # ============================================================================= 9 | 10 | FROM golang:1.24-alpine AS builder 11 | 12 | # Install build dependencies (git, make, C compiler for amneziawg-tools) 13 | RUN apk add --no-cache git make build-base linux-headers 14 | 15 | # Build amneziawg-go from source 16 | WORKDIR /build 17 | RUN git clone --depth 1 https://github.com/amnezia-vpn/amneziawg-go.git && \ 18 | cd amneziawg-go && \ 19 | make && \ 20 | cp amneziawg-go /usr/local/bin/ 21 | 22 | # Build amneziawg-tools 23 | RUN git clone --depth 1 https://github.com/amnezia-vpn/amneziawg-tools.git && \ 24 | cd amneziawg-tools/src && \ 25 | make && \ 26 | cp wg /usr/local/bin/amneziawg 27 | 28 | # ============================================================================= 29 | # Runtime image 30 | # ============================================================================= 31 | FROM alpine:3.20 32 | 33 | LABEL maintainer="Privacy Router Project" 34 | LABEL description="AmneziaWG VPN Client with kill switch for privacy routing" 35 | LABEL org.opencontainers.image.source="https://github.com/your-repo/privacy-router" 36 | 37 | # Install runtime dependencies 38 | RUN apk add --no-cache \ 39 | bash \ 40 | iptables \ 41 | ip6tables \ 42 | iproute2 \ 43 | curl \ 44 | bind-tools \ 45 | conntrack-tools \ 46 | procps \ 47 | && rm -rf /var/cache/apk/* 48 | 49 | # Copy built binaries from builder 50 | COPY --from=builder /usr/local/bin/amneziawg-go /usr/local/bin/ 51 | COPY --from=builder /usr/local/bin/amneziawg /usr/local/bin/ 52 | 53 | # Create directories 54 | RUN mkdir -p /etc/amneziawg /opt/scripts /var/log 55 | 56 | # Copy scripts 57 | COPY scripts/ /opt/scripts/ 58 | 59 | # Make scripts executable 60 | RUN chmod +x /opt/scripts/*.sh 61 | 62 | # Environment defaults 63 | ENV VPN_IP="" \ 64 | VPN_ENDPOINT_IP="" \ 65 | VPN_ENDPOINT_PORT="51820" \ 66 | LAN_SUBNET="192.168.1.0/24" \ 67 | AWG_PROFILE="basic" \ 68 | WATCHDOG_ENABLED="true" \ 69 | WATCHDOG_INTERVAL="30" \ 70 | WATCHDOG_FAIL_THRESHOLD="3" \ 71 | PROBE_TARGETS="1.1.1.1 8.8.8.8 9.9.9.9" \ 72 | TZ="UTC" 73 | 74 | # Health check 75 | HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ 76 | CMD /opt/scripts/healthcheck.sh 77 | 78 | # Expose ports 79 | # DNS (AdGuard runs in same network namespace) 80 | EXPOSE 53/udp 53/tcp 81 | # AdGuard Web UI 82 | EXPOSE 3000/tcp 83 | 84 | ENTRYPOINT ["/opt/scripts/entrypoint.sh"] 85 | -------------------------------------------------------------------------------- /scripts/setup-firewall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # Firewall Setup Script - Kill Switch Configuration 4 | # ============================================================================= 5 | # 6 | # Configures OpenWrt firewall zones for VPN kill switch. 7 | # Run once during initial setup. 8 | # 9 | # Usage: 10 | # chmod +x setup-firewall.sh 11 | # ./setup-firewall.sh 12 | # 13 | # ============================================================================= 14 | 15 | set -e 16 | 17 | echo "Setting up firewall zones for VPN kill switch..." 18 | 19 | # ============================================================================= 20 | # VPN Zone 21 | # ============================================================================= 22 | 23 | echo "Creating VPN zone..." 24 | 25 | # Check if VPN zone already exists 26 | if uci show firewall | grep -q "firewall.vpn=zone"; then 27 | echo "VPN zone already exists, updating..." 28 | else 29 | uci set firewall.vpn=zone 30 | fi 31 | 32 | uci set firewall.vpn.name='vpn' 33 | uci set firewall.vpn.device='awg0' 34 | uci set firewall.vpn.input='REJECT' 35 | uci set firewall.vpn.output='ACCEPT' 36 | uci set firewall.vpn.forward='REJECT' 37 | uci set firewall.vpn.masq='1' 38 | uci set firewall.vpn.mtu_fix='1' 39 | 40 | # ============================================================================= 41 | # LAN to VPN Forwarding 42 | # ============================================================================= 43 | 44 | echo "Creating LAN->VPN forwarding rule..." 45 | 46 | # Check if forwarding already exists 47 | if uci show firewall | grep -q "firewall.lan_vpn=forwarding"; then 48 | echo "LAN->VPN forwarding already exists, updating..." 49 | else 50 | uci set firewall.lan_vpn=forwarding 51 | fi 52 | 53 | uci set firewall.lan_vpn.src='lan' 54 | uci set firewall.lan_vpn.dest='vpn' 55 | 56 | # ============================================================================= 57 | # Verify Kill Switch (No LAN->WAN) 58 | # ============================================================================= 59 | 60 | echo "Verifying kill switch (no LAN->WAN forwarding)..." 61 | 62 | # Check for any lan->wan forwarding and warn 63 | if uci show firewall | grep -E "src='lan'.*dest='wan'" | grep -q forwarding; then 64 | echo "WARNING: Found LAN->WAN forwarding rule. This breaks the kill switch!" 65 | echo "Remove it with: uci delete firewall.@forwarding[X]" 66 | fi 67 | 68 | # ============================================================================= 69 | # Commit and Restart 70 | # ============================================================================= 71 | 72 | echo "Committing changes..." 73 | uci commit firewall 74 | 75 | echo "Restarting firewall..." 76 | /etc/init.d/firewall restart 77 | 78 | echo "" 79 | echo "Firewall configured successfully!" 80 | echo "" 81 | echo "Zone summary:" 82 | echo " LAN -> VPN: ALLOWED (internet access)" 83 | echo " LAN -> WAN: BLOCKED (kill switch)" 84 | echo "" 85 | echo "When VPN is down, all internet traffic will be blocked." 86 | echo "LAN access (SSH, web UI) will still work." 87 | -------------------------------------------------------------------------------- /docker/.env.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Privacy Router - Docker Environment Configuration 3 | # ============================================================================= 4 | # Copy this file to .env and customize for your setup 5 | # 6 | # Required values are marked with (REQUIRED) 7 | # ============================================================================= 8 | 9 | # ----------------------------------------------------------------------------- 10 | # VPN Configuration (REQUIRED - from your VPN provider) 11 | # ----------------------------------------------------------------------------- 12 | 13 | # Your VPN internal IP address (from provider config) 14 | # Example for Mullvad: 10.64.123.45/32 15 | # Find this in your WireGuard config as "Address" 16 | VPN_IP=10.64.0.1/32 17 | 18 | # VPN server endpoint IP address (NOT hostname!) 19 | # Use: dig +short vpn.mullvad.net (or your provider's hostname) 20 | # IMPORTANT: Must be an IP, not a hostname 21 | VPN_ENDPOINT_IP=185.213.154.68 22 | 23 | # VPN server port (usually 51820) 24 | VPN_ENDPOINT_PORT=51820 25 | 26 | # ----------------------------------------------------------------------------- 27 | # LAN Configuration (adjust to match your network) 28 | # ----------------------------------------------------------------------------- 29 | 30 | # Docker host's LAN-facing network interface 31 | # Find with: ip link show 32 | LAN_INTERFACE=eth0 33 | 34 | # Your LAN subnet 35 | LAN_SUBNET=192.168.1.0/24 36 | 37 | # Your modem/upstream router IP (existing gateway) 38 | LAN_GATEWAY=192.168.1.1 39 | 40 | # IP address for the privacy-router container on your LAN 41 | # This is what LAN devices will use as their gateway 42 | # Choose an unused IP in your subnet 43 | CONTAINER_LAN_IP=192.168.1.5 44 | 45 | # ----------------------------------------------------------------------------- 46 | # Watchdog Configuration (auto-recovery) 47 | # ----------------------------------------------------------------------------- 48 | 49 | # Enable automatic tunnel recovery 50 | WATCHDOG_ENABLED=true 51 | 52 | # Seconds between health checks 53 | WATCHDOG_INTERVAL=30 54 | 55 | # Number of failures before restart 56 | WATCHDOG_FAIL_THRESHOLD=3 57 | 58 | # IPs to ping for connectivity test (space-separated) 59 | PROBE_TARGETS=1.1.1.1 8.8.8.8 9.9.9.9 60 | 61 | # ----------------------------------------------------------------------------- 62 | # Obfuscation Profile (AmneziaWG 1.5) 63 | # ----------------------------------------------------------------------------- 64 | # Choose protocol mimicry level based on your censorship environment: 65 | # 66 | # basic - Junk packets + header obfuscation (default, works everywhere) 67 | # quic - QUIC protocol mimic (appears as HTTP/3 traffic) 68 | # dns - DNS query mimic (appears as DNS resolution) 69 | # sip - SIP/VoIP mimic (appears as voice traffic) 70 | # stealth - Maximum obfuscation (QUIC + aggressive junk packets) 71 | # 72 | # All profiles work with standard WireGuard servers (Mullvad, IVPN, etc.) 73 | AWG_PROFILE=basic 74 | 75 | # ----------------------------------------------------------------------------- 76 | # General 77 | # ----------------------------------------------------------------------------- 78 | 79 | # Timezone for logs 80 | TZ=UTC 81 | -------------------------------------------------------------------------------- /scripts/disable-ipv6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # Disable IPv6 Script - Leak Prevention 4 | # ============================================================================= 5 | # 6 | # Completely disables IPv6 to prevent potential VPN leaks. 7 | # Run once during initial setup. 8 | # 9 | # Usage: 10 | # chmod +x disable-ipv6.sh 11 | # ./disable-ipv6.sh 12 | # 13 | # ============================================================================= 14 | 15 | set -e 16 | 17 | echo "Disabling IPv6 to prevent VPN leaks..." 18 | 19 | # ============================================================================= 20 | # UCI Network Configuration 21 | # ============================================================================= 22 | 23 | echo "Disabling IPv6 in network config..." 24 | 25 | # Disable on WAN 26 | uci set network.wan.ipv6='0' 27 | 28 | # Disable on LAN 29 | uci set network.lan.ipv6='' 30 | uci delete network.lan.ip6assign 2>/dev/null || true 31 | 32 | # Remove wan6 interface if exists 33 | uci delete network.wan6 2>/dev/null || true 34 | 35 | uci commit network 36 | 37 | # ============================================================================= 38 | # UCI DHCP Configuration 39 | # ============================================================================= 40 | 41 | echo "Disabling DHCPv6 and RA..." 42 | 43 | # Disable DHCPv6 on LAN 44 | uci set dhcp.lan.dhcpv6='disabled' 45 | uci set dhcp.lan.ra='disabled' 46 | 47 | # Disable odhcpd if present 48 | uci set dhcp.odhcpd.maindhcp='0' 2>/dev/null || true 49 | 50 | uci commit dhcp 51 | 52 | # ============================================================================= 53 | # Kernel Parameters 54 | # ============================================================================= 55 | 56 | echo "Disabling IPv6 in kernel..." 57 | 58 | # Add sysctl settings 59 | cat >> /etc/sysctl.conf << 'EOF' 60 | 61 | # Disable IPv6 (VPN leak prevention) 62 | net.ipv6.conf.all.disable_ipv6 = 1 63 | net.ipv6.conf.default.disable_ipv6 = 1 64 | net.ipv6.conf.lo.disable_ipv6 = 1 65 | EOF 66 | 67 | # Apply immediately 68 | sysctl -w net.ipv6.conf.all.disable_ipv6=1 69 | sysctl -w net.ipv6.conf.default.disable_ipv6=1 70 | sysctl -w net.ipv6.conf.lo.disable_ipv6=1 71 | 72 | # ============================================================================= 73 | # Firewall Rules 74 | # ============================================================================= 75 | 76 | echo "Adding IPv6 drop rules to firewall..." 77 | 78 | # Drop all IPv6 traffic as extra protection 79 | if ! uci show firewall | grep -q "option name 'Drop-IPv6'"; then 80 | uci add firewall rule 81 | uci set firewall.@rule[-1].name='Drop-IPv6-Forward' 82 | uci set firewall.@rule[-1].family='ipv6' 83 | uci set firewall.@rule[-1].proto='all' 84 | uci set firewall.@rule[-1].target='DROP' 85 | uci commit firewall 86 | fi 87 | 88 | # ============================================================================= 89 | # Restart Services 90 | # ============================================================================= 91 | 92 | echo "Restarting network services..." 93 | /etc/init.d/network restart 94 | /etc/init.d/firewall restart 95 | /etc/init.d/dnsmasq restart 96 | /etc/init.d/odhcpd stop 2>/dev/null || true 97 | /etc/init.d/odhcpd disable 2>/dev/null || true 98 | 99 | echo "" 100 | echo "IPv6 disabled successfully!" 101 | echo "" 102 | echo "Verification:" 103 | ip -6 addr 2>/dev/null || echo "No IPv6 addresses (good)" 104 | echo "" 105 | echo "To verify no IPv6 leaks, visit: https://ipv6leak.com" 106 | -------------------------------------------------------------------------------- /openwrt/firewall/zones.example: -------------------------------------------------------------------------------- 1 | # OpenWrt Firewall Configuration 2 | # /etc/config/firewall 3 | # 4 | # Kill switch architecture: LAN -> VPN only, NO LAN -> WAN 5 | 6 | config defaults 7 | option syn_flood '1' 8 | option input 'REJECT' 9 | option output 'ACCEPT' 10 | option forward 'REJECT' 11 | # Drop invalid packets 12 | option drop_invalid '1' 13 | 14 | # ============================================================================= 15 | # ZONES 16 | # ============================================================================= 17 | 18 | # LAN Zone - Internal network 19 | # Accepts all traffic (management access) 20 | config zone 21 | option name 'lan' 22 | option network 'lan' 23 | option input 'ACCEPT' 24 | option output 'ACCEPT' 25 | option forward 'ACCEPT' 26 | 27 | # WAN Zone - ISP connection 28 | # Rejects input, allows output (for VPN endpoint only) 29 | config zone 30 | option name 'wan' 31 | list network 'wan' 32 | option input 'REJECT' 33 | option output 'ACCEPT' 34 | option forward 'REJECT' 35 | option masq '1' 36 | option mtu_fix '1' 37 | 38 | # VPN Zone - AmneziaWG tunnel 39 | # This is where all internet-bound traffic goes 40 | config zone 41 | option name 'vpn' 42 | option device 'awg0' 43 | option input 'REJECT' 44 | option output 'ACCEPT' 45 | option forward 'REJECT' 46 | option masq '1' 47 | option mtu_fix '1' 48 | 49 | # ============================================================================= 50 | # FORWARDING RULES (Kill Switch Core) 51 | # ============================================================================= 52 | 53 | # LAN to VPN - ALLOWED 54 | # This is the ONLY path to the internet 55 | config forwarding 56 | option src 'lan' 57 | option dest 'vpn' 58 | 59 | # IMPORTANT: No lan->wan forwarding rule exists 60 | # This is the kill switch - if VPN is down, traffic has nowhere to go 61 | 62 | # ============================================================================= 63 | # STANDARD RULES 64 | # ============================================================================= 65 | 66 | # Allow DHCP renewal from WAN (for router to get IP from modem) 67 | config rule 68 | option name 'Allow-DHCP-Renew' 69 | option src 'wan' 70 | option proto 'udp' 71 | option dest_port '68' 72 | option target 'ACCEPT' 73 | option family 'ipv4' 74 | 75 | # Allow ping to WAN interface 76 | config rule 77 | option name 'Allow-Ping' 78 | option src 'wan' 79 | option proto 'icmp' 80 | option icmp_type 'echo-request' 81 | option family 'ipv4' 82 | option target 'ACCEPT' 83 | 84 | # Allow IGMP for multicast (optional) 85 | config rule 86 | option name 'Allow-IGMP' 87 | option src 'wan' 88 | option proto 'igmp' 89 | option family 'ipv4' 90 | option target 'ACCEPT' 91 | 92 | # ============================================================================= 93 | # DNS HIJACK PREVENTION (Recommended - Mullvad Best Practice) 94 | # ============================================================================= 95 | # Blocks devices from bypassing AdGuard with hardcoded DNS (e.g., 8.8.8.8) 96 | # Exception for AdGuard IP allows it to reach upstream DNS via VPN 97 | # Change 192.168.1.5 to your AdGuard/DNS server IP 98 | 99 | config rule 100 | option name 'Block-External-DNS-TCP' 101 | option src 'lan' 102 | option dest 'vpn' 103 | option dest_port '53' 104 | option proto 'tcp' 105 | option target 'REJECT' 106 | option family 'ipv4' 107 | option src_ip '!192.168.1.5' 108 | 109 | config rule 110 | option name 'Block-External-DNS-UDP' 111 | option src 'lan' 112 | option dest 'vpn' 113 | option dest_port '53' 114 | option proto 'udp' 115 | option target 'REJECT' 116 | option family 'ipv4' 117 | option src_ip '!192.168.1.5' 118 | -------------------------------------------------------------------------------- /docker/config/AdGuardHome.yaml.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AdGuard Home Configuration 3 | # ============================================================================= 4 | # Optional: Place in config/ to pre-configure AdGuard Home 5 | # If not provided, AdGuard will run setup wizard on first access 6 | # 7 | # Access web UI at: http://:3000 8 | # ============================================================================= 9 | 10 | http: 11 | pprof: 12 | port: 6060 13 | enabled: false 14 | address: 0.0.0.0:3000 15 | session_ttl: 720h 16 | 17 | users: 18 | # Default admin user - CHANGE PASSWORD on first login! 19 | - name: admin 20 | password: $2a$10$Placeholder.ChangeThisOnFirstLogin 21 | 22 | dns: 23 | bind_hosts: 24 | - 0.0.0.0 25 | port: 53 26 | 27 | # Upstream DNS servers 28 | # Using DNS-over-HTTPS for encrypted DNS queries 29 | upstream_dns: 30 | # Mullvad's ad-blocking DNS (if using Mullvad VPN) 31 | - https://adblock.dns.mullvad.net/dns-query 32 | # Alternatives: 33 | # - https://dns.quad9.net/dns-query 34 | # - https://cloudflare-dns.com/dns-query 35 | 36 | # Bootstrap DNS (for resolving upstream hostnames) 37 | # These are only used to resolve the DoH server hostnames 38 | bootstrap_dns: 39 | - 9.9.9.9 40 | - 1.1.1.1 41 | 42 | # Enable DNSSEC validation 43 | enable_dnssec: true 44 | 45 | # Cache settings 46 | cache_size: 4194304 47 | cache_ttl_min: 300 48 | cache_ttl_max: 86400 49 | cache_optimistic: true 50 | 51 | # Query all upstreams in parallel (faster) 52 | all_servers: false 53 | fastest_addr: false 54 | 55 | # Block IPv6 AAAA queries (prevents IPv6 DNS leaks) 56 | aaaa_disabled: true 57 | 58 | # Rate limiting 59 | ratelimit: 0 60 | ratelimit_whitelist: [] 61 | 62 | # Refuse ANY queries (security) 63 | refuse_any: true 64 | 65 | filtering: 66 | filtering_enabled: true 67 | parental_enabled: false 68 | safesearch_enabled: false 69 | safebrowsing_enabled: false 70 | 71 | # Block mode: null_ip returns 0.0.0.0 for blocked domains 72 | blocking_mode: null_ip 73 | blocked_response_ttl: 10 74 | 75 | # Protection toggle 76 | protection_enabled: true 77 | 78 | # Filter lists (ad/tracker blocking) 79 | filters: 80 | - enabled: true 81 | url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt 82 | name: AdGuard DNS filter 83 | id: 1 84 | 85 | - enabled: true 86 | url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts 87 | name: Steven Black's Unified Hosts 88 | id: 2 89 | 90 | - enabled: true 91 | url: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&showintro=1&mimetype=plaintext 92 | name: Peter Lowe's List 93 | id: 3 94 | 95 | # Whitelist (domains that should never be blocked) 96 | whitelist_filters: [] 97 | 98 | # User-defined rules 99 | user_rules: [] 100 | 101 | # Query log settings 102 | querylog: 103 | enabled: true 104 | file_enabled: true 105 | interval: 24h 106 | size_memory: 1000 107 | ignored: [] 108 | 109 | # Statistics 110 | statistics: 111 | enabled: true 112 | interval: 24h 113 | ignored: [] 114 | 115 | # DHCP (disabled - using router's DHCP) 116 | dhcp: 117 | enabled: false 118 | 119 | # Client settings 120 | clients: 121 | runtime_sources: 122 | whois: true 123 | arp: true 124 | rdns: true 125 | dhcp: true 126 | hosts: true 127 | 128 | log: 129 | file: "" 130 | max_backups: 0 131 | max_size: 100 132 | max_age: 3 133 | compress: false 134 | local_time: false 135 | verbose: false 136 | 137 | schema_version: 28 138 | -------------------------------------------------------------------------------- /openwrt/amneziawg/mullvad-awg0.conf.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Mullvad AmneziaWG Configuration Example 3 | # ============================================================================= 4 | # This stack was developed and tested with Mullvad VPN 5 | # /etc/amneziawg/awg0.conf 6 | # 7 | # Get your config: https://mullvad.net/en/account/wireguard-config 8 | # Select "Linux" platform and download config 9 | # 10 | # REQUIRED: Replace all YOUR_* values with your actual credentials 11 | # REQUIRED: chmod 600 /etc/amneziawg/awg0.conf 12 | 13 | [Interface] 14 | # Your Mullvad private key (from downloaded config) 15 | PrivateKey = YOUR_MULLVAD_PRIVATE_KEY 16 | 17 | # ============================================================================= 18 | # AmneziaWG Obfuscation Parameters 19 | # ============================================================================= 20 | # These parameters make traffic appear as random noise to DPI systems 21 | # Mullvad doesn't provide AWG configs directly - use these recommended values 22 | # or generate your own for additional obfuscation 23 | 24 | # Junk packet count (1-128) - packets added during handshake 25 | Jc = 4 26 | 27 | # Junk packet size range (bytes, 0-1280) 28 | Jmin = 40 29 | Jmax = 70 30 | 31 | # Init packet magic values (obfuscate handshake pattern) 32 | S1 = 0 33 | S2 = 0 34 | 35 | # Header obfuscation values 36 | H1 = 1 37 | H2 = 2 38 | H3 = 3 39 | H4 = 4 40 | 41 | [Peer] 42 | # Mullvad server public key (from your chosen server) 43 | PublicKey = YOUR_MULLVAD_SERVER_PUBLIC_KEY 44 | 45 | # Route all traffic through VPN 46 | AllowedIPs = 0.0.0.0/0, ::/0 47 | 48 | # ============================================================================= 49 | # Mullvad Server Endpoints 50 | # ============================================================================= 51 | # Choose a server near you from: https://mullvad.net/en/servers 52 | # Use IP address (not hostname) to avoid DNS dependency at startup 53 | # 54 | # Example servers (verify current IPs at mullvad.net): 55 | # au-mel-wg-001: Melbourne, Australia 56 | # au-syd-wg-001: Sydney, Australia 57 | # us-nyc-wg-001: New York, USA 58 | # us-lax-wg-001: Los Angeles, USA 59 | # gb-lon-wg-001: London, UK 60 | # de-fra-wg-001: Frankfurt, Germany 61 | # nl-ams-wg-001: Amsterdam, Netherlands 62 | # sg-sin-wg-001: Singapore 63 | # jp-tyo-wg-001: Tokyo, Japan 64 | # 65 | # Port options: 66 | # 51820 - Default WireGuard port 67 | # 53 - May bypass some firewalls (DNS port) 68 | # 443 - May bypass some firewalls (HTTPS port) 69 | 70 | Endpoint = MULLVAD_SERVER_IP:51820 71 | 72 | # Keepalive for NAT traversal 73 | PersistentKeepalive = 25 74 | 75 | # ============================================================================= 76 | # Mullvad DNS Servers (For Router Configuration) 77 | # ============================================================================= 78 | # Use these IPs in your OpenWrt network.lan.dns setting 79 | # See: https://mullvad.net/en/help/dns-over-https-and-dns-over-tls 80 | # 81 | # Standard DNS (no blocking): 82 | # 10.64.0.1 83 | # 84 | # Content-filtering DNS options: 85 | # 100.64.0.1 - Ad-blocking 86 | # 100.64.0.2 - Ad + Tracker blocking 87 | # 100.64.0.3 - Ad + Tracker + Malware blocking 88 | # 100.64.0.4 - Ad + Tracker + Malware + Adult blocking 89 | # 90 | # DoH endpoints (for AdGuard Home upstream): 91 | # https://dns.mullvad.net/dns-query (standard) 92 | # https://adblock.dns.mullvad.net/dns-query (ad-blocking) 93 | # https://base.dns.mullvad.net/dns-query (tracker+malware blocking) 94 | # https://all.dns.mullvad.net/dns-query (all blocking) 95 | # 96 | # OpenWrt configuration example: 97 | # uci set network.lan.dns='100.64.0.4' 98 | # uci commit network 99 | -------------------------------------------------------------------------------- /openwrt/firewall-vpn-zone.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # OpenWrt Firewall - VPN Zone Configuration (Kill Switch) 3 | # ============================================================================= 4 | # 5 | # This configuration creates a VPN zone for the AmneziaWG tunnel and implements 6 | # a kill switch that blocks all internet traffic when the VPN is down. 7 | # 8 | # How it works: 9 | # - LAN zone can only forward to VPN zone (not to WAN) 10 | # - If VPN tunnel is down, traffic has nowhere to go 11 | # - LAN management (SSH, LuCI) always works (stays within LAN zone) 12 | # 13 | # Installation: 14 | # Add these lines to /etc/config/firewall 15 | # Or use UCI commands below 16 | # 17 | # ============================================================================= 18 | 19 | # --- Add to /etc/config/firewall --- 20 | 21 | config zone 'vpn' 22 | option name 'vpn' 23 | option device 'awg0' 24 | option input 'REJECT' 25 | option output 'ACCEPT' 26 | option forward 'REJECT' 27 | option masq '1' 28 | option mtu_fix '1' 29 | 30 | config forwarding 'lan_vpn' 31 | option src 'lan' 32 | option dest 'vpn' 33 | 34 | # Block all IPv6 forwarding (prevents IPv6 leaks) 35 | config rule 'block_ipv6_forward' 36 | option name 'Block-IPv6-Forward' 37 | option family 'ipv6' 38 | option src '*' 39 | option dest '*' 40 | option target 'DROP' 41 | 42 | # ============================================================================= 43 | # UCI Commands (alternative to editing config file) 44 | # ============================================================================= 45 | # 46 | # # Create VPN zone 47 | # uci set firewall.vpn=zone 48 | # uci set firewall.vpn.name='vpn' 49 | # uci set firewall.vpn.device='awg0' 50 | # uci set firewall.vpn.input='REJECT' 51 | # uci set firewall.vpn.output='ACCEPT' 52 | # uci set firewall.vpn.forward='REJECT' 53 | # uci set firewall.vpn.masq='1' 54 | # uci set firewall.vpn.mtu_fix='1' 55 | # 56 | # # Create LAN → VPN forwarding (this is the kill switch) 57 | # uci set firewall.lan_vpn=forwarding 58 | # uci set firewall.lan_vpn.src='lan' 59 | # uci set firewall.lan_vpn.dest='vpn' 60 | # 61 | # # Block IPv6 forwarding 62 | # uci set firewall.block_ipv6_forward=rule 63 | # uci set firewall.block_ipv6_forward.name='Block-IPv6-Forward' 64 | # uci set firewall.block_ipv6_forward.family='ipv6' 65 | # uci set firewall.block_ipv6_forward.src='*' 66 | # uci set firewall.block_ipv6_forward.dest='*' 67 | # uci set firewall.block_ipv6_forward.target='DROP' 68 | # 69 | # # Commit and restart 70 | # uci commit firewall 71 | # /etc/init.d/firewall restart 72 | # 73 | # ============================================================================= 74 | # Verification 75 | # ============================================================================= 76 | # 77 | # # Check zones 78 | # uci show firewall | grep -E "zone|forwarding" 79 | # 80 | # # Check nftables rules 81 | # nft list ruleset | grep -A5 "chain forward" 82 | # 83 | # # Test kill switch (WARNING: will drop internet!) 84 | # ip link set awg0 down 85 | # ping -c 3 8.8.8.8 # Should fail 86 | # ip link set awg0 up 87 | # ping -c 3 8.8.8.8 # Should work 88 | # 89 | # ============================================================================= 90 | # Important Notes 91 | # ============================================================================= 92 | # 93 | # 1. Do NOT create lan → wan forwarding rule - this breaks the kill switch 94 | # 95 | # 2. LAN management access (SSH, LuCI) works independently of VPN state 96 | # because it stays within the LAN zone (input=ACCEPT) 97 | # 98 | # 3. The kill switch only blocks FORWARDED traffic (internet-bound from clients) 99 | # INPUT/OUTPUT on LAN zone is always allowed 100 | # 101 | # ============================================================================= 102 | -------------------------------------------------------------------------------- /docker/scripts/healthcheck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Docker Health Check 4 | # ============================================================================= 5 | # 5-layer health check for VPN tunnel 6 | # Returns 0 (healthy) or 1 (unhealthy) 7 | # 8 | # Docker will restart container after consecutive failures 9 | # ============================================================================= 10 | 11 | set -e 12 | 13 | MAX_HANDSHAKE_AGE=180 # 3 minutes in seconds 14 | 15 | # ----------------------------------------------------------------------------- 16 | # CHECK 1: Interface exists 17 | # ----------------------------------------------------------------------------- 18 | if ! ip link show awg0 &>/dev/null; then 19 | echo "UNHEALTHY: awg0 interface missing" 20 | exit 1 21 | fi 22 | 23 | # ----------------------------------------------------------------------------- 24 | # CHECK 2: Interface is UP 25 | # ----------------------------------------------------------------------------- 26 | if ! ip link show awg0 | grep -q "state UP\|state UNKNOWN"; then 27 | echo "UNHEALTHY: awg0 interface DOWN" 28 | exit 1 29 | fi 30 | 31 | # ----------------------------------------------------------------------------- 32 | # CHECK 3: Recent handshake 33 | # ----------------------------------------------------------------------------- 34 | HANDSHAKE_LINE=$(amneziawg show awg0 2>/dev/null | grep "latest handshake" || echo "") 35 | 36 | if [[ -z "$HANDSHAKE_LINE" ]]; then 37 | echo "UNHEALTHY: No VPN handshake detected" 38 | exit 1 39 | fi 40 | 41 | # Parse handshake age 42 | # Format: " latest handshake: X seconds/minutes/hours ago" 43 | HANDSHAKE_VALUE=$(echo "$HANDSHAKE_LINE" | awk '{print $3}') 44 | HANDSHAKE_UNIT=$(echo "$HANDSHAKE_LINE" | awk '{print $4}') 45 | 46 | case "$HANDSHAKE_UNIT" in 47 | second*) 48 | HANDSHAKE_SECS=$HANDSHAKE_VALUE 49 | ;; 50 | minute*) 51 | HANDSHAKE_SECS=$((HANDSHAKE_VALUE * 60)) 52 | ;; 53 | hour*) 54 | HANDSHAKE_SECS=$((HANDSHAKE_VALUE * 3600)) 55 | ;; 56 | *) 57 | HANDSHAKE_SECS=9999 58 | ;; 59 | esac 60 | 61 | if [[ $HANDSHAKE_SECS -gt $MAX_HANDSHAKE_AGE ]]; then 62 | echo "UNHEALTHY: Handshake too old (${HANDSHAKE_SECS}s > ${MAX_HANDSHAKE_AGE}s)" 63 | exit 1 64 | fi 65 | 66 | # ----------------------------------------------------------------------------- 67 | # CHECK 4: Connectivity through tunnel 68 | # ----------------------------------------------------------------------------- 69 | # Try multiple targets in case one is temporarily unreachable 70 | PROBE_TARGETS="${PROBE_TARGETS:-1.1.1.1 8.8.8.8 9.9.9.9}" 71 | PING_SUCCESS=false 72 | 73 | for target in $PROBE_TARGETS; do 74 | if ping -c 1 -W 5 -I awg0 "$target" &>/dev/null; then 75 | PING_SUCCESS=true 76 | break 77 | fi 78 | done 79 | 80 | if [[ "$PING_SUCCESS" != "true" ]]; then 81 | echo "UNHEALTHY: Cannot ping through VPN tunnel" 82 | exit 1 83 | fi 84 | 85 | # ----------------------------------------------------------------------------- 86 | # CHECK 5: Exit IP verification (optional) 87 | # ----------------------------------------------------------------------------- 88 | # Only run if EXPECTED_EXIT_IP is set 89 | if [[ -n "$EXPECTED_EXIT_IP" ]]; then 90 | CURRENT_IP=$(curl -s --max-time 10 --interface awg0 https://ipinfo.io/ip 2>/dev/null || echo "") 91 | 92 | if [[ -z "$CURRENT_IP" ]]; then 93 | echo "UNHEALTHY: Could not retrieve exit IP" 94 | exit 1 95 | fi 96 | 97 | if [[ "$CURRENT_IP" != "$EXPECTED_EXIT_IP" ]]; then 98 | echo "UNHEALTHY: Exit IP mismatch (got: $CURRENT_IP, expected: $EXPECTED_EXIT_IP)" 99 | exit 1 100 | fi 101 | fi 102 | 103 | # ----------------------------------------------------------------------------- 104 | # All checks passed 105 | # ----------------------------------------------------------------------------- 106 | echo "HEALTHY: VPN tunnel operational (handshake: ${HANDSHAKE_SECS}s ago)" 107 | exit 0 108 | -------------------------------------------------------------------------------- /openwrt/banip/banip.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # BanIP Configuration for OpenWrt 3 | # ============================================================================= 4 | # 5 | # BanIP blocks malicious IPs using threat intelligence feeds. 6 | # This is an OPTIONAL security addon - the core VPN functionality works without it. 7 | # 8 | # Installation: 9 | # opkg update 10 | # opkg install banip 11 | # 12 | # Copy to: /etc/config/banip 13 | # Then: /etc/init.d/banip enable && /etc/init.d/banip start 14 | # 15 | # Web UI: Services → BanIP (if luci-app-banip installed) 16 | # 17 | # ============================================================================= 18 | 19 | config banip 'global' 20 | # Enable/disable BanIP 21 | option ban_enabled '1' 22 | 23 | # Automatically start on boot 24 | option ban_autostart '1' 25 | 26 | # Debug level (0=off, 1=basic, 2=verbose) 27 | option ban_debug '0' 28 | 29 | # ========================================================================== 30 | # NETWORK INTERFACES 31 | # ========================================================================== 32 | # Which interfaces to protect 33 | # - WAN: Block inbound attacks from internet 34 | # - LAN: Usually not needed (trusted network) 35 | 36 | list ban_iface 'wan' 37 | 38 | # ========================================================================== 39 | # BLOCKLIST SOURCES 40 | # ========================================================================== 41 | # Enable threat intelligence feeds 42 | # Each feed blocks different threat categories 43 | # 44 | # Recommended minimum set for privacy router: 45 | 46 | # Spamhaus DROP - Known bad networks (conservative, low false positives) 47 | list ban_feed 'drop' 48 | 49 | # Spamhaus EDROP - Extended DROP list 50 | list ban_feed 'edrop' 51 | 52 | # Abuse.ch Feodo Tracker - Banking trojans C2 servers 53 | list ban_feed 'feodo' 54 | 55 | # Abuse.ch SSL Blacklist - Malicious SSL certificates 56 | list ban_feed 'sslbl' 57 | 58 | # Emerging Threats - Compromised IPs 59 | list ban_feed 'etcompromised' 60 | 61 | # DShield - Top attacking IPs 62 | list ban_feed 'dshield' 63 | 64 | # ========================================================================== 65 | # OPTIONAL ADDITIONAL FEEDS 66 | # ========================================================================== 67 | # Uncomment to enable more aggressive blocking 68 | # WARNING: May increase false positives 69 | 70 | # Tor exit nodes (blocks Tor - only if you don't use Tor) 71 | # list ban_feed 'tor' 72 | 73 | # Firehol Level 1 - Conservative threat list 74 | # list ban_feed 'firehol1' 75 | 76 | # Firehol Level 2 - More aggressive 77 | # list ban_feed 'firehol2' 78 | 79 | # Blocklist.de - Fail2ban reported IPs 80 | # list ban_feed 'blocklist' 81 | 82 | # ========================================================================== 83 | # WHITELIST 84 | # ========================================================================== 85 | # IPs/networks that should never be blocked 86 | # Add your VPN provider's server IPs here to prevent lockouts 87 | 88 | # Example: Whitelist your VPN endpoint 89 | # list ban_allowlist 'MULLVAD_SERVER_IP/32' 90 | 91 | # Whitelist your local networks 92 | list ban_allowlist '192.168.0.0/16' 93 | list ban_allowlist '10.0.0.0/8' 94 | list ban_allowlist '172.16.0.0/12' 95 | 96 | # ========================================================================== 97 | # PERFORMANCE TUNING 98 | # ========================================================================== 99 | 100 | # Maximum entries per set (0=unlimited) 101 | option ban_maxelem '262144' 102 | 103 | # Feed update interval in seconds (default: 86400 = 24h) 104 | option ban_fetchinterval '86400' 105 | 106 | # Use compressed downloads 107 | option ban_compressed '1' 108 | 109 | # Report mode only (log but don't block) 110 | option ban_reportonly '0' 111 | 112 | # ========================================================================== 113 | # LOGGING 114 | # ========================================================================== 115 | 116 | # Log blocked packets to syslog 117 | option ban_logterm '1' 118 | 119 | # Log limit (packets per minute) 120 | option ban_loglimit '100' 121 | 122 | # Mail notifications (requires msmtp) 123 | option ban_mail '0' 124 | # option ban_mailreceiver 'admin@example.com' 125 | -------------------------------------------------------------------------------- /openwrt/rc.local.example: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # VPN Bypass Policy Rules - Add to /etc/rc.local 4 | # ============================================================================= 5 | # 6 | # PURPOSE: 7 | # These rules direct specific devices to use routing table 100 (bypass table) 8 | # instead of the main table. Table 100 has only a WAN default route, so 9 | # devices using it exit directly to the internet without going through VPN. 10 | # 11 | # HOW IT WORKS: 12 | # 1. 99-awg hotplug creates table 100 with: default via WAN_GW dev eth0 13 | # 2. Main table gets VPN split routes: 0.0.0.0/1 + 128.0.0.0/1 via awg0 14 | # 3. These policy rules intercept packets BEFORE main table lookup 15 | # 4. Matching packets use table 100 → WAN, others use main → VPN 16 | # 17 | # INSTALLATION: 18 | # 1. Edit /etc/rc.local (before the 'exit 0' line) 19 | # 2. Add your bypass rules (examples below) 20 | # 3. Run: chmod +x /etc/rc.local && /etc/rc.local 21 | # 4. Also add matching firewall rules (see firewall-bypass-rules.example) 22 | # 23 | # VERIFICATION: 24 | # ip rule show | grep 100 # See active rules 25 | # ip route show table 100 # See bypass table (should have WAN default) 26 | # traceroute -n 1.1.1.1 # Test from bypass device (should NOT see VPN) 27 | # 28 | # ============================================================================= 29 | 30 | # ============================================================================= 31 | # BYPASS RULES - Customize for your network 32 | # ============================================================================= 33 | # Syntax: ip rule add from lookup 100 priority 100 34 | # 35 | # Priority 100 means these rules are checked before the default (32766). 36 | # Lower number = higher priority. 37 | # 38 | # IMPORTANT: Every device here also needs a firewall rule allowing lan→wan! 39 | # See: /etc/config/firewall or firewall-bypass-rules.example 40 | # ============================================================================= 41 | 42 | # ----------------------------------------------------------------------------- 43 | # Infrastructure Devices (RECOMMENDED for any setup) 44 | # ----------------------------------------------------------------------------- 45 | # These typically need direct internet access for updates, monitoring, etc. 46 | 47 | # Hypervisor/Host - Replace with your hypervisor IP 48 | # ip rule add from 192.168.1.3 lookup 100 priority 100 49 | 50 | # DNS Server (if using AdGuard, Pi-hole, etc.) 51 | # Needs WAN access for upstream DNS queries 52 | # ip rule add from 192.168.1.5 lookup 100 priority 100 53 | 54 | # Proxmox/ESXi nodes - Replace with your node IPs 55 | # ip rule add from 192.168.1.10 lookup 100 priority 100 56 | # ip rule add from 192.168.1.11 lookup 100 priority 100 57 | 58 | # ----------------------------------------------------------------------------- 59 | # Workstations/Servers (Add as needed) 60 | # ----------------------------------------------------------------------------- 61 | # Devices that need direct WAN for work, development, or specific services 62 | 63 | # Primary workstation 64 | # ip rule add from 192.168.1.20 lookup 100 priority 100 65 | 66 | # Server LXC/VM 67 | # ip rule add from 192.168.1.100 lookup 100 priority 100 68 | 69 | # ----------------------------------------------------------------------------- 70 | # User Devices (Optional - most should go through VPN) 71 | # ----------------------------------------------------------------------------- 72 | # Only add here if device specifically needs to bypass VPN 73 | # Example: streaming device that needs local geo content 74 | 75 | # MacBook/Laptop with geo-specific needs 76 | # ip rule add from 192.168.1.4 lookup 100 priority 100 77 | 78 | # ----------------------------------------------------------------------------- 79 | # Future/Reserved IPs 80 | # ----------------------------------------------------------------------------- 81 | # Add rules for devices you plan to deploy 82 | 83 | # Future gateway/firewall VM 84 | # ip rule add from 192.168.1.200 lookup 100 priority 100 85 | 86 | # ============================================================================= 87 | # VERIFICATION COMMANDS 88 | # ============================================================================= 89 | # Run after adding rules to verify they're active: 90 | # 91 | # ip rule show | grep "lookup 100" 92 | # # Should list all your bypass rules 93 | # 94 | # ip route show table 100 95 | # # Should show: default via dev eth0 96 | # 97 | # From a bypass device, test that traffic goes direct: 98 | # curl -s https://am.i.mullvad.net/connected 99 | # # Should return "You are not connected to Mullvad" 100 | # 101 | # From a VPN device, test that traffic goes through VPN: 102 | # curl -s https://am.i.mullvad.net/connected 103 | # # Should return connection details 104 | # ============================================================================= 105 | 106 | exit 0 107 | -------------------------------------------------------------------------------- /openwrt/amneziawg/awg0.conf.example: -------------------------------------------------------------------------------- 1 | # AmneziaWG Configuration Template 2 | # /etc/amneziawg/awg0.conf 3 | # 4 | # Replace ALL placeholder values before use 5 | # chmod 600 /etc/amneziawg/awg0.conf 6 | 7 | [Interface] 8 | # Your private key (generate with: amneziawg genkey) 9 | PrivateKey = YOUR_PRIVATE_KEY_BASE64 10 | 11 | # ============================================================================= 12 | # AmneziaWG Obfuscation Parameters 13 | # ============================================================================= 14 | # These parameters add client-side obfuscation to disguise WireGuard traffic. 15 | # The obfuscation happens LOCALLY - servers don't need to support AmneziaWG. 16 | # 17 | # IMPORTANT: Most VPN providers (Mullvad, IVPN, Proton, etc.) do NOT provide 18 | # AmneziaWG parameters because they use standard WireGuard servers. 19 | # 20 | # Official AWG parameters (from amnezia-vpn/amneziawg-tools): 21 | # Jc, Jmin, Jmax - Junk packets before handshake 22 | # S1-S4 - Packet padding sizes 23 | # H1-H4 - Magic header values 24 | # I1-I5 - Init packet specs (protocol mimicry) 25 | # 26 | # NOT official (wgtunnel Android app only - will cause errors): 27 | # j1, itime - DO NOT USE with official AWG tools 28 | 29 | # Jc: Junk packets during handshake (DPI obfuscation) 30 | Jc = 4 31 | 32 | # Jmin/Jmax: Size range of junk packets in bytes 33 | Jmin = 40 34 | Jmax = 70 35 | 36 | # S1/S2: Init packet padding (0 for standard WireGuard compatibility) 37 | S1 = 0 38 | S2 = 0 39 | 40 | # H1-H4: Header obfuscation (sequential for standard WireGuard compatibility) 41 | H1 = 1 42 | H2 = 2 43 | H3 = 3 44 | H4 = 4 45 | 46 | # ============================================================================= 47 | # QUIC Protocol Mimicry (Optional - for DPI-heavy networks) 48 | # ============================================================================= 49 | # Uncomment I1 below to make traffic appear as QUIC/HTTP3. 50 | # This is a QUIC Long Header Initial packet from official AmneziaVPN docs. 51 | # Source: https://amneziavpn.org/documentation/instructions/new-amneziawg-selfhosted 52 | # 53 | # I1 = 54 | 55 | [Peer] 56 | # VPN server's public key (from your provider) 57 | PublicKey = SERVER_PUBLIC_KEY_BASE64 58 | 59 | # Optional: Pre-shared key for post-quantum security 60 | # PresharedKey = PRESHARED_KEY_BASE64 61 | 62 | # Route all IPv4 and IPv6 through tunnel 63 | # For split tunneling, specify only certain ranges 64 | AllowedIPs = 0.0.0.0/0, ::/0 65 | 66 | # VPN server address and port 67 | # Use IP address, not hostname (avoids DNS dependency at startup) 68 | Endpoint = VPN_SERVER_IP:51820 69 | 70 | # Send keepalive every 25 seconds 71 | # Required for NAT traversal, prevents connection timeout 72 | PersistentKeepalive = 25 73 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Privacy Router - Docker Deployment 3 | # ============================================================================= 4 | # Routes ALL LAN traffic through AmneziaWG VPN with AdGuard DNS filtering 5 | # 6 | # Quick Start: 7 | # 1. cp .env.example .env && nano .env 8 | # 2. cp config/awg0.conf.example config/awg0.conf && nano config/awg0.conf 9 | # 3. docker compose up -d 10 | # 4. docker exec privacy-router /opt/scripts/test-suite.sh 11 | # 12 | # LAN Gateway Setup: 13 | # Set your devices' gateway to ${CONTAINER_LAN_IP} (default: 192.168.1.5) 14 | # Set your devices' DNS to ${CONTAINER_LAN_IP} 15 | # ============================================================================= 16 | 17 | services: 18 | # --------------------------------------------------------------------------- 19 | # AmneziaWG VPN Client + Gateway 20 | # --------------------------------------------------------------------------- 21 | privacy-router: 22 | build: 23 | context: . 24 | dockerfile: Dockerfile 25 | image: privacy-router:latest 26 | container_name: privacy-router 27 | hostname: privacy-router 28 | restart: unless-stopped 29 | 30 | # Required capabilities for VPN tunnel and routing 31 | cap_add: 32 | - NET_ADMIN 33 | - SYS_MODULE 34 | 35 | # TUN device for VPN tunnel 36 | devices: 37 | - /dev/net/tun:/dev/net/tun 38 | 39 | # Kernel parameters for routing and security 40 | sysctls: 41 | - net.ipv4.conf.all.src_valid_mark=1 42 | - net.ipv4.ip_forward=1 43 | - net.ipv6.conf.all.disable_ipv6=1 44 | - net.ipv6.conf.default.disable_ipv6=1 45 | 46 | environment: 47 | # VPN Configuration (from your provider) 48 | - VPN_IP=${VPN_IP:?VPN_IP required - your VPN internal IP} 49 | - VPN_ENDPOINT_IP=${VPN_ENDPOINT_IP:?VPN_ENDPOINT_IP required} 50 | - VPN_ENDPOINT_PORT=${VPN_ENDPOINT_PORT:-51820} 51 | 52 | # Network Configuration 53 | - LAN_SUBNET=${LAN_SUBNET:-192.168.1.0/24} 54 | - LAN_GATEWAY=${LAN_GATEWAY:-192.168.1.1} 55 | 56 | # Watchdog Configuration 57 | - WATCHDOG_ENABLED=${WATCHDOG_ENABLED:-true} 58 | - WATCHDOG_INTERVAL=${WATCHDOG_INTERVAL:-30} 59 | - WATCHDOG_FAIL_THRESHOLD=${WATCHDOG_FAIL_THRESHOLD:-3} 60 | - PROBE_TARGETS=${PROBE_TARGETS:-1.1.1.1 8.8.8.8 9.9.9.9} 61 | 62 | # General 63 | - TZ=${TZ:-UTC} 64 | 65 | volumes: 66 | # VPN configuration (read-only for security) 67 | - ./config/awg0.conf:/etc/amneziawg/awg0.conf:ro 68 | - ./config/postup.sh:/etc/amneziawg/postup.sh:ro 69 | - ./config/predown.sh:/etc/amneziawg/predown.sh:ro 70 | 71 | # Logs (persistent) 72 | - privacy_router_logs:/var/log 73 | 74 | networks: 75 | # Internal network for container communication 76 | vpn_internal: 77 | ipv4_address: 172.20.0.2 78 | # LAN-facing network (macvlan) for gateway functionality 79 | lan: 80 | ipv4_address: ${CONTAINER_LAN_IP:-192.168.1.5} 81 | 82 | healthcheck: 83 | test: ["CMD", "/opt/scripts/healthcheck.sh"] 84 | interval: 30s 85 | timeout: 10s 86 | retries: 3 87 | start_period: 60s 88 | 89 | # --------------------------------------------------------------------------- 90 | # AdGuard Home - DNS Filtering 91 | # --------------------------------------------------------------------------- 92 | adguard: 93 | image: adguard/adguardhome:latest 94 | container_name: adguard 95 | # hostname removed - can't set when using network_mode: service 96 | restart: unless-stopped 97 | 98 | # Share network namespace with VPN container 99 | # All AdGuard traffic routes through VPN automatically 100 | network_mode: "service:privacy-router" 101 | 102 | depends_on: 103 | privacy-router: 104 | condition: service_healthy 105 | 106 | volumes: 107 | - adguard_work:/opt/adguardhome/work 108 | - adguard_conf:/opt/adguardhome/conf 109 | # Optional: custom initial config 110 | # - ./config/AdGuardHome.yaml:/opt/adguardhome/conf/AdGuardHome.yaml:ro 111 | 112 | # ============================================================================= 113 | # Networks 114 | # ============================================================================= 115 | networks: 116 | # Internal bridge network for container-to-container communication 117 | vpn_internal: 118 | driver: bridge 119 | ipam: 120 | config: 121 | - subnet: 172.20.0.0/24 122 | gateway: 172.20.0.1 123 | 124 | # macvlan network - gives container a real IP on your LAN 125 | # Container appears as a separate device to LAN clients 126 | lan: 127 | driver: macvlan 128 | driver_opts: 129 | parent: ${LAN_INTERFACE:-eth0} 130 | ipam: 131 | config: 132 | - subnet: ${LAN_SUBNET:-192.168.1.0/24} 133 | gateway: ${LAN_GATEWAY:-192.168.1.1} 134 | 135 | # ============================================================================= 136 | # Volumes 137 | # ============================================================================= 138 | volumes: 139 | privacy_router_logs: 140 | name: privacy_router_logs 141 | adguard_work: 142 | name: adguard_work 143 | adguard_conf: 144 | name: adguard_conf 145 | -------------------------------------------------------------------------------- /docker/config/awg0.conf.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AmneziaWG VPN Configuration 3 | # ============================================================================= 4 | # Copy to awg0.conf and fill in your VPN provider credentials 5 | # 6 | # For Mullvad: 7 | # 1. Log in to https://mullvad.net/en/account/wireguard-config 8 | # 2. Generate a WireGuard key pair 9 | # 3. Copy PrivateKey and PublicKey values below 10 | # 11 | # For IVPN: 12 | # 1. Log in to https://www.ivpn.net/clientarea/vpn-setup 13 | # 2. Generate WireGuard keys 14 | # 3. Copy configuration values 15 | # ============================================================================= 16 | 17 | [Interface] 18 | # Your private key (KEEP SECRET - never share!) 19 | # Generate new: amneziawg genkey 20 | PrivateKey = YOUR_PRIVATE_KEY_HERE 21 | 22 | # ============================================================================= 23 | # AmneziaWG Obfuscation Parameters 24 | # ============================================================================= 25 | # These parameters add client-side obfuscation to disguise WireGuard traffic. 26 | # The obfuscation happens LOCALLY - servers don't need to support AmneziaWG. 27 | # 28 | # IMPORTANT: Most VPN providers (Mullvad, IVPN, Proton, etc.) do NOT provide 29 | # AmneziaWG parameters because they use standard WireGuard servers. 30 | # 31 | # Official AWG parameters (from amnezia-vpn/amneziawg-tools): 32 | # Jc, Jmin, Jmax - Junk packets before handshake 33 | # S1-S4 - Packet padding sizes 34 | # H1-H4 - Magic header values 35 | # I1-I5 - Init packet specs (protocol mimicry) 36 | # 37 | # NOT official (wgtunnel Android app only - will cause errors): 38 | # j1, itime - DO NOT USE with official AWG tools 39 | 40 | # Junk packet count (sent during handshake for DPI obfuscation) 41 | # Range: 1-128 42 | Jc = 4 43 | 44 | # Junk packet size range (bytes) 45 | # Range: 0-1280 46 | Jmin = 40 47 | Jmax = 70 48 | 49 | # Init/Response packet padding (0 for standard WireGuard compatibility) 50 | # Range: 0-2147483647 51 | S1 = 0 52 | S2 = 0 53 | 54 | # Header obfuscation (sequential values for standard WireGuard compatibility) 55 | # Range: 0-2147483647 56 | H1 = 1 57 | H2 = 2 58 | H3 = 3 59 | H4 = 4 60 | 61 | # ============================================================================= 62 | # QUIC Protocol Mimicry (Optional - for DPI-heavy networks) 63 | # ============================================================================= 64 | # Uncomment I1 below to make traffic appear as QUIC/HTTP3. 65 | # This is a QUIC Long Header Initial packet from official AmneziaVPN docs. 66 | # Source: https://amneziavpn.org/documentation/instructions/new-amneziawg-selfhosted 67 | # 68 | # I1 = 69 | 70 | [Peer] 71 | # VPN server's public key (from your provider) 72 | PublicKey = SERVER_PUBLIC_KEY_HERE 73 | 74 | # Optional: Pre-shared key for additional security 75 | # PresharedKey = PRESHARED_KEY_HERE 76 | 77 | # Route all traffic through VPN 78 | AllowedIPs = 0.0.0.0/0, ::/0 79 | 80 | # VPN server address and port 81 | # Use IP address, not hostname (hostname in .env for reference) 82 | Endpoint = 185.213.154.68:51820 83 | 84 | # Keep connection alive (important for NAT traversal) 85 | PersistentKeepalive = 25 86 | -------------------------------------------------------------------------------- /docker/config/postup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Kill Switch - PostUp Script 4 | # ============================================================================= 5 | # Applied after VPN tunnel comes up 6 | # Implements iptables rules that ONLY allow traffic through VPN 7 | # 8 | # SECURITY: Default policy is DROP - if VPN fails, all traffic blocked 9 | # ============================================================================= 10 | 11 | set -e 12 | 13 | # Load environment variables (passed from entrypoint) 14 | VPN_ENDPOINT_IP="${VPN_ENDPOINT_IP:?VPN_ENDPOINT_IP not set}" 15 | VPN_ENDPOINT_PORT="${VPN_ENDPOINT_PORT:-51820}" 16 | LAN_SUBNET="${LAN_SUBNET:-192.168.1.0/24}" 17 | 18 | log() { 19 | echo "[$(date '+%Y-%m-%d %H:%M:%S')] POSTUP: $1" 20 | } 21 | 22 | log "Applying kill switch rules..." 23 | 24 | # ============================================================================= 25 | # IPv4 Rules 26 | # ============================================================================= 27 | 28 | # Flush existing rules 29 | iptables -F INPUT 30 | iptables -F OUTPUT 31 | iptables -F FORWARD 32 | iptables -t nat -F POSTROUTING 33 | 34 | # Set default policies to DROP (kill switch foundation) 35 | iptables -P INPUT DROP 36 | iptables -P OUTPUT DROP 37 | iptables -P FORWARD DROP 38 | 39 | log "Default policies set to DROP" 40 | 41 | # ----------------------------------------------------------------------------- 42 | # INPUT Rules (traffic TO this container) 43 | # ----------------------------------------------------------------------------- 44 | 45 | # Allow loopback 46 | iptables -A INPUT -i lo -j ACCEPT 47 | 48 | # Allow established/related connections 49 | iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 50 | 51 | # Allow from LAN (for DNS, management) 52 | iptables -A INPUT -s "$LAN_SUBNET" -j ACCEPT 53 | 54 | # Allow from VPN tunnel 55 | iptables -A INPUT -i awg0 -j ACCEPT 56 | 57 | # Allow ICMP (ping) for diagnostics 58 | iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT 59 | 60 | log "INPUT rules applied" 61 | 62 | # ----------------------------------------------------------------------------- 63 | # OUTPUT Rules (traffic FROM this container) 64 | # ----------------------------------------------------------------------------- 65 | 66 | # Allow loopback 67 | iptables -A OUTPUT -o lo -j ACCEPT 68 | 69 | # Allow established/related connections 70 | iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 71 | 72 | # Allow to LAN 73 | iptables -A OUTPUT -d "$LAN_SUBNET" -j ACCEPT 74 | 75 | # CRITICAL: Only UDP to VPN endpoint allowed to escape container 76 | # This is the ONLY non-VPN traffic permitted 77 | iptables -A OUTPUT -p udp -d "$VPN_ENDPOINT_IP" --dport "$VPN_ENDPOINT_PORT" -j ACCEPT 78 | 79 | # Allow ALL traffic through VPN tunnel 80 | iptables -A OUTPUT -o awg0 -j ACCEPT 81 | 82 | # Allow ICMP for diagnostics 83 | iptables -A OUTPUT -p icmp -j ACCEPT 84 | 85 | log "OUTPUT rules applied" 86 | 87 | # ----------------------------------------------------------------------------- 88 | # FORWARD Rules (LAN gateway - routing traffic for other devices) 89 | # ----------------------------------------------------------------------------- 90 | 91 | # Enable IP forwarding (if not already set via Docker sysctls) 92 | if [[ "$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null)" != "1" ]]; then 93 | echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || log "IP forwarding already enabled via sysctls" 94 | fi 95 | 96 | # Allow LAN to VPN tunnel 97 | iptables -A FORWARD -i eth0 -o awg0 -s "$LAN_SUBNET" -j ACCEPT 98 | 99 | # Allow established/related back to LAN 100 | iptables -A FORWARD -i awg0 -o eth0 -d "$LAN_SUBNET" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 101 | 102 | log "FORWARD rules applied" 103 | 104 | # ----------------------------------------------------------------------------- 105 | # NAT (Masquerade for LAN gateway) 106 | # ----------------------------------------------------------------------------- 107 | 108 | # Masquerade all traffic going out the VPN tunnel 109 | iptables -t nat -A POSTROUTING -o awg0 -j MASQUERADE 110 | 111 | log "NAT masquerade applied" 112 | 113 | # ============================================================================= 114 | # IPv6 Rules (Block everything - prevent leaks) 115 | # ============================================================================= 116 | 117 | # Flush IPv6 rules 118 | ip6tables -F INPUT 2>/dev/null || true 119 | ip6tables -F OUTPUT 2>/dev/null || true 120 | ip6tables -F FORWARD 2>/dev/null || true 121 | 122 | # Block all IPv6 123 | ip6tables -P INPUT DROP 2>/dev/null || true 124 | ip6tables -P OUTPUT DROP 2>/dev/null || true 125 | ip6tables -P FORWARD DROP 2>/dev/null || true 126 | 127 | # Only allow loopback 128 | ip6tables -A INPUT -i lo -j ACCEPT 2>/dev/null || true 129 | ip6tables -A OUTPUT -o lo -j ACCEPT 2>/dev/null || true 130 | 131 | log "IPv6 blocked (leak prevention)" 132 | 133 | # ============================================================================= 134 | # Logging for debugging (rate-limited) 135 | # ============================================================================= 136 | 137 | # Log dropped OUTPUT packets (helps debug connectivity issues) 138 | iptables -A OUTPUT -j LOG --log-prefix "KILLSWITCH-OUT-DROP: " --log-level 4 -m limit --limit 5/min 139 | 140 | # Log dropped FORWARD packets 141 | iptables -A FORWARD -j LOG --log-prefix "KILLSWITCH-FWD-DROP: " --log-level 4 -m limit --limit 5/min 142 | 143 | log "Kill switch active - only VPN traffic allowed" 144 | log "VPN endpoint: $VPN_ENDPOINT_IP:$VPN_ENDPOINT_PORT" 145 | log "LAN subnet: $LAN_SUBNET" 146 | -------------------------------------------------------------------------------- /openwrt/firewall-bypass-rules.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Firewall Bypass Rules - Add to /etc/config/firewall 3 | # ============================================================================= 4 | # 5 | # PURPOSE: 6 | # By default, the kill switch blocks lan→wan forwarding (all traffic must go 7 | # through VPN). These rules create exceptions for specific bypass devices. 8 | # 9 | # IMPORTANT: 10 | # Each bypass device needs BOTH: 11 | # 1. Policy routing rule in /etc/rc.local (ip rule add from X lookup 100) 12 | # 2. Firewall rule here (allowing lan→wan forwarding) 13 | # 14 | # Without the firewall rule, packets are dropped even if policy routing works. 15 | # 16 | # INSTALLATION: 17 | # 1. Add rules to /etc/config/firewall (before any generic reject rules) 18 | # 2. Run: /etc/init.d/firewall restart 19 | # 3. Verify with: iptables -L FORWARD -n -v | grep ACCEPT 20 | # 21 | # MAC ADDRESS BINDING (Recommended): 22 | # For physical devices, bind both IP and MAC to prevent IP spoofing. 23 | # For VMs/containers with dynamic MACs, IP-only rules are acceptable. 24 | # 25 | # ============================================================================= 26 | 27 | # ----------------------------------------------------------------------------- 28 | # TEMPLATE: Physical Device (with MAC binding) 29 | # ----------------------------------------------------------------------------- 30 | # Use for physical devices where MAC is static and known 31 | # 32 | # config rule 33 | # option name 'Bypass-DeviceName' 34 | # option src 'lan' 35 | # option src_ip '192.168.1.X' 36 | # option src_mac 'XX:XX:XX:XX:XX:XX' 37 | # option dest 'wan' 38 | # option target 'ACCEPT' 39 | 40 | # ----------------------------------------------------------------------------- 41 | # TEMPLATE: Virtual Device (IP only) 42 | # ----------------------------------------------------------------------------- 43 | # Use for VMs/containers where MAC may change on restart 44 | # 45 | # config rule 46 | # option name 'Bypass-VMName' 47 | # option src 'lan' 48 | # option src_ip '192.168.1.X' 49 | # option dest 'wan' 50 | # option target 'ACCEPT' 51 | 52 | # ============================================================================= 53 | # EXAMPLE RULES (Customize for your network) 54 | # ============================================================================= 55 | 56 | # ----------------------------------------------------------------------------- 57 | # Infrastructure Devices 58 | # ----------------------------------------------------------------------------- 59 | 60 | # Hypervisor host (IP only - internal to Proxmox) 61 | # config rule 62 | # option name 'Bypass-Hypervisor' 63 | # option src 'lan' 64 | # option src_ip '192.168.1.3' 65 | # option dest 'wan' 66 | # option target 'ACCEPT' 67 | 68 | # DNS Server (IP only - container MAC may change) 69 | # config rule 70 | # option name 'Bypass-DNS-Server' 71 | # option src 'lan' 72 | # option src_ip '192.168.1.5' 73 | # option dest 'wan' 74 | # option target 'ACCEPT' 75 | 76 | # Proxmox Node 1 (physical - use MAC binding) 77 | # config rule 78 | # option name 'Bypass-PVE-Node1' 79 | # option src 'lan' 80 | # option src_ip '192.168.1.10' 81 | # option src_mac 'XX:XX:XX:XX:XX:XX' 82 | # option dest 'wan' 83 | # option target 'ACCEPT' 84 | 85 | # Proxmox Node 2 (physical - use MAC binding) 86 | # config rule 87 | # option name 'Bypass-PVE-Node2' 88 | # option src 'lan' 89 | # option src_ip '192.168.1.11' 90 | # option src_mac 'XX:XX:XX:XX:XX:XX' 91 | # option dest 'wan' 92 | # option target 'ACCEPT' 93 | 94 | # ----------------------------------------------------------------------------- 95 | # Workstations/Servers 96 | # ----------------------------------------------------------------------------- 97 | 98 | # Primary Workstation (physical - use MAC binding) 99 | # config rule 100 | # option name 'Bypass-Workstation' 101 | # option src 'lan' 102 | # option src_ip '192.168.1.20' 103 | # option src_mac 'XX:XX:XX:XX:XX:XX' 104 | # option dest 'wan' 105 | # option target 'ACCEPT' 106 | 107 | # Server LXC (IP only - container) 108 | # config rule 109 | # option name 'Bypass-Server-LXC' 110 | # option src 'lan' 111 | # option src_ip '192.168.1.100' 112 | # option dest 'wan' 113 | # option target 'ACCEPT' 114 | 115 | # ----------------------------------------------------------------------------- 116 | # User Devices (as needed) 117 | # ----------------------------------------------------------------------------- 118 | 119 | # Laptop needing geo-specific access (physical - MAC binding) 120 | # config rule 121 | # option name 'Bypass-Laptop' 122 | # option src 'lan' 123 | # option src_ip '192.168.1.4' 124 | # option src_mac 'XX:XX:XX:XX:XX:XX' 125 | # option dest 'wan' 126 | # option target 'ACCEPT' 127 | 128 | # ============================================================================= 129 | # VERIFICATION 130 | # ============================================================================= 131 | # 132 | # After adding rules and restarting firewall: 133 | # 134 | # # Check rules are loaded 135 | # iptables -L forwarding_rule -n -v 136 | # 137 | # # From bypass device, test connectivity 138 | # curl -s https://am.i.mullvad.net/connected 139 | # # Should show: "You are not connected to Mullvad" 140 | # 141 | # # From VPN device, verify VPN still works 142 | # curl -s https://am.i.mullvad.net/connected 143 | # # Should show Mullvad connection details 144 | # 145 | # ============================================================================= 146 | -------------------------------------------------------------------------------- /adguard/mullvad-AdGuardHome.yaml.example: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # AdGuard Home Configuration - Mullvad DNS 3 | # ============================================================================= 4 | # Optimized for use with Mullvad VPN 5 | # /opt/AdGuardHome/AdGuardHome.yaml 6 | # 7 | # This config uses Mullvad's DNS-over-HTTPS servers which: 8 | # - Support ad blocking at DNS level 9 | # - Are encrypted (DoH) 10 | # - Don't log queries (Mullvad policy) 11 | # - Work inside the VPN tunnel for maximum privacy 12 | # 13 | # Run setup wizard first, then customize with these settings 14 | 15 | http: 16 | pprof: 17 | port: 6060 18 | enabled: false 19 | address: 0.0.0.0:3000 20 | session_ttl: 720h 21 | 22 | users: 23 | # Generated during setup wizard - this is just a placeholder 24 | - name: admin 25 | password: $2a$10$CHANGE_THIS_RUN_SETUP_WIZARD_FIRST 26 | 27 | auth_attempts: 5 28 | block_auth_min: 15 29 | 30 | dns: 31 | bind_hosts: 32 | - 0.0.0.0 33 | port: 53 34 | 35 | # ========================================================================== 36 | # MULLVAD DNS-OVER-HTTPS SERVERS 37 | # ========================================================================== 38 | # These are Mullvad's official DoH servers 39 | # Queries go through your VPN tunnel for full encryption 40 | upstream_dns: 41 | # Mullvad DoH with ad/tracker/malware blocking (RECOMMENDED) 42 | - https://adblock.dns.mullvad.net/dns-query 43 | 44 | # Alternative: Mullvad DoH without blocking (if using AdGuard lists only) 45 | # - https://dns.mullvad.net/dns-query 46 | 47 | # Alternative: Mullvad extended blocking (ads + trackers + adult content) 48 | # - https://all.dns.mullvad.net/dns-query 49 | 50 | # Alternative: Mullvad base blocking (trackers only) 51 | # - https://base.dns.mullvad.net/dns-query 52 | 53 | # ========================================================================== 54 | # BOOTSTRAP DNS 55 | # ========================================================================== 56 | # Used ONLY to resolve DoH hostnames at startup 57 | # After VPN connects, all DNS goes through the tunnel 58 | bootstrap_dns: 59 | - 9.9.9.9 60 | - 1.1.1.1 61 | 62 | # Parallel upstream queries for speed 63 | upstream_mode: load_balance 64 | 65 | # ========================================================================== 66 | # PRIVACY & SECURITY 67 | # ========================================================================== 68 | # Enable DNSSEC validation 69 | enable_dnssec: true 70 | 71 | # Block IPv6 responses (prevents leaks - Mullvad IPv6 support varies by server) 72 | aaaa_disabled: true 73 | 74 | # Refuse ANY queries (often used for DNS amplification attacks) 75 | refuse_any: true 76 | 77 | # ========================================================================== 78 | # CACHING 79 | # ========================================================================== 80 | cache_size: 4194304 81 | cache_ttl_min: 300 82 | cache_ttl_max: 86400 83 | cache_optimistic: true 84 | 85 | # Block common information disclosure queries 86 | blocked_hosts: 87 | - version.bind 88 | - id.server 89 | - hostname.bind 90 | 91 | trusted_proxies: 92 | - 127.0.0.0/8 93 | - ::1/128 94 | 95 | protection_enabled: true 96 | blocking_mode: default 97 | blocking_ipv4: "" 98 | blocking_ipv6: "" 99 | 100 | # ============================================================================= 101 | # FILTERING 102 | # ============================================================================= 103 | filtering: 104 | filtering_enabled: true 105 | filters_update_interval: 24 106 | parental_enabled: false 107 | safebrowsing_enabled: false 108 | safe_search: 109 | enabled: false 110 | # Return 0.0.0.0 for blocked domains 111 | blocking_mode: null_ip 112 | 113 | # ============================================================================= 114 | # FILTER LISTS 115 | # ============================================================================= 116 | # These complement Mullvad's DNS-level blocking 117 | # Note: Using Mullvad's adblock DNS + these lists = comprehensive coverage 118 | filters: 119 | # AdGuard DNS filter (comprehensive) 120 | - enabled: true 121 | url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt 122 | name: AdGuard DNS filter 123 | id: 1 124 | 125 | # StevenBlack unified hosts 126 | - enabled: true 127 | url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts 128 | name: StevenBlack Unified Hosts 129 | id: 2 130 | 131 | # AdAway default blocklist 132 | - enabled: true 133 | url: https://adaway.org/hosts.txt 134 | name: AdAway Default Blocklist 135 | id: 3 136 | 137 | # Peter Lowe's ad/tracking list 138 | - enabled: true 139 | url: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus 140 | name: Peter Lowe's List 141 | id: 4 142 | 143 | whitelist_filters: [] 144 | user_rules: [] 145 | 146 | # DHCP disabled - using OpenWrt DHCP server 147 | dhcp: 148 | enabled: false 149 | 150 | clients: 151 | runtime_sources: 152 | whois: true 153 | arp: true 154 | rdns: true 155 | dhcp: true 156 | hosts: true 157 | persistent: [] 158 | 159 | log: 160 | file: "" 161 | max_backups: 0 162 | max_size: 100 163 | max_age: 3 164 | compress: false 165 | local_time: false 166 | verbose: false 167 | 168 | querylog: 169 | enabled: true 170 | file_enabled: true 171 | interval: 24h 172 | size_memory: 1000 173 | ignored: [] 174 | 175 | statistics: 176 | enabled: true 177 | interval: 24h 178 | ignored: [] 179 | 180 | os: 181 | group: "" 182 | user: "" 183 | rlimit_nofile: 0 184 | 185 | schema_version: 27 186 | -------------------------------------------------------------------------------- /docker/scripts/watchdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # VPN Watchdog - Auto-Recovery Daemon 4 | # ============================================================================= 5 | # Monitors VPN tunnel health and restarts if necessary 6 | # Runs as background process inside the container 7 | # 8 | # Configuration via environment variables: 9 | # WATCHDOG_INTERVAL - Seconds between checks (default: 30) 10 | # WATCHDOG_FAIL_THRESHOLD - Failures before restart (default: 3) 11 | # PROBE_TARGETS - IPs to ping (default: 1.1.1.1 8.8.8.8 9.9.9.9) 12 | # ============================================================================= 13 | 14 | LOG_FILE="/var/log/awg-watchdog.log" 15 | 16 | # Configuration 17 | CHECK_INTERVAL="${WATCHDOG_INTERVAL:-30}" 18 | FAIL_THRESHOLD="${WATCHDOG_FAIL_THRESHOLD:-3}" 19 | PROBE_TARGETS="${PROBE_TARGETS:-1.1.1.1 8.8.8.8 9.9.9.9}" 20 | 21 | # State 22 | fail_count=0 23 | 24 | # ============================================================================= 25 | # Logging 26 | # ============================================================================= 27 | log() { 28 | local msg="[$(date '+%Y-%m-%d %H:%M:%S')] WATCHDOG: $1" 29 | echo "$msg" | tee -a "$LOG_FILE" 30 | } 31 | 32 | # ============================================================================= 33 | # Health Checks 34 | # ============================================================================= 35 | check_interface() { 36 | ip link show awg0 2>/dev/null | grep -q "state UP\|state UNKNOWN" 37 | } 38 | 39 | check_handshake() { 40 | amneziawg show awg0 2>/dev/null | grep -q "latest handshake" 41 | } 42 | 43 | check_connectivity() { 44 | for target in $PROBE_TARGETS; do 45 | if ping -c 1 -W 3 -I awg0 "$target" &>/dev/null; then 46 | return 0 47 | fi 48 | done 49 | return 1 50 | } 51 | 52 | # ============================================================================= 53 | # Tunnel Restart 54 | # ============================================================================= 55 | restart_tunnel() { 56 | log "==========================================" 57 | log "RESTARTING VPN TUNNEL" 58 | log "==========================================" 59 | 60 | # Run predown script 61 | if [[ -x /etc/amneziawg/predown.sh ]]; then 62 | /etc/amneziawg/predown.sh 63 | fi 64 | 65 | # Remove existing interface and kill userspace daemon 66 | ip link del dev awg0 2>/dev/null || true 67 | pkill -f "amneziawg-go awg0" 2>/dev/null || true 68 | sleep 2 69 | 70 | # Start userspace AmneziaWG daemon (creates TUN interface) 71 | log "Creating awg0 interface (userspace daemon)..." 72 | amneziawg-go awg0 & 73 | sleep 2 74 | 75 | # Verify interface was created 76 | if ! ip link show awg0 >/dev/null 2>&1; then 77 | log "ERROR: Failed to create awg0 interface" 78 | return 1 79 | fi 80 | 81 | # Apply configuration 82 | log "Applying VPN configuration..." 83 | amneziawg setconf awg0 /etc/amneziawg/awg0.conf 84 | 85 | # Add IP address 86 | log "Adding VPN IP: $VPN_IP" 87 | ip address add "$VPN_IP" dev awg0 88 | 89 | # Bring interface up 90 | ip link set up dev awg0 91 | log "Interface awg0 UP" 92 | 93 | # Setup routing 94 | log "Configuring routes..." 95 | 96 | # Get WAN gateway 97 | local wan_gateway 98 | wan_gateway=$(ip route | grep "default" | grep -v awg | awk '{print $3}' | head -1) 99 | [[ -z "$wan_gateway" ]] && wan_gateway="$LAN_GATEWAY" 100 | 101 | # Add endpoint route 102 | ip route add "$VPN_ENDPOINT_IP" via "$wan_gateway" 2>/dev/null || true 103 | 104 | # Set default route through VPN 105 | ip route del default 2>/dev/null || true 106 | ip route add default dev awg0 107 | 108 | # Re-apply kill switch 109 | log "Re-applying kill switch..." 110 | if [[ -x /etc/amneziawg/postup.sh ]]; then 111 | /etc/amneziawg/postup.sh 112 | fi 113 | 114 | log "Tunnel restart complete" 115 | log "==========================================" 116 | 117 | # Give tunnel time to establish 118 | sleep 5 119 | } 120 | 121 | # ============================================================================= 122 | # Main Loop 123 | # ============================================================================= 124 | main() { 125 | log "==========================================" 126 | log "Watchdog starting" 127 | log " Check interval: ${CHECK_INTERVAL}s" 128 | log " Fail threshold: ${FAIL_THRESHOLD}" 129 | log " Probe targets: $PROBE_TARGETS" 130 | log "==========================================" 131 | 132 | while true; do 133 | # Run health checks 134 | if check_interface && check_handshake && check_connectivity; then 135 | # Success 136 | if [[ $fail_count -gt 0 ]]; then 137 | log "Connectivity restored after $fail_count failures" 138 | fi 139 | fail_count=0 140 | else 141 | # Failure 142 | fail_count=$((fail_count + 1)) 143 | log "Health check FAILED ($fail_count/$FAIL_THRESHOLD)" 144 | 145 | # Log which check failed 146 | check_interface || log " - Interface check failed" 147 | check_handshake || log " - Handshake check failed" 148 | check_connectivity || log " - Connectivity check failed" 149 | 150 | # Restart if threshold reached 151 | if [[ $fail_count -ge $FAIL_THRESHOLD ]]; then 152 | log "Fail threshold reached - initiating restart" 153 | restart_tunnel 154 | fail_count=0 155 | fi 156 | fi 157 | 158 | sleep "$CHECK_INTERVAL" 159 | done 160 | } 161 | 162 | main "$@" 163 | -------------------------------------------------------------------------------- /openwrt/amneziawg/99-awg.hotplug: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # AmneziaWG Hotplug Script - Auto-start tunnel on WAN up 4 | # ============================================================================= 5 | # 6 | # This script automatically brings up the VPN tunnel when the WAN interface 7 | # comes online. Useful for: 8 | # - Boot-time VPN establishment 9 | # - Automatic reconnection after WAN interruption 10 | # - Router failover scenarios 11 | # 12 | # Features: 13 | # - Dynamic WAN gateway detection (works with DHCP WANs) 14 | # - Bypass routing table (table 100) for devices that need direct WAN access 15 | # - Split routes (0.0.0.0/1 + 128.0.0.0/1) for kill switch compatibility 16 | # - Profile-based obfuscation support 17 | # 18 | # Installation: 19 | # 1. Copy to /etc/hotplug.d/iface/99-awg 20 | # 2. chmod +x /etc/hotplug.d/iface/99-awg 21 | # 3. Edit configuration section below (VPN_IP is required) 22 | # 4. Create bypass policy rules in /etc/rc.local (see rc.local.example) 23 | # 24 | # Note: If using the watchdog with failover, the watchdog handles reconnection. 25 | # This script is for initial tunnel establishment only. 26 | # 27 | # ============================================================================= 28 | 29 | # Only act on wan interface coming up 30 | [ "$ACTION" = "ifup" ] || exit 0 31 | [ "$INTERFACE" = "wan" ] || exit 0 32 | 33 | sleep 2 34 | 35 | # Check if AWG is already up 36 | if ip link show awg0 2>/dev/null | grep -q UP; then 37 | logger -t awg-hotplug "awg0 already up" 38 | exit 0 39 | fi 40 | 41 | logger -t awg-hotplug "WAN up, starting AmneziaWG tunnel" 42 | 43 | # ============================================================================= 44 | # CONFIGURATION - Edit these values for your setup 45 | # ============================================================================= 46 | 47 | CONFIG_FILE="/etc/amneziawg/awg0.conf" 48 | 49 | # Your VPN internal IP (assigned by VPN provider) 50 | # Example: 10.64.0.x for Mullvad, 10.x.x.x for IVPN 51 | # REQUIRED: Get this from your provider's WireGuard config generator 52 | VPN_IP="CHANGE_ME" 53 | 54 | # Obfuscation profile (AmneziaWG 1.5) 55 | # Options: basic, quic, dns, sip, stealth 56 | AWG_PROFILE="basic" 57 | 58 | # ============================================================================= 59 | # DYNAMIC GATEWAY DETECTION 60 | # ============================================================================= 61 | # Gets the WAN gateway from DHCP or UCI config - adapts to your network 62 | 63 | get_wan_gateway() { 64 | local gw 65 | # Try: current routing table (DHCP-assigned gateway) 66 | gw=$(ip route show dev eth0 2>/dev/null | grep default | awk '{print $3}' | head -1) 67 | # Fallback: UCI network config 68 | [ -z "$gw" ] && gw=$(uci -q get network.wan.gateway) 69 | echo "$gw" 70 | } 71 | 72 | WAN_GW=$(get_wan_gateway) 73 | if [ -z "$WAN_GW" ]; then 74 | logger -t awg-hotplug "ERROR: Cannot determine WAN gateway - check network.wan config" 75 | exit 1 76 | fi 77 | 78 | logger -t awg-hotplug "Detected WAN gateway: $WAN_GW" 79 | 80 | # ============================================================================= 81 | # PROFILE SUPPORT (Optional obfuscation escalation) 82 | # ============================================================================= 83 | 84 | PROFILES_LIB="/etc/amneziawg/awg-profiles.sh" 85 | RUNTIME_CONFIG="/tmp/awg0-runtime.conf" 86 | 87 | if [ -f "$PROFILES_LIB" ]; then 88 | . "$PROFILES_LIB" 89 | apply_awg_profile "$CONFIG_FILE" "$RUNTIME_CONFIG" "$AWG_PROFILE" 90 | CONFIG_TO_USE="$RUNTIME_CONFIG" 91 | logger -t awg-hotplug "Using obfuscation profile: $AWG_PROFILE" 92 | else 93 | CONFIG_TO_USE="$CONFIG_FILE" 94 | fi 95 | 96 | # ============================================================================= 97 | # TUNNEL CREATION 98 | # ============================================================================= 99 | 100 | # Clean up any existing tunnel 101 | ip link del dev awg0 2>/dev/null 102 | sleep 1 103 | 104 | # Create interface 105 | if ! ip link add dev awg0 type amneziawg; then 106 | logger -t awg-hotplug "ERROR: Failed to create awg0 interface - check kmod-amneziawg installed" 107 | exit 1 108 | fi 109 | 110 | # Apply config 111 | if ! /usr/bin/amneziawg setconf awg0 "$CONFIG_TO_USE"; then 112 | logger -t awg-hotplug "ERROR: Failed to apply config from $CONFIG_FILE" 113 | ip link del dev awg0 114 | exit 1 115 | fi 116 | 117 | # Configure interface 118 | ip address add "$VPN_IP/32" dev awg0 119 | ip link set up dev awg0 120 | 121 | # ============================================================================= 122 | # ROUTING SETUP - ORDER MATTERS! 123 | # ============================================================================= 124 | # 1. Route VPN endpoint via WAN (MUST be first - prevents routing loop) 125 | # 2. Create bypass routing table 100 (for devices that need direct WAN access) 126 | # 3. Add VPN split routes (captures all traffic except bypass devices) 127 | 128 | # Extract endpoint IP from config (handles "Endpoint=IP:PORT" format) 129 | ENDPOINT=$(grep "^Endpoint=" "$CONFIG_FILE" | cut -d= -f2 | cut -d: -f1) 130 | 131 | # Step 1: Route VPN endpoint via WAN gateway 132 | # CRITICAL: Without this, VPN handshake packets would go into the tunnel (loop) 133 | if [ -n "$ENDPOINT" ]; then 134 | ip route replace ${ENDPOINT}/32 via $WAN_GW dev eth0 135 | logger -t awg-hotplug "Endpoint $ENDPOINT routed via WAN ($WAN_GW)" 136 | fi 137 | 138 | # Step 2: Create bypass routing table (table 100) 139 | # Devices can be routed here via policy rules in /etc/rc.local 140 | # They'll exit directly via WAN instead of through VPN 141 | ip route replace default via $WAN_GW dev eth0 table 100 142 | logger -t awg-hotplug "Bypass table 100 configured via $WAN_GW" 143 | 144 | # Step 3: Add VPN split routes 145 | # These are MORE SPECIFIC than "default" and cover ALL IPv4 space: 146 | # 0.0.0.0/1 = 0.0.0.0 - 127.255.255.255 147 | # 128.0.0.0/1 = 128.0.0.0 - 255.255.255.255 148 | # Result: All traffic goes to awg0 UNLESS policy routing says otherwise 149 | ip route replace 0.0.0.0/1 dev awg0 150 | ip route replace 128.0.0.0/1 dev awg0 151 | 152 | logger -t awg-hotplug "AmneziaWG tunnel started - VPN_IP=$VPN_IP, profile=$AWG_PROFILE" 153 | 154 | # ============================================================================= 155 | # BYPASS ROUTING (configured separately) 156 | # ============================================================================= 157 | # Policy rules for bypass devices are in /etc/rc.local 158 | # Example: ip rule add from 192.168.1.100 lookup 100 priority 100 159 | # 160 | # Bypass devices also need firewall rules to allow lan→wan forwarding 161 | # See: /etc/config/firewall bypass rules 162 | # ============================================================================= 163 | -------------------------------------------------------------------------------- /adguard/AdGuardHome.yaml.example: -------------------------------------------------------------------------------- 1 | # AdGuard Home Configuration 2 | # /opt/AdGuardHome/AdGuardHome.yaml 3 | # 4 | # This is a template - run initial setup wizard first, then customize 5 | 6 | http: 7 | pprof: 8 | port: 6060 9 | enabled: false 10 | address: 0.0.0.0:3000 11 | session_ttl: 720h 12 | 13 | users: 14 | # Generated during setup wizard 15 | # Password is bcrypt hash 16 | - name: admin 17 | password: $2a$10$CHANGE_THIS_HASH 18 | 19 | auth_attempts: 5 20 | block_auth_min: 15 21 | 22 | dns: 23 | # Listen on all interfaces 24 | bind_hosts: 25 | - 0.0.0.0 26 | port: 53 27 | 28 | # Anonymize client IPs in logs (privacy) 29 | anonymize_client_ip: false 30 | 31 | # Rate limiting 32 | ratelimit: 0 33 | ratelimit_subnet_len_ipv4: 24 34 | ratelimit_subnet_len_ipv6: 56 35 | ratelimit_whitelist: [] 36 | 37 | # Refuse queries to specified domains 38 | refuse_any: true 39 | 40 | # ========================================================================== 41 | # UPSTREAM DNS SERVERS 42 | # ========================================================================== 43 | # Use DNS-over-HTTPS to your VPN provider for maximum privacy 44 | # These queries go through the VPN tunnel 45 | upstream_dns: 46 | # Mullvad (with ad blocking) 47 | - https://adblock.dns.mullvad.net/dns-query 48 | # Alternative: Mullvad (no ad blocking, if using AdGuard filtering only) 49 | # - https://dns.mullvad.net/dns-query 50 | # Alternative: Cloudflare 51 | # - https://cloudflare-dns.com/dns-query 52 | # Alternative: Quad9 (malware blocking) 53 | # - https://dns.quad9.net/dns-query 54 | 55 | # ========================================================================== 56 | # BOOTSTRAP DNS 57 | # ========================================================================== 58 | # Used to resolve upstream DNS hostnames at startup 59 | # These are only used before VPN connects 60 | bootstrap_dns: 61 | - 9.9.9.9 62 | - 1.1.1.1 63 | 64 | # Fallback DNS (if upstream fails) 65 | fallback_dns: [] 66 | 67 | # Use upstream servers in parallel for speed 68 | all_servers: false 69 | fastest_addr: false 70 | 71 | # Allow parallel requests to upstreams 72 | upstream_mode: load_balance 73 | 74 | # ========================================================================== 75 | # CACHING 76 | # ========================================================================== 77 | cache_size: 4194304 78 | cache_ttl_min: 300 79 | cache_ttl_max: 86400 80 | cache_optimistic: true 81 | 82 | # ========================================================================== 83 | # SECURITY 84 | # ========================================================================== 85 | # Enable DNSSEC validation 86 | enable_dnssec: true 87 | 88 | # Block known-bad domains 89 | blocked_hosts: 90 | - version.bind 91 | - id.server 92 | - hostname.bind 93 | 94 | # Trusted proxies (for X-Forwarded-For) 95 | trusted_proxies: 96 | - 127.0.0.0/8 97 | - ::1/128 98 | 99 | # ========================================================================== 100 | # IPv6 101 | # ========================================================================== 102 | # Disable AAAA responses (prevents IPv6 leaks) 103 | aaaa_disabled: true 104 | 105 | # Handle DNS rebinding protection 106 | protection_enabled: true 107 | blocking_mode: default 108 | blocking_ipv4: "" 109 | blocking_ipv6: "" 110 | 111 | # Block private reverse DNS 112 | private_networks: [] 113 | use_private_ptr_resolvers: false 114 | local_ptr_upstreams: [] 115 | 116 | # Serve plain DNS on all interfaces 117 | serve_http3: false 118 | use_http3_upstreams: false 119 | 120 | # ============================================================================= 121 | # FILTERING 122 | # ============================================================================= 123 | filtering: 124 | # Master switch 125 | filtering_enabled: true 126 | 127 | # Update filters automatically 128 | filters_update_interval: 24 129 | 130 | # Parental controls (optional) 131 | parental_enabled: false 132 | 133 | # Safe browsing (optional) 134 | safebrowsing_enabled: false 135 | 136 | # Safe search (optional) 137 | safe_search: 138 | enabled: false 139 | 140 | # How to respond to blocked queries 141 | # null_ip: Return 0.0.0.0 (recommended) 142 | # refused: Return REFUSED 143 | # nxdomain: Return NXDOMAIN 144 | # custom_ip: Return specified IP 145 | blocking_mode: null_ip 146 | 147 | # ============================================================================= 148 | # FILTER LISTS 149 | # ============================================================================= 150 | filters: 151 | # AdGuard DNS filter (default, comprehensive) 152 | - enabled: true 153 | url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt 154 | name: AdGuard DNS filter 155 | id: 1 156 | 157 | # AdAway (mobile focused) 158 | - enabled: true 159 | url: https://adaway.org/hosts.txt 160 | name: AdAway Default Blocklist 161 | id: 2 162 | 163 | # StevenBlack hosts (unified) 164 | - enabled: true 165 | url: https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts 166 | name: StevenBlack Unified Hosts 167 | id: 3 168 | 169 | # Peter Lowe's ad/tracking list 170 | - enabled: true 171 | url: https://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus 172 | name: Peter Lowe's List 173 | id: 4 174 | 175 | # OISD (comprehensive, may be aggressive) 176 | - enabled: false 177 | url: https://small.oisd.nl 178 | name: OISD Small 179 | id: 5 180 | 181 | # Whitelist (domains that should never be blocked) 182 | whitelist_filters: [] 183 | 184 | # User-defined rules 185 | user_rules: [] 186 | 187 | # ============================================================================= 188 | # DHCP (Disabled - using OpenWrt DHCP) 189 | # ============================================================================= 190 | dhcp: 191 | enabled: false 192 | 193 | # ============================================================================= 194 | # CLIENTS (Optional client-specific settings) 195 | # ============================================================================= 196 | clients: 197 | runtime_sources: 198 | whois: true 199 | arp: true 200 | rdns: true 201 | dhcp: true 202 | hosts: true 203 | persistent: [] 204 | 205 | # ============================================================================= 206 | # LOGGING 207 | # ============================================================================= 208 | log: 209 | file: "" 210 | max_backups: 0 211 | max_size: 100 212 | max_age: 3 213 | compress: false 214 | local_time: false 215 | verbose: false 216 | 217 | # Query log 218 | querylog: 219 | enabled: true 220 | file_enabled: true 221 | interval: 24h 222 | size_memory: 1000 223 | ignored: [] 224 | 225 | # Statistics 226 | statistics: 227 | enabled: true 228 | interval: 24h 229 | ignored: [] 230 | 231 | # OS-specific 232 | os: 233 | group: "" 234 | user: "" 235 | rlimit_nofile: 0 236 | 237 | schema_version: 27 238 | -------------------------------------------------------------------------------- /docker/scripts/awg-profiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # AmneziaWG Obfuscation Profiles (Shared) 4 | # ============================================================================= 5 | # Unified profile system for all deployment types (OpenWrt, Docker, systemd) 6 | # 7 | # Usage: 8 | # source /path/to/awg-profiles.sh 9 | # AWG_PROFILE=quic 10 | # apply_awg_profile /etc/amneziawg/awg0.conf /tmp/awg0.runtime.conf 11 | # 12 | # Profiles: 13 | # basic - Junk packets + header obfuscation (default) 14 | # quic - QUIC/HTTP3 protocol mimic (high DPI resistance) 15 | # dns - DNS query protocol mimic 16 | # sip - SIP/VoIP protocol mimic 17 | # stealth - Maximum obfuscation (QUIC + aggressive junk) 18 | # 19 | # Source: wgtunnel (https://github.com/zaneschepke/wgtunnel) 20 | # ============================================================================= 21 | 22 | # Profile hex blobs (AmneziaWG 1.5 protocol mimic) 23 | # These are injected as i1/i2/j1/itime parameters 24 | 25 | AWG_QUIC_I1='' 26 | AWG_QUIC_I2='' 27 | AWG_QUIC_J1='' 28 | 29 | AWG_DNS_I1='' 30 | 31 | AWG_SIP_I1='' 32 | AWG_SIP_I2='' 33 | AWG_SIP_J1='' 34 | 35 | # ============================================================================= 36 | # apply_awg_profile - Generate runtime config with profile-specific parameters 37 | # ============================================================================= 38 | # Arguments: 39 | # $1 - Source config file (e.g., /etc/amneziawg/awg0.conf) 40 | # $2 - Output runtime config (e.g., /tmp/awg0.runtime.conf) 41 | # $3 - Profile name (optional, uses AWG_PROFILE env var if not set) 42 | # 43 | # Returns: 44 | # 0 on success, 1 on error 45 | # Sets AWG_RUNTIME_CONFIG to output path 46 | # ============================================================================= 47 | apply_awg_profile() { 48 | local source_config="$1" 49 | local runtime_config="$2" 50 | local profile="${3:-${AWG_PROFILE:-basic}}" 51 | 52 | # Validate source exists 53 | if [[ ! -f "$source_config" ]]; then 54 | echo "Error: Source config not found: $source_config" >&2 55 | return 1 56 | fi 57 | 58 | # Create runtime config directory if needed 59 | mkdir -p "$(dirname "$runtime_config")" 2>/dev/null || true 60 | 61 | # Start with base config 62 | cp "$source_config" "$runtime_config" 63 | 64 | # Apply profile-specific parameters 65 | case "$profile" in 66 | basic) 67 | # Default profile - base config already has Jc/Jmin/Jmax/H1-H4 68 | echo "# Profile: basic (junk packets + header obfuscation)" >> "$runtime_config" 69 | ;; 70 | 71 | quic) 72 | # QUIC protocol mimic - appears as HTTP/3 traffic 73 | cat >> "$runtime_config" << EOF 74 | 75 | # Profile: quic (QUIC/HTTP3 protocol mimic) 76 | # Injects QUIC Long Header Initial packet signature 77 | i1 = $AWG_QUIC_I1 78 | i2 = $AWG_QUIC_I2 79 | j1 = $AWG_QUIC_J1 80 | itime = 120 81 | EOF 82 | ;; 83 | 84 | dns) 85 | # DNS query mimic - appears as DNS resolution 86 | cat >> "$runtime_config" << EOF 87 | 88 | # Profile: dns (DNS query protocol mimic) 89 | # Injects DNS query packet signature 90 | i1 = $AWG_DNS_I1 91 | itime = 120 92 | EOF 93 | ;; 94 | 95 | sip) 96 | # SIP/VoIP mimic - appears as voice traffic 97 | cat >> "$runtime_config" << EOF 98 | 99 | # Profile: sip (SIP/VoIP protocol mimic) 100 | # Injects SIP INVITE packet signature 101 | i1 = $AWG_SIP_I1 102 | i2 = $AWG_SIP_I2 103 | j1 = $AWG_SIP_J1 104 | itime = 120 105 | EOF 106 | ;; 107 | 108 | stealth) 109 | # Maximum obfuscation - QUIC + aggressive junk 110 | cat >> "$runtime_config" << EOF 111 | 112 | # Profile: stealth (maximum obfuscation) 113 | # QUIC mimic + aggressive junk packet settings 114 | Jc = 16 115 | Jmin = 100 116 | Jmax = 200 117 | i1 = $AWG_QUIC_I1 118 | i2 = $AWG_QUIC_I2 119 | j1 = $AWG_QUIC_J1 120 | itime = 120 121 | EOF 122 | ;; 123 | 124 | *) 125 | echo "Warning: Unknown profile '$profile', using basic" >&2 126 | echo "# Profile: basic (unknown profile fallback)" >> "$runtime_config" 127 | ;; 128 | esac 129 | 130 | # Export for use by calling script 131 | export AWG_RUNTIME_CONFIG="$runtime_config" 132 | return 0 133 | } 134 | 135 | # ============================================================================= 136 | # get_awg_profile_description - Get human-readable profile description 137 | # ============================================================================= 138 | get_awg_profile_description() { 139 | local profile="${1:-${AWG_PROFILE:-basic}}" 140 | 141 | case "$profile" in 142 | basic) echo "basic (junk packets + header obfuscation)" ;; 143 | quic) echo "quic (QUIC/HTTP3 protocol mimic)" ;; 144 | dns) echo "dns (DNS query protocol mimic)" ;; 145 | sip) echo "sip (SIP/VoIP protocol mimic)" ;; 146 | stealth) echo "stealth (maximum obfuscation)" ;; 147 | *) echo "$profile (unknown)" ;; 148 | esac 149 | } 150 | 151 | # ============================================================================= 152 | # list_awg_profiles - List available profiles 153 | # ============================================================================= 154 | list_awg_profiles() { 155 | cat << 'EOF' 156 | Available AmneziaWG obfuscation profiles: 157 | 158 | basic - Junk packets + header obfuscation (default) 159 | Works everywhere, minimal overhead 160 | 161 | quic - QUIC/HTTP3 protocol mimic 162 | High DPI resistance, traffic appears as HTTP/3 163 | 164 | dns - DNS query protocol mimic 165 | Traffic appears as DNS resolution 166 | 167 | sip - SIP/VoIP protocol mimic 168 | Traffic appears as voice/video calls 169 | 170 | stealth - Maximum obfuscation 171 | QUIC mimic + aggressive junk packets (Jc=16) 172 | 173 | All profiles work with standard WireGuard servers (Mullvad, IVPN, Proton, etc.) 174 | EOF 175 | } 176 | -------------------------------------------------------------------------------- /docs/OPTIONAL_ADDONS.md: -------------------------------------------------------------------------------- 1 | # Optional Security Addons 2 | 3 | The core privacy router provides VPN tunneling with a firewall-based kill switch. These optional addons enhance security and privacy but are **not required** for basic operation. 4 | 5 | --- 6 | 7 | ## Quick Reference 8 | 9 | | Addon | Purpose | Recommended? | 10 | |-------|---------|--------------| 11 | | [AdGuard Home](#adguard-home) | DNS filtering, ad/tracker blocking | ✅ Highly recommended | 12 | | [BanIP](#banip) | IP blocklist, threat intelligence | ✅ Recommended | 13 | | [HTTPS for LuCI](#https-for-luci) | Encrypted admin interface | ⚠️ Optional | 14 | | [Intrusion Detection](#intrusion-detection) | Deep packet inspection | ⚠️ Advanced users | 15 | 16 | --- 17 | 18 | ## AdGuard Home 19 | 20 | **Purpose:** Network-wide ad blocking, tracker blocking, and DNS-over-HTTPS encryption. 21 | 22 | **Why use it:** 23 | - Blocks ads and trackers at DNS level (works for all devices) 24 | - Encrypts DNS queries via DoH (ISP can't see your DNS lookups) 25 | - Provides query logging and statistics 26 | - Parental controls available 27 | 28 | **Why skip it:** 29 | - Adds complexity 30 | - Requires additional resources (RAM/storage) 31 | - VPN provider's DNS already provides basic protection 32 | 33 | ### Installation (OpenWrt) 34 | 35 | ```bash 36 | # Download AdGuard Home 37 | cd /tmp 38 | wget https://static.adguard.com/adguardhome/release/AdGuardHome_linux_arm64.tar.gz 39 | # Use linux_armv7 for Pi3, linux_amd64 for x86 40 | 41 | tar xzf AdGuardHome_linux_arm64.tar.gz 42 | mv AdGuardHome/AdGuardHome /usr/bin/ 43 | chmod +x /usr/bin/AdGuardHome 44 | 45 | # Run initial setup 46 | AdGuardHome -s install 47 | 48 | # Access web UI at http://router-ip:3000 49 | # Complete setup wizard 50 | ``` 51 | 52 | ### Installation (Linux/systemd) 53 | 54 | ```bash 55 | # Download and extract 56 | curl -s -S -L https://static.adguard.com/adguardhome/release/AdGuardHome_linux_arm64.tar.gz | sudo tar xz -C /opt 57 | 58 | # Copy systemd service 59 | sudo cp scripts/adguardhome.service /etc/systemd/system/ 60 | 61 | # Enable and start 62 | sudo systemctl daemon-reload 63 | sudo systemctl enable adguardhome 64 | sudo systemctl start adguardhome 65 | 66 | # Access web UI at http://router-ip:3000 67 | ``` 68 | 69 | ### Configuration 70 | 71 | Use the example config as a starting point: 72 | 73 | ```bash 74 | # For Mullvad users (recommended) 75 | cp adguard/mullvad-AdGuardHome.yaml.example /opt/AdGuardHome/AdGuardHome.yaml 76 | 77 | # For other VPN providers 78 | cp adguard/AdGuardHome.yaml.example /opt/AdGuardHome/AdGuardHome.yaml 79 | # Edit upstream_dns to your provider's DoH URL 80 | ``` 81 | 82 | **Key settings:** 83 | 84 | | Setting | Mullvad Value | Purpose | 85 | |---------|---------------|---------| 86 | | `upstream_dns` | `https://adblock.dns.mullvad.net/dns-query` | Encrypted upstream | 87 | | `enable_dnssec` | `true` | Validate DNS responses | 88 | | `aaaa_disabled` | `true` | Block IPv6 (leak prevention) | 89 | 90 | ### Push AdGuard DNS to Clients 91 | 92 | Configure DHCP to announce AdGuard as DNS server: 93 | 94 | ```bash 95 | # On OpenWrt 96 | uci add_list dhcp.lan.dhcp_option='6,ADGUARD_IP' 97 | uci commit dhcp 98 | /etc/init.d/dnsmasq restart 99 | 100 | # Replace ADGUARD_IP with AdGuard's IP address 101 | ``` 102 | 103 | ### Verification 104 | 105 | ```bash 106 | # Test ad blocking 107 | nslookup doubleclick.net 108 | # Should return 0.0.0.0 or NXDOMAIN 109 | 110 | # Check upstream is working 111 | dig google.com @ADGUARD_IP 112 | ``` 113 | 114 | --- 115 | 116 | ## BanIP 117 | 118 | **Purpose:** Blocks malicious IPs using threat intelligence feeds (IP reputation lists). 119 | 120 | **Why use it:** 121 | - Blocks known malicious IPs before they reach your network 122 | - Uses curated blocklists (Spamhaus, Emerging Threats, etc.) 123 | - Automatic updates 124 | - Low resource overhead 125 | 126 | **Why skip it:** 127 | - VPN already hides your real IP 128 | - Primarily protects against inbound attacks (less relevant behind VPN) 129 | - May block legitimate services if blocklists are too aggressive 130 | 131 | ### Installation (OpenWrt) 132 | 133 | ```bash 134 | opkg update 135 | opkg install banip 136 | 137 | # Optional: Web UI 138 | opkg install luci-app-banip 139 | ``` 140 | 141 | ### Configuration 142 | 143 | ```bash 144 | # Copy example config 145 | cp openwrt/banip/banip.example /etc/config/banip 146 | 147 | # IMPORTANT: Whitelist your VPN server IP 148 | # Edit /etc/config/banip and add: 149 | # list ban_allowlist 'YOUR_VPN_SERVER_IP/32' 150 | 151 | # Enable and start 152 | /etc/init.d/banip enable 153 | /etc/init.d/banip start 154 | 155 | # Check status 156 | /etc/init.d/banip status 157 | ``` 158 | 159 | ### Recommended Feeds 160 | 161 | **Conservative (low false positives):** 162 | ``` 163 | drop - Spamhaus DROP (worst of the worst) 164 | edrop - Spamhaus Extended DROP 165 | feodo - Banking trojan C2 servers 166 | sslbl - Malicious SSL certificates 167 | ``` 168 | 169 | **Moderate:** 170 | ``` 171 | etcompromised - Emerging Threats compromised IPs 172 | dshield - DShield top attackers 173 | firehol1 - Firehol Level 1 174 | ``` 175 | 176 | **Aggressive (may cause false positives):** 177 | ``` 178 | firehol2 - Firehol Level 2 179 | blocklist - Blocklist.de reported IPs 180 | tor - Tor exit nodes (blocks Tor!) 181 | ``` 182 | 183 | ### Verification 184 | 185 | ```bash 186 | # Check loaded sets 187 | /etc/init.d/banip status 188 | 189 | # View blocked IPs 190 | nft list set inet banip blocklist 191 | 192 | # Check logs 193 | logread | grep banip 194 | ``` 195 | 196 | ### Troubleshooting 197 | 198 | **VPN stops working after enabling BanIP:** 199 | - Your VPN server IP was blocked 200 | - Add to whitelist: `list ban_allowlist 'VPN_SERVER_IP/32'` 201 | - Restart: `/etc/init.d/banip restart` 202 | 203 | **High memory usage:** 204 | - Reduce feeds or set `ban_maxelem` lower 205 | - Disable aggressive feeds 206 | 207 | --- 208 | 209 | ## HTTPS for LuCI 210 | 211 | **Purpose:** Encrypt OpenWrt admin interface. 212 | 213 | **Why use it:** 214 | - Prevents snooping on admin credentials 215 | - Required if accessing router over untrusted network 216 | 217 | **Why skip it:** 218 | - LAN is already trusted (behind your firewall) 219 | - Adds complexity with certificates 220 | - Self-signed certs cause browser warnings 221 | 222 | ### Installation 223 | 224 | ```bash 225 | opkg update 226 | opkg install luci-ssl 227 | 228 | # Force HTTPS redirect 229 | uci set uhttpd.main.redirect_https='1' 230 | uci commit uhttpd 231 | /etc/init.d/uhttpd restart 232 | ``` 233 | 234 | Access via `https://router-ip` (accept certificate warning). 235 | 236 | --- 237 | 238 | ## Intrusion Detection 239 | 240 | **Purpose:** Deep packet inspection for malware/attack signatures. 241 | 242 | **Why use it:** 243 | - Detects malware, exploits, suspicious patterns 244 | - Alerts on potential breaches 245 | 246 | **Why skip it:** 247 | - High resource usage (not suitable for low-power devices) 248 | - Complex configuration 249 | - VPN already encrypts traffic (IDS can't inspect) 250 | - Better suited for enterprise networks 251 | 252 | ### Options 253 | 254 | | Tool | Platform | Notes | 255 | |------|----------|-------| 256 | | Snort | OpenWrt (x86 only) | Heavy, needs 1GB+ RAM | 257 | | Suricata | Linux | Production-grade IDS | 258 | | Zeek | Linux | Network analysis framework | 259 | 260 | **Recommendation:** Skip for most home users. VPN encryption makes IDS less effective anyway. 261 | 262 | --- 263 | 264 | ## Configuration Summary 265 | 266 | ### Minimal Setup (VPN only) 267 | ``` 268 | ✅ AmneziaWG tunnel 269 | ✅ Kill switch (firewall zones) 270 | ✅ Watchdog (auto-recovery) 271 | ❌ AdGuard Home 272 | ❌ BanIP 273 | ``` 274 | 275 | ### Recommended Setup 276 | ``` 277 | ✅ AmneziaWG tunnel 278 | ✅ Kill switch (firewall zones) 279 | ✅ Watchdog (auto-recovery) 280 | ✅ AdGuard Home (DNS filtering + DoH) 281 | ✅ BanIP (threat intelligence) 282 | ``` 283 | 284 | ### Maximum Security 285 | ``` 286 | ✅ AmneziaWG tunnel 287 | ✅ Kill switch (firewall zones) 288 | ✅ Watchdog (auto-recovery) 289 | ✅ AdGuard Home (DNS filtering + DoH) 290 | ✅ BanIP (threat intelligence) 291 | ✅ HTTPS for LuCI 292 | ✅ SSH key-only auth 293 | ✅ Disable password login 294 | ``` 295 | 296 | --- 297 | 298 | ## Resource Requirements 299 | 300 | | Addon | RAM | Storage | CPU | 301 | |-------|-----|---------|-----| 302 | | AdGuard Home | ~50MB | ~100MB | Low | 303 | | BanIP (6 feeds) | ~30MB | ~10MB | Low | 304 | | BanIP (all feeds) | ~100MB | ~50MB | Medium | 305 | | Snort/Suricata | 1GB+ | 500MB+ | High | 306 | 307 | **Raspberry Pi 4/5:** Can run all recommended addons comfortably. 308 | 309 | **Low-power devices:** Stick to minimal setup or AdGuard only. 310 | -------------------------------------------------------------------------------- /scripts/awg-profiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # AmneziaWG Obfuscation Profiles (Shared) 4 | # ============================================================================= 5 | # Unified profile system for all deployment types (OpenWrt, Docker, systemd) 6 | # 7 | # Usage: 8 | # source /path/to/awg-profiles.sh 9 | # AWG_PROFILE=quic 10 | # apply_awg_profile /etc/amneziawg/awg0.conf /tmp/awg0.runtime.conf 11 | # 12 | # Profiles: 13 | # basic - Junk packets + header obfuscation (default) 14 | # quic - QUIC/HTTP3 protocol mimic (high DPI resistance) 15 | # dns - DNS query protocol mimic 16 | # sip - SIP/VoIP protocol mimic 17 | # stealth - Maximum obfuscation (QUIC + aggressive junk) 18 | # 19 | # Official AWG Parameters (from amnezia-vpn/amneziawg-tools): 20 | # I1-I5 - Init packet specs (hex blobs sent before handshake) 21 | # Jc - Junk packet count 22 | # Jmin - Min junk packet size 23 | # Jmax - Max junk packet size 24 | # S1-S4 - Packet padding sizes 25 | # H1-H4 - Magic header values 26 | # 27 | # NOT Official (wgtunnel-only, will cause errors): 28 | # j1, itime - DO NOT USE 29 | # ============================================================================= 30 | 31 | # Profile hex blobs (AmneziaWG 1.5 protocol mimic) 32 | # These are injected as I1/I2 parameters (UPPERCASE per official spec) 33 | # QUIC blob from official AmneziaVPN docs (https://amneziavpn.org/documentation/instructions/new-amneziawg-selfhosted) 34 | 35 | AWG_QUIC_I1='' 36 | 37 | AWG_DNS_I1='' 38 | 39 | AWG_SIP_I1='' 40 | AWG_SIP_I2='' 41 | 42 | # ============================================================================= 43 | # apply_awg_profile - Generate runtime config with profile-specific parameters 44 | # ============================================================================= 45 | # Arguments: 46 | # $1 - Source config file (e.g., /etc/amneziawg/awg0.conf) 47 | # $2 - Output runtime config (e.g., /tmp/awg0.runtime.conf) 48 | # $3 - Profile name (optional, uses AWG_PROFILE env var if not set) 49 | # 50 | # Returns: 51 | # 0 on success, 1 on error 52 | # Sets AWG_RUNTIME_CONFIG to output path 53 | # ============================================================================= 54 | apply_awg_profile() { 55 | local source_config="$1" 56 | local runtime_config="$2" 57 | local profile="${3:-${AWG_PROFILE:-basic}}" 58 | 59 | # Validate source exists 60 | if [[ ! -f "$source_config" ]]; then 61 | echo "Error: Source config not found: $source_config" >&2 62 | return 1 63 | fi 64 | 65 | # Create runtime config directory if needed 66 | mkdir -p "$(dirname "$runtime_config")" 2>/dev/null || true 67 | 68 | # Start with base config 69 | cp "$source_config" "$runtime_config" 70 | 71 | # Apply profile-specific parameters 72 | case "$profile" in 73 | basic) 74 | # Default profile - base config already has Jc/Jmin/Jmax/H1-H4 75 | echo "# Profile: basic (junk packets + header obfuscation)" >> "$runtime_config" 76 | ;; 77 | 78 | quic) 79 | # QUIC protocol mimic - appears as HTTP/3 traffic 80 | cat >> "$runtime_config" << EOF 81 | 82 | # Profile: quic (QUIC/HTTP3 protocol mimic) 83 | # Injects QUIC Long Header Initial packet signature 84 | # Official I1 param from AmneziaVPN docs (0xc7 header byte) 85 | I1 = $AWG_QUIC_I1 86 | EOF 87 | ;; 88 | 89 | dns) 90 | # DNS query mimic - appears as DNS resolution 91 | cat >> "$runtime_config" << EOF 92 | 93 | # Profile: dns (DNS query protocol mimic) 94 | # Injects DNS query packet signature 95 | I1 = $AWG_DNS_I1 96 | EOF 97 | ;; 98 | 99 | sip) 100 | # SIP/VoIP mimic - appears as voice traffic 101 | cat >> "$runtime_config" << EOF 102 | 103 | # Profile: sip (SIP/VoIP protocol mimic) 104 | # Injects SIP INVITE packet signature 105 | I1 = $AWG_SIP_I1 106 | I2 = $AWG_SIP_I2 107 | EOF 108 | ;; 109 | 110 | stealth) 111 | # Maximum obfuscation - QUIC + aggressive junk 112 | cat >> "$runtime_config" << EOF 113 | 114 | # Profile: stealth (maximum obfuscation) 115 | # QUIC mimic + aggressive junk packet settings 116 | Jc = 16 117 | Jmin = 100 118 | Jmax = 200 119 | I1 = $AWG_QUIC_I1 120 | EOF 121 | ;; 122 | 123 | *) 124 | echo "Warning: Unknown profile '$profile', using basic" >&2 125 | echo "# Profile: basic (unknown profile fallback)" >> "$runtime_config" 126 | ;; 127 | esac 128 | 129 | # Export for use by calling script 130 | export AWG_RUNTIME_CONFIG="$runtime_config" 131 | return 0 132 | } 133 | 134 | # ============================================================================= 135 | # get_awg_profile_description - Get human-readable profile description 136 | # ============================================================================= 137 | get_awg_profile_description() { 138 | local profile="${1:-${AWG_PROFILE:-basic}}" 139 | 140 | case "$profile" in 141 | basic) echo "basic (junk packets + header obfuscation)" ;; 142 | quic) echo "quic (QUIC/HTTP3 protocol mimic)" ;; 143 | dns) echo "dns (DNS query protocol mimic)" ;; 144 | sip) echo "sip (SIP/VoIP protocol mimic)" ;; 145 | stealth) echo "stealth (maximum obfuscation)" ;; 146 | *) echo "$profile (unknown)" ;; 147 | esac 148 | } 149 | 150 | # ============================================================================= 151 | # list_awg_profiles - List available profiles 152 | # ============================================================================= 153 | list_awg_profiles() { 154 | cat << 'EOF' 155 | Available AmneziaWG obfuscation profiles: 156 | 157 | basic - Junk packets + header obfuscation (default) 158 | Works everywhere, minimal overhead 159 | 160 | quic - QUIC/HTTP3 protocol mimic 161 | High DPI resistance, traffic appears as HTTP/3 162 | 163 | dns - DNS query protocol mimic 164 | Traffic appears as DNS resolution 165 | 166 | sip - SIP/VoIP protocol mimic 167 | Traffic appears as voice/video calls 168 | 169 | stealth - Maximum obfuscation 170 | QUIC mimic + aggressive junk packets (Jc=16) 171 | 172 | All profiles work with standard WireGuard servers (Mullvad, IVPN, Proton, etc.) 173 | EOF 174 | } 175 | -------------------------------------------------------------------------------- /docker/scripts/test-suite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Privacy Router - Full Test Suite 4 | # ============================================================================= 5 | # Comprehensive validation of VPN tunnel, kill switch, and DNS filtering 6 | # 7 | # Usage: docker exec privacy-router /opt/scripts/test-suite.sh 8 | # 9 | # CRITICAL: Test 6 (Kill Switch) temporarily disables VPN to verify 10 | # that traffic is blocked when tunnel is down 11 | # ============================================================================= 12 | 13 | set -e 14 | 15 | # Colors for output 16 | RED='\033[0;31m' 17 | GREEN='\033[0;32m' 18 | YELLOW='\033[1;33m' 19 | NC='\033[0m' # No Color 20 | 21 | # Counters 22 | TESTS_PASSED=0 23 | TESTS_FAILED=0 24 | TESTS_SKIPPED=0 25 | 26 | # ============================================================================= 27 | # Test Framework 28 | # ============================================================================= 29 | log_test() { 30 | local name="$1" 31 | local result="$2" 32 | local details="$3" 33 | 34 | case "$result" in 35 | PASS) 36 | TESTS_PASSED=$((TESTS_PASSED + 1)) 37 | echo -e "[${GREEN}PASS${NC}] $name" 38 | ;; 39 | FAIL) 40 | TESTS_FAILED=$((TESTS_FAILED + 1)) 41 | echo -e "[${RED}FAIL${NC}] $name" 42 | [[ -n "$details" ]] && echo -e " ${RED}$details${NC}" 43 | ;; 44 | SKIP) 45 | TESTS_SKIPPED=$((TESTS_SKIPPED + 1)) 46 | echo -e "[${YELLOW}SKIP${NC}] $name" 47 | [[ -n "$details" ]] && echo -e " ${YELLOW}$details${NC}" 48 | ;; 49 | esac 50 | } 51 | 52 | log_info() { 53 | echo -e "[${YELLOW}INFO${NC}] $1" 54 | } 55 | 56 | # ============================================================================= 57 | # Tests 58 | # ============================================================================= 59 | 60 | echo "==========================================" 61 | echo "Privacy Router - Full Test Suite" 62 | echo "==========================================" 63 | echo "" 64 | 65 | # ----------------------------------------------------------------------------- 66 | # TEST 1: VPN Interface Exists 67 | # ----------------------------------------------------------------------------- 68 | if ip link show awg0 &>/dev/null; then 69 | log_test "VPN interface exists" "PASS" 70 | else 71 | log_test "VPN interface exists" "FAIL" "awg0 interface not found" 72 | fi 73 | 74 | # ----------------------------------------------------------------------------- 75 | # TEST 2: VPN Interface is UP 76 | # ----------------------------------------------------------------------------- 77 | if ip link show awg0 2>/dev/null | grep -q "state UP\|state UNKNOWN"; then 78 | log_test "VPN interface UP" "PASS" 79 | else 80 | log_test "VPN interface UP" "FAIL" "awg0 interface is DOWN" 81 | fi 82 | 83 | # ----------------------------------------------------------------------------- 84 | # TEST 3: VPN Handshake Active 85 | # ----------------------------------------------------------------------------- 86 | HANDSHAKE=$(amneziawg show awg0 2>/dev/null | grep "latest handshake" || echo "") 87 | if [[ -n "$HANDSHAKE" ]]; then 88 | log_test "VPN handshake active" "PASS" 89 | log_info "Handshake: $HANDSHAKE" 90 | else 91 | log_test "VPN handshake active" "FAIL" "No handshake detected" 92 | fi 93 | 94 | # ----------------------------------------------------------------------------- 95 | # TEST 4: Tunnel Connectivity 96 | # ----------------------------------------------------------------------------- 97 | if curl -s --max-time 5 --interface awg0 http://1.1.1.1 &>/dev/null; then 98 | log_test "Tunnel connectivity (HTTP)" "PASS" 99 | else 100 | log_test "Tunnel connectivity (HTTP)" "FAIL" "Cannot reach internet through awg0" 101 | fi 102 | 103 | # ----------------------------------------------------------------------------- 104 | # TEST 5: Exit IP Retrieved 105 | # ----------------------------------------------------------------------------- 106 | EXIT_IP=$(curl -s --max-time 10 --interface awg0 https://ipinfo.io/ip 2>/dev/null || echo "") 107 | if [[ -n "$EXIT_IP" ]]; then 108 | log_test "Exit IP retrieved" "PASS" 109 | log_info "Your exit IP: $EXIT_IP" 110 | 111 | # Verify it's not the container's LAN IP 112 | CONTAINER_IP=$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1 || echo "") 113 | if [[ "$EXIT_IP" != "$CONTAINER_IP" ]]; then 114 | log_test "Exit IP differs from LAN IP" "PASS" 115 | else 116 | log_test "Exit IP differs from LAN IP" "FAIL" "Traffic may be leaking!" 117 | fi 118 | else 119 | log_test "Exit IP retrieved" "FAIL" "Could not get external IP" 120 | fi 121 | 122 | # ----------------------------------------------------------------------------- 123 | # TEST 6: KILL SWITCH (CRITICAL) 124 | # ----------------------------------------------------------------------------- 125 | echo "" 126 | log_info "Testing kill switch (VPN will be temporarily down)..." 127 | log_info "This is the most important test!" 128 | echo "" 129 | 130 | # Save current state 131 | SAVED_IP="$EXIT_IP" 132 | 133 | # Bring VPN down 134 | ip link set awg0 down 2>/dev/null || true 135 | sleep 2 136 | 137 | # Flush connection tracking 138 | conntrack -F 2>/dev/null || true 139 | sleep 1 140 | 141 | # Try to access internet without VPN 142 | LEAK_TEST=$(curl -s --max-time 5 https://ipinfo.io/ip 2>/dev/null || echo "BLOCKED") 143 | 144 | # Bring VPN back up and restore routing 145 | ip link set awg0 up 2>/dev/null || true 146 | ip route add default dev awg0 2>/dev/null || true 147 | sleep 3 148 | 149 | # Verify kill switch 150 | if [[ "$LEAK_TEST" == "BLOCKED" ]] || [[ -z "$LEAK_TEST" ]]; then 151 | log_test "Kill switch blocks traffic when VPN down" "PASS" 152 | log_info "Traffic was correctly BLOCKED when VPN was down" 153 | else 154 | log_test "Kill switch blocks traffic when VPN down" "FAIL" "TRAFFIC LEAKED! Got IP: $LEAK_TEST" 155 | echo "" 156 | echo -e "${RED}==========================================" 157 | echo "CRITICAL SECURITY ISSUE!" 158 | echo "Traffic leaked when VPN was down!" 159 | echo "Your real IP may have been exposed!" 160 | echo -e "==========================================${NC}" 161 | echo "" 162 | fi 163 | 164 | # ----------------------------------------------------------------------------- 165 | # TEST 7: DNS Resolution 166 | # ----------------------------------------------------------------------------- 167 | if nslookup google.com &>/dev/null || dig google.com +short &>/dev/null; then 168 | log_test "DNS resolution" "PASS" 169 | else 170 | log_test "DNS resolution" "FAIL" "Cannot resolve DNS" 171 | fi 172 | 173 | # ----------------------------------------------------------------------------- 174 | # TEST 8: Ad Blocking (AdGuard) 175 | # ----------------------------------------------------------------------------- 176 | # doubleclick.net should return 0.0.0.0 or NXDOMAIN if blocked 177 | AD_RESULT=$(dig +short doubleclick.net 2>/dev/null || nslookup doubleclick.net 2>/dev/null || echo "") 178 | if echo "$AD_RESULT" | grep -qE "0\.0\.0\.0|NXDOMAIN|SERVFAIL" || [[ -z "$AD_RESULT" ]]; then 179 | log_test "Ad blocking (doubleclick.net)" "PASS" 180 | log_info "doubleclick.net is blocked" 181 | else 182 | log_test "Ad blocking (doubleclick.net)" "SKIP" "AdGuard may not be configured yet" 183 | log_info "Got: $AD_RESULT" 184 | fi 185 | 186 | # ----------------------------------------------------------------------------- 187 | # TEST 9: No IPv6 Leaks 188 | # ----------------------------------------------------------------------------- 189 | IPV6_RESULT=$(curl -6 --max-time 5 https://ipv6.icanhazip.com 2>/dev/null || echo "BLOCKED") 190 | if [[ "$IPV6_RESULT" == "BLOCKED" ]] || [[ -z "$IPV6_RESULT" ]]; then 191 | log_test "No IPv6 leaks" "PASS" 192 | else 193 | log_test "No IPv6 leaks" "FAIL" "IPv6 leaked: $IPV6_RESULT" 194 | fi 195 | 196 | # ----------------------------------------------------------------------------- 197 | # TEST 10: iptables Kill Switch Rules Present 198 | # ----------------------------------------------------------------------------- 199 | if iptables -L OUTPUT -n 2>/dev/null | grep -q "DROP"; then 200 | log_test "iptables kill switch rules present" "PASS" 201 | else 202 | log_test "iptables kill switch rules present" "FAIL" "Kill switch rules not found" 203 | fi 204 | 205 | # ============================================================================= 206 | # Summary 207 | # ============================================================================= 208 | echo "" 209 | echo "==========================================" 210 | echo "Test Summary" 211 | echo "==========================================" 212 | echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}" 213 | echo -e "Failed: ${RED}$TESTS_FAILED${NC}" 214 | echo -e "Skipped: ${YELLOW}$TESTS_SKIPPED${NC}" 215 | echo "==========================================" 216 | 217 | if [[ $TESTS_FAILED -eq 0 ]]; then 218 | echo "" 219 | echo -e "${GREEN}ALL TESTS PASSED${NC}" 220 | echo "Your privacy router is properly configured!" 221 | echo "" 222 | exit 0 223 | else 224 | echo "" 225 | echo -e "${RED}SOME TESTS FAILED${NC}" 226 | echo "Review the failures above and fix before use." 227 | echo "" 228 | exit 1 229 | fi 230 | -------------------------------------------------------------------------------- /docker/scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ============================================================================= 3 | # Privacy Router - Container Entrypoint 4 | # ============================================================================= 5 | # Initializes VPN tunnel, applies kill switch, starts watchdog 6 | # ============================================================================= 7 | 8 | set -e 9 | 10 | # ============================================================================= 11 | # Logging 12 | # ============================================================================= 13 | LOG_FILE="/var/log/privacy-router.log" 14 | 15 | log() { 16 | local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" 17 | echo "$msg" | tee -a "$LOG_FILE" 18 | } 19 | 20 | log_error() { 21 | log "ERROR: $1" >&2 22 | } 23 | 24 | # ============================================================================= 25 | # Obfuscation Profiles (AmneziaWG 1.5) 26 | # ============================================================================= 27 | # Source shared profile library 28 | PROFILES_LIB="/opt/scripts/awg-profiles.sh" 29 | if [[ -f "$PROFILES_LIB" ]]; then 30 | source "$PROFILES_LIB" 31 | fi 32 | 33 | apply_obfuscation_profile() { 34 | local profile="${AWG_PROFILE:-basic}" 35 | local config_file="/etc/amneziawg/awg0.conf" 36 | local runtime_config="/tmp/awg0.runtime.conf" 37 | 38 | log "Applying obfuscation profile: $profile" 39 | 40 | # Use shared library if available 41 | if [[ -f "$PROFILES_LIB" ]]; then 42 | apply_awg_profile "$config_file" "$runtime_config" "$profile" 43 | log "Profile: $(get_awg_profile_description "$profile")" 44 | else 45 | # Fallback: just copy base config 46 | cp "$config_file" "$runtime_config" 47 | log "Profile library not found, using base config" 48 | export AWG_RUNTIME_CONFIG="$runtime_config" 49 | fi 50 | } 51 | 52 | # ============================================================================= 53 | # Validation 54 | # ============================================================================= 55 | validate_config() { 56 | log "Validating configuration..." 57 | 58 | # Check required environment variables 59 | if [[ -z "$VPN_IP" ]]; then 60 | log_error "VPN_IP not set. Set in .env file." 61 | exit 1 62 | fi 63 | 64 | if [[ -z "$VPN_ENDPOINT_IP" ]]; then 65 | log_error "VPN_ENDPOINT_IP not set. Set in .env file." 66 | exit 1 67 | fi 68 | 69 | # Check config file exists 70 | if [[ ! -f /etc/amneziawg/awg0.conf ]]; then 71 | log_error "VPN config not found at /etc/amneziawg/awg0.conf" 72 | log_error "Copy config/awg0.conf.example to config/awg0.conf and fill in your credentials" 73 | exit 1 74 | fi 75 | 76 | # Check for placeholder values in config 77 | if grep -q "YOUR_PRIVATE_KEY_HERE\|SERVER_PUBLIC_KEY_HERE" /etc/amneziawg/awg0.conf; then 78 | log_error "VPN config contains placeholder values!" 79 | log_error "Edit config/awg0.conf with your actual VPN credentials" 80 | exit 1 81 | fi 82 | 83 | # Check TUN device 84 | if [[ ! -c /dev/net/tun ]]; then 85 | log_error "/dev/net/tun not available. Ensure --device=/dev/net/tun is set" 86 | exit 1 87 | fi 88 | 89 | log "Configuration validated successfully" 90 | } 91 | 92 | # ============================================================================= 93 | # Network Setup 94 | # ============================================================================= 95 | setup_tunnel() { 96 | log "Setting up AmneziaWG tunnel..." 97 | 98 | # Remove existing interface if present 99 | ip link del dev awg0 2>/dev/null || true 100 | 101 | # Kill any existing amneziawg-go process 102 | pkill -f "amneziawg-go awg0" 2>/dev/null || true 103 | 104 | # Start userspace AmneziaWG daemon (creates TUN interface) 105 | # This replaces kernel-based `ip link add dev awg0 type amneziawg` 106 | amneziawg-go awg0 & 107 | AWG_PID=$! 108 | sleep 2 109 | 110 | # Verify interface was created 111 | if ! ip link show awg0 >/dev/null 2>&1; then 112 | log_error "Failed to create awg0 interface via amneziawg-go" 113 | exit 1 114 | fi 115 | log "Created awg0 interface (userspace daemon PID: $AWG_PID)" 116 | 117 | # Apply configuration (use runtime config with profile if set) 118 | local config_to_use="${AWG_RUNTIME_CONFIG:-/etc/amneziawg/awg0.conf}" 119 | amneziawg setconf awg0 "$config_to_use" 120 | log "Applied VPN configuration from: $config_to_use" 121 | 122 | # Add VPN internal IP address 123 | ip address add "$VPN_IP" dev awg0 124 | log "Added VPN IP: $VPN_IP" 125 | 126 | # Bring interface up 127 | ip link set up dev awg0 128 | log "Interface awg0 is UP" 129 | 130 | # Setup routing 131 | setup_routing 132 | } 133 | 134 | setup_routing() { 135 | log "Configuring routing..." 136 | 137 | # Get default gateway (WAN gateway) for endpoint route 138 | local wan_gateway 139 | wan_gateway=$(ip route | grep "default" | grep -v awg | awk '{print $3}' | head -1) 140 | 141 | if [[ -z "$wan_gateway" ]]; then 142 | log "Warning: Could not detect WAN gateway, using LAN_GATEWAY" 143 | wan_gateway="$LAN_GATEWAY" 144 | fi 145 | 146 | log "WAN gateway: $wan_gateway" 147 | 148 | # Add specific route to VPN endpoint via WAN 149 | # This ensures VPN packets can reach the server 150 | ip route add "$VPN_ENDPOINT_IP" via "$wan_gateway" 2>/dev/null || true 151 | log "Added endpoint route: $VPN_ENDPOINT_IP via $wan_gateway" 152 | 153 | # Remove default route (will be replaced by VPN) 154 | ip route del default 2>/dev/null || true 155 | 156 | # Add default route through VPN tunnel 157 | ip route add default dev awg0 158 | log "Default route set through awg0" 159 | } 160 | 161 | # ============================================================================= 162 | # Kill Switch 163 | # ============================================================================= 164 | apply_kill_switch() { 165 | log "Applying kill switch..." 166 | 167 | if [[ -x /etc/amneziawg/postup.sh ]]; then 168 | /etc/amneziawg/postup.sh 169 | else 170 | log_error "postup.sh not found or not executable" 171 | exit 1 172 | fi 173 | } 174 | 175 | # ============================================================================= 176 | # Watchdog 177 | # ============================================================================= 178 | start_watchdog() { 179 | if [[ "${WATCHDOG_ENABLED:-true}" == "true" ]]; then 180 | log "Starting watchdog daemon..." 181 | /opt/scripts/watchdog.sh & 182 | WATCHDOG_PID=$! 183 | log "Watchdog started (PID: $WATCHDOG_PID)" 184 | else 185 | log "Watchdog disabled" 186 | fi 187 | } 188 | 189 | # ============================================================================= 190 | # Signal Handlers 191 | # ============================================================================= 192 | cleanup() { 193 | log "Received shutdown signal..." 194 | 195 | # Run predown script 196 | if [[ -x /etc/amneziawg/predown.sh ]]; then 197 | /etc/amneziawg/predown.sh 198 | fi 199 | 200 | # Kill watchdog 201 | [[ -n "$WATCHDOG_PID" ]] && kill "$WATCHDOG_PID" 2>/dev/null || true 202 | 203 | # Remove interface 204 | ip link del dev awg0 2>/dev/null || true 205 | 206 | log "Cleanup complete" 207 | exit 0 208 | } 209 | 210 | trap cleanup SIGTERM SIGINT SIGQUIT 211 | 212 | # ============================================================================= 213 | # Main 214 | # ============================================================================= 215 | main() { 216 | log "==========================================" 217 | log "Privacy Router Starting" 218 | log "==========================================" 219 | log "VPN IP: ${VPN_IP:-not set}" 220 | log "VPN Endpoint: ${VPN_ENDPOINT_IP:-not set}:${VPN_ENDPOINT_PORT:-51820}" 221 | log "LAN Subnet: ${LAN_SUBNET:-192.168.1.0/24}" 222 | log "Obfuscation: ${AWG_PROFILE:-basic}" 223 | log "==========================================" 224 | 225 | # Validate 226 | validate_config 227 | 228 | # Apply obfuscation profile (generates runtime config) 229 | apply_obfuscation_profile 230 | 231 | # Setup tunnel 232 | setup_tunnel 233 | 234 | # Apply kill switch 235 | apply_kill_switch 236 | 237 | # Verify handshake 238 | log "Waiting for VPN handshake..." 239 | sleep 5 240 | 241 | if amneziawg show awg0 | grep -q "latest handshake"; then 242 | log "VPN handshake successful!" 243 | else 244 | log "Warning: No handshake yet. VPN may still be connecting..." 245 | fi 246 | 247 | # Start watchdog 248 | start_watchdog 249 | 250 | # Show status 251 | log "==========================================" 252 | log "Privacy Router Ready" 253 | log "==========================================" 254 | amneziawg show awg0 | tee -a "$LOG_FILE" 255 | 256 | # Keep container running 257 | log "Container running. Monitoring VPN tunnel..." 258 | 259 | # Wait indefinitely (or until signal) 260 | while true; do 261 | sleep 60 262 | # Periodic status log 263 | if amneziawg show awg0 | grep -q "latest handshake"; then 264 | log "VPN tunnel active" 265 | else 266 | log "Warning: VPN tunnel may be disconnected" 267 | fi 268 | done 269 | } 270 | 271 | main "$@" 272 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Privacy Router - Docker Deployment (Option C) 2 | 3 | ## What This Is (And Isn't) 4 | 5 | **This is a VPN gateway add-on** - a container that provides VPN tunneling, kill switch, and DNS filtering. It runs on your existing Linux server, NAS, or VM. 6 | 7 | **This is NOT a router replacement.** Unlike Options A & B (which deploy OpenWrt as a full router OS), this container doesn't do DHCP, WiFi, or general routing. Your existing router keeps those jobs. 8 | 9 | | Feature | Options A/B (OpenWrt) | Option C (Docker) | 10 | |---------|----------------------|-------------------| 11 | | AmneziaWG obfuscation | ✅ | ✅ | 12 | | AdGuard DNS filtering | ✅ | ✅ | 13 | | Kill switch | ✅ | ✅ | 14 | | Watchdog recovery | ✅ | ✅ | 15 | | Replaces your router | ✅ Yes | ❌ No | 16 | | Runs on existing server | ❌ No | ✅ Yes | 17 | 18 | ``` 19 | How Option C fits into your network: 20 | 21 | Modem → [Your Existing Router] → All Devices 22 | ↓ 23 | [This Docker Container] 24 | (on server/NAS/VM) 25 | 26 | Devices that want VPN protection point their gateway + DNS 27 | to the container's IP. Other devices use router normally. 28 | ``` 29 | 30 | > **Who should use this:** Users with an existing Linux server, NAS, or VM who want to add VPN protection without dedicated hardware. Comfortable with Docker and basic networking. 31 | 32 | > **Who should use Options A/B instead:** Users who want a dedicated privacy router appliance, or don't have a server to run Docker on. See [DEPLOYMENT.md](../docs/DEPLOYMENT.md). 33 | 34 | --- 35 | 36 | ## Overview 37 | 38 | ``` 39 | ┌─────────────────────────────────────────────────────────────┐ 40 | │ Docker Host │ 41 | │ ┌───────────────────────────────────────────────────────┐ │ 42 | │ │ privacy-router container │ │ 43 | │ │ ┌─────────────┐ ┌──────────────────────────┐ │ │ 44 | │ │ │ AdGuard │ │ AmneziaWG │ │ │ 45 | │ │ │ Home │◄────►│ (VPN Client + Gateway) │ │ │ 46 | │ │ │ :53 DNS │ │ │ │ │ 47 | │ │ └─────────────┘ └──────────┬───────────────┘ │ │ 48 | │ │ │ awg0 │ │ 49 | │ └──────────────────────────────────┼───────────────────┘ │ 50 | │ │ │ 51 | │ ┌──────────────────────────────────┴───────────────────┐ │ 52 | │ │ macvlan network │ │ 53 | │ │ Container IP: 192.168.1.250 │ │ 54 | │ └──────────────────────────────────────────────────────┘ │ 55 | └─────────────────────────────────────┬───────────────────────┘ 56 | │ 57 | ┌─────────────▼─────────────┐ 58 | │ LAN: 192.168.1.0/24 │ 59 | │ Gateway: 192.168.1.1 │ 60 | └───────────────────────────┘ 61 | ``` 62 | 63 | ## Prerequisites 64 | 65 | - Docker Engine 24.0+ with Compose V2 66 | - Linux host with kernel 5.6+ (for WireGuard) 67 | - LAN interface available for macvlan (e.g., `eth0`, `enp3s0`) 68 | - VPN subscription (Mullvad, IVPN, or AmneziaVPN) 69 | 70 | ## Quick Start 71 | 72 | ### 1. Clone and Configure 73 | 74 | ```bash 75 | cd docker/ 76 | 77 | # Copy environment template 78 | cp .env.example .env 79 | 80 | # Copy VPN config template 81 | cp config/awg0.conf.example config/awg0.conf 82 | ``` 83 | 84 | ### 2. Edit `.env` 85 | 86 | ```bash 87 | # Required - Get from your VPN provider 88 | VPN_IP=10.68.xxx.xxx/32 # Your assigned VPN IP 89 | VPN_ENDPOINT_IP=xxx.xxx.xxx.xxx # VPN server IP 90 | VPN_ENDPOINT_PORT=51820 # Usually 51820 91 | 92 | # Network - Adjust for your LAN 93 | LAN_INTERFACE=eth0 # Your Docker host's LAN interface 94 | LAN_SUBNET=192.168.1.0/24 95 | LAN_GATEWAY=192.168.1.1 96 | CONTAINER_LAN_IP=192.168.1.250 # IP for container on your LAN 97 | ``` 98 | 99 | ### 3. Edit `config/awg0.conf` 100 | 101 | Fill in your VPN credentials from your provider: 102 | 103 | ```ini 104 | [Interface] 105 | PrivateKey = YOUR_PRIVATE_KEY_HERE 106 | 107 | # AmneziaWG obfuscation parameters 108 | # These add CLIENT-SIDE obfuscation - servers don't need AmneziaWG support. 109 | # The defaults below work with ANY standard WireGuard server (Mullvad, IVPN, etc.) 110 | # Source: wgtunnel compatibility mode (https://github.com/zaneschepke/wgtunnel) 111 | Jc = 4 112 | Jmin = 40 113 | Jmax = 70 114 | S1 = 0 115 | S2 = 0 116 | H1 = 1 117 | H2 = 2 118 | H3 = 3 119 | H4 = 4 120 | 121 | [Peer] 122 | PublicKey = SERVER_PUBLIC_KEY_HERE 123 | Endpoint = ${VPN_ENDPOINT_IP}:${VPN_ENDPOINT_PORT} 124 | AllowedIPs = 0.0.0.0/0, ::/0 125 | PersistentKeepalive = 25 126 | ``` 127 | 128 | ### 4. Deploy 129 | 130 | ```bash 131 | docker compose up -d 132 | ``` 133 | 134 | ### 5. Verify 135 | 136 | ```bash 137 | # Quick check 138 | docker exec privacy-router /opt/scripts/quick-test.sh 139 | 140 | # Full validation (includes kill switch test) 141 | docker exec privacy-router /opt/scripts/test-suite.sh 142 | ``` 143 | 144 | ## Configure LAN Clients 145 | 146 | Point your devices to use the container as gateway and DNS: 147 | 148 | | Setting | Value | 149 | |---------|-------| 150 | | Gateway | 192.168.1.250 (CONTAINER_LAN_IP) | 151 | | DNS | 192.168.1.250 | 152 | 153 | Or configure your router's DHCP to distribute these settings automatically. 154 | 155 | ## AdGuard Home Setup 156 | 157 | Access the web UI at: `http://192.168.1.250:3000` 158 | 159 | 1. Complete initial setup wizard 160 | 2. Set upstream DNS (recommended: DoH) 161 | 3. Enable blocklists 162 | 4. Configure client settings 163 | 164 | Alternatively, copy the example config: 165 | ```bash 166 | cp config/AdGuardHome.yaml.example config/AdGuardHome.yaml 167 | ``` 168 | 169 | ## Management 170 | 171 | ```bash 172 | # View logs 173 | docker compose logs -f 174 | 175 | # View VPN status 176 | docker exec privacy-router amneziawg show awg0 177 | 178 | # Check exit IP 179 | docker exec privacy-router curl -s https://ipinfo.io/ip 180 | 181 | # Restart container 182 | docker compose restart 183 | 184 | # Stop 185 | docker compose down 186 | 187 | # Update 188 | docker compose pull && docker compose up -d 189 | ``` 190 | 191 | ## Testing 192 | 193 | ### Quick Test (~10 seconds) 194 | ```bash 195 | docker exec privacy-router /opt/scripts/quick-test.sh 196 | ``` 197 | 198 | Validates: 199 | - VPN interface UP 200 | - Handshake active 201 | - Tunnel connectivity 202 | - Exit IP 203 | - Kill switch rules 204 | 205 | ### Full Test Suite (~60 seconds) 206 | ```bash 207 | docker exec privacy-router /opt/scripts/test-suite.sh 208 | ``` 209 | 210 | **10 comprehensive tests including:** 211 | - VPN interface exists and UP 212 | - Handshake active 213 | - Tunnel connectivity (ping) 214 | - Exit IP retrieved 215 | - Exit IP differs from LAN IP 216 | - **Kill switch verification** (temporarily drops VPN to test) 217 | - DNS resolution 218 | - Ad blocking (doubleclick.net) 219 | - No IPv6 leaks 220 | - iptables rules present 221 | 222 | ## Kill Switch 223 | 224 | The kill switch is **always active** and cannot be disabled. It ensures: 225 | 226 | - **Default DROP** policy on all chains 227 | - **Only allows:** 228 | - Loopback traffic 229 | - LAN subnet traffic 230 | - UDP to VPN endpoint (encrypted tunnel only) 231 | - Traffic through `awg0` interface 232 | 233 | If the VPN disconnects, ALL internet traffic is blocked until reconnection. 234 | 235 | ## Troubleshooting 236 | 237 | ### Container won't start 238 | ```bash 239 | # Check logs 240 | docker compose logs privacy-router 241 | 242 | # Verify TUN device 243 | ls -la /dev/net/tun 244 | 245 | # Check config file 246 | docker exec privacy-router cat /etc/amneziawg/awg0.conf 247 | ``` 248 | 249 | ### No handshake 250 | ```bash 251 | # Check endpoint reachability (before VPN) 252 | ping -c 3 YOUR_VPN_ENDPOINT_IP 253 | 254 | # Check config 255 | docker exec privacy-router amneziawg show awg0 256 | ``` 257 | 258 | ### macvlan not working 259 | ```bash 260 | # Verify interface name matches 261 | ip link show 262 | 263 | # Check for IP conflicts 264 | ping 192.168.1.250 265 | 266 | # Docker macvlan limitation: Host cannot reach container 267 | # Access container from another LAN device or use bridge network 268 | ``` 269 | 270 | ### DNS not working 271 | ```bash 272 | # Test from container 273 | docker exec privacy-router nslookup google.com 274 | 275 | # Check AdGuard is running 276 | docker compose logs adguard 277 | ``` 278 | 279 | ### Watchdog keeps restarting tunnel 280 | ```bash 281 | # View watchdog logs 282 | docker exec privacy-router tail -f /var/log/awg-watchdog.log 283 | 284 | # Increase fail threshold if network is flaky 285 | # Edit .env: WATCHDOG_FAIL_THRESHOLD=5 286 | ``` 287 | 288 | ## Obfuscation Profiles (AmneziaWG 1.5) 289 | 290 | Choose your obfuscation level in `.env`: 291 | 292 | ```bash 293 | AWG_PROFILE=quic # Enable QUIC protocol mimic 294 | ``` 295 | 296 | | Profile | DPI Resistance | Use Case | 297 | |---------|----------------|----------| 298 | | `basic` | Medium | Home ISP, light censorship (default) | 299 | | `quic` | High | Moderate DPI, traffic appears as HTTP/3 | 300 | | `dns` | Medium | Environments where DNS traffic is allowed | 301 | | `sip` | Medium | Environments where VoIP traffic is common | 302 | | `stealth` | Maximum | Heavy censorship, aggressive DPI | 303 | 304 | **All profiles work with standard WireGuard servers** (Mullvad, IVPN, Proton, etc.). The obfuscation is client-side only. 305 | 306 | **Switching profiles:** 307 | ```bash 308 | # Edit .env 309 | AWG_PROFILE=quic 310 | 311 | # Restart container 312 | docker compose restart 313 | ``` 314 | 315 | ## Environment Variables 316 | 317 | | Variable | Default | Description | 318 | |----------|---------|-------------| 319 | | `VPN_IP` | *required* | VPN internal IP (e.g., 10.68.x.x/32) | 320 | | `VPN_ENDPOINT_IP` | *required* | VPN server IP address | 321 | | `VPN_ENDPOINT_PORT` | 51820 | VPN server port | 322 | | `LAN_INTERFACE` | eth0 | Host's LAN interface | 323 | | `LAN_SUBNET` | 192.168.1.0/24 | Local network CIDR | 324 | | `LAN_GATEWAY` | 192.168.1.1 | LAN router IP | 325 | | `CONTAINER_LAN_IP` | 192.168.1.250 | Container's LAN IP | 326 | | `AWG_PROFILE` | basic | Obfuscation profile (basic/quic/dns/sip/stealth) | 327 | | `WATCHDOG_ENABLED` | true | Enable auto-recovery | 328 | | `WATCHDOG_INTERVAL` | 30 | Seconds between checks | 329 | | `WATCHDOG_FAIL_THRESHOLD` | 3 | Failures before restart | 330 | | `PROBE_TARGETS` | 1.1.1.1 8.8.8.8 9.9.9.9 | IPs to ping for health | 331 | | `EXPECTED_EXIT_IP` | *optional* | Expected exit IP (strict mode) | 332 | 333 | ## Files 334 | 335 | ``` 336 | docker/ 337 | ├── Dockerfile # AmneziaWG client image 338 | ├── docker-compose.yml # Service definitions 339 | ├── .env.example # Environment template 340 | ├── config/ 341 | │ ├── awg0.conf.example # VPN config template 342 | │ ├── postup.sh # Kill switch rules 343 | │ ├── predown.sh # Cleanup on stop 344 | │ └── AdGuardHome.yaml.example # DNS config template 345 | └── scripts/ 346 | ├── entrypoint.sh # Container startup 347 | ├── healthcheck.sh # Docker health check 348 | ├── watchdog.sh # Auto-recovery daemon 349 | ├── test-suite.sh # Full validation (10 tests) 350 | └── quick-test.sh # Fast daily check 351 | ``` 352 | 353 | ## Security Notes 354 | 355 | 1. **Kill switch is mandatory** - Cannot be disabled 356 | 2. **IPv6 is fully blocked** - Prevents leaks 357 | 3. **DNS queries go through VPN** - No DNS leaks 358 | 4. **Config files contain secrets** - Keep `awg0.conf` private 359 | 5. **Container needs elevated privileges** - NET_ADMIN, SYS_MODULE required 360 | 361 | ## License 362 | 363 | MIT License - See repository root. 364 | -------------------------------------------------------------------------------- /openwrt/amneziawg/awg-watchdog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ============================================================================= 3 | # AmneziaWG Watchdog with Server Failover 4 | # ============================================================================= 5 | # 6 | # Advanced watchdog that: 7 | # - Monitors VPN tunnel health 8 | # - Fails over to backup servers when primary is down 9 | # - Automatically returns to primary when it recovers 10 | # - Maintains kill switch during failover 11 | # 12 | # Installation: 13 | # 1. Copy to /etc/awg-watchdog.sh 14 | # 2. chmod +x /etc/awg-watchdog.sh 15 | # 3. Create server config: /etc/amneziawg/servers.conf 16 | # 4. Create procd service (see awg-watchdog.init) 17 | # 5. Enable: /etc/init.d/awg-watchdog enable 18 | # 6. Start: /etc/init.d/awg-watchdog start 19 | # 20 | # ============================================================================= 21 | 22 | # ============================================================================= 23 | # CONFIGURATION - Edit these values for your setup 24 | # ============================================================================= 25 | 26 | # Server configuration file (contains server list) 27 | SERVERS_FILE="/etc/amneziawg/servers.conf" 28 | 29 | # AmneziaWG config template 30 | CONFIG_DIR="/etc/amneziawg" 31 | 32 | # Log file location 33 | LOG_FILE="/var/log/awg-watchdog.log" 34 | 35 | # Current server tracking 36 | STATE_FILE="/tmp/awg_current_server" 37 | 38 | # Seconds between connectivity checks 39 | CHECK_INTERVAL=30 40 | 41 | # Number of consecutive failures before failover 42 | FAIL_THRESHOLD=3 43 | 44 | # Number of successful checks before attempting failback to primary 45 | FAILBACK_THRESHOLD=10 46 | 47 | # IPs to ping for connectivity test (use reliable, geo-distributed targets) 48 | PROBE_TARGETS="1.1.1.1 8.8.8.8 9.9.9.9" 49 | 50 | # Minimum successful probes (out of total PROBE_TARGETS) 51 | MIN_PROBES=2 52 | 53 | # Your VPN internal IP (assigned by VPN provider) 54 | # Example: 10.64.0.x for Mullvad, 10.x.x.x for IVPN 55 | # Get this from your provider's WireGuard config generator 56 | VPN_IP="CHANGE_ME" 57 | 58 | # Obfuscation profile (AmneziaWG 1.5) 59 | # Options: basic, quic, dns, sip, stealth 60 | # All profiles work with standard WireGuard servers 61 | AWG_PROFILE="basic" 62 | 63 | # ============================================================================= 64 | # PROFILE SUPPORT 65 | # ============================================================================= 66 | # Source shared profile library if available 67 | PROFILES_LIB="/etc/amneziawg/awg-profiles.sh" 68 | if [ -f "$PROFILES_LIB" ]; then 69 | . "$PROFILES_LIB" 70 | PROFILES_AVAILABLE=1 71 | else 72 | PROFILES_AVAILABLE=0 73 | fi 74 | 75 | # ============================================================================= 76 | # FUNCTIONS 77 | # ============================================================================= 78 | 79 | log() { 80 | local timestamp 81 | timestamp=$(date '+%Y-%m-%d %H:%M:%S') 82 | echo "$timestamp - $1" >> "$LOG_FILE" 83 | logger -t awg-watchdog "$1" 84 | } 85 | 86 | # Load server list from config file 87 | # Format: NAME ENDPOINT_IP PORT PUBLIC_KEY (one per line) 88 | load_servers() { 89 | if [ ! -f "$SERVERS_FILE" ]; then 90 | log "ERROR: Server config not found: $SERVERS_FILE" 91 | exit 1 92 | fi 93 | 94 | SERVER_COUNT=$(grep -v '^#' "$SERVERS_FILE" | grep -v '^$' | wc -l) 95 | if [ "$SERVER_COUNT" -eq 0 ]; then 96 | log "ERROR: No servers defined in $SERVERS_FILE" 97 | exit 1 98 | fi 99 | 100 | log "Loaded $SERVER_COUNT servers from config" 101 | } 102 | 103 | # Get server info by index (0-based) 104 | get_server() { 105 | local idx=$1 106 | grep -v '^#' "$SERVERS_FILE" | grep -v '^$' | sed -n "$((idx + 1))p" 107 | } 108 | 109 | # Get current server index 110 | get_current_index() { 111 | if [ -f "$STATE_FILE" ]; then 112 | cat "$STATE_FILE" 113 | else 114 | echo 0 115 | fi 116 | } 117 | 118 | # Save current server index 119 | set_current_index() { 120 | echo "$1" > "$STATE_FILE" 121 | } 122 | 123 | # Detect WAN gateway (for endpoint routing) 124 | # Uses same logic as hotplug for consistency 125 | get_wan_gateway() { 126 | local gw 127 | # Try: current routing table (non-VPN default route) 128 | gw=$(ip route | grep "default via" | grep -v awg | head -1 | awk '{print $3}') 129 | # Fallback: check WAN interface directly 130 | [ -z "$gw" ] && gw=$(ip route show dev eth0 2>/dev/null | grep default | awk '{print $3}' | head -1) 131 | # Fallback: UCI network config 132 | [ -z "$gw" ] && gw=$(uci -q get network.wan.gateway) 133 | echo "$gw" 134 | } 135 | 136 | # Check connectivity through VPN tunnel 137 | check_connectivity() { 138 | local success=0 139 | 140 | for target in $PROBE_TARGETS; do 141 | if ping -c 1 -W 3 -I awg0 "$target" > /dev/null 2>&1; then 142 | success=$((success + 1)) 143 | fi 144 | done 145 | 146 | [ "$success" -ge "$MIN_PROBES" ] 147 | } 148 | 149 | # Check if specific endpoint is reachable (for failback test) 150 | check_endpoint() { 151 | local endpoint_ip=$1 152 | ping -c 1 -W 5 "$endpoint_ip" > /dev/null 2>&1 153 | } 154 | 155 | # Switch to a specific server 156 | switch_server() { 157 | local server_line=$1 158 | local name endpoint port pubkey 159 | 160 | name=$(echo "$server_line" | awk '{print $1}') 161 | endpoint=$(echo "$server_line" | awk '{print $2}') 162 | port=$(echo "$server_line" | awk '{print $3}') 163 | pubkey=$(echo "$server_line" | awk '{print $4}') 164 | 165 | log "Switching to server: $name ($endpoint:$port)" 166 | 167 | local gateway 168 | gateway=$(get_wan_gateway) 169 | 170 | # 1. Tear down existing interface 171 | ip link del dev awg0 2>/dev/null 172 | sleep 1 173 | 174 | # 2. Create new interface 175 | ip link add dev awg0 type amneziawg 176 | if [ $? -ne 0 ]; then 177 | log "ERROR: Failed to create awg0 interface" 178 | return 1 179 | fi 180 | 181 | # 3. Generate runtime config with new endpoint 182 | # Uses base config and updates endpoint 183 | local base_config="$CONFIG_DIR/awg0.conf" 184 | local runtime_config="/tmp/awg0-runtime.conf" 185 | local final_config="/tmp/awg0-final.conf" 186 | 187 | # Copy base config and update endpoint 188 | sed "s/^Endpoint=.*/Endpoint=$endpoint:$port/" "$base_config" > "$runtime_config" 189 | 190 | # If public key differs, update it too (for multi-city failover) 191 | # Use "-" in servers.conf to keep base config's key (same-city servers) 192 | if [ -n "$pubkey" ] && [ "$pubkey" != "-" ]; then 193 | sed -i "s/^PublicKey=.*/PublicKey=$pubkey/" "$runtime_config" 194 | fi 195 | 196 | # 3b. Apply obfuscation profile (AmneziaWG 1.5) 197 | if [ "$PROFILES_AVAILABLE" = "1" ]; then 198 | apply_awg_profile "$runtime_config" "$final_config" "$AWG_PROFILE" 199 | runtime_config="$final_config" 200 | log "Applied profile: $(get_awg_profile_description)" 201 | fi 202 | 203 | # 4. Apply configuration 204 | /usr/bin/amneziawg setconf awg0 "$runtime_config" 205 | if [ $? -ne 0 ]; then 206 | log "ERROR: Failed to apply configuration for $name" 207 | return 1 208 | fi 209 | 210 | # 5. Assign VPN internal IP 211 | ip address add "$VPN_IP/32" dev awg0 212 | 213 | # 6. Bring interface up 214 | ip link set up dev awg0 215 | 216 | # 7. Add endpoint route via WAN gateway (prevents routing loop) 217 | ip route replace "$endpoint" via "$gateway" dev eth0 218 | 219 | # 8. Maintain bypass routing table (table 100) 220 | # Devices using policy routing with table 100 exit via WAN instead of VPN 221 | # Must refresh on each server switch in case WAN gateway changed 222 | ip route replace default via "$gateway" dev eth0 table 100 223 | 224 | # 9. Add VPN split routes (more specific than default, covers all IPv4) 225 | # 0.0.0.0/1 = 0.0.0.0 - 127.255.255.255 226 | # 128.0.0.0/1 = 128.0.0.0 - 255.255.255.255 227 | # This ensures kill switch works - all traffic goes to awg0 228 | ip route replace 0.0.0.0/1 dev awg0 229 | ip route replace 128.0.0.0/1 dev awg0 230 | 231 | # Wait for handshake 232 | sleep 3 233 | 234 | # Verify connectivity 235 | if check_connectivity; then 236 | log "SUCCESS: Connected to $name" 237 | return 0 238 | else 239 | log "FAILED: Could not connect to $name" 240 | return 1 241 | fi 242 | } 243 | 244 | # Initiate failover to next server 245 | do_failover() { 246 | local current_idx 247 | current_idx=$(get_current_index) 248 | 249 | log "Initiating failover from server index $current_idx" 250 | 251 | local tried=0 252 | local next_idx=$((current_idx + 1)) 253 | 254 | while [ $tried -lt "$SERVER_COUNT" ]; do 255 | # Wrap around to beginning 256 | if [ $next_idx -ge "$SERVER_COUNT" ]; then 257 | next_idx=0 258 | fi 259 | 260 | local server_line 261 | server_line=$(get_server $next_idx) 262 | 263 | if switch_server "$server_line"; then 264 | set_current_index $next_idx 265 | return 0 266 | fi 267 | 268 | next_idx=$((next_idx + 1)) 269 | tried=$((tried + 1)) 270 | done 271 | 272 | log "CRITICAL: All servers failed! Kill switch remains engaged." 273 | return 1 274 | } 275 | 276 | # Check if primary is back and switch if so 277 | try_failback() { 278 | local current_idx 279 | current_idx=$(get_current_index) 280 | 281 | # Only failback if not already on primary (index 0) 282 | if [ "$current_idx" -eq 0 ]; then 283 | return 0 284 | fi 285 | 286 | # Get primary server endpoint 287 | local primary_line primary_endpoint 288 | primary_line=$(get_server 0) 289 | primary_endpoint=$(echo "$primary_line" | awk '{print $2}') 290 | 291 | log "Testing if primary ($primary_endpoint) is back..." 292 | 293 | if check_endpoint "$primary_endpoint"; then 294 | log "Primary server responding - attempting failback" 295 | 296 | if switch_server "$primary_line"; then 297 | set_current_index 0 298 | log "Failback to primary successful" 299 | return 0 300 | else 301 | log "Failback failed - staying on current server" 302 | fi 303 | fi 304 | 305 | return 1 306 | } 307 | 308 | # ============================================================================= 309 | # MAIN LOOP 310 | # ============================================================================= 311 | 312 | # Validate configuration 313 | if [ "$VPN_IP" = "CHANGE_ME" ]; then 314 | echo "ERROR: Please configure VPN_IP before running" 315 | echo "Get your VPN internal IP from your provider's config generator" 316 | exit 1 317 | fi 318 | 319 | # Load server configuration 320 | load_servers 321 | 322 | # Initialize state 323 | fail_count=0 324 | success_count=0 325 | set_current_index 0 326 | 327 | log "AWG watchdog starting (check: ${CHECK_INTERVAL}s, failover: ${FAIL_THRESHOLD} failures, failback: ${FAILBACK_THRESHOLD} successes)" 328 | 329 | # Main monitoring loop 330 | while true; do 331 | if check_connectivity; then 332 | # Tunnel is healthy 333 | if [ $fail_count -gt 0 ]; then 334 | log "Connectivity restored" 335 | fi 336 | fail_count=0 337 | success_count=$((success_count + 1)) 338 | 339 | # Check for failback opportunity 340 | if [ $success_count -ge $FAILBACK_THRESHOLD ]; then 341 | try_failback 342 | success_count=0 343 | fi 344 | else 345 | # Connectivity check failed 346 | fail_count=$((fail_count + 1)) 347 | success_count=0 348 | log "Connectivity check failed ($fail_count/$FAIL_THRESHOLD)" 349 | 350 | if [ $fail_count -ge $FAIL_THRESHOLD ]; then 351 | # Threshold reached, failover 352 | do_failover 353 | fail_count=0 354 | # Wait for tunnel to stabilize 355 | sleep 5 356 | fi 357 | fi 358 | 359 | sleep $CHECK_INTERVAL 360 | done 361 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Privacy Router Architecture 2 | 3 | A privacy-focused home router stack that routes all traffic through an obfuscated VPN tunnel with automatic failover, DNS-level ad blocking, and a firewall-based kill switch. 4 | 5 | ## Overview 6 | 7 | ``` 8 | ┌─────────────────────────────────────────────────────────────────────────────┐ 9 | │ PRIVACY ROUTER STACK │ 10 | ├─────────────────────────────────────────────────────────────────────────────┤ 11 | │ │ 12 | │ Internet │ 13 | │ │ │ 14 | │ ▼ │ 15 | │ ┌───────────┐ │ 16 | │ │ Modem/ │ Your ISP connection │ 17 | │ │ ONT │ (receives public IP) │ 18 | │ └─────┬─────┘ │ 19 | │ │ │ 20 | │ ▼ │ 21 | │ ┌─────────────────────────────────────────────────────┐ │ 22 | │ │ OPENWRT ROUTER │ │ 23 | │ │ ┌─────────────────────────────────────────────┐ │ │ 24 | │ │ │ WAN Interface │ │ │ 25 | │ │ │ - DHCP from modem │ │ │ 26 | │ │ │ - Endpoint route to VPN server │ │ │ 27 | │ │ └─────────────────────────────────────────────┘ │ │ 28 | │ │ │ │ │ 29 | │ │ ▼ │ │ 30 | │ │ ┌─────────────────────────────────────────────┐ │ │ 31 | │ │ │ AMNEZIAWG TUNNEL (awg0) │ │ │ 32 | │ │ │ - Obfuscated WireGuard (DPI-resistant) │ │ │ 33 | │ │ │ - Default route for all traffic │ │ │ 34 | │ │ │ - Kill switch (no bypass possible) │ │ │ 35 | │ │ └─────────────────────────────────────────────┘ │ │ 36 | │ │ │ │ │ 37 | │ │ ▼ │ │ 38 | │ │ ┌─────────────────────────────────────────────┐ │ │ 39 | │ │ │ LAN Interface (br-lan) │ │ │ 40 | │ │ │ - Gateway for all LAN devices │ │ │ 41 | │ │ │ - DHCP server (assigns IPs + DNS) │ │ │ 42 | │ │ │ - Forwards to VPN zone ONLY │ │ │ 43 | │ │ └─────────────────────────────────────────────┘ │ │ 44 | │ └─────────────────────────────────────────────────────┘ │ 45 | │ │ │ │ 46 | │ │ DNS queries │ All other traffic │ 47 | │ ▼ │ │ 48 | │ ┌─────────────┐ │ │ 49 | │ │ ADGUARD │ │ │ 50 | │ │ HOME │ │ │ 51 | │ │ - Blocks │ │ │ 52 | │ │ ads/ │ │ │ 53 | │ │ trackers │ │ │ 54 | │ │ - DoH to │ │ │ 55 | │ │ VPN DNS │ │ │ 56 | │ └─────────────┘ │ │ 57 | │ │ │ │ 58 | │ └────────────────┬───────────────────┘ │ 59 | │ │ │ 60 | │ ▼ │ 61 | │ ┌─────────────────────────────────────────────────────┐ │ 62 | │ │ WIRELESS ACCESS POINT │ │ 63 | │ │ (Existing router in AP/Bridge mode) │ │ 64 | │ │ - WiFi only, no routing │ │ 65 | │ │ - Passes all traffic to OpenWrt │ │ 66 | │ └─────────────────────────────────────────────────────┘ │ 67 | │ │ │ 68 | │ ▼ │ 69 | │ ┌─────────────────────────────────────────────────────┐ │ 70 | │ │ YOUR DEVICES │ │ 71 | │ │ Phones, laptops, smart TVs, IoT devices │ │ 72 | │ │ - All traffic tunneled through VPN │ │ 73 | │ │ - All DNS filtered through AdGuard │ │ 74 | │ │ - Protected by kill switch │ │ 75 | │ └─────────────────────────────────────────────────────┘ │ 76 | │ │ 77 | └─────────────────────────────────────────────────────────────────────────────┘ 78 | ``` 79 | 80 | ## Components 81 | 82 | ### 1. OpenWrt Router 83 | 84 | The core routing platform running on dedicated hardware (Raspberry Pi, x86 mini PC), virtual machine, or container. 85 | 86 | **Responsibilities:** 87 | - WAN connection management (DHCP from ISP modem) 88 | - VPN tunnel establishment and maintenance 89 | - Kill switch enforcement via firewall zones 90 | - DHCP server for LAN devices 91 | - Traffic routing (LAN → VPN only) 92 | 93 | **Key Features:** 94 | - Stateless firewall with zone-based policies 95 | - No LAN→WAN forwarding (traffic cannot bypass VPN) 96 | - Automatic tunnel recovery via watchdog script 97 | - Boot persistence for unattended operation 98 | 99 | ### 2. AmneziaWG VPN Tunnel 100 | 101 | An obfuscated fork of WireGuard that resists Deep Packet Inspection (DPI). 102 | 103 | **Why AmneziaWG over standard WireGuard:** 104 | - Standard WireGuard has identifiable packet patterns 105 | - DPI systems can detect and block WireGuard traffic 106 | - AmneziaWG adds junk packets (Jc/Jmin/Jmax parameters) to disguise traffic 107 | - Appears as random UDP noise to network observers 108 | 109 | **Configuration:** 110 | - Connects to privacy-focused VPN provider (Mullvad, IVPN, etc.) 111 | - Uses provider's ad-blocking DNS servers 112 | - Receives internal VPN IP for tunnel communication 113 | 114 | ### 3. AdGuard Home DNS Server 115 | 116 | A network-wide ad/tracker blocker operating at DNS level. 117 | 118 | **Responsibilities:** 119 | - Resolves DNS queries for all LAN devices 120 | - Blocks ads, trackers, malware domains 121 | - Upstream queries via DNS-over-HTTPS (DoH) to VPN provider 122 | - Provides encrypted DNS to prevent ISP snooping 123 | 124 | **Benefits:** 125 | - Blocks ads in apps that don't support browser extensions 126 | - Reduces bandwidth (blocked requests never load) 127 | - Single point of control for all devices 128 | - No client-side software required 129 | 130 | ### 4. Kill Switch 131 | 132 | A firewall architecture that prevents any traffic from bypassing the VPN. 133 | 134 | **Implementation:** 135 | ``` 136 | Firewall Zones: 137 | ├── LAN Zone 138 | │ ├── input: ACCEPT (management access) 139 | │ ├── output: ACCEPT 140 | │ └── forward: ACCEPT (within zone) 141 | │ 142 | ├── VPN Zone (awg0) 143 | │ ├── input: REJECT 144 | │ ├── output: ACCEPT 145 | │ ├── forward: REJECT 146 | │ └── masquerade: enabled 147 | │ 148 | └── WAN Zone 149 | ├── input: REJECT 150 | ├── output: ACCEPT (for VPN endpoint only) 151 | └── forward: REJECT 152 | 153 | Forwarding Rules: 154 | ├── LAN → VPN: ALLOWED ✓ 155 | ├── LAN → WAN: BLOCKED ✗ (kill switch) 156 | └── VPN → WAN: BLOCKED ✗ 157 | ``` 158 | 159 | **Behavior:** 160 | - VPN up: All traffic flows through tunnel 161 | - VPN down: All internet traffic blocked, LAN still accessible 162 | - No manual intervention required - automatic protection 163 | 164 | ### 5. Watchdog Service 165 | 166 | A background script that monitors tunnel health and performs recovery. 167 | 168 | **Features:** 169 | - Checks connectivity every 30 seconds 170 | - Restarts tunnel after 3 consecutive failures 171 | - Adds correct routes after tunnel restart 172 | - Logs all events for troubleshooting 173 | 174 | ## Security Model 175 | 176 | ### Threat Protection 177 | 178 | | Threat | Protection | 179 | |--------|------------| 180 | | ISP surveillance | All traffic encrypted through VPN tunnel | 181 | | DNS leaks | AdGuard uses DoH to VPN provider's DNS | 182 | | VPN failure leaks | Kill switch blocks all non-VPN traffic | 183 | | IP address exposure | VPN provider's exit IP shown to websites | 184 | | DPI/VPN blocking | AmneziaWG obfuscation disguises traffic | 185 | | Ad/tracker networks | DNS-level blocking via AdGuard | 186 | | IPv6 leaks | IPv6 disabled at all levels | 187 | 188 | ### What This Does NOT Protect Against 189 | 190 | - Malware on your devices (use endpoint security) 191 | - WebRTC leaks (disable in browser settings) 192 | - Browser fingerprinting (use privacy browsers) 193 | - Logging by websites you authenticate to 194 | - Physical access to your network 195 | - Compromised VPN provider 196 | 197 | ## Network Flow 198 | 199 | ### Outbound Request (e.g., loading reddit.com) 200 | 201 | ``` 202 | 1. DEVICE → DNS QUERY 203 | Your phone asks: "What IP is reddit.com?" 204 | 205 | 2. DNS RESOLUTION (AdGuard) 206 | AdGuard checks blocklist → reddit.com NOT blocked 207 | AdGuard queries upstream via DoH → gets 151.101.1.140 208 | Returns answer to device 209 | 210 | 3. DEVICE → TCP CONNECTION 211 | Phone sends packet: src=192.168.1.x, dst=151.101.1.140 212 | 213 | 4. OPENWRT ROUTING 214 | Packet arrives on LAN interface 215 | Routing table: default via awg0 216 | Forward to VPN zone ✓ 217 | 218 | 5. VPN ENCAPSULATION 219 | Original packet encrypted 220 | Wrapped in new packet: src=router_wan_ip, dst=vpn_server_ip 221 | AmneziaWG adds obfuscation 222 | 223 | 6. WAN TRANSMISSION 224 | Encrypted packet sent to VPN server via ISP 225 | ISP sees: "encrypted UDP to some IP" 226 | 227 | 7. VPN SERVER EXIT 228 | VPN server decrypts 229 | Forwards to reddit.com 230 | Reddit sees: VPN exit IP (not your real IP) 231 | 232 | 8. RETURN PATH 233 | Reddit → VPN server → encrypted → OpenWrt → decrypt → device 234 | ``` 235 | 236 | ### Kill Switch Activation 237 | 238 | ``` 239 | 1. VPN TUNNEL FAILS 240 | awg0 interface goes down 241 | No default route exists 242 | 243 | 2. DEVICE TRIES TO CONNECT 244 | Phone sends packet to internet 245 | No route to destination (default route gone) 246 | Packet dropped by kernel 247 | 248 | 3. EVEN IF ROUTE EXISTED 249 | Firewall has no LAN→WAN forwarding rule 250 | Packet would be rejected by nftables 251 | 252 | 4. MANAGEMENT STILL WORKS 253 | LAN→LAN traffic stays in LAN zone 254 | SSH, web UI, AdGuard all accessible 255 | 256 | 5. WATCHDOG DETECTS FAILURE 257 | After 3 failed pings (90 seconds) 258 | Tunnel restarted automatically 259 | Internet restored 260 | ``` 261 | 262 | ## Deployment Options 263 | 264 | ### Option A: Dedicated Hardware (Recommended) 265 | 266 | ``` 267 | Internet → Modem → [Raspberry Pi / Mini PC] → WiFi AP → Devices 268 | └── OpenWrt + AdGuard 269 | ``` 270 | 271 | **Pros:** Simple, low power, reliable, full control 272 | **Cons:** Requires hardware purchase 273 | 274 | ### Option B: Virtual Machine (Homelab/Enterprise) 275 | 276 | ``` 277 | Internet → Modem → [Hypervisor Host] → WiFi AP → Devices 278 | ├── OpenWrt VM 279 | └── AdGuard VM/Container 280 | ``` 281 | 282 | **Pros:** Leverage existing hardware, easy snapshots/backups, flexible resources 283 | **Cons:** More complex networking (requires bridged NICs), hypervisor dependency 284 | 285 | ## Requirements 286 | 287 | ### Hardware 288 | - Device with 2 network interfaces (WAN + LAN) 289 | - Minimum 512MB RAM, 1 CPU core 290 | - Ethernet for WAN connection 291 | - Storage for logs and configs 292 | 293 | ### Software 294 | - OpenWrt 23.05+ (or compatible Linux) 295 | - AmneziaWG kernel module and tools 296 | - AdGuard Home (or Pi-hole alternative) 297 | 298 | ### Network 299 | - Existing WiFi router (to use as AP) 300 | - VPN provider account with WireGuard support 301 | - Static LAN IP scheme 302 | 303 | ## Next Steps 304 | 305 | 1. **[HOW-IT-WORKS.md](HOW-IT-WORKS.md)** - Deep technical explanation 306 | 2. **[DEPLOYMENT.md](DEPLOYMENT.md)** - Step-by-step installation 307 | 3. **[CONFIGURATION.md](CONFIGURATION.md)** - Config file reference 308 | 4. **[TROUBLESHOOTING.md](TROUBLESHOOTING.md)** - Common issues and fixes 309 | -------------------------------------------------------------------------------- /docs/DEPLOYMENT.md: -------------------------------------------------------------------------------- 1 | # Deployment Guide 2 | 3 | Step-by-step instructions for deploying the privacy router stack. 4 | 5 | ## Prerequisites 6 | 7 | ### Hardware Requirements 8 | 9 | | Component | Minimum | Recommended | 10 | |-----------|---------|-------------| 11 | | CPU | 1 core | 2+ cores | 12 | | RAM | 512 MB | 1 GB | 13 | | Storage | 1 GB | 4+ GB | 14 | | Network | 2 interfaces | 2 interfaces | 15 | 16 | **Supported Platforms:** 17 | - Raspberry Pi 4/5 18 | - x86/x64 mini PC 19 | - Virtual machine (any hypervisor) 20 | 21 | ### Software Requirements 22 | 23 | - OpenWrt 23.05 or later (or compatible Linux) 24 | - AmneziaWG packages (kmod-amneziawg, amneziawg-tools) 25 | - AdGuard Home (latest) 26 | 27 | ### Network Requirements 28 | 29 | - Internet connection via modem/ONT 30 | - Existing WiFi router (to convert to AP mode) 31 | - VPN provider account with WireGuard/AmneziaWG support 32 | 33 | --- 34 | 35 | ## Deployment Options 36 | 37 | Choose your deployment path: 38 | 39 | | Option | Architecture | Best For | 40 | |--------|--------------|----------| 41 | | [A. Dedicated Hardware](#option-a-dedicated-hardware) | Router replacement | **Recommended** - Network-wide protection, dedicated device | 42 | | [B. Virtual Machine](#option-b-virtual-machine) | Router replacement | Homelab with existing hypervisor, 2 NICs available | 43 | | [C. Docker Container](#option-c-docker-container) | VPN gateway add-on | Existing infrastructure, opt-in device protection | 44 | 45 | **All options provide the same core protection:** AmneziaWG obfuscation, AdGuard DNS filtering, kill switch, and watchdog recovery. The choice is about deployment architecture: 46 | 47 | **Options A & B (OpenWrt):** Full router replacement. OpenWrt becomes your network's router - handles routing, DHCP, firewall, VPN. Existing router becomes WiFi access point only. All devices protected automatically. 48 | 49 | **Option C (Docker):** VPN gateway add-on. Runs on existing Docker host (NAS, server, VM). Existing router keeps all functions. Devices opt-in by pointing gateway/DNS at container. 50 | 51 | ``` 52 | A/B: Modem → [OpenWrt Privacy Router] → WiFi AP → All Devices Protected 53 | C: Modem → [Existing Router] → Devices (some point to Docker container) 54 | ``` 55 | 56 | > **Default recommendation:** OpenWrt (A or B) for most users - simpler architecture, network-wide protection. Docker (C) is appropriate when you want to add VPN capability alongside existing infrastructure without replacing your router. 57 | 58 | --- 59 | 60 | ## Option A: Dedicated Hardware 61 | 62 | ### A1. Install OpenWrt 63 | 64 | **For Raspberry Pi:** 65 | 66 | ```bash 67 | # Download OpenWrt image for your device 68 | # https://openwrt.org/toh/raspberry_pi_foundation/raspberry_pi 69 | 70 | # Write to SD card (Linux/macOS) 71 | sudo dd if=openwrt-*.img of=/dev/sdX bs=4M status=progress 72 | 73 | # Boot the Pi, connect via ethernet 74 | ssh root@192.168.1.1 75 | ``` 76 | 77 | **For x86 Mini PC:** 78 | 79 | ```bash 80 | # Download x86/64 image from openwrt.org 81 | # Write to USB/SSD and boot 82 | ``` 83 | 84 | ### A2. Configure Network Interfaces 85 | 86 | Edit `/etc/config/network`: 87 | 88 | ```bash 89 | uci set network.lan=interface 90 | uci set network.lan.device='br-lan' 91 | uci set network.lan.proto='static' 92 | uci set network.lan.ipaddr='192.168.1.1' # Your gateway IP 93 | uci set network.lan.netmask='255.255.255.0' 94 | 95 | uci set network.wan=interface 96 | uci set network.wan.device='eth0' # Your WAN interface 97 | uci set network.wan.proto='dhcp' 98 | 99 | uci commit network 100 | ``` 101 | 102 | ### A3. Install AmneziaWG 103 | 104 | **Method 1: Pre-built packages (recommended)** 105 | 106 | ```bash 107 | # Check your OpenWrt version 108 | cat /etc/openwrt_release 109 | 110 | # Download packages from: 111 | # https://github.com/amnezia-vpn/amneziawg-openwrt/releases 112 | 113 | # Install dependencies 114 | opkg update 115 | opkg install kmod-crypto-lib-chacha20 kmod-crypto-lib-chacha20poly1305 \ 116 | kmod-crypto-lib-curve25519 kmod-udptunnel4 kmod-udptunnel6 117 | 118 | # Install AmneziaWG (adjust filename for your version) 119 | opkg install /tmp/kmod-amneziawg_*.ipk 120 | opkg install /tmp/amneziawg-tools_*.ipk 121 | ``` 122 | 123 | **Method 2: Build from source** 124 | 125 | See: https://github.com/amnezia-vpn/amneziawg-openwrt 126 | 127 | ### A4. Configure VPN Tunnel 128 | 129 | Create config directory: 130 | 131 | ```bash 132 | mkdir -p /etc/amneziawg 133 | ``` 134 | 135 | Create `/etc/amneziawg/awg0.conf`: 136 | 137 | ```ini 138 | [Interface] 139 | PrivateKey = YOUR_PRIVATE_KEY_HERE 140 | 141 | # AmneziaWG obfuscation parameters 142 | # These add CLIENT-SIDE obfuscation - servers don't need to support AmneziaWG. 143 | # The defaults below work with ANY standard WireGuard server (Mullvad, IVPN, etc.) 144 | # Source: wgtunnel compatibility mode (https://github.com/zaneschepke/wgtunnel) 145 | Jc = 4 146 | Jmin = 40 147 | Jmax = 70 148 | S1 = 0 149 | S2 = 0 150 | H1 = 1 151 | H2 = 2 152 | H3 = 3 153 | H4 = 4 154 | 155 | [Peer] 156 | PublicKey = VPN_SERVER_PUBLIC_KEY 157 | AllowedIPs = 0.0.0.0/0, ::/0 158 | Endpoint = VPN_SERVER_IP:51820 159 | PersistentKeepalive = 25 160 | ``` 161 | 162 | Set permissions: 163 | 164 | ```bash 165 | chmod 600 /etc/amneziawg/awg0.conf 166 | ``` 167 | 168 | ### A5. Configure Firewall 169 | 170 | Create VPN zone and kill switch: 171 | 172 | ```bash 173 | # Create VPN zone 174 | uci set firewall.vpn=zone 175 | uci set firewall.vpn.name='vpn' 176 | uci set firewall.vpn.device='awg0' 177 | uci set firewall.vpn.input='REJECT' 178 | uci set firewall.vpn.output='ACCEPT' 179 | uci set firewall.vpn.forward='REJECT' 180 | uci set firewall.vpn.masq='1' 181 | uci set firewall.vpn.mtu_fix='1' 182 | 183 | # Allow LAN to VPN forwarding ONLY (kill switch) 184 | uci set firewall.lan_vpn=forwarding 185 | uci set firewall.lan_vpn.src='lan' 186 | uci set firewall.lan_vpn.dest='vpn' 187 | 188 | # Ensure NO lan->wan forwarding exists (verify kill switch) 189 | # This should already be the default - no lan->wan rule 190 | 191 | uci commit firewall 192 | ``` 193 | 194 | ### A6. Install Obfuscation Profiles (Optional) 195 | 196 | For environments with deep packet inspection, install the profile library: 197 | 198 | ```bash 199 | # Copy profile library 200 | cp scripts/awg-profiles.sh /etc/amneziawg/awg-profiles.sh 201 | chmod +x /etc/amneziawg/awg-profiles.sh 202 | ``` 203 | 204 | Available profiles: 205 | 206 | | Profile | DPI Resistance | Use Case | 207 | |---------|----------------|----------| 208 | | `basic` | Medium | Home ISP, light censorship (default) | 209 | | `quic` | High | Moderate DPI, traffic appears as HTTP/3 | 210 | | `dns` | Medium | Environments where DNS traffic is allowed | 211 | | `sip` | Medium | Environments where VoIP traffic is common | 212 | | `stealth` | Maximum | Heavy censorship, aggressive DPI | 213 | 214 | Set the profile in your scripts (watchdog and hotplug): 215 | ```bash 216 | AWG_PROFILE="quic" # or dns, sip, stealth 217 | ``` 218 | 219 | ### A7. Install Startup Scripts 220 | 221 | Copy from `scripts/` directory: 222 | 223 | ```bash 224 | # Copy watchdog script 225 | cp scripts/awg-watchdog.sh /etc/awg-watchdog.sh 226 | chmod +x /etc/awg-watchdog.sh 227 | 228 | # Edit watchdog with your values (REQUIRED): 229 | vi /etc/awg-watchdog.sh 230 | # Set: VPN_IP, ENDPOINT_IP (find these in your VPN provider config) 231 | 232 | # Copy hotplug script (auto-starts VPN on WAN up) 233 | mkdir -p /etc/hotplug.d/iface 234 | cp scripts/99-awg-hotplug /etc/hotplug.d/iface/99-awg 235 | chmod +x /etc/hotplug.d/iface/99-awg 236 | 237 | # Edit hotplug script with same values: 238 | vi /etc/hotplug.d/iface/99-awg 239 | # Set: VPN_IP, ENDPOINT_IP 240 | ``` 241 | 242 | **For OpenWrt (init.d):** 243 | 244 | ```bash 245 | # Install init script for boot persistence 246 | cp scripts/awg-watchdog.init /etc/init.d/awg-watchdog 247 | chmod +x /etc/init.d/awg-watchdog 248 | 249 | # Enable at boot 250 | /etc/init.d/awg-watchdog enable 251 | 252 | # Start now 253 | /etc/init.d/awg-watchdog start 254 | 255 | # Verify running 256 | ps | grep awg-watchdog 257 | ``` 258 | 259 | **For standard Linux (systemd):** 260 | 261 | ```bash 262 | # Copy systemd service files 263 | sudo cp scripts/awg-watchdog.service /etc/systemd/system/ 264 | sudo cp scripts/adguardhome.service /etc/systemd/system/ 265 | 266 | # Reload systemd 267 | sudo systemctl daemon-reload 268 | 269 | # Enable and start services 270 | sudo systemctl enable awg-watchdog adguardhome 271 | sudo systemctl start awg-watchdog adguardhome 272 | 273 | # Verify running 274 | sudo systemctl status awg-watchdog 275 | sudo systemctl status adguardhome 276 | ``` 277 | 278 | **Configuration values you need:** 279 | | Variable | Description | Where to Find | 280 | |----------|-------------|---------------| 281 | | `VPN_IP` | Your VPN internal IP | Provider config (e.g., `10.64.x.x/32` for Mullvad) | 282 | | `ENDPOINT_IP` | VPN server IP address | Resolve provider hostname or server list | 283 | | `WAN_GATEWAY` | Usually "auto" (auto-detected) | `auto` or your modem's IP (e.g., `192.168.1.1`) | 284 | 285 | ### A8. Test VPN Manually 286 | 287 | ```bash 288 | # Create interface 289 | ip link add dev awg0 type amneziawg 290 | 291 | # Apply config 292 | amneziawg setconf awg0 /etc/amneziawg/awg0.conf 293 | 294 | # Add address (use your VPN internal IP) 295 | ip address add 10.x.x.x/32 dev awg0 296 | 297 | # Bring up 298 | ip link set up dev awg0 299 | 300 | # Add routes (use your VPN server IP and WAN gateway) 301 | ip route add VPN_SERVER_IP via WAN_GATEWAY 302 | ip route del default 2>/dev/null 303 | ip route add default dev awg0 304 | 305 | # Test 306 | curl https://am.i.mullvad.net/ip 307 | # Should show VPN exit IP, not your real IP 308 | ``` 309 | 310 | ### A9. Deploy AdGuard Home 311 | 312 | **Option 1: On OpenWrt (limited resources)** 313 | 314 | ```bash 315 | # Download AdGuard Home binary 316 | cd /tmp 317 | wget https://static.adguard.com/adguardhome/release/AdGuardHome_linux_arm64.tar.gz 318 | tar xzf AdGuardHome_linux_arm64.tar.gz 319 | mv AdGuardHome/AdGuardHome /usr/bin/ 320 | 321 | # Run setup 322 | AdGuardHome -s install 323 | 324 | # Access web UI at http://router-ip:3000 325 | ``` 326 | 327 | **Option 2: Separate device/container (recommended)** 328 | 329 | Deploy AdGuard Home on a separate LXC container, VM, or device: 330 | 331 | ```bash 332 | # On Debian/Ubuntu 333 | curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v 334 | 335 | # Configure at http://adguard-ip:3000 336 | ``` 337 | 338 | ### A10. Configure DHCP to Push AdGuard DNS 339 | 340 | On OpenWrt: 341 | 342 | ```bash 343 | # Push AdGuard as DNS server to clients 344 | uci add_list dhcp.lan.dhcp_option='6,ADGUARD_IP' 345 | uci commit dhcp 346 | /etc/init.d/dnsmasq restart 347 | ``` 348 | 349 | ### A11. Configure AdGuard Upstream DNS 350 | 351 | In AdGuard web UI (Settings → DNS Settings → Upstream DNS): 352 | 353 | ``` 354 | # Use DNS-over-HTTPS to your VPN provider 355 | # Mullvad: https://adblock.dns.mullvad.net/dns-query 356 | # IVPN: https://dns.ivpn.net/dns-query 357 | # Proton: https://dns.protonvpn.net/dns-query 358 | https://YOUR_PROVIDER_DOH_URL/dns-query 359 | 360 | # Or use VPN provider's plain DNS (if inside VPN tunnel) 361 | # Mullvad: 100.64.0.4 | IVPN: 10.0.254.1 | Proton: 10.2.0.1 362 | VPN_PROVIDER_DNS_IP 363 | ``` 364 | 365 | Enable: 366 | - DNSSEC 367 | - Parallel requests 368 | - Cache enabled 369 | 370 | ### A12. Security Hardening 371 | 372 | ```bash 373 | # Disable IPv6 (prevents leaks) 374 | uci set network.wan.ipv6='0' 375 | uci set network.lan.ipv6='0' 376 | uci delete network.wan6 2>/dev/null 377 | uci commit network 378 | 379 | # Disable IPv6 in kernel 380 | echo 'net.ipv6.conf.all.disable_ipv6=1' >> /etc/sysctl.conf 381 | echo 'net.ipv6.conf.default.disable_ipv6=1' >> /etc/sysctl.conf 382 | sysctl -p 383 | 384 | # Restrict SSH to LAN only 385 | uci set dropbear.@dropbear[0].Interface='lan' 386 | uci commit dropbear 387 | /etc/init.d/dropbear restart 388 | 389 | # Enable HTTPS for web UI (optional) 390 | opkg update 391 | opkg install luci-ssl 392 | uci set uhttpd.main.redirect_https='1' 393 | uci commit uhttpd 394 | /etc/init.d/uhttpd restart 395 | ``` 396 | 397 | ### A13. Final Cutover 398 | 399 | 1. **Set existing router to AP mode** 400 | - Disable DHCP 401 | - Disable routing/NAT 402 | - Set to bridge mode 403 | - Assign static IP (e.g., 192.168.1.4) 404 | 405 | 2. **Connect cables** 406 | - Modem → OpenWrt WAN port 407 | - OpenWrt LAN port → WiFi AP 408 | 409 | 3. **Verify** 410 | ```bash 411 | # Check VPN 412 | curl https://am.i.mullvad.net/ip 413 | 414 | # Check DNS (should return 0.0.0.0) 415 | nslookup doubleclick.net 416 | 417 | # Check kill switch (disconnect VPN, verify no internet) 418 | ip link set awg0 down 419 | curl google.com # Should fail 420 | ip link set awg0 up 421 | ``` 422 | 423 | --- 424 | 425 | ## Option B: Virtual Machine 426 | 427 | For homelab users with existing hypervisors (Proxmox, ESXi, Hyper-V, etc.). 428 | 429 | ### B1. Create OpenWrt VM 430 | 431 | **General requirements:** 432 | - 512MB+ RAM, 1-2 vCPUs 433 | - Two virtual network interfaces (WAN and LAN) 434 | - Download OpenWrt x86/64 image from openwrt.org 435 | 436 | **Network configuration:** 437 | - NIC 1 → Bridge to WAN network (connected to modem) 438 | - NIC 2 → Bridge to LAN network (connected to your devices) 439 | 440 | **Steps:** 441 | 1. Download OpenWrt x86/64 combined image 442 | 2. Create VM with 2 NICs bridged appropriately 443 | 3. Import/attach the OpenWrt disk image 444 | 4. Boot and continue from [A2](#a2-configure-network-interfaces) 445 | 446 | ### B2. Deploy AdGuard 447 | 448 | Deploy AdGuard Home in a separate VM or container on the same hypervisor: 449 | 450 | ```bash 451 | # On Debian/Ubuntu VM or container 452 | curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v 453 | ``` 454 | 455 | Ensure the AdGuard VM/container is on the LAN bridge so it can serve DNS to clients. 456 | 457 | ### B3. Continue Setup 458 | 459 | Continue from [A4](#a4-configure-vpn-tunnel) onwards - the configuration is identical to dedicated hardware once OpenWrt is running. 460 | 461 | --- 462 | 463 | ## Option C: Docker Container 464 | 465 | VPN gateway add-on for users with existing Docker infrastructure who want to add VPN capability without replacing their router. 466 | 467 | **When to choose Docker:** 468 | - You have an existing Docker host (NAS, server, VM) 469 | - You want to keep your current router's functions 470 | - You only need some devices protected (opt-in model) 471 | - You're adding VPN to an existing infrastructure 472 | 473 | **When to choose OpenWrt instead:** 474 | - You want network-wide automatic protection 475 | - You have dedicated hardware available (Pi, mini-PC) 476 | - You prefer a simpler single-device architecture 477 | 478 | > **AI-assisted setup:** If using an AI assistant (Claude, GPT, etc.), give it access to the `docker/` folder - it can guide you through macvlan networking and container configuration. 479 | 480 | ### C1. Overview 481 | 482 | The Docker deployment provides: 483 | - **AmneziaWG VPN client** with kill switch 484 | - **AdGuard Home** for DNS filtering 485 | - **macvlan networking** for LAN gateway mode 486 | - **Auto-recovery watchdog** and health checks 487 | - **Comprehensive test suite** (10 tests including kill switch verification) 488 | 489 | ``` 490 | ┌────────────────────────────────────────────────────┐ 491 | │ Docker Host (Linux) │ 492 | │ ┌──────────────────────────────────────────────┐ │ 493 | │ │ privacy-router container │ │ 494 | │ │ AmneziaWG + AdGuard Home + Kill Switch │ │ 495 | │ └──────────────────┬───────────────────────────┘ │ 496 | │ │ macvlan (192.168.1.250) │ 497 | └─────────────────────┼──────────────────────────────┘ 498 | │ 499 | LAN: 192.168.1.0/24 500 | ``` 501 | 502 | ### C2. Prerequisites 503 | 504 | - Docker Engine 24.0+ with Compose V2 505 | - Linux host with kernel 5.6+ (for WireGuard) 506 | - LAN interface available for macvlan 507 | - VPN subscription with WireGuard/AmneziaWG support 508 | 509 | ### C3. Quick Start 510 | 511 | ```bash 512 | cd docker/ 513 | 514 | # Copy templates 515 | cp .env.example .env 516 | cp config/awg0.conf.example config/awg0.conf 517 | 518 | # Edit with your values 519 | nano .env # Set VPN_IP, VPN_ENDPOINT_IP, network config 520 | nano config/awg0.conf # Set PrivateKey, PublicKey 521 | 522 | # Deploy 523 | docker compose up -d 524 | 525 | # Verify (quick check) 526 | docker exec privacy-router /opt/scripts/quick-test.sh 527 | 528 | # Full validation (includes kill switch test) 529 | docker exec privacy-router /opt/scripts/test-suite.sh 530 | ``` 531 | 532 | ### C4. Configure LAN Clients 533 | 534 | Point devices to use the container as gateway and DNS: 535 | 536 | | Setting | Value | 537 | |---------|-------| 538 | | Gateway | 192.168.1.250 (CONTAINER_LAN_IP) | 539 | | DNS | 192.168.1.250 | 540 | 541 | Or configure your router's DHCP to distribute these settings. 542 | 543 | ### C5. Full Documentation 544 | 545 | See **[docker/README.md](../docker/README.md)** for: 546 | - Complete configuration reference 547 | - Environment variables 548 | - Troubleshooting guide 549 | - Security notes 550 | 551 | --- 552 | 553 | ## Post-Deployment Checklist 554 | 555 | - [ ] VPN connected (check exit IP) 556 | - [ ] Kill switch working (no internet when VPN down) 557 | - [ ] DNS resolving through AdGuard 558 | - [ ] Ads blocked (test doubleclick.net) 559 | - [ ] IPv6 disabled 560 | - [ ] SSH restricted to LAN 561 | - [ ] All services start on boot 562 | - [ ] Watchdog running 563 | - [ ] Existing router in AP mode 564 | 565 | ## Next Steps 566 | 567 | - [CONFIGURATION.md](CONFIGURATION.md) - Detailed config reference 568 | - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Common issues 569 | --------------------------------------------------------------------------------