├── start-client.sh ├── client ├── post-up.sh ├── pre-down.sh ├── clean.sh ├── domestic.nft ├── hook.sh ├── cn.js ├── client.sh └── generate-nftables-ip-sets.js ├── start-server.sh ├── doc ├── wg0.conf └── server.exmaple.sh ├── install-tools.sh ├── server-install.sh ├── server ├── server.sh └── prepare.sh ├── client-install.sh └── README.md /start-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./client/client.sh 4 | -------------------------------------------------------------------------------- /client/post-up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Add rules... >&2 4 | cd "$( dirname "${BASH_SOURCE[0]}" )" 5 | source ./data/rule.sh 6 | echo Add rules done. >&2 7 | -------------------------------------------------------------------------------- /client/pre-down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Delete rules... >&2 4 | cd "$( dirname "${BASH_SOURCE[0]}" )" 5 | source ./clean.sh 6 | echo Delete rules done. >&2 7 | -------------------------------------------------------------------------------- /start-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | ADDRESS=192.168.128.1 6 | export PORT=12345 7 | export INTERFACE="wg0rand" 8 | 9 | . ./server/prepare.sh 10 | 11 | socat EXEC:./server/server.sh,pty,rawer TCP-LISTEN:${PORT},bind=${ADDRESS},fork,reuseaddr 12 | -------------------------------------------------------------------------------- /client/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PRIORITY=${PRIORITY:-1024} 4 | 5 | # IPv4 6 | while ip -4 rule delete priority ${PRIORITY} 2>/dev/null; 7 | do 8 | true; 9 | done 10 | 11 | # IPv6 12 | while ip -6 rule delete priority ${PRIORITY} 2>/dev/null; 13 | do 14 | true; 15 | done 16 | -------------------------------------------------------------------------------- /doc/wg0.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = wFyqSfOWJRxVHfcL+8LDt4RHeX6Cy3nGeJK5fSoCrGo= 3 | Address = 192.168.128.254/24 4 | PostUp = export PRIORITY=1024; source /path/to/wireguard/post-up.sh 5 | PreDown = export PRIORITY=1024; source /path/to/wireguard/pre-down.sh 6 | 7 | [Peer] 8 | PublicKey = X1dcDFsAQJFm7YEUa7HtRnKH2SeeN2P75UDzMxacIQk= 9 | PersistentKeepalive = 128 10 | Endpoint = wireguard.example.org:12345 11 | AllowedIPs = 0.0.0.0/0 12 | -------------------------------------------------------------------------------- /install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | function install_for_debian() { 4 | code_name=$(lsb_release -cs) 5 | target_release=stable 6 | case "${code_name}" in 7 | 'buster' ) 8 | echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list.d/backports.list 9 | target_release=buster-backports 10 | ;; 11 | 'stretch' ) 12 | printf 'deb http://deb.debian.org/debian/ unstable main\n' > /etc/apt/sources.list.d/unstable.list 13 | printf 'Package: *\nPin: release a=unstable\nPin-Priority: 150\n' > /etc/apt/preferences.d/limit-unstable 14 | target_release=unstable 15 | ;; 16 | * ) 17 | ;; 18 | esac 19 | apt update 20 | apt -t $target_release install -yq wireguard 21 | } 22 | 23 | if ! command -v wg >/dev/null 2>&1; 24 | then 25 | echo 'Wireguard not found!'; 26 | dist=$(LANG=C hostnamectl status | grep 'Operating System:' | awk '{print $3}') 27 | case "${dist}" in 28 | 'Debian' ) 29 | echo 'Installing...' 30 | install_for_debian 31 | ;; 32 | 'Arch' ) 33 | echo 'Installing...' 34 | pacman -Sy --confirm wireguard-arch wireguard-tools 35 | ;; 36 | * ) 37 | echo 'Please install wireguard from https://www.wireguard.com manually.' 38 | ;; 39 | esac 40 | fi 41 | -------------------------------------------------------------------------------- /server-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | ./install-tools.sh 4 | 5 | mkdir -p /etc/wireguard 6 | 7 | private_key=`wg genkey` 8 | public_key=`printf ${private_key} | wg pubkey` 9 | echo "Public Key: ${public_key}" 10 | 11 | printf 'Please enter wireguard configuration name: [wg0] ' 12 | read wg_cfg_name 13 | : ${wg_cfg_name:='wg0'} 14 | 15 | printf 'Please enter address: [192.168.128.1/24] ' 16 | read address 17 | : ${address:='192.168.128.1/24'} 18 | 19 | printf 'Please enter listen port: [8443] ' 20 | read listen_port 21 | : ${listen_port:='8443'} 22 | 23 | printf 'Please enter peer public key: ' 24 | read peer_public_key 25 | 26 | printf 'Please enter peer allowed ips: [192.168.128.2/32] ' 27 | read peer_allowed_ips 28 | : ${peer_allowed_ips:='192.168.128.2/32'} 29 | 30 | config_file_path="/etc/wireguard/${wg_cfg_name}.conf" 31 | config=$(printf "\ 32 | [Interface] 33 | Address = ${address} 34 | PrivateKey = ${private_key} 35 | ListenPort = ${listen_port} 36 | 37 | [Peer] 38 | PublicKey = ${peer_public_key} 39 | AllowedIPs = ${peer_allowed_ips}\ 40 | ") 41 | 42 | if [ -f "${config_file_path}" ]; 43 | then 44 | printf "Warning! ${config_file_path} already exists, do you override it? [y/N] " 45 | read confirm 46 | confirm=$(printf "${confirm}" | tr '[:upper:]' '[:lower:]') 47 | if [ "${confirm}" = "y" -o "${confirm}" = "yes" ]; 48 | then 49 | # Ignore 50 | :; 51 | else 52 | echo "${config}" 53 | exit 54 | fi 55 | fi 56 | 57 | echo "${config}" > "${config_file_path}" 58 | echo Saved to ${config_file_path} 59 | -------------------------------------------------------------------------------- /server/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IPV4_ADDRESS=${IPV4_ADDRESS:-"192.168.129.1/24"} 4 | IPV6_ADDRESS=${IPV6_ADDRESS:-"fdff:eedd:ccbb::1/64"} 5 | ALLOWED_IPS=${ALLOWED_IPS:-"192.168.129.254/32, fdff:eedd:ccbb::ffff/128"} 6 | MTU=${MTU:-1420} 7 | 8 | INTERFACE=${INTERFACE:-"wg0rnd"} 9 | MIN_PORT=${MIN_PORT:-49152} 10 | MAX_PORT=${MAX_PORT:-65535} 11 | CONFIG_FILE_DIR=$(mktemp -d) 12 | chmod 0700 ${CONFIG_FILE_DIR} 13 | 14 | CONFIG_FILE_PATH="${CONFIG_FILE_DIR}/${INTERFACE}.conf" 15 | 16 | __cleanup() { 17 | wg-quick down "${CONFIG_FILE_PATH}" 18 | [[ -d "${CONFIG_FILE_DIR}" ]] && rm -r "${CONFIG_FILE_DIR}" 19 | } 20 | 21 | trap 'trap __cleanup EXIT' HUP INT QUIT KILL TERM 22 | trap __cleanup EXIT 23 | 24 | while read -r line || [[ -n ${line} ]]; 25 | do 26 | read -r method rid remote_pubkey <<<${line} 27 | if [[ "${method}" != "INIT" ]]; 28 | then 29 | echo ERROR 30 | continue 31 | fi 32 | 33 | private_key=$(wg genkey) 34 | public_key=$(wg pubkey <<<${private_key}) 35 | port=$((MIN_PORT + $RANDOM % (MAX_PORT - MIN_PORT))) 36 | 37 | echo "OK" "${rid}" "${public_key}" "${port}" 38 | 39 | conf=$(cat < "${CONFIG_FILE_PATH}" 55 | wg-quick up "$CONFIG_FILE_PATH" 56 | done <${1:-/dev/stdin} 57 | -------------------------------------------------------------------------------- /client/domestic.nft: -------------------------------------------------------------------------------- 1 | #!/usr/bin/nft -f 2 | 3 | include "./data/var.nft" 4 | 5 | table inet wg.domestic 6 | delete table inet wg.domestic 7 | table inet wg.domestic { 8 | set v4 { 9 | type ipv4_addr 10 | flags constant, interval 11 | elements = $IPV4_ELEMENTS 12 | } 13 | 14 | set v6 { 15 | type ipv6_addr 16 | flags constant, interval 17 | elements = $IPV6_ELEMENTS 18 | } 19 | 20 | chain try_restore_mark { 21 | ct mark & $FWMARK == $FWMARK meta mark set ct mark 22 | ct mark & $FWMARK != $FWMARK ip daddr != @v4 meta mark set mark | $FWMARK 23 | ct mark & $FWMARK != $FWMARK ip6 daddr != @v6 meta mark set mark | $FWMARK 24 | } 25 | 26 | chain try_save_mark { 27 | ct mark & $FWMARK != $FWMARK meta mark & $FWMARK == $FWMARK ct mark set meta mark 28 | } 29 | 30 | chain filter.prerouting.mangle { 31 | type filter hook prerouting priority mangle 32 | #meta nftrace set 1 33 | 34 | jump try_restore_mark 35 | } 36 | 37 | chain route.output.mangle { 38 | type route hook output priority mangle 39 | #meta nftrace set 1 40 | 41 | jump try_restore_mark 42 | } 43 | 44 | chain filter.postrouting.mangle { 45 | type filter hook postrouting priority mangle 46 | #meta nftrace set 1 47 | 48 | jump try_save_mark 49 | } 50 | 51 | chain nat.postrouting.srcnat { 52 | type nat hook postrouting priority srcnat 53 | #meta nftrace set 1 54 | 55 | meta mark & $FWMARK == $FWMARK masquerade 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | ./install-tools.sh 4 | 5 | mkdir -p /etc/wireguard 6 | 7 | private_key=`wg genkey` 8 | public_key=`printf ${private_key} | wg pubkey` 9 | echo "Public Key: ${public_key}" 10 | 11 | printf 'Please enter wireguard configuration name: [wg0] ' 12 | read wg_cfg_name 13 | : ${wg_cfg_name:='wg0'} 14 | 15 | printf 'Please enter address: [192.168.128.2/24] ' 16 | read address 17 | : ${address:='192.168.128.2/24'} 18 | 19 | printf 'Please enter peer public key: ' 20 | read peer_public_key 21 | 22 | printf 'Please enter peer persistent keep-alive (seconds): ' 23 | read keep_alive 24 | 25 | printf 'Please enter endpoint: ' 26 | read endpoint 27 | 28 | printf 'Please enter peer allowed ips: [192.168.128.0/24] ' 29 | read peer_allowed_ips 30 | : ${peer_allowed_ips:='192.168.128.0/24'} 31 | 32 | config_file_path="/etc/wireguard/${wg_cfg_name}.conf" 33 | config=$(printf "\ 34 | [Interface] 35 | Address = ${address} 36 | PrivateKey = ${private_key} 37 | ListenPort = 8443 38 | 39 | [Peer] 40 | PublicKey = ${peer_public_key}\ 41 | $(printf "${keep_alive:+\nPersistentKeepalive = ${keep_alive}}") 42 | Endpoint = ${endpoint} 43 | AllowedIPs = ${peer_allowed_ips}\ 44 | ") 45 | 46 | if [ -f "${config_file_path}" ]; 47 | then 48 | printf "Warning! ${config_file_path} already exists, do you override it? [y/N] " 49 | read confirm 50 | confirm=$(printf "${confirm}" | tr '[:upper:]' '[:lower:]') 51 | if [ "${confirm}" = "y" -o "${confirm}" = "yes" ]; 52 | then 53 | # Ignore 54 | :; 55 | else 56 | echo "${config}" 57 | exit 58 | fi 59 | fi 60 | 61 | echo "${config}" > "${config_file_path}" 62 | echo Saved to ${config_file_path} 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Server side 3 | 4 | Requirements: 5 | 6 | * socat 7 | * nmap 8 | * iptables/ip6tables 9 | 10 | ```sh 11 | # socat EXEC:./server.sh,pty,rawer TCP-LISTEN:12345,bind=192.168.128.1,fork,reuseaddr 12 | ./start-server.sh 13 | # or 14 | sudo ./start-server.sh 15 | ``` 16 | 17 | ## Client side 18 | 19 | ```sh 20 | export ENDPOINT=<...> 21 | export REMOTE_HOSTNAME=192.168.128.1 22 | export REMOTE_PORT=12345 23 | ./start-client.sh 24 | ``` 25 | 26 | ## Alt 27 | 28 | Wireguard over Websocket (*TODO*) 29 | 30 | 1. Install `websocat` 31 | 32 | ```sh 33 | cargo install --features=ssl websocat 34 | ``` 35 | 36 | 2. 37 | 38 | If use nginx as websocat proxy, first configure nginx. 39 | 40 | Server: 41 | 42 | ```sh 43 | websocat --udp-reuseaddr -E -b --restrict-uri / ws-listen:172.17.0.1:8443 udp:127.0.0.1:8443 44 | ``` 45 | 46 | Or direct use websocat, generate pkcs12 cert: 47 | 48 | ```sh 49 | openssl pkcs12 -export -out cert.pkcs12 -inkey key.pem -in cert.pem 50 | ``` 51 | 52 | then 53 | 54 | ```sh 55 | websocat --udp-reuseaddr -E -b --restrict-uri / --pkcs12-der ./cert.pkcs12 --pkcs12-passwd ws-listen:172.17.0.1:443 udp:127.0.0.1:8443 56 | ``` 57 | 58 | Client: 59 | 60 | ```sh 61 | websocat -E --ping-interval 10 --ping-timeout 30 -b udp-listen:127.0.0.1:8443 autoreconnect:wss:// 62 | ``` 63 | or 64 | ```sh 65 | websocat -E --ping-interval 10 --ping-timeout 30 --ws-c-uri ws:// --tls-domain -b udp-listen:127.0.0.1:8443 autoreconnect:ws-c:tls-connect:tcp:: 66 | ``` 67 | 68 | and configure wireguard endpoint as `127.0.0.1:8443`. 69 | 70 | **NOTE:** The websocket server ip must be bypass wireguard. 71 | -------------------------------------------------------------------------------- /doc/server.exmaple.sh: -------------------------------------------------------------------------------- 1 | sysctl -w net.ipv4.ip_forward=1 2 | iptables -t nat -A POSTROUTING -o $I -j MASQUERADE 3 | iptables -t filter -A FORWARD -i wg+ -j ACCEPT 4 | iptables -t filter -A FORWARD -o wg+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 5 | 6 | sysctl -w net.ipv6.conf.all.accept_ra=2 net.ipv6.conf.default.accept_ra=2 net.ipv6.conf.all.forwarding=1 net.ipv6.conf.default.forwarding=1 7 | ip6tables -t nat -A POSTROUTING -o $I -j MASQUERADE 8 | ip6tables -t filter -A FORWARD -i wg+ -j ACCEPT 9 | ip6tables -t filter -A FORWARD -o wg+ -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 10 | 11 | 12 | firewall-cmd --zone=public --add-port=853/upd --permanent 13 | firewall-cmd --zone=public --add-port=8443/tcp --permanent 14 | 15 | firewall-cmd --direct --permanent --add-rule ipv4 nat POSTROUTING 0 -o eth0 -j MASQUERADE 16 | firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -i wg+ -j ACCEPT 17 | firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -o wg+ -m state --state RELATED,ESTABLISHED -j ACCEPT 18 | 19 | firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -i tun+ -j ACCEPT 20 | firewall-cmd --direct --permanent --add-rule ipv4 filter FORWARD 0 -o tun+ -m state --state RELATED,ESTABLISHED -j ACCEPT 21 | 22 | iptables -t filter -A INPUT -p udp --dport 49152:65535 -j ACCEPT 23 | 24 | 25 | # nftables 26 | nft add table inet wg-random-port 27 | nft add chain inet wg-random-port forward \{ type filter hook forward priority filter - 1 \;\} 28 | nft add rule inet wg-random-port forward meta iifname "wg*" accept 29 | nft add rule inet wg-random-port forward ct state \{ established, related \} meta oifname "wg*" accept 30 | nft add chain inet wg-random-port postrouting \{ type nat hook postrouting priority srcnat \;\} 31 | nft add rule inet wg-random-port postrouting meta iifname "wg*" meta oifname != "wg*" masquerade 32 | -------------------------------------------------------------------------------- /server/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NFTABLES_TABLE_NAME="wg-random-${PORT}" 4 | 5 | prestart() { 6 | if command -v nft >/dev/null; 7 | then 8 | cat <<-EOF | nft -f - 9 | table inet ${NFTABLES_TABLE_NAME} 10 | delete table inet ${NFTABLES_TABLE_NAME} 11 | table inet ${NFTABLES_TABLE_NAME} { 12 | chain forward { 13 | type filter hook forward priority filter - 1; 14 | meta iifname ${INTERFACE} accept 15 | ct state { established, related } meta oifname ${INTERFACE} accept 16 | } 17 | chain postrouting { 18 | type nat hook postrouting priority srcnat; 19 | meta iifname ${INTERFACE} meta oifname != ${INTERFACE} masquerade 20 | } 21 | } 22 | EOF 23 | else 24 | iptables -t filter -A FORWARD -i ${INTERFACE} -j ACCEPT 25 | iptables -t filter -A FORWARD -o ${INTERFACE} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 26 | iptables -t nat -A POSTROUTING -i ${INTERFACE} -o !${INTERFACE} -j MASQUERADE 27 | ip6tables -t filter -A FORWARD -i ${INTERFACE} -j ACCEPT 28 | ip6tables -t filter -A FORWARD -o ${INTERFACE} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 29 | ip6tables -t nat -A POSTROUTING -i ${INTERFACE} -o !${INTERFACE} -j MASQUERADE 30 | fi 31 | } 32 | 33 | postend() { 34 | if command -v nft >/dev/null; 35 | then 36 | cat <<-EOF | nft -f - 37 | table inet ${NFTABLES_TABLE_NAME} 38 | delete table inet ${NFTABLES_TABLE_NAME} 39 | EOF 40 | else 41 | iptables -t filter -D FORWARD -i ${INTERFACE} -j ACCEPT 42 | iptables -t filter -D FORWARD -o ${INTERFACE} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 43 | iptables -t nat -D POSTROUTING -i ${INTERFACE} -o !${INTERFACE} -j MASQUERADE 44 | ip6tables -t filter -D FORWARD -i ${INTERFACE} -j ACCEPT 45 | ip6tables -t filter -D FORWARD -o ${INTERFACE} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 46 | ip6tables -t nat -D POSTROUTING -i ${INTERFACE} -o !${INTERFACE} -j MASQUERADE 47 | fi 48 | } 49 | 50 | prestart 51 | trap 'trap postend EXIT' HUP INT QUIT KILL TERM 52 | trap postend EXIT 53 | -------------------------------------------------------------------------------- /client/hook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | : ${FWMARK:=0x00003000} 6 | 7 | usage() { 8 | echo "Usage: $0 [option] " 9 | echo "options:" 10 | echo " -i INTERFACE interface" 11 | echo " -h Help" 12 | } 13 | 14 | while getopts ":i:" option; 15 | do 16 | case $option in 17 | i) 18 | INTERFACE=$OPTARG 19 | ;; 20 | \?) 21 | usage 22 | exit 1 23 | ;; 24 | esac 25 | done 26 | shift `expr $OPTIND - 1` 27 | 28 | if [[ $1 == "" ]] 29 | then 30 | echo "Missing argument " 31 | usage 32 | exit 1 33 | fi 34 | 35 | : ${WG_DEV:=$INTERFACE} 36 | 37 | if [[ -z "$ENDPOINT" ]]; 38 | then 39 | ENDPOINT=$(wg show ${WG_DEV} endpoints | awk '{gsub(/\[|(\]?:[0-9]+$)/, "", $2); print $2}') 40 | fi 41 | 42 | if [[ $ENDPOINT =~ ':' ]] 43 | then 44 | endpoint_is_ipv6=TRUE 45 | else 46 | endpoint_is_ipv6=FALSE 47 | fi 48 | 49 | case $1 in 50 | (up) 51 | cd "$( dirname "${BASH_SOURCE[0]}" )" 52 | nft -f ./domestic.nft 53 | ip -4 route add 0.0.0.0/0 dev $WG_DEV table $FWMARK 54 | ip -6 route add ::/0 dev $WG_DEV table $FWMARK 55 | ip -4 rule add fwmark $FWMARK table $FWMARK 56 | ip -6 rule add fwmark $FWMARK table $FWMARK 57 | ip -4 rule add table main suppress_prefixlength 0 58 | ip -6 rule add table main suppress_prefixlength 0 59 | if [[ $endpoint_is_ipv6 == TRUE ]] 60 | then 61 | ip -6 rule add to $ENDPOINT table main 62 | else 63 | ip -4 rule add to $ENDPOINT table main 64 | fi 65 | ;; 66 | (down) 67 | if [[ $endpoint_is_ipv6 == TRUE ]] 68 | then 69 | ip -6 rule delete to $ENDPOINT table main || true 70 | else 71 | ip -4 rule delete to $ENDPOINT table main || true 72 | fi 73 | ip -6 rule delete table main suppress_prefixlength 0 || true 74 | ip -4 rule delete table main suppress_prefixlength 0 || true 75 | ip -6 rule delete fwmark $FWMARK table $FWMARK || true 76 | ip -4 rule delete fwmark $FWMARK table $FWMARK || true 77 | nft delete table inet wg.domestic || true 78 | ;; 79 | (*) 80 | usage 81 | exit 1 82 | ;; 83 | esac 84 | -------------------------------------------------------------------------------- /client/cn.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | 3 | const https = require('https'); 4 | const { promisify } = require('util'); 5 | const readline = require('readline'); 6 | 7 | const DOWNLOAD_URI = 'https://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest'; 8 | const PRIORITY = 1024; 9 | 10 | async function fetch(uri) { 11 | return new Promise((resolve, reject) => { 12 | https.get(uri, resolve); 13 | }); 14 | } 15 | 16 | async function parse(res) { 17 | const rl = new readline.createInterface({ 18 | input: res, 19 | }); 20 | return new Promise((resolve, reject) => { 21 | const result = []; 22 | rl.on('line', line => { 23 | if (/^#/.test(line)) { 24 | return; 25 | } 26 | const [_registry, cc, type, start, value, _date, _status] = line.split('|'); 27 | if (cc !== 'CN') { 28 | return; 29 | } 30 | if (type === 'ipv4') { 31 | const address = start; 32 | const len = Math.log2(Number(value)); 33 | if (Math.ceil(len) !== len) { 34 | console.error(`!!!!WARNING!!!!: ${address}/${value} can't coverter to CIDR-style range.`); 35 | } 36 | const netmask = 32 - Math.ceil(len); 37 | result.push([address, netmask]); 38 | } else if (type === 'ipv6') { 39 | result.push([start, value]); 40 | } 41 | }); 42 | rl.on('error', reject); 43 | rl.on('close', () => { 44 | result.sort((a, b) => Math.sign(b[1] - a[1])); 45 | resolve(result); 46 | }); 47 | }); 48 | } 49 | 50 | function output(ary) { 51 | console.log(`PRIORITY=$\{PRIORITY:-${PRIORITY}}`); 52 | 53 | const otherAddresses = [ 54 | /** IPv4 **/ 55 | 56 | '1.1.1.1', '1.0.0.1', /* Cloudflare DNS */ 57 | '8.8.8.8', '8.8.4.4', /* Google DNS */ 58 | '9.9.9.9', '149.112.112.112', /* Qua9 DNS */ 59 | 60 | '0.0.0.0/8', /* Current network (From wiki) */ 61 | '127.0.0.0/8', /* Loopback */ 62 | '10.0.0.0/8', /* Private Internet Address */ 63 | '100.64.0.0/10', /* Private Internet Address */ 64 | '169.254.0.0/16', /* APIPA - Automatic Private IP Addressing */ 65 | '172.16.0.0/12', /* Private Internet Address */ 66 | '192.168.0.0/16', /* 192.168.0.0-192.168.255.255 */ 67 | '198.18.0.0/15', /* Private network */ 68 | '224.0.0.0/4', /* IP multicast */ 69 | '192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', /* Documentation */ 70 | '192.88.99.0/24', /* 6to4 */ 71 | '255.255.255.255/32', 72 | 73 | /** IPv6 **/ 74 | '::/8', /* Loopback */ 75 | 'fc00::/7', /* Unique local addresses */ 76 | 'fe80::/10', /* Link-local addresses */ 77 | 'fec0::/10', /* Site-local addresses */ 78 | 'ff00::/8', /* Multicast addresses */ 79 | '100::/64', /* Discard prefix */ 80 | '2001:0000::/32', /* Teredo tunneling */ 81 | '2001:0002::/48', /* Benchmarking, Documentation */ 82 | '2001:0010::/28', /* ORCHID */ 83 | '2001:0020::/28', /* ORCHIDv2 */ 84 | '2001:db8::/32', /* Documentation */ 85 | '2002::/16', /* 6to4 */ 86 | ]; 87 | for (const r of otherAddresses) { 88 | const type = r.indexOf(':') >= 0 ? '6' : '4'; 89 | console.log(`ip -${type} rule add to ${r} priority $\{PRIORITY}`); 90 | } 91 | 92 | for (const [address, netmask] of ary) { 93 | const type = address.indexOf(':') >= 0 ? '6' : '4'; 94 | console.log(`ip -${type} rule add to ${address}/${netmask} priority $\{PRIORITY}`); 95 | } 96 | } 97 | 98 | fetch(DOWNLOAD_URI).then(parse).then(output); 99 | -------------------------------------------------------------------------------- /client/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ENDPOINT=${ENDPOINT:-"example.org"} 4 | REMOTE_HOSTNAME=${REMOTE_HOSTNAME:-"192.168.128.1"} 5 | REMOTE_PORT=${REMOTE_PORT:-12345} 6 | IPV4_ADDRESS=${IPV4_ADDRESS:-"192.168.129.254/24"} 7 | IPV6_ADDRESS=${IPV6_ADDRESS:-"fdff:eedd:ccbb::ffff/64"} 8 | MTU=${MTU:-1420} 9 | 10 | INTERFACE=${INTERFACE:-"wg1"} 11 | RND_INTERFACE="${INTERFACE}rnd" 12 | CONFIG_FILE_DIR=$(mktemp -d) 13 | chmod 0700 ${CONFIG_FILE_DIR} 14 | 15 | CONFIG_FILE_PATH="${CONFIG_FILE_DIR}/${RND_INTERFACE}.conf" 16 | 17 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 18 | 19 | __cleanup() { 20 | ret=$? 21 | wg-quick down ${INTERFACE} 2>/dev/null 22 | wg-quick down "${CONFIG_FILE_PATH}" 23 | [[ -d "${CONFIG_FILE_DIR}" ]] && rm -r "${CONFIG_FILE_DIR}" 24 | exit $ret 25 | } 26 | 27 | trap '__=$? && trap __cleanup EXIT && exit $__' HUP INT QUIT KILL TERM 28 | trap __cleanup EXIT 29 | 30 | exists() { 31 | command -v "$1" >/dev/null 32 | return $? 33 | } 34 | 35 | if exists nft 36 | then 37 | hooks=$(cat <&2 57 | 58 | coproc CONN { 59 | if exists socat 60 | then 61 | socat - tcp:${REMOTE_HOSTNAME}:${REMOTE_PORT},connect-timeout=10 62 | elif exists ncat 63 | then 64 | ncat -w 10s ${REMOTE_HOSTNAME} ${REMOTE_PORT} 65 | elif exists nc 66 | then 67 | nc -w 10 ${REMOTE_HOSTNAME} ${REMOTE_PORT} 68 | else 69 | echo "No found netcat/nmap/socat installed, please install anyone of them!" >&2 70 | exit 1 71 | fi 72 | } 73 | echo "INIT" "${id}" "${public_key}" >&"${CONN[1]}" 74 | while read -r line || [[ -n ${line} ]]; 75 | do 76 | echo $line 77 | read -r method rid remote_pubkey port <<<${line} 78 | 79 | if [[ "${method}" != "OK" ]]; 80 | then 81 | echo Invalid response: ${line}! >&2 82 | continue 83 | fi 84 | 85 | if [[ "${rid}" != "${id}" ]]; 86 | then 87 | echo Invalid id: ${rid}! >&2 88 | continue 89 | fi 90 | 91 | break; 92 | done <&"${CONN[0]}" 93 | [[ -n "$CONN_PID" ]] && kill "$CONN_PID" 94 | 95 | [[ -z "$remote_pubkey" ]] && echo "Could not fetch remote_pubkey!" >&2 && return 1 96 | [[ -z "$port" ]] && echo "Could not fetch port!" >&2 && return 1 97 | 98 | conf=$(cat < "${CONFIG_FILE_PATH}" 115 | } 116 | 117 | wg-quick down $INTERFACE 2>/dev/null 118 | while true; 119 | do 120 | echo Updating... 121 | wg-quick down "${CONFIG_FILE_PATH}" 2>/dev/null 122 | wg-quick up $INTERFACE 123 | up=$? 124 | [[ $up -ne 0 ]] && { 125 | echo "Error: Could not up $INTERFACE!" >&2 126 | exit 1 127 | } 128 | update 129 | success=$? 130 | wg-quick down $INTERFACE 2>/dev/null 131 | if [[ $success -eq 0 ]]; 132 | then 133 | wg-quick up "${CONFIG_FILE_PATH}" 134 | echo Updated! 135 | #sleep $((300 + $RANDOM % 600)) # 5~15 minutes 136 | sleep 100000 137 | else 138 | echo "Could not up ${CONFIG_FILE_PATH}!" 139 | echo "Try again after 10 seconds..." 140 | sleep 10 141 | fi 142 | done 143 | -------------------------------------------------------------------------------- /client/generate-nftables-ip-sets.js: -------------------------------------------------------------------------------- 1 | #!/bin/env node 2 | 3 | const net = require('net'); 4 | const https = require('https'); 5 | const { promisify } = require('util'); 6 | const readline = require('readline'); 7 | 8 | const DOWNLOAD_URI = 'https://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest'; 9 | const PRIORITY = 1024; 10 | 11 | async function fetch(uri) { 12 | return new Promise((resolve, reject) => { 13 | https.get(uri, resolve); 14 | }); 15 | } 16 | 17 | function ip2Int(ip) { 18 | const ary = ip.split('.'); 19 | return (ary[0] << 24 | ary[1] << 16 | ary[2] << 8 | ary[3]) >>> 0; 20 | } 21 | function int2Ip(int) { 22 | return [int >>> 24, int >>> 16 & 0xff, int >>> 8 & 0xff, int & 0xff].join('.'); 23 | } 24 | function ipRange2Cidr([start, end]) { 25 | const value = end - start + 1; 26 | const len = Math.log2(value); 27 | const address = int2Ip(start); 28 | if (Math.ceil(len) !== len) { 29 | console.error(`!!!!WARNING!!!!: ${address},${value} can't coverter to CIDR-style range.`); 30 | } 31 | const netmask = 32 - Math.ceil(len); 32 | return [address, netmask]; 33 | } 34 | 35 | async function parse(res) { 36 | const rl = new readline.createInterface({ 37 | input: res || process.stdin, 38 | }); 39 | return new Promise((resolve, reject) => { 40 | const result = [[], []]; 41 | const ipv4 = []; 42 | rl.on('line', line => { 43 | if (/^#/.test(line)) { 44 | return; 45 | } 46 | const [_registry, cc, type, start, value, _date, _status] = line.split('|'); 47 | if (cc !== 'CN') { 48 | return; 49 | } 50 | if (type === 'ipv4') { 51 | const _ = ip2Int(start); 52 | ipv4.push([_, _ + Number(value) - 1]); 53 | } else if (type === 'ipv6') { 54 | result[1].push([start, value].join('/')); 55 | } 56 | }); 57 | rl.on('error', reject); 58 | rl.on('close', () => { 59 | ipv4.sort((a, b) => Math.sign(a[0] - b[0])); 60 | const last = ipv4.reduce((prev, curr) => { 61 | if (!prev) { 62 | return curr; 63 | } 64 | if (curr[0] === prev[1] + 1) { 65 | console.error(`merged: ${int2Ip(prev[0])}-${int2Ip(curr[1])}`); 66 | return [prev[0], curr[1]]; 67 | } else { 68 | result[0].push(prev.map(int2Ip).join('-')); 69 | return curr; 70 | } 71 | }, null); 72 | result.push(last.map(int2Ip).join('-')); 73 | result.sort((a, b) => Math.sign(b[1] - a[1])); 74 | resolve(result); 75 | }); 76 | }); 77 | } 78 | 79 | function output(ary) { 80 | console.log(` 81 | #!/usr/bin/nft -f 82 | # Auto generated by generate-nftables-set.js on ${new Date()} 83 | `.trim()); 84 | 85 | const otherAddresses = [ 86 | /** IPv4 **/ 87 | //'1.1.1.1', '1.0.0.1', /* Cloudflare DNS */ 88 | //'8.8.8.8', '8.8.4.4', /* Google DNS */ 89 | //'9.9.9.9', '149.112.112.112', /* Qua9 DNS */ 90 | 91 | '0.0.0.0/8', /* Current network (From wiki) */ 92 | '127.0.0.0/8', /* Loopback */ 93 | '10.0.0.0/8', /* Private Internet Address */ 94 | '100.64.0.0/10', /* Private Internet Address */ 95 | '169.254.0.0/16', /* APIPA - Automatic Private IP Addressing */ 96 | '172.16.0.0/12', /* Private Internet Address */ 97 | '192.168.0.0/16', /* 192.168.0.0-192.168.255.255 */ 98 | '198.18.0.0/15', /* Private network */ 99 | '224.0.0.0/4', /* IP multicast */ 100 | '192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', /* Documentation */ 101 | '192.88.99.0/24', /* 6to4 */ 102 | '255.255.255.255/32', 103 | 104 | /** IPv6 **/ 105 | '::/8', /* Loopback */ 106 | 'fc00::/7', /* Unique local addresses */ 107 | 'fe80::/10', /* Link-local addresses */ 108 | 'fec0::/10', /* Site-local addresses */ 109 | 'ff00::/8', /* Multicast addresses */ 110 | '100::/64', /* Discard prefix */ 111 | '2001:0000::/32', /* Teredo tunneling */ 112 | '2001:0002::/48', /* Benchmarking, Documentation */ 113 | '2001:0010::/28', /* ORCHID */ 114 | '2001:0020::/28', /* ORCHIDv2 */ 115 | '2001:db8::/32', /* Documentation */ 116 | '2002::/16', /* 6to4 */ 117 | ]; 118 | console.log(''); 119 | console.log('define FWMARK = 0x00003000'); 120 | console.log(`\ 121 | define IPV4_ELEMENTS = { 122 | ${otherAddresses.filter(addr => addr.indexOf('.') !== -1).map(addr => addr.padStart(4 + addr.length, ' ')).join(',\n')}, 123 | ${ary[0].map(addr => addr.padStart(4 + addr.length, ' ')).join(',\n')}, 124 | } 125 | `.trimEnd()); 126 | console.log(`\ 127 | define IPV6_ELEMENTS = { 128 | ${otherAddresses.filter(addr => addr.indexOf(':') !== -1).map(addr => addr.padStart(4 + addr.length, ' ')).join(',\n')}, 129 | ${ary[1].map(addr => addr.padStart(4 + addr.length, ' ')).join(',\n')}, 130 | } 131 | `.trimEnd()); 132 | } 133 | 134 | fetch(DOWNLOAD_URI).then(parse).then(output); 135 | --------------------------------------------------------------------------------