├── .gitignore ├── Makefile ├── wireguard ├── ping.sh ├── danted.conf ├── tinyproxy.conf ├── gen-proxy-config.sh ├── Dockerfile.wireguard ├── wg-client-up.sh ├── gen-wg-client-config.sh └── wireguard-ip-calculator.py ├── cloak ├── ck-client-up.sh ├── Dockerfile.cloak └── gen-ck-client-config.sh ├── doc ├── shadow-client.service ├── shadow-client-snap.service └── env.txt ├── docker-compose.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | start: 2 | docker-compose up --build 3 | 4 | stop: 5 | docker-compose down -------------------------------------------------------------------------------- /wireguard/ping.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while true 4 | do 5 | random_delay=$((MIN_PING_DELAY + RANDOM % (MAX_PING_DELAY - MIN_PING_DELAY + 1))) 6 | ping -c 1 ${PING_HOST} 2>&1 > /dev/null 7 | sleep ${random_delay} 8 | done 9 | -------------------------------------------------------------------------------- /cloak/ck-client-up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "${REMOTE_IP}" ]; then 6 | echo "Error: environment variable REMOTE_IP is required." 7 | exit 1 8 | fi 9 | 10 | /gen-ck-client-config.sh 11 | 12 | ./ck-client -u -c ./ck-client.json -s ${REMOTE_IP} -i 0.0.0.0 & 13 | 14 | # Save PID of ck-client process 15 | PID=$! 16 | 17 | # Wait for SIGTERM signal 18 | trap 'kill -TERM $PID' TERM 19 | wait $PID -------------------------------------------------------------------------------- /doc/shadow-client.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Shadow VPN client 3 | Requires=docker.service 4 | After=docker.service 5 | 6 | [Service] 7 | Type=simple 8 | Restart=on-failure 9 | WorkingDirectory=/root/shadow-client 10 | ExecStart=/usr/local/bin/docker-compose up --build 11 | ExecReload=/usr/local/bin/docker-compose down && /usr/local/bin/docker-compose up --build 12 | ExecStop=/usr/local/bin/docker-compose down 13 | 14 | [Install] 15 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /doc/shadow-client-snap.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Shadow VPN client 3 | Requires=snap.docker.dockerd.service 4 | After=snap.docker.dockerd.service 5 | 6 | [Service] 7 | Type=simple 8 | Restart=on-failure 9 | WorkingDirectory=/root/shadow-client 10 | ExecStart=/snap/bin/docker-compose up --build 11 | ExecReload=/snap/bin/docker-compose down && /snap/bin/docker-compose up --build 12 | ExecStop=/snap/bin/docker-compose down 13 | 14 | [Install] 15 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /cloak/Dockerfile.cloak: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update && apt-get install -y wget iproute2 iptables 4 | 5 | RUN wget https://github.com/cbeuw/Cloak/releases/download/v2.7.0/ck-client-linux-amd64-v2.7.0 6 | RUN mv ck-client-linux-amd64-v2.7.0 ck-client 7 | RUN chmod +x ck-client 8 | 9 | COPY ck-client-up.sh /ck-client-up.sh 10 | RUN chmod +x /ck-client-up.sh 11 | 12 | COPY gen-ck-client-config.sh /gen-ck-client-config.sh 13 | RUN chmod +x /gen-ck-client-config.sh 14 | 15 | CMD ["/ck-client-up.sh"] -------------------------------------------------------------------------------- /wireguard/danted.conf: -------------------------------------------------------------------------------- 1 | logoutput: stderr 2 | internal: 0.0.0.0 port = PROXY_SOCKS_PORT 3 | external: EXTERNAL_INTERFACE 4 | socksmethod: username none 5 | clientmethod: none 6 | user.privileged: proxy 7 | user.unprivileged: nobody 8 | user.libwrap: nobody 9 | 10 | client pass { 11 | from: 0.0.0.0/0 to: 0.0.0.0/0 12 | } 13 | socks pass { 14 | from: 0.0.0.0/0 to: 0.0.0.0/0 15 | command: bind connect udpassociate 16 | } 17 | socks pass { 18 | from: 0.0.0.0/0 to: 0.0.0.0/0 19 | command: bindreply udpreply 20 | } 21 | -------------------------------------------------------------------------------- /wireguard/tinyproxy.conf: -------------------------------------------------------------------------------- 1 | User tinyproxy 2 | Group tinyproxy 3 | 4 | Port PROXY_HTTP_PORT 5 | Listen 0.0.0.0 6 | Timeout 600 7 | 8 | DefaultErrorFile "/usr/share/tinyproxy/default.html" 9 | StatFile "/usr/share/tinyproxy/stats.html" 10 | LogFile "/var/log/tinyproxy/tinyproxy.log" 11 | LogLevel Info 12 | PidFile "/run/tinyproxy/tinyproxy.pid" 13 | 14 | MaxClients 1000 15 | 16 | Allow 127.0.0.1 17 | Allow ::1 18 | Allow 192.168.0.0/16 19 | Allow 172.16.0.0/12 20 | Allow 10.0.0.0/8 21 | 22 | ViaProxyName "tinyproxy" 23 | 24 | ConnectPort 443 25 | ConnectPort 563 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /wireguard/gen-proxy-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -n "${PROXY_SOCKS_PORT}" ]; then 6 | DEFAULT_INTERFACE=$(ip -o -4 route show to default | awk '{print $5}') 7 | sed -i "s/EXTERNAL_INTERFACE/${DEFAULT_INTERFACE}/g" ./danted.conf 8 | echo "Danted using interface: ${DEFAULT_INTERFACE}" 9 | 10 | sed -i "s/PROXY_SOCKS_PORT/${PROXY_SOCKS_PORT}/g" ./danted.conf 11 | echo "Danted socks port: ${PROXY_SOCKS_PORT}" 12 | fi 13 | 14 | if [ -n "${PROXY_HTTP_PORT}" ]; then 15 | sed -i "s/PROXY_HTTP_PORT/${PROXY_HTTP_PORT}/g" ./tinyproxy.conf 16 | echo "Tinyproxy http port: ${PROXY_HTTP_PORT}" 17 | fi -------------------------------------------------------------------------------- /wireguard/Dockerfile.wireguard: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | RUN apt-get update && apt-get install -y wireguard openresolv iproute2 iptables python3-pip dnsutils tinyproxy dante-server iputils-ping 4 | RUN pip install netaddr 5 | 6 | COPY wireguard-ip-calculator.py /wireguard-ip-calculator.py 7 | 8 | COPY wg-client-up.sh /wg-client-up.sh 9 | RUN chmod +x /wg-client-up.sh 10 | 11 | COPY gen-wg-client-config.sh /gen-wg-client-config.sh 12 | RUN chmod +x /gen-wg-client-config.sh 13 | 14 | COPY gen-proxy-config.sh /gen-proxy-config.sh 15 | RUN chmod +x /gen-proxy-config.sh 16 | 17 | COPY ping.sh /ping.sh 18 | RUN chmod +x /ping.sh 19 | 20 | COPY danted.conf /danted.conf 21 | COPY tinyproxy.conf /tinyproxy.conf 22 | 23 | CMD ["/wg-client-up.sh"] -------------------------------------------------------------------------------- /wireguard/wg-client-up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | _term() { 6 | echo "Request to STOP received" 7 | wg-quick down /wg0.conf 8 | echo "STOPPED" 9 | kill -TERM "$child" 2>/dev/null 10 | } 11 | 12 | /gen-proxy-config.sh 13 | 14 | if [ -n "${PROXY_SOCKS_PORT}" ]; then 15 | tinyproxy -c /tinyproxy.conf 16 | echo "Tinyproxy started" 17 | fi 18 | 19 | if [ -n "${PROXY_HTTP_PORT}" ]; then 20 | danted -D -f /danted.conf 21 | echo "Danted started" 22 | fi 23 | 24 | /gen-wg-client-config.sh 25 | wg-quick up /wg0.conf 26 | 27 | if [ -n "${PING_HOST}" ]; then 28 | if [ -z "${MIN_PING_DELAY}" ]; then 29 | export MIN_PING_DELAY=10 30 | echo "MIN_PING_DELAY not set, using default value: ${MIN_PING_DELAY}" 31 | fi 32 | if [ -z "${MAX_PING_DELAY}" ]; then 33 | export MAX_PING_DELAY=360 34 | echo "MAX_PING_DELAY not set, using default value: ${MAX_PING_DELAY}" 35 | fi 36 | 37 | /ping.sh & 38 | fi 39 | 40 | # Sets up a handler for the SIGTERM signal to gracefully terminate a process 41 | trap _term SIGTERM 42 | # Displays information about the current WireGuard interface state 43 | #wg show 44 | # Launches an indefinitely sleeping process in the background 45 | sleep infinity & 46 | # Stores the process ID of the background sleep process in the variable child 47 | child=$! 48 | # Pauses the script until the sleep process, stored in child, completes 49 | wait "$child" 50 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | wireguard: 4 | build: 5 | context: ./wireguard 6 | dockerfile: Dockerfile.wireguard 7 | restart: unless-stopped 8 | depends_on: 9 | - cloak 10 | logging: 11 | driver: journald 12 | cap_add: 13 | - NET_ADMIN 14 | - SYS_ADMIN 15 | - SYS_MODULE 16 | sysctls: 17 | - net.ipv4.conf.all.src_valid_mark=1 18 | - net.ipv4.ip_forward=1 19 | networks: 20 | shadow: 21 | ipv4_address: ${VPN_GATEWAY} 22 | environment: 23 | - WG_CLIENT_PRIVATE_KEY 24 | - WG_SERVER_PUBLIC_KEY 25 | - WG_DNS 26 | - WG_ALLOWED_IPS 27 | - WG_EXCLUDED_DOMAINS 28 | - WG_EXCLUDED_IPS 29 | - WG_MTU 30 | - CK_IP 31 | - PROXY_SOCKS_PORT 32 | - PROXY_HTTP_PORT 33 | - MIN_PING_DELAY 34 | - MAX_PING_DELAY 35 | - PING_HOST 36 | 37 | cloak: 38 | build: 39 | context: ./cloak 40 | dockerfile: Dockerfile.cloak 41 | restart: unless-stopped 42 | logging: 43 | driver: journald 44 | networks: 45 | shadow: 46 | ipv4_address: ${CK_IP} 47 | environment: 48 | - CK_UID 49 | - CK_PUBLIC_KEY 50 | - CK_TRANSPORT 51 | - CK_ENCRYPTION_METHOD 52 | - CK_NUM_CONN 53 | - CK_KEEP_ALIVE 54 | - CK_BROWSER_SIG 55 | - CK_STREAM_TIMEOUT 56 | - CK_SERVER_NAME 57 | - CK_ALTERNATIVE_NAMES 58 | - REMOTE_IP 59 | 60 | networks: 61 | shadow: 62 | name: shadow 63 | driver: macvlan 64 | driver_opts: 65 | parent: ${PARENT_INTERFACE} 66 | ipam: 67 | config: 68 | - subnet: ${SUBNET} 69 | ip_range: ${SUBNET} # can be same as subnet because we use only two fixed ip addresses ${VPN_GATEWAY} and ${CK_IP} 70 | gateway: ${LAN_GATEWAY} -------------------------------------------------------------------------------- /doc/env.txt: -------------------------------------------------------------------------------- 1 | # ip address of remote vpn server 2 | REMOTE_IP= 3 | # default interface on host (eth0). 4 | PARENT_INTERFACE= 5 | # lan subnet (192.168.1.0/24) 6 | SUBNET= 7 | # lan gateway (192.168.1.1) 8 | LAN_GATEWAY= 9 | # gateway for lan clients to access vpn. must be in lan subnet and not used by any other device 10 | # it's ip of wireguard server (192.168.1.11) 11 | VPN_GATEWAY= 12 | # ip address of cloak server. must be in lan subnet and not used by any other device (192.168.1.12) 13 | CK_IP= 14 | # private key from wireguard client 15 | WG_CLIENT_PRIVATE_KEY= 16 | # public key from wireguard server 17 | WG_SERVER_PUBLIC_KEY= 18 | # uid from cloak server 19 | CK_UID= 20 | # public key from cloak server 21 | CK_PUBLIC_KEY= 22 | 23 | # cloak transport method (direct,cdn). optional 24 | CK_TRANSPORT=direct 25 | # cloak encryption method (aes-128-gcm, aes-256-gcm, chacha20-poly1305). optional 26 | CK_ENCRYPTION_METHOD=aes-128-gcm 27 | # cloak number of connections (>=4). optional 28 | CK_NUM_CONN=100 29 | # fake browser signature (chrome, firefox). optional 30 | CK_BROWSER_SIG=chrome 31 | # cloak stream timeout. optional 32 | CK_STREAM_TIMEOUT=300 33 | # cloak fake server name. optional 34 | CK_SERVER_NAME=cloudflare.com 35 | # cloak fake alternative names. optional 36 | CK_ALTERNATIVE_NAMES=github.com, bing.com, google.com, microsoft.com, intel.com, stackoverflow.com, leetcode.com 37 | # cloak KeepAlive timeout in seconds. optional 38 | CK_KEEP_ALIVE=0 39 | 40 | # dns servers. optional 41 | WG_DNS=8.8.8.8,8.8.4.4 42 | # allowed ips. optional 43 | WG_ALLOWED_IPS=0.0.0.0/0 44 | # excluded hosts. optional (domain1.com,domain2.net) 45 | WG_EXCLUDED_DOMAINS= 46 | # excluded ips. optional 47 | WG_EXCLUDED_IPS 48 | 49 | # MTU for wireguard interface. optional 50 | WG_MTU=1420 51 | 52 | # Socks5 proxy port (danted server). optional 53 | PROXY_SOCKS_PORT=1080 54 | # Http proxy port (tinyproxy server). optional 55 | PROXY_HTTP_PORT=8888 56 | 57 | # Host for ping to maintain internet connection activity. optional 58 | PING_HOST=8.8.8.8 59 | # Minimal delay between pings to remote server in seconds. optional 60 | MIN_PING_DELAY=10 61 | # Maximal delay between pings to remote server in seconds. optional 62 | MAX_PING_DELAY=360 -------------------------------------------------------------------------------- /wireguard/gen-wg-client-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "${WG_CLIENT_PRIVATE_KEY}" ]; then 6 | echo "Error: environment variable WG_CLIENT_PRIVATE_KEY is required." 7 | exit 1 8 | fi 9 | if [ -z "${WG_SERVER_PUBLIC_KEY}" ]; then 10 | echo "Error: environment variable WG_SERVER_PUBLIC_KEY is required." 11 | exit 1 12 | fi 13 | if [ -z "${CK_IP}" ]; then 14 | echo "Error: environment variable CK_IP is required." 15 | exit 1 16 | fi 17 | 18 | if [ -z "${WG_DNS}" ]; then 19 | WG_DNS="8.8.8.8,8.8.4.4" 20 | echo "WG_DNS is not set. Defaulting to ${WG_DNS}." 21 | fi 22 | if [ -z "${WG_MTU}" ]; then 23 | WG_MTU=1420 24 | echo "WG_MTU is not set. Defaulting to ${WG_MTU}." 25 | fi 26 | if [ -z "${WG_ALLOWED_IPS}" ]; then 27 | WG_ALLOWED_IPS="0.0.0.0/0" 28 | echo "WG_ALLOWED_IPS is not set. Defaulting to ${WG_ALLOWED_IPS}." 29 | fi 30 | 31 | if [ -n "${WG_EXCLUDED_DOMAINS}" ] || [ -n "${WG_EXCLUDED_IPS}" ]; then 32 | if [ -n "${WG_EXCLUDED_IPS}" ]; then 33 | EXCLUDED_IPS=${WG_EXCLUDED_IPS}"," 34 | else 35 | EXCLUDED_IPS="" 36 | fi 37 | IFS=',' read -ra DOMAIN_ARRAY <<< "$WG_EXCLUDED_DOMAINS" 38 | 39 | for DOMAIN in "${DOMAIN_ARRAY[@]}" 40 | do 41 | IP=$(dig +short "$DOMAIN" | tr '\n' ',' | sed 's/,$//') 42 | if [ -z "${IP}" ] || [[ ! "${IP}" =~ ^[0-9,.]+$ ]]; then 43 | echo "IP for domain ${DOMAIN} not found. Skipping." 44 | continue 45 | fi 46 | 47 | EXCLUDED_IPS="$EXCLUDED_IPS$IP," 48 | echo "IPs for domain ${DOMAIN} are ${IP}." 49 | done 50 | EXCLUDED_IPS="${EXCLUDED_IPS%,}" 51 | echo "EXCLUDED_IPS calculated to be ${EXCLUDED_IPS}." 52 | if [ -n "${EXCLUDED_IPS}" ]; then 53 | WG_ALLOWED_IPS=$(python3 wireguard-ip-calculator.py -a ${WG_ALLOWED_IPS} -d ${EXCLUDED_IPS}) 54 | fi 55 | fi 56 | 57 | echo "ALLOWED_IPS is set to ${WG_ALLOWED_IPS}." 58 | 59 | cat < wg0.conf 60 | [Interface] 61 | PrivateKey = ${WG_CLIENT_PRIVATE_KEY} 62 | Address = 10.66.66.2/32 63 | DNS = ${WG_DNS} 64 | MTU = ${WG_MTU} 65 | 66 | PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE 67 | PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE 68 | 69 | [Peer] 70 | PublicKey = ${WG_SERVER_PUBLIC_KEY} 71 | Endpoint = ${CK_IP}:1984 72 | AllowedIPs = ${WG_ALLOWED_IPS} 73 | EOF 74 | 75 | echo "Configuration file wg0.conf created successfully." -------------------------------------------------------------------------------- /cloak/gen-ck-client-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "${CK_UID}" ]; then 6 | echo "Error: environment variable CK_UID is required." 7 | exit 1 8 | fi 9 | 10 | if [ -z "${CK_PUBLIC_KEY}" ]; then 11 | echo "Error: environment variable CK_PUBLIC_KEY is required." 12 | exit 1 13 | fi 14 | 15 | if [ -z "${CK_TRANSPORT}" ]; then 16 | CK_TRANSPORT="direct" # direct, cdn 17 | echo "CK_TRANSPORT is not set. Defaulting to ${CK_TRANSPORT}." 18 | fi 19 | 20 | if [ -z "${CK_ENCRYPTION_METHOD}" ]; then 21 | CK_ENCRYPTION_METHOD="aes-128-gcm" # aes-128-gcm, aes-256-gcm, chacha20-poly1305 22 | echo "CK_ENCRYPTION_METHOD is not set. Defaulting to ${CK_ENCRYPTION_METHOD}." 23 | fi 24 | 25 | if [ -z "${CK_NUM_CONN}" ]; then 26 | CK_NUM_CONN=4 27 | echo "CK_NUM_CONN is not set. Defaulting to ${CK_NUM_CONN}." 28 | fi 29 | 30 | if [ -z "${CK_KEEP_ALIVE}" ]; then 31 | CK_KEEP_ALIVE=0 32 | echo "CK_KEEP_ALIVE is not set. Defaulting to ${CK_KEEP_ALIVE}." 33 | fi 34 | 35 | if [ -z "${CK_BROWSER_SIG}" ]; then 36 | CK_BROWSER_SIG="chrome" 37 | echo "CK_BROWSER_SIG is not set. Defaulting to ${CK_BROWSER_SIG}." 38 | fi 39 | 40 | if [ -z "${CK_STREAM_TIMEOUT}" ]; then 41 | CK_STREAM_TIMEOUT=300 42 | echo "CK_STREAM_TIMEOUT is not set. Defaulting to ${CK_STREAM_TIMEOUT}." 43 | fi 44 | 45 | if [ -z "${CK_SERVER_NAME}" ]; then 46 | CK_SERVER_NAME="cloudflare.com" 47 | echo "CK_SERVER_NAME is not set. Defaulting to ${CK_SERVER_NAME}." 48 | fi 49 | 50 | if [ -z "${CK_ALTERNATIVE_NAMES}" ]; then 51 | CK_ALTERNATIVE_NAMES='github.com, bing.com, google.com, microsoft.com, intel.com, stackoverflow.com, leetcode.com' 52 | echo "CK_ALTERNATIVE_NAMES is not set. Defaulting to: ${CK_ALTERNATIVE_NAMES}." 53 | fi 54 | CK_ALTERNATIVE_NAMES=$(echo "${CK_ALTERNATIVE_NAMES}" | sed 's/ //g') # Remove spaces 55 | CK_ALTERNATIVE_NAMES=$(echo "${CK_ALTERNATIVE_NAMES}" | sed 's/,/","/g') # Add quotes 56 | CK_ALTERNATIVE_NAMES="\"${CK_ALTERNATIVE_NAMES}\"" # Add quotes to the beginning and end 57 | 58 | cat < ck-client.json 59 | { 60 | "Transport": "${CK_TRANSPORT}", 61 | "ProxyMethod": "wireguard", 62 | "EncryptionMethod": "${CK_ENCRYPTION_METHOD}", 63 | "UID": "${CK_UID}", 64 | "PublicKey": "${CK_PUBLIC_KEY}", 65 | "ServerName": "${CK_SERVER_NAME}", 66 | "AlternativeNames": [${CK_ALTERNATIVE_NAMES}], 67 | "NumConn": ${CK_NUM_CONN}, 68 | "KeepAlive": ${CK_KEEP_ALIVE}, 69 | "BrowserSig": "${CK_BROWSER_SIG}", 70 | "StreamTimeout": ${CK_STREAM_TIMEOUT} 71 | } 72 | EOF 73 | 74 | echo "Configuration file ck-client.json created successfully." -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloak + WireGuard + Docker + LAN gateway (client-side) 2 | 3 | ## Client-side setup for internet access through a separate gateway in the local network. Server-side here 4 | 5 | ### The first step is to install the server-side component because during its installation, encryption keys are generated, which will be needed here. 6 | 7 | Data flows through the following chain: 8 | 9 | - Computer (LAN) with the client part of this configuration specified as gateway or proxy server 10 | - Gateway (LAN) 11 | - WireGuard client (LAN) 12 | - Cloak client (LAN) 13 | - Censored Internet 14 | - Cloak server (remote) 15 | - WireGuard server (remote) 16 | - Free Internet 17 | 18 | For simplicity, all operations are performed as root, using Ubuntu 22.04 as an example. All settings are for IPv4 only. 19 | 20 | ## Go to the home folder 21 | 22 | ```bash 23 | cd /root 24 | ``` 25 | 26 | ## docker setup 27 | 28 | ### Install docker Manually 29 | 30 | Install docker manually using manual at + install docker-compose: 31 | 32 | ```bash 33 | apt update && apt install -y ca-certificates curl gnupg && \ 34 | install -m 0755 -d /etc/apt/keyrings && \ 35 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes && \ 36 | chmod a+r /etc/apt/keyrings/docker.gpg && \ 37 | if [ ! -e /etc/apt/sources.list.d/docker.list ]; then 38 | echo \ 39 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ 40 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 41 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 42 | fi && \ 43 | apt update && \ 44 | apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin && \ 45 | wget https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-linux-x86_64 && \ 46 | mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose && \ 47 | chmod +x /usr/local/bin/docker-compose 48 | ``` 49 | 50 | ### Alternatively, install docker via snap (in Ubuntu 22.04, it is not working at the moment) 51 | 52 | ```bash 53 | apt install -y snapd && snap install docker 54 | ``` 55 | 56 | ## Setup 57 | 58 | ### Download this repository 59 | 60 | ```bash 61 | git clone https://github.com/n-r-w/shadow-client.git && cd shadow-client 62 | ``` 63 | 64 | ### Set up environment variables for docker 65 | 66 | In the doc directory there is an example file with environment variables ```env.txt```. Copy it to the ```.env``` file, which contains environment variables for ```docker-compose``` 67 | 68 | ```bash 69 | apt install -y nano && \ 70 | cp ./doc/env.txt ./.env && \ 71 | nano ./.env 72 | ``` 73 | 74 | Setting the values ​​of the variables 75 | 76 | - ```REMOTE_IP``` ip address of 77 | - ```PARENT_INTERFACE``` default interface on host 78 | - ```SUBNET``` LAN subnet 79 | - ```LAN_GATEWAY``` LAN gateway 80 | - ```VPN_GATEWAY``` gateway for lan clients to access vpn. must be in LAN subnet and not used by any other LAN device. It's ip of wireguard server 81 | - ```CK_IP``` ip address of cloak server. must be in LAN subnet and not used by any other LAN device 82 | 83 | Encryption keys that were generated earlier during the server installation process: 84 | 85 | - ```WG_CLIENT_PRIVATE_KEY``` wireguard client private key 86 | - ```WG_SERVER_PUBLIC_KEY``` wireguard server public key 87 | - ```CK_UID```cloak client UID 88 | - ```CK_PUBLIC_KEY``` cloak server public key 89 | 90 | If it is necessary to exclude certain domains from the VPN: 91 | 92 | - Specify their names in the ```WG_EXCLUDED_DOMAINS``` variable 93 | - If necessary, specify the IP in the ```WG_EXCLUDED_IPS``` variable 94 | 95 | Rules for exclusions are generated at startup, so after changing ```WG_EXCLUDED_DOMAINS```/```WG_EXCLUDED_IPS```, it is necessary to reboot the operating system (better) or restart the wireguard container. 96 | 97 | If you want to use a proxy server, specify variables: 98 | 99 | - ```PROXY_SOCKS_PORT``` danted server will be launched on this port 100 | - ```PROXY_HTTP_PORT``` tinyproxy server will be launched on this port 101 | 102 | Despite the presence of the cloak keepalive option, the connection to the cloak server may be interrupted due to inactivity and then not restored. To solve this problem, you can use the following environment variables. Ping will be performed in a random range from MIN_PING_DELAY to MAX_PING_DELAY seconds. 103 | 104 | - ```PING_HOST``` host to ping 105 | - ```MIN_PING_DELAY``` minimum ping delay 106 | - ```MAX_PING_DELAY``` maximum ping delay 107 | 108 | ## Test run 109 | 110 | We check that everything starts (the first launch is long) 111 | 112 | ```bash 113 | docker-compose up 114 | ``` 115 | 116 | Press CTRL+C and then 117 | 118 | ```bash 119 | docker-compose down 120 | ``` 121 | 122 | ## Create systemd service to automatically launch a container 123 | 124 | If installed via ```snap```: 125 | 126 | ```bash 127 | cp ./doc/shadow-client-snap.service /etc/systemd/system/shadow-client-snap.service && \ 128 | systemctl daemon-reload && \ 129 | systemctl enable shadow-client-snap && \ 130 | systemctl start shadow-client-snap 131 | ``` 132 | 133 | If you installed it according to the instructions from the ubuntu website: 134 | 135 | ```bash 136 | cp ./doc/shadow-client.service /etc/systemd/system/shadow-client.service && \ 137 | systemctl daemon-reload && \ 138 | systemctl enable shadow-client && \ 139 | systemctl start shadow-client 140 | ``` 141 | 142 | ## Configuring devices in the local network to connect to the VPN 143 | 144 | In case to use this configuration as a gateway: 145 | 146 | - Set the IP address specified in the ```VPN_GATEWAY``` variable as the gateway 147 | - In the network interface settings, set the ```MTU``` to ```1420```. This is necessary because the traffic is routed through WireGuard, which reduces the packet size. 148 | 149 | In case to use this configuration as a proxy server: 150 | 151 | - Set OS proxy settings to ```VPN_GATEWAY``` address and ports specified in the PROXY_HTTP_PORT/PROXY_SOCKS_PORT variables 152 | - Under linux set environment variables ```http_proxy```, ```https_proxy``` according your needs 153 | -------------------------------------------------------------------------------- /wireguard/wireguard-ip-calculator.py: -------------------------------------------------------------------------------- 1 | # based on https://github.com/MagomedovTimur/WireGuard-AllowedIPs-Calculator 2 | 3 | import sys 4 | from netaddr import iprange_to_cidrs 5 | from ipaddress import IPv4Network 6 | from ipaddress import IPv4Address 7 | from ipaddress import summarize_address_range 8 | 9 | 10 | # Default network sorting algorithm takes into account the network mask. 11 | # We need to sort network only by netowrk address 12 | def mySorted(netArray): 13 | 14 | i = 0 15 | 16 | while i < len(netArray) - 1: 17 | a = (netArray[i].split('/'))[0] 18 | b = (netArray[i+1].split('/'))[0] 19 | 20 | # Breaking into the int(octets 21 | octetsA = a.split(".") 22 | octetsB = b.split(".") 23 | 24 | if int(octetsA[0]) > int(octetsB[0]): 25 | current = netArray[i] 26 | netArray[i] = netArray[i+1] 27 | netArray[i+1] = current 28 | i=-1 29 | elif int(octetsA[0]) < int(octetsB[0]): 30 | pass 31 | elif int(octetsA[1]) > int(octetsB[1]): 32 | current = netArray[i] 33 | netArray[i] = netArray[i+1] 34 | netArray[i+1] = current 35 | i=-1 36 | elif int(octetsA[1]) < int(octetsB[1]): 37 | pass 38 | elif int(octetsA[2]) > int(octetsB[2]): 39 | current = netArray[i] 40 | netArray[i] = netArray[i+1] 41 | netArray[i+1] = current 42 | i=-1 43 | elif int(octetsA[2]) < int(octetsB[2]): 44 | pass 45 | elif int(octetsA[3]) > int(octetsB[3]): 46 | current = netArray[i] 47 | netArray[i] = netArray[i+1] 48 | netArray[i+1] = current 49 | i=-1 50 | elif int(octetsA[3]) < int(octetsB[3]): 51 | pass 52 | i+=1 53 | 54 | return netArray 55 | 56 | 57 | def convertRangesToNets(netRange): 58 | 59 | result = [] 60 | 61 | # Transforming each network range into a networks and write each of them to the result array 62 | for net in netRange: 63 | resultNetworks = iprange_to_cidrs(str(net[0]), str(net[1])) 64 | for resultNet in resultNetworks: 65 | result.append(IPv4Network(str(resultNet))) 66 | return result 67 | 68 | def rangeSubstraction(allowedRange, disallowedRange): 69 | 70 | i = 0 71 | while i < len(allowedRange): 72 | 73 | for disallowedNet in disallowedRange: 74 | 75 | 76 | if (allowedRange[i][0] >= disallowedNet[0]) and (allowedRange[i][1] <= disallowedNet[1]): 77 | allowedRange.pop(i) 78 | i=0 79 | break 80 | if (allowedRange[i][0] < disallowedNet[0]) and (allowedRange[i][1] > disallowedNet[1]): 81 | allowedRange.append([allowedRange[i][0], (disallowedNet[0])-1]) 82 | allowedRange.append([disallowedNet[1]+1, allowedRange[i][1]]) 83 | allowedRange.pop(i) 84 | i=0 85 | break 86 | if (allowedRange[i][0] < disallowedNet[0]) and (allowedRange[i][1] >= disallowedNet[0]): 87 | allowedRange.append([allowedRange[i][0], disallowedNet[0]-1]) 88 | allowedRange.pop(i) 89 | i=0 90 | break 91 | if (allowedRange[i][0] >= disallowedNet[0]) and (allowedRange[i][0] < disallowedNet[1]): 92 | allowedRange.append([disallowedNet[1]+1, allowedRange[i][1]]) 93 | allowedRange.pop(i) 94 | i=0 95 | break 96 | i+=1 97 | return allowedRange 98 | 99 | def summarizeNets(IPArr): 100 | # Insert all nets as a start and end addresses 101 | IPrange = [] 102 | for currentNetwork in IPArr: 103 | IPrange.append([currentNetwork[0], currentNetwork[-1]]) 104 | 105 | i = 0 106 | while i < len(IPrange)-1: 107 | 108 | if IPrange[i][1]+1 >= IPrange[i+1][0]: 109 | if IPrange[i][0] <= IPrange[i+1][0]: 110 | if IPrange[i][1] <= IPrange[i+1][1]: 111 | IPrange[i] = [IPrange[i][0], IPrange[i+1][1]] 112 | 113 | else: 114 | IPrange[i] = [IPrange[i][0], IPrange[i][1]] 115 | else: 116 | if IPrange[i][1] <= IPrange[i+1][1]: 117 | IPrange[i] = [IPrange[i+1][0], IPrange[i+1][1]] 118 | else: 119 | IPrange[i] = [IPrange[i+1][0], IPrange[i][1]] 120 | IPrange.pop(i+1) 121 | i=0 122 | i+=1 123 | 124 | return IPrange 125 | 126 | def mainCalculator(allowedNet, disallowedNet): 127 | 128 | # Making IP inputs computer readable 129 | allowedNetTextArr = allowedNet.split(',') 130 | disallowedNetTextArr = disallowedNet.split(',') 131 | 132 | # Sorting IP arrays 133 | allowedNetTextArr = mySorted(allowedNetTextArr) 134 | disallowedNetTextArr = mySorted(disallowedNetTextArr) 135 | 136 | # Pushing text ip addresses from arrays to ipaddress.ip_network array 137 | allowedNetArr = [] 138 | disallowedNetArr = [] 139 | 140 | for x in allowedNetTextArr: 141 | try: 142 | allowedNetArr.append(IPv4Network(x)) 143 | except ValueError: 144 | print('Error: ' + x + ' has host bits set or incorrect format') 145 | return 146 | for x in disallowedNetTextArr: 147 | try: 148 | disallowedNetArr.append(IPv4Network(x)) 149 | except ValueError: 150 | print('Error: ' + x + ' has host bits set or incorrect format') 151 | return 152 | 153 | # Summarizing networks if they overlap and get address ranges back 154 | allowedNetRanges = summarizeNets(allowedNetArr) 155 | disallowedNetRanges = summarizeNets(disallowedNetArr) 156 | 157 | # Subtracting blocked networks from allowed 158 | resultRanges = rangeSubstraction(allowedNetRanges,disallowedNetRanges) 159 | 160 | # Check if there any possible allowed networks 161 | if len(resultRanges) == 0: 162 | print('There are no allowed networks!') 163 | return 164 | 165 | # Removing 0.0.0.0 address in the start of resultRanges 166 | if resultRanges[0][0] == IPv4Address('0.0.0.0'): 167 | resultRanges[0][0] = IPv4Address('1.0.0.0') 168 | 169 | # Converting result ranges to result array with ip networks 170 | resultArray = convertRangesToNets(resultRanges) 171 | 172 | # Sorting netowrk result array 173 | resultArray = sorted(resultArray) 174 | 175 | # Printing result 176 | result = '' 177 | 178 | if len(resultArray) == 0: 179 | return 180 | elif len(resultArray) == 1: 181 | result += str(resultArray[0]) 182 | else: 183 | for i in range(0, len(resultArray)-1): 184 | result += str(resultArray[i]) + ',' 185 | result = result[:-1] 186 | 187 | print(result) 188 | 189 | 190 | 191 | return 192 | 193 | def main(): 194 | 195 | # If there is no key or "-h" display help message and quit 196 | try: 197 | if (len(sys.argv) == 1) or sys.argv.index("-h") != None: 198 | help = """ 199 | WireGuard allowedNets Calculator (https://github.com/MagomedovTimur/WireGuard-allowedNets-Calculator) 200 | 201 | Keys: 202 | -a : Allowed networks. Leave blank for 0.0.0.0/0 203 | -d : Disallowed networks. Optional if "-e" is used 204 | -r : Reverse wiregurad config string to allowed and disallowed networks 205 | -e: Preset for fast excluding local networks 206 | -h: print help message to console 207 | """ 208 | print(help) 209 | return 210 | except: 211 | pass 212 | 213 | 214 | disallowedNet = '' 215 | allowedNet = '' 216 | 217 | # Getting allowed networks from console. If there are no networks => 0.0.0.0/0 218 | try: 219 | allowedNet = sys.argv[sys.argv.index("-a") + 1] 220 | except ValueError: 221 | allowedNet = '0.0.0.0/0' 222 | 223 | 224 | # Getting disallowed netowrks from console. If there are no networks check if key "-e" is used. If no => print error 225 | try: 226 | disallowedNet = sys.argv[sys.argv.index("-d") + 1] 227 | except ValueError: 228 | try: 229 | 230 | excludeLocal = sys.argv[sys.argv.index("-e")] 231 | if disallowedNet != '': 232 | disallowedNet+=',' 233 | disallowedNet+='10.0.0.0/8,127.0.0.0/8,169.254.0.0/16,172.16.0.0/12,192.168.0.0/16' 234 | 235 | except ValueError: 236 | print('Either a disallowed or preset networks are needed') 237 | return 238 | 239 | mainCalculator(allowedNet, disallowedNet) 240 | return 241 | 242 | 243 | if __name__ == '__main__': 244 | main() 245 | --------------------------------------------------------------------------------