Repository: jnsgruk/wireguard-over-wss Branch: main Commit: 9d80ec6d003a Files: 5 Total size: 10.6 KB Directory structure: gitextract_9snuufv1/ ├── LICENSE ├── README.md ├── wss.conf ├── wss.wstunnel └── wstunnel.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Jon Seager Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Wireguard-over-Websockets Config This project explains the steps to enable a Wireguard VPN connection to be tunnelled over a secure websockets connection for use cases where outbound VPN traffic may be blocked/filtered/monitored. The following steps assume that there is already a Wireguard connection established that is to be modified for tunnelling over WSS. ## Server Configuration No modifications need to be made to the Wireguard server configuration itself, but `wstunnel` needs to be installed and configured as a systemd unit. 1. Download the latest wstunnel [release](https://github.com/erebe/wstunnel/releases) 2. Copy the binary to `/usr/local/bin/wstunnel` 3. Allow the binary to listen on privileged ports: ```bash version="$(curl -sL https://api.github.com/repos/erebe/wstunnel/releases | grep -m1 -Po 'tag_name": "\K[^"]+')" curl -sL "https://github.com/erebe/wstunnel/releases/download/${version}/wstunnel_${version/v/}_linux_amd64.tar.gz" > wstunnel.tar.gz tar xvzf wstunnel.tar.gz sudo install -Dm 0755 wstunnel /usr/local/bin/wstunnel sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/wstunnel ``` 4. Create the following service file at `/etc/systemd/system/wstunnel.service`: ```bash [Unit] Description=Tunnel WG UDP over websocket After=network.target [Service] Type=simple User=nobody ExecStart=/usr/local/bin/wstunnel server wss://0.0.0.0:443 --restrict-to 127.0.0.1:51820 Restart=no [Install] WantedBy=multi-user.target ``` 5. Start and enable the service: ```bash $ sudo systemctl enable wstunnel $ sudo systemctl start wstunnel ``` If relying solely on the software firewall installed on the droplet, ensure that inbound traffic to port 443 is permitted. ## Client Configuration Ensure dependencies are installed (debian-based example): ``` apt update && apt install -y curl jq ``` 1. Download the latest wstunnel [release](https://github.com/erebe/wstunnel/releases) 2. Copy the binary to `/usr/local/bin/wstunnel` 3. Copy existing config to `/etc/wireguard/wss.conf` 4. Install `wstunnel.sh` to `/etc/wireguard/wstunnel.sh` [(script)](./wstunnel.sh) 5. Create a connection specific config file at `/etc/wireguard/wss.wstunnel` [(example)](./wss.wstunnel): ``` REMOTE_HOST=some.server.com REMOTE_PORT=51820 UPDATE_HOSTS='/etc/hosts' # Change if using nginx with custom prefix for added security # WS_PREFIX='E7m5vGDqryd55MMP' # Change if running WSS on a non-standard port, i.e. 4443 # WSS_PORT=443 # Can change local port of the wstunnel, don't forget to change Peer.Endpoint # LOCAL_PORT=${REMOTE_PORT} # If using dnsmasq can supply other file than /etc/hosts # UPDATE_HOSTS='/usr/local/etc/dnsmasq.d/hosts/tunnels' # Will send -HUP to dnsmasq to reload hosts # USING_DNSMASQ=1 ``` Next we will modify the client config to configure routing and point at the correct endpoint for our websockets tunnel. (Or cheat, and look at the [example config](./wss.conf)) 1. Ensure the `Endpoint` directive is pointing at `127.0.0.1:51820` 2. Add the following lines to the `[Interface]` section: ``` Table = off PreUp = source /etc/wireguard/wstunnel.sh && pre_up %i PostUp = source /etc/wireguard/wstunnel.sh && post_up %i PostDown = source /etc/wireguard/wstunnel.sh && post_down %i ``` ## Finish The tunnelling should now be configured - ensure the server is running and `wstunnel` is started on the server and initiate a connection - you should then be able to see the tunnel established by running `wg`. Ensure that all files under `/etc/wireguard` are owned by root: ``` $ chown -R root: /etc/wireguard $ chmod 600 /etc/wireguard/* ``` ================================================ FILE: wss.conf ================================================ [Interface] PrivateKey = REPLACE_ME Address = 10.0.0.3/24 Table = off PreUp = source /etc/wireguard/wstunnel.sh && pre_up %i PostUp = source /etc/wireguard/wstunnel.sh && post_up %i PostDown = source /etc/wireguard/wstunnel.sh && post_down %i [Peer] PublicKey = REPLACE_ME Endpoint = 127.0.0.1:51820 # Note that this points to locahost! AllowedIPs = 0.0.0.0/0 # Probably preferred if using tunnelling PersistentKeepAlive = 25 ================================================ FILE: wss.wstunnel ================================================ REMOTE_HOST=some.server.com REMOTE_PORT=51820 UPDATE_HOSTS='/etc/hosts' # Change if using CDN with custom IP or need use custom ip for domain (It's optional) # if not set => auto get one of REMOTE_HOST ip # REMOTE_IP=1.1.1.1 # Change if using nginx with custom prefix for added security # WS_PREFIX='E7m5vGDqryd55MMP' # Change if running WSS on a non-standard port, i.e. 4443 # export WSS_PORT=443 # Can change local port of the wstunnel, don't forget to change Peer.Endpoint # LOCAL_PORT=${REMOTE_PORT} # If using dnsmasq can supply other file than /etc/hosts # UPDATE_HOSTS='/usr/local/etc/dnsmasq.d/hosts/tunnels' # Will send -HUP to dnsmasq to reload hosts # USING_DNSMASQ=1 ================================================ FILE: wstunnel.sh ================================================ #!/usr/bin/env bash # # Original script downloaded from: https://github.com/Kirill888/notes/blob/wg-tunnel-update/wireguard/scripts/wstunnel.sh # Modified by jnsgruk to use `ip route` for modern Linux distros # DEFAULT_HOSTS_FILE='/etc/hosts' read_host_entry () { local host=$1 local hfile=${2:-${DEFAULT_HOSTS_FILE}} awk -v host="$host" '{ if( !($0~"^[ ]*#") && $2==host ) print $1, ($0~"## wstunnel end-point")?"auto":"manual" }' "${hfile}" } add_host_entry () { local host=$1 local ip=$2 local hfile=${3:-${DEFAULT_HOSTS_FILE}} echo -e "${ip}\t${host}\t## wstunnel end-point" >> "${hfile}" } update_host_entry () { local host=$1 local ip=$2 local hfile=${3:-${DEFAULT_HOSTS_FILE}} local edited edited=$(awk -v host="$host" -v ip="$ip" '{ if( !($0~"^[ ]*#") && $2==host && ($0~"## wstunnel end-point") ) print ip "\t" host "\t" "## wstunnel end-point" else print $0 }' "${hfile}") echo "${edited}" > "${hfile}" } delete_host_entry () { local host=$1 local hfile=${2:-${DEFAULT_HOSTS_FILE}} local edited edited=$(awk -v host="$host" '{ if( !($0~"^[ ]*#") && $2==host && ($0~"## wstunnel end-point") ) ; else print $0 }' "${hfile}") echo "${edited}" > "${hfile}" } maybe_update_host () { local host="$1" local current_ip="$2" local hfile=${3:-${DEFAULT_HOSTS_FILE}} local recorded_ip h_mode read -r recorded_ip h_mode < <(read_host_entry "${host}" "${hfile}") || true if [[ -z "${recorded_ip}" ]]; then echo "[#] Add new entry ${host} => <${current_ip}>" add_host_entry "${host}" "${current_ip}" "${hfile}" else if [[ "${recorded_ip}" == "${current_ip}" ]]; then echo "[#] Recorded address is already correct" else if [[ "${h_mode}" == "auto" ]]; then echo "[#] Updating ${recorded_ip} -> ${current_ip}" update_host_entry "${host}" "${current_ip}" "${hfile}" else echo "[#] Manual entry doesn't match current ip: ${recorded_ip} -> ${current_ip}" exit 2 fi fi fi } launch_wstunnel () { local host=${REMOTE_HOST} local rport=${REMOTE_PORT:-51820} local wssport=${WSS_PORT:-443} local lport=${LOCAL_PORT:-${rport}} local prefix=${WS_PREFIX:-"wstunnel"} local user=${1:-"nobody"} local cmd cmd=$(command -v wstunnel) cmd="sudo -n -u ${user} -- $cmd" export NO_COLOR=true nohup $cmd &>/dev/null \ client \ --http-upgrade-path-prefix "${prefix}" \ -L "udp://127.0.0.1:${lport}:127.0.0.1:${rport}" \ "wss://${host}:${wssport}" & echo "$!" } pre_up () { local wg=$1 local cfg="/etc/wireguard/${wg}.wstunnel" local remote remote_ip gw wstunnel_pid hosts_file _dnsmasq if [[ -f "${cfg}" ]]; then # shellcheck disable=SC1090 source "${cfg}" remote=${REMOTE_HOST} remote_ip=${REMOTE_IP:-$(dig +short "${remote}" | head -n 1)} hosts_file=${UPDATE_HOSTS} _dnsmasq=${USING_DNSMASQ:-0} else echo "[#] Missing config file: ${cfg}" exit 1 fi if [[ -z "${remote_ip}" ]]; then echo "[#] Can't resolve ${remote}" exit 1 fi if [[ -f "${hosts_file}" ]]; then # Cache DNS in maybe_update_host "${remote}" "${remote_ip}" "${hosts_file}" [[ $_dnsmasq -eq 0 ]] || killall -HUP dnsmasq || true fi # Find out current route to ${remote_ip} and make it explicit gw=$(ip route get "${remote_ip}" | cut -d" " -f3) ip route add "${remote_ip}" via "${gw}" > /dev/null 2>&1 || true # Start wstunnel in the background wstunnel_pid=$(launch_wstunnel nobody) # save state mkdir -p /var/run/wireguard echo "${wstunnel_pid} ${remote} ${remote_ip} \"${hosts_file}\" ${_dnsmasq}" > "/var/run/wireguard/${wg}.wstunnel" } post_up () { local tun=$1 ip route add 0.0.0.0/1 dev "${tun}" > /dev/null 2>&1 ip route add ::0/1 dev "${tun}" > /dev/null 2>&1 ip route add 128.0.0.0/1 dev "${tun}" > /dev/null 2>&1 ip route add 8000::/1 dev "${tun}" > /dev/null 2>&1 } post_down () { local tun=$1 local state_file="/var/run/wireguard/${tun}.wstunnel" local wstunnel_pid remote remote_ip hosts_file _dnsmasq if [[ -f "${state_file}" ]]; then read -r wstunnel_pid remote remote_ip hosts_file _dnsmasq < "${state_file}" # unquote hosts_file=${hosts_file%\"} hosts_file=${hosts_file#\"} rm "${state_file}" else echo "[#] Missing state file: ${state_file}" exit 1 fi kill -TERM "${wstunnel_pid}" > /dev/null 2>&1 || true if [[ -n "${remote_ip}" ]]; then ip route delete "${remote_ip}" > /dev/null 2>&1 || true fi if [[ -f "${hosts_file}" ]]; then delete_host_entry "${remote}" "${hosts_file}" [[ $_dnsmasq -eq 0 ]] || killall -HUP dnsmasq || true fi }