[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Jon Seager\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Wireguard-over-Websockets Config\n\nThis 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.\n\nThe following steps assume that there is already a Wireguard connection established that is to be modified for tunnelling over WSS.\n\n## Server Configuration\n\nNo modifications need to be made to the Wireguard server configuration itself, but `wstunnel` needs to be installed and configured as a systemd unit.\n\n1. Download the latest wstunnel [release](https://github.com/erebe/wstunnel/releases)\n2. Copy the binary to `/usr/local/bin/wstunnel`\n3. Allow the binary to listen on privileged ports:\n\n```bash\nversion=\"$(curl -sL https://api.github.com/repos/erebe/wstunnel/releases | grep -m1 -Po 'tag_name\": \"\\K[^\"]+')\"\ncurl -sL \"https://github.com/erebe/wstunnel/releases/download/${version}/wstunnel_${version/v/}_linux_amd64.tar.gz\" > wstunnel.tar.gz\ntar xvzf wstunnel.tar.gz\nsudo install -Dm 0755 wstunnel /usr/local/bin/wstunnel\nsudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/wstunnel\n```\n\n4. Create the following service file at `/etc/systemd/system/wstunnel.service`:\n\n```bash\n[Unit]\nDescription=Tunnel WG UDP over websocket\nAfter=network.target\n\n[Service]\nType=simple\nUser=nobody\nExecStart=/usr/local/bin/wstunnel server wss://0.0.0.0:443 --restrict-to 127.0.0.1:51820\nRestart=no\n\n[Install]\nWantedBy=multi-user.target\n```\n\n5. Start and enable the service:\n\n```bash\n$ sudo systemctl enable wstunnel\n$ sudo systemctl start wstunnel\n```\n\nIf relying solely on the software firewall installed on the droplet, ensure that inbound traffic to port 443 is permitted.\n\n## Client Configuration\n\nEnsure dependencies are installed (debian-based example):\n\n```\napt update && apt install -y curl jq\n```\n\n1. Download the latest wstunnel [release](https://github.com/erebe/wstunnel/releases)\n2. Copy the binary to `/usr/local/bin/wstunnel`\n3. Copy existing config to `/etc/wireguard/wss.conf`\n4. Install `wstunnel.sh` to `/etc/wireguard/wstunnel.sh` [(script)](./wstunnel.sh)\n5. Create a connection specific config file at `/etc/wireguard/wss.wstunnel` [(example)](./wss.wstunnel):\n\n```\nREMOTE_HOST=some.server.com\nREMOTE_PORT=51820\nUPDATE_HOSTS='/etc/hosts'\n\n# Change if using nginx with custom prefix for added security\n# WS_PREFIX='E7m5vGDqryd55MMP'\n\n# Change if running WSS on a non-standard port, i.e. 4443\n# WSS_PORT=443\n\n# Can change local port of the wstunnel, don't forget to change Peer.Endpoint\n# LOCAL_PORT=${REMOTE_PORT}\n\n# If using dnsmasq can supply other file than /etc/hosts\n# UPDATE_HOSTS='/usr/local/etc/dnsmasq.d/hosts/tunnels'\n\n# Will send -HUP to dnsmasq to reload hosts\n# USING_DNSMASQ=1\n```\n\nNext 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))\n\n1. Ensure the `Endpoint` directive is pointing at `127.0.0.1:51820`\n2. Add the following lines to the `[Interface]` section:\n\n```\nTable = off\nPreUp = source /etc/wireguard/wstunnel.sh && pre_up %i\nPostUp = source /etc/wireguard/wstunnel.sh && post_up %i\nPostDown = source /etc/wireguard/wstunnel.sh && post_down %i\n```\n\n## Finish\n\nThe 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`.\n\nEnsure that all files under `/etc/wireguard` are owned by root:\n\n```\n$ chown -R root: /etc/wireguard\n$ chmod 600 /etc/wireguard/*\n```\n"
  },
  {
    "path": "wss.conf",
    "content": "[Interface]\nPrivateKey = REPLACE_ME\nAddress = 10.0.0.3/24\n\nTable = off\nPreUp = source /etc/wireguard/wstunnel.sh && pre_up %i\nPostUp = source /etc/wireguard/wstunnel.sh && post_up %i\nPostDown = source /etc/wireguard/wstunnel.sh && post_down %i \n\n[Peer]\nPublicKey = REPLACE_ME\nEndpoint = 127.0.0.1:51820 # Note that this points to locahost!\nAllowedIPs = 0.0.0.0/0 # Probably preferred if using tunnelling\nPersistentKeepAlive = 25\n"
  },
  {
    "path": "wss.wstunnel",
    "content": "REMOTE_HOST=some.server.com\nREMOTE_PORT=51820\nUPDATE_HOSTS='/etc/hosts'\n\n# Change if using CDN with custom IP or need use custom ip for domain (It's optional)\n# if not set => auto get one of REMOTE_HOST ip\n# REMOTE_IP=1.1.1.1\n\n# Change if using nginx with custom prefix for added security\n# WS_PREFIX='E7m5vGDqryd55MMP'\n\n# Change if running WSS on a non-standard port, i.e. 4443\n# export WSS_PORT=443\n\n# Can change local port of the wstunnel, don't forget to change Peer.Endpoint\n# LOCAL_PORT=${REMOTE_PORT}\n\n# If using dnsmasq can supply other file than /etc/hosts\n# UPDATE_HOSTS='/usr/local/etc/dnsmasq.d/hosts/tunnels'\n\n# Will send -HUP to dnsmasq to reload hosts\n# USING_DNSMASQ=1"
  },
  {
    "path": "wstunnel.sh",
    "content": "#!/usr/bin/env bash\n\n#\n# Original script downloaded from: https://github.com/Kirill888/notes/blob/wg-tunnel-update/wireguard/scripts/wstunnel.sh\n# Modified by jnsgruk to use `ip route` for modern Linux distros\n#\nDEFAULT_HOSTS_FILE='/etc/hosts'\n\nread_host_entry () {\n    local host=$1\n    local hfile=${2:-${DEFAULT_HOSTS_FILE}}\n    awk -v host=\"$host\" '{\n     if( !($0~\"^[ ]*#\") && $2==host )\n       print $1, ($0~\"## wstunnel end-point\")?\"auto\":\"manual\"\n     }' \"${hfile}\"\n}\n\nadd_host_entry () {\n    local host=$1\n    local ip=$2\n    local hfile=${3:-${DEFAULT_HOSTS_FILE}}\n    echo -e \"${ip}\\t${host}\\t## wstunnel end-point\" >> \"${hfile}\"\n}\n\nupdate_host_entry () {\n    local host=$1\n    local ip=$2\n    local hfile=${3:-${DEFAULT_HOSTS_FILE}}\n    local edited\n    edited=$(awk -v host=\"$host\" -v ip=\"$ip\" '{\n      if( !($0~\"^[ ]*#\") && $2==host && ($0~\"## wstunnel end-point\") )\n        print ip \"\\t\" host \"\\t\" \"## wstunnel end-point\"\n      else\n        print $0\n      }' \"${hfile}\")\n\n    echo \"${edited}\" > \"${hfile}\"\n}\n\ndelete_host_entry () {\n    local host=$1\n    local hfile=${2:-${DEFAULT_HOSTS_FILE}}\n    local edited\n    edited=$(awk -v host=\"$host\" '{\n      if( !($0~\"^[ ]*#\") && $2==host && ($0~\"## wstunnel end-point\") )\n        ;\n      else\n        print $0\n      }' \"${hfile}\")\n\n    echo \"${edited}\" > \"${hfile}\"\n}\n\nmaybe_update_host () {\n    local host=\"$1\"\n    local current_ip=\"$2\"\n    local hfile=${3:-${DEFAULT_HOSTS_FILE}}\n    local recorded_ip h_mode\n\n    read -r recorded_ip h_mode < <(read_host_entry \"${host}\" \"${hfile}\") || true\n\n    if [[ -z \"${recorded_ip}\" ]]; then\n        echo \"[#] Add new entry ${host} => <${current_ip}>\"\n        add_host_entry \"${host}\" \"${current_ip}\" \"${hfile}\"\n    else\n        if [[ \"${recorded_ip}\" == \"${current_ip}\" ]]; then\n            echo \"[#] Recorded address is already correct\"\n        else\n            if [[ \"${h_mode}\" == \"auto\" ]]; then\n                echo \"[#] Updating ${recorded_ip} -> ${current_ip}\"\n                update_host_entry \"${host}\" \"${current_ip}\" \"${hfile}\"\n            else\n                echo \"[#] Manual entry doesn't match current ip: ${recorded_ip} -> ${current_ip}\"\n                exit 2\n            fi\n        fi\n    fi\n}\n\nlaunch_wstunnel () {\n    local host=${REMOTE_HOST}\n    local rport=${REMOTE_PORT:-51820}\n    local wssport=${WSS_PORT:-443}\n    local lport=${LOCAL_PORT:-${rport}}\n    local prefix=${WS_PREFIX:-\"wstunnel\"}\n    local user=${1:-\"nobody\"}\n    local cmd\n\n    cmd=$(command -v wstunnel)\n    cmd=\"sudo -n -u ${user} -- $cmd\"\n\n    export NO_COLOR=true\n    nohup $cmd &>/dev/null \\\n      client \\\n      --http-upgrade-path-prefix \"${prefix}\" \\\n      -L \"udp://127.0.0.1:${lport}:127.0.0.1:${rport}\" \\\n      \"wss://${host}:${wssport}\" &\n    echo \"$!\"\n}\n\npre_up () {\n    local wg=$1\n    local cfg=\"/etc/wireguard/${wg}.wstunnel\"\n    local remote remote_ip gw wstunnel_pid hosts_file _dnsmasq\n\n    if [[ -f \"${cfg}\" ]]; then\n        # shellcheck disable=SC1090\n        source \"${cfg}\"\n        remote=${REMOTE_HOST}\n        remote_ip=${REMOTE_IP:-$(dig +short \"${remote}\" | head -n 1)}\n        hosts_file=${UPDATE_HOSTS}\n        _dnsmasq=${USING_DNSMASQ:-0}\n    else\n        echo \"[#] Missing config file: ${cfg}\"\n        exit 1\n    fi\n\n    if [[ -z \"${remote_ip}\" ]]; then\n        echo \"[#] Can't resolve ${remote}\"\n        exit 1\n    fi\n\n    if [[ -f \"${hosts_file}\" ]]; then\n        # Cache DNS in\n        maybe_update_host \"${remote}\" \"${remote_ip}\" \"${hosts_file}\"\n\n        [[ $_dnsmasq -eq 0 ]] || killall -HUP dnsmasq || true\n    fi\n\n    # Find out current route to ${remote_ip} and make it explicit\n    gw=$(ip route get \"${remote_ip}\" | cut -d\" \" -f3)\n    ip route add \"${remote_ip}\" via \"${gw}\" > /dev/null 2>&1 || true\n    # Start wstunnel in the background\n    wstunnel_pid=$(launch_wstunnel nobody)\n\n    # save state\n    mkdir -p /var/run/wireguard\n    echo \"${wstunnel_pid} ${remote} ${remote_ip} \\\"${hosts_file}\\\" ${_dnsmasq}\" > \"/var/run/wireguard/${wg}.wstunnel\"\n}\n\npost_up () {\n    local tun=$1\n    ip route add 0.0.0.0/1 dev \"${tun}\" > /dev/null 2>&1\n    ip route add ::0/1 dev \"${tun}\" > /dev/null 2>&1\n    ip route add 128.0.0.0/1 dev \"${tun}\" > /dev/null 2>&1\n    ip route add 8000::/1 dev \"${tun}\" > /dev/null 2>&1\n}\n\npost_down () {\n    local tun=$1\n    local state_file=\"/var/run/wireguard/${tun}.wstunnel\"\n    local wstunnel_pid remote remote_ip hosts_file _dnsmasq\n\n    if [[ -f \"${state_file}\" ]]; then\n        read -r wstunnel_pid remote remote_ip hosts_file _dnsmasq < \"${state_file}\"\n        # unquote\n        hosts_file=${hosts_file%\\\"}\n        hosts_file=${hosts_file#\\\"}\n\n        rm \"${state_file}\"\n    else\n        echo \"[#] Missing state file: ${state_file}\"\n        exit 1\n    fi\n\n    kill -TERM \"${wstunnel_pid}\" > /dev/null 2>&1 || true\n\n    if [[ -n \"${remote_ip}\" ]]; then\n\t    ip route delete \"${remote_ip}\" > /dev/null 2>&1 || true\n    fi\n\n    if [[ -f \"${hosts_file}\" ]]; then\n        delete_host_entry \"${remote}\" \"${hosts_file}\"\n        [[ $_dnsmasq -eq 0 ]] || killall -HUP dnsmasq || true\n    fi\n}\n"
  }
]