├── .github └── workflows │ ├── main.yml │ └── pr.yml ├── .gitignore ├── API.md ├── CLI.md ├── LICENSE ├── README.md ├── broker ├── akmanager.go └── broker.go ├── changelogs ├── 0.1.0.md ├── 0.1.1.md ├── 0.2.0.md ├── 0.2.1.md ├── 0.2.2.md ├── 0.3.0.md ├── 0.3.1.md ├── 0.3.2.md ├── 0.4.0.md ├── 0.5.0.md ├── 0.5.1.md ├── 0.5.2.md ├── 0.5.3.md ├── 0.5.4.md ├── 0.5.5.md ├── 0.5.6.md ├── 0.6.0.md ├── 0.6.1.md ├── 0.6.2.md └── 0.6.3.md ├── circuit └── circuit.go ├── clientcfg └── cfg.go ├── clientlib ├── circuitdialer.go ├── cliutil.go ├── contractinfo.go └── traceorigin.go ├── contrib ├── build-bin.sh ├── docker │ ├── build-bin.sh │ ├── goversion.sh │ └── run-tests.sh ├── gen-cli-md.sh ├── get-darwin.sh ├── get-linux.sh ├── get-windows.ps1 ├── get.sh ├── gitversion.sh ├── run-tests.sh └── speedtest.sh ├── dnscachedial └── dnscachedial.go ├── filenames └── filenames.go ├── go.mod ├── go.sum ├── main.go ├── restapi ├── accesskeys.go ├── fwder.go ├── fwder_binarystate_darwin.go ├── fwder_binarystate_linux.go ├── fwder_binarystate_windows.go ├── restapi.go ├── runtime.go ├── status.go ├── unixserver.go └── version.go ├── socks └── socks.go ├── sub ├── accesskeyscmd │ └── accesskeyscmd.go ├── configcmd │ └── configcmd.go ├── execcmd │ └── execcmd.go ├── httpgetcmd │ └── httpget.go ├── initcmd │ ├── embedded │ │ ├── completion.bash │ │ ├── embedded_darwin.go │ │ ├── embedded_linux.go │ │ ├── embedded_windows.go │ │ ├── scripts_darwin │ │ │ ├── README │ │ │ └── default │ │ │ │ ├── README │ │ │ │ ├── brave-browser │ │ │ │ ├── chromium-browser │ │ │ │ ├── curl │ │ │ │ ├── firefox │ │ │ │ ├── git │ │ │ │ ├── google-chrome │ │ │ │ ├── list │ │ │ │ └── surf │ │ ├── scripts_linux │ │ │ ├── README │ │ │ └── default │ │ │ │ ├── README │ │ │ │ ├── brave-browser │ │ │ │ ├── chromium-browser │ │ │ │ ├── curl │ │ │ │ ├── firefox │ │ │ │ ├── git │ │ │ │ ├── google-chrome │ │ │ │ ├── list │ │ │ │ └── surf │ │ └── scripts_windows │ │ │ ├── README │ │ │ └── default │ │ │ ├── README │ │ │ ├── brave-browser.bat │ │ │ ├── chromium-browser.bat │ │ │ ├── curl.bat │ │ │ ├── firefox.bat │ │ │ ├── git.bat │ │ │ ├── google-chrome.bat │ │ │ └── list.bat │ ├── initcmd.go │ └── initcmd_test.go ├── interceptcmd │ ├── available_darwin.go │ ├── available_linux.go │ ├── available_windows.go │ ├── interceptcmd_darwin.go │ ├── interceptcmd_linux.go │ └── interceptcmd_windows.go ├── logcmd │ └── logcmd.go ├── reloadcmd │ └── reloadcmd.go ├── restartcmd │ └── restartcmd.go ├── sockscmd │ └── sockscmd.go ├── startcmd │ └── startcmd.go ├── statuscmd │ └── statuscmd.go ├── stopcmd │ └── stopcmd.go ├── tuncmd │ ├── tuncmd.go │ └── tuncmd_platform │ │ ├── tuncmd_darwin.go │ │ ├── tuncmd_linux.go │ │ └── tuncmd_windows.go └── versioncmd │ └── versioncmd.go ├── version └── version.go ├── wireleap_intercept ├── Makefile └── wireleap_intercept.c ├── wireleap_socks └── main.go └── wireleap_tun ├── bypasslist.go ├── main.go ├── netsetup ├── netsetup.go ├── route_darwin.go └── route_linux.go ├── ptable └── ptable.go ├── tun ├── reader.go ├── tun.go └── writer.go └── tunsplice.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Master/tag update 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | build: 12 | name: Test, build & deploy 13 | runs-on: ubuntu-latest 14 | if: github.repository == 'wireleap/client' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'testable')) 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Run component build action 21 | uses: wireleap/gh-build@master 22 | with: 23 | token: ${{ secrets.GH_TEST_TOKEN }} 24 | ssh_key: ${{ secrets.SSH_KEY }} 25 | upload_target: ${{ secrets.UPLOAD_TARGET }} 26 | gpg_key: ${{ secrets.GPG_KEY }} 27 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR test 2 | 3 | on: 4 | pull_request_target: 5 | types: [labeled] 6 | 7 | jobs: 8 | build: 9 | name: Test & build 10 | runs-on: ubuntu-latest 11 | if: github.repository == 'wireleap/client' && contains(github.event.pull_request.labels.*.name, 'testable') 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | ref: ${{ github.event.pull_request.head.sha }} 18 | - name: Run component build action 19 | uses: wireleap/gh-build@master 20 | with: 21 | token: ${{ secrets.GH_TEST_TOKEN }} 22 | ssh_key: ${{ secrets.SSH_KEY }} 23 | upload_target: ${{ secrets.UPLOAD_TARGET }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | 3 | wireleap_intercept/wireleap_intercept.so 4 | wireleap_intercept/wireleap_intercept.o 5 | 6 | sub/initcmd/embedded/* 7 | !sub/initcmd/embedded/*.go 8 | !sub/initcmd/embedded/scripts_linux 9 | !sub/initcmd/embedded/scripts_darwin 10 | !sub/initcmd/embedded/scripts_windows 11 | !sub/initcmd/embedded/completion.bash 12 | 13 | wireleap_tun/wireleap_tun 14 | wireleap_socks/wireleap_socks 15 | -------------------------------------------------------------------------------- /CLI.md: -------------------------------------------------------------------------------- 1 | # Wireleap client command line reference 2 | 3 | ## Table of contents 4 | 5 | - [wireleap](#wireleap) 6 | - [wireleap init](#wireleap-init) 7 | - [wireleap config](#wireleap-config) 8 | - [wireleap accesskeys](#wireleap-accesskeys) 9 | - [wireleap start](#wireleap-start) 10 | - [wireleap status](#wireleap-status) 11 | - [wireleap reload](#wireleap-reload) 12 | - [wireleap restart](#wireleap-restart) 13 | - [wireleap stop](#wireleap-stop) 14 | - [wireleap log](#wireleap-log) 15 | - [wireleap tun](#wireleap-tun) 16 | - [wireleap socks](#wireleap-socks) 17 | - [wireleap intercept](#wireleap-intercept) 18 | - [wireleap exec](#wireleap-exec) 19 | - [wireleap upgrade](#wireleap-upgrade) 20 | - [wireleap rollback](#wireleap-rollback) 21 | - [wireleap version](#wireleap-version) 22 | 23 | ## wireleap 24 | 25 | ``` 26 | $ wireleap help 27 | Usage: wireleap COMMAND [OPTIONS] 28 | 29 | Commands: 30 | help Display this help message or help on a command 31 | init Initialize wireleap home directory 32 | config Get or set wireleap configuration settings 33 | accesskeys Manage accesskeys 34 | start Start wireleap controller daemon 35 | status Report wireleap controller daemon status 36 | reload Reload wireleap controller daemon configuration 37 | restart Restart wireleap controller daemon 38 | stop Stop wireleap controller daemon 39 | log Show wireleap controller daemon logs 40 | tun Control TUN device forwarder 41 | socks Control SOCKSv5 proxy forwarder 42 | intercept Run executable and redirect connections (req. SOCKS forwarder) 43 | exec Execute script from scripts directory (req. SOCKS forwarder) 44 | upgrade Upgrade wireleap to the latest version per directory 45 | rollback Undo a partially completed upgrade 46 | version Show version and exit 47 | 48 | Run 'wireleap help COMMAND' for more information on a command. 49 | ``` 50 | 51 | ## wireleap init 52 | 53 | ``` 54 | $ wireleap help init 55 | Usage: wireleap init [OPTIONS] 56 | 57 | Initialize wireleap home directory 58 | 59 | Options: 60 | --force-unpack-only Overwrite embedded files only 61 | ``` 62 | 63 | ## wireleap config 64 | 65 | ``` 66 | $ wireleap help config 67 | Usage: wireleap config [KEY [VALUE]] 68 | 69 | Get or set wireleap configuration settings 70 | 71 | Keys: 72 | address (str) Controller address 73 | broker.address (str) Override default broker address 74 | broker.accesskey.use_on_demand (bool) Activate accesskeys as needed 75 | broker.circuit.timeout (str) Dial timeout duration 76 | broker.circuit.hops (int) Number of relays to use in a circuit 77 | broker.circuit.whitelist (list) Relay addresses to use in circuit 78 | forwarders.socks.address (str) SOCKSv5 proxy address 79 | forwarders.tun.address (str) TUN device address (not loopback) 80 | 81 | To unset a key, specify `null` as the value 82 | ``` 83 | 84 | ## wireleap accesskeys 85 | 86 | ``` 87 | $ wireleap help accesskeys 88 | Usage: wireleap accesskeys COMMAND 89 | 90 | Manage accesskeys 91 | 92 | Commands: 93 | list List accesskeys 94 | import Import accesskeys from URL and set up associated contract 95 | activate Trigger accesskey activation (accesskey.use_on_demand=false) 96 | ``` 97 | 98 | ## wireleap start 99 | 100 | ``` 101 | $ wireleap help start 102 | Usage: wireleap start [OPTIONS] 103 | 104 | Start wireleap controller daemon 105 | 106 | Options: 107 | --fg Run in foreground, don't detach 108 | 109 | Signals: 110 | SIGUSR1 (10) Reload configuration, contract information and circuit 111 | SIGTERM (15) Gracefully stop wireleap daemon and exit 112 | SIGQUIT (3) Gracefully stop wireleap daemon and exit 113 | SIGINT (2) Gracefully stop wireleap daemon and exit 114 | 115 | Environment: 116 | WIRELEAP_TARGET_PROTOCOL Resolve target IP via tcp4, tcp6 or tcp (default) 117 | ``` 118 | 119 | ## wireleap status 120 | 121 | ``` 122 | $ wireleap help status 123 | Usage: wireleap status 124 | 125 | Report wireleap controller daemon status 126 | 127 | Exit codes: 128 | 0 wireleap controller is active 129 | 1 wireleap controller is inactive 130 | 2 wireleap controller is activating or deactivating 131 | 3 wireleap controller failed or state is unknown 132 | ``` 133 | 134 | ## wireleap reload 135 | 136 | ``` 137 | $ wireleap help reload 138 | Usage: wireleap reload 139 | 140 | Reload wireleap controller daemon configuration 141 | ``` 142 | 143 | ## wireleap restart 144 | 145 | ``` 146 | $ wireleap help restart 147 | Usage: wireleap restart 148 | 149 | Restart wireleap controller daemon 150 | ``` 151 | 152 | ## wireleap stop 153 | 154 | ``` 155 | $ wireleap help stop 156 | Usage: wireleap stop 157 | 158 | Stop wireleap controller daemon 159 | ``` 160 | 161 | ## wireleap log 162 | 163 | ``` 164 | $ wireleap help log 165 | Usage: wireleap log 166 | 167 | Show wireleap controller daemon logs 168 | ``` 169 | 170 | ## wireleap tun 171 | 172 | ``` 173 | $ wireleap help tun 174 | Usage: wireleap tun COMMAND [OPTIONS] 175 | 176 | Control TUN device forwarder 177 | 178 | Commands: 179 | start Start wireleap_tun daemon 180 | stop Stop wireleap_tun daemon 181 | status Report wireleap_tun daemon status 182 | restart Restart wireleap_tun daemon 183 | log Show wireleap_tun logs 184 | ``` 185 | 186 | ## wireleap socks 187 | 188 | ``` 189 | $ wireleap help socks 190 | Usage: wireleap socks COMMAND [OPTIONS] 191 | 192 | Control SOCKSv5 proxy forwarder 193 | 194 | Commands: 195 | start Start wireleap_socks daemon 196 | stop Stop wireleap_socks daemon 197 | status Report wireleap_socks daemon status 198 | restart Restart wireleap_socks daemon 199 | log Show wireleap_socks logs 200 | ``` 201 | 202 | ## wireleap intercept 203 | 204 | ``` 205 | $ wireleap help intercept 206 | Usage: wireleap intercept [ARGS] 207 | 208 | Run executable and redirect connections (req. SOCKS forwarder) 209 | ``` 210 | 211 | ## wireleap exec 212 | 213 | ``` 214 | $ wireleap help exec 215 | Usage: wireleap exec COMMAND|FILENAME 216 | 217 | Execute script from scripts directory (req. SOCKS forwarder) 218 | 219 | Commands: 220 | list List available scripts in scripts directory 221 | ``` 222 | 223 | ## wireleap upgrade 224 | 225 | ``` 226 | $ wireleap help upgrade 227 | Usage: wireleap upgrade 228 | 229 | Upgrade wireleap to the latest version per directory 230 | ``` 231 | 232 | ## wireleap rollback 233 | 234 | ``` 235 | $ wireleap help rollback 236 | Usage: wireleap rollback 237 | 238 | Undo a partially completed upgrade 239 | ``` 240 | 241 | ## wireleap version 242 | 243 | ``` 244 | $ wireleap help version 245 | Usage: wireleap version [OPTIONS] 246 | 247 | Show version and exit 248 | 249 | Options: 250 | -v show verbose output 251 | ``` 252 | 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Wireleap 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /changelogs/0.1.0.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | 3 | - Initial release accordingly to new versioning scheme. 4 | - As MAJOR is 0, MINOR is used for component interaction incompatibility. 5 | 6 | -------------------------------------------------------------------------------- /changelogs/0.1.1.md: -------------------------------------------------------------------------------- 1 | # 0.1.1 2 | 3 | - No changes. 4 | 5 | -------------------------------------------------------------------------------- /changelogs/0.2.0.md: -------------------------------------------------------------------------------- 1 | # 0.2.0 2 | 3 | - Implemented multiplexed circuit connections via HTTP/2 transport. 4 | - Added experimental TUN device support (Linux). 5 | - Added `tun` sub-command for TUN device usage. 6 | - Added local H2C listening port for TUN interaction. 7 | - Added SOCKSv5 UDP relay functionality. 8 | 9 | -------------------------------------------------------------------------------- /changelogs/0.2.1.md: -------------------------------------------------------------------------------- 1 | # 0.2.1 2 | 3 | - Added 2s start delay for process health verification. 4 | 5 | - UI fixes and improvements: 6 | - Access keys can now be imported from stdin. 7 | - Duplicate access keys are skipped with a warning. 8 | - Don't hard-fail if relays from whitelist not present in directory. 9 | - Empty and `null` whitelist are no longer treated equally. 10 | 11 | -------------------------------------------------------------------------------- /changelogs/0.2.2.md: -------------------------------------------------------------------------------- 1 | # 0.2.2 2 | 3 | - Transition to Go v1.16. 4 | - Implemented usage of Go v1.16 embed functionality. 5 | - Final executable file size reduced by 47.2%. 6 | - Improved security and UX by using `seteuid` in `tun`. 7 | 8 | -------------------------------------------------------------------------------- /changelogs/0.3.0.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 2 | 3 | - Rebrand changes: 4 | 5 | - Binary: `wireleap` 6 | - Relay URL scheme: `wireleap://` 7 | - Embedded assets: `wireleap_tun`, `wireleap_intercept.so` 8 | - Filenames: `wireleap.pid`, `wireleap.log` 9 | - Filenames: `wireleap_tun.pid`, `wireleap_tun.log` 10 | - Script exported envvar: `WIRELEAP_SOCKS` 11 | - Target protocol envvar: `WIRELEAP_TARGET_PROTOCOL` 12 | 13 | - Address configuration changes: 14 | 15 | - `socks_addr` changed to `address.socks` (default: `127.0.0.1:13491`) 16 | - `h2c_addr` changed to `address.h2c` (default: `127.0.0.1:13492`) 17 | - `tun_addr` changed to `address.tun` (default: `10.13.49.0:13493`) 18 | 19 | - Essential file changes: 20 | 21 | - `pubkey.json` replaced with `contract.json` (full snapshot) 22 | 23 | -------------------------------------------------------------------------------- /changelogs/0.3.1.md: -------------------------------------------------------------------------------- 1 | # 0.3.1 2 | 3 | - Manual client upgrade support: 4 | 5 | - Added `upgrade` and `rollback` commands 6 | - Added `init --force-unpack-only` to only overwrite embedded files 7 | - Bundled `exec` scripts are now located in `scripts/default/` 8 | - Version verification when pulling directory information on startup 9 | 10 | - UI fixes and improvements: 11 | 12 | - Help and usage formatting and content improvements 13 | - Access keys can now be imported directly from an `https://` URL 14 | - Avoid new access key activation on startup 15 | - Bundled `exec` scripts are now located in `scripts/default/` 16 | - User defined `exec` scripts in `scripts/` take precedence 17 | - SetUID bit and root ownership verification on `wireleap tun` 18 | 19 | - Default `circuit.hops` changed to 1 20 | - Fixed issue where some errors during initial splice weren't reported 21 | 22 | -------------------------------------------------------------------------------- /changelogs/0.3.2.md: -------------------------------------------------------------------------------- 1 | # 0.3.2 2 | 3 | - Technical release after code refactoring (mono-repo split). 4 | - Depends on `wireleap/common` v0.1.0. 5 | 6 | - Other changes: 7 | 8 | - Upgrade URL endpoint updated to GitHub releases. 9 | - Required removal of `wireleap_tun` if not writable prior to upgrade. 10 | - Imported get.sh convenience script, updated for GitHub releases. 11 | - Added missing `stop` sub-command to `help tun`. 12 | 13 | -------------------------------------------------------------------------------- /changelogs/0.4.0.md: -------------------------------------------------------------------------------- 1 | # 0.4.0 2 | 3 | - Depends on `wireleap/common` v0.2.1. 4 | - Improved `bypass.json` generation logic to cover edge cases. 5 | - Improved SKSource synchronization to avoid race conditions. 6 | - Added `firefox` exec script. 7 | - Uses new `version` command code. 8 | - `config circuit.whitelist` UI changed, now accepts space-separated 9 | list of relays to use. 10 | - Bash completion script added to embedded assets and documented. 11 | - Fixed issue where some config changes persisted after `reload`. 12 | 13 | - Uses new interfaces versioning: 14 | 15 | - `clientdir` v0.1.0 16 | - `clientcontract` v0.1.0 17 | - `clientrelay` v0.1.0 18 | 19 | - tun changes: 20 | 21 | - Set IPv4 TTL & IPv6 HopLimit to 64 for UDP. 22 | - Refactored to read and write from the tun device asynchronously. 23 | - Improved synchronization to avoid race conditions. 24 | - ptable functions are now atomic. 25 | -------------------------------------------------------------------------------- /changelogs/0.5.0.md: -------------------------------------------------------------------------------- 1 | # 0.5.0 2 | 3 | - Depends on `wireleap/common` v0.2.2. 4 | - Uses `upgrade_channels` instead of now deprecated `update_channels`. 5 | - Updated circuit dialer to use the new H/2 header/trailer payload code. 6 | - `tun` permission check now verifies ownership before setuid bit. 7 | - Pre-release versions are not forced to upgrade on startup anymore. 8 | - Fixed relay version not being nil-checked when building circuit. 9 | 10 | - Uses interfaces: 11 | 12 | - `clientdir` v0.2.0 (new version) 13 | - `clientcontract` v0.1.0 14 | - `clientrelay` v0.2.0 (new version) 15 | -------------------------------------------------------------------------------- /changelogs/0.5.1.md: -------------------------------------------------------------------------------- 1 | # 0.5.1 2 | 3 | - Ported `wireleap tun` functionality to macOS. 4 | - Updated `completion.bash` script to be compatible with `zsh` via 5 | `bashcompinit` on both Linux and macOS. 6 | -------------------------------------------------------------------------------- /changelogs/0.5.2.md: -------------------------------------------------------------------------------- 1 | # 0.5.2 2 | 3 | - Depends on `wireleap/common` v0.3.0. 4 | - Implemented initial Windows support (no `reload`, no `tun`). 5 | - Added Windows `exec` scripts using batch files. 6 | - Split `get.sh` into `get-linux.sh` and `get-darwin.sh`. 7 | - Added `get-windows.ps1` (PowerShell Wireleap installation script). 8 | - Split `exec` scripts per OS in the source tree. 9 | -------------------------------------------------------------------------------- /changelogs/0.5.3.md: -------------------------------------------------------------------------------- 1 | # 0.5.3 2 | 3 | - Depends on `wireleap/common` v0.3.1. 4 | - Fixed client transport multiple hops regression introduced in `common` 5 | v0.2.2. 6 | -------------------------------------------------------------------------------- /changelogs/0.5.4.md: -------------------------------------------------------------------------------- 1 | # 0.5.4 2 | 3 | - Depends on `wireleap/common` v0.3.1. 4 | - Modified Windows scripts to search for binaries in "Program Files 5 | (x86)" as well. 6 | -------------------------------------------------------------------------------- /changelogs/0.5.5.md: -------------------------------------------------------------------------------- 1 | # 0.5.5 2 | 3 | - Depends on `wireleap/common` v0.3.1. 4 | - WebRTC leak workaround added to `chromium-browser` & `google-chrome` 5 | exec scripts. 6 | - Brave Browser exec script added. 7 | - Fixed incorrect SOCKSv5 behavior in UDP proxy mode. 8 | -------------------------------------------------------------------------------- /changelogs/0.5.6.md: -------------------------------------------------------------------------------- 1 | # 0.5.6 2 | 3 | - Depends on `wireleap/common` v0.3.4. 4 | - Fixed timing-dependent CPU hog when idle. 5 | -------------------------------------------------------------------------------- /changelogs/0.6.0.md: -------------------------------------------------------------------------------- 1 | # 0.6.0 2 | 3 | - Depends on `wireleap/common` v0.3.6. 4 | 5 | - wireleap client restructuring: 6 | - `wireleap_socks` is now a separate binary 7 | - `wireleap` split into broker & REST API controller 8 | - REST API implemented according to spec 9 | - REST API controller listens on TCP or Unix socket 10 | - accesskey-related subcommands now grouped under `accesskeys` 11 | subcommand 12 | - `import` now requires proper URL as input (`file://` or 13 | `https://`) 14 | - all CLI commands except `exec` and `intercept` now use the API 15 | - all CLI commands except `start` require `wireleap` API to be 16 | running 17 | - `completion.bash` updated to match new CLI 18 | - config file format changed: 19 | - `contract` made obsolete by `contract.json` 20 | - `address` split into `address` and `broker.address` 21 | - `address.h2c` is now `address` 22 | - `circuit` moved under `broker.circuit` 23 | - `timeout` moved under `broker.circuit.timeout` 24 | - new `forwarders` section with `socks` and `tun` 25 | - `address.socks` moved under `forwarders.socks.address` 26 | - `address.tun` moved under `forwarders.tun.address` 27 | - migration to new config file format added 28 | -------------------------------------------------------------------------------- /changelogs/0.6.1.md: -------------------------------------------------------------------------------- 1 | # 0.6.1 2 | 3 | - Depends on `wireleap/common` v0.3.6. 4 | 5 | - `wireleap_tun` bypass API: 6 | - `wireleap` no longer creates/uses `bypass.json` 7 | - bypass list changes are written directly to `wireleap_tun.sock` 8 | - removed dependency on `fsnotify/fsnotify` in `wireleap_tun` 9 | - `wireleap_tun` now handles setting IPv6 routes correctly 10 | - `wireleap_tun` IPv6 bind failure workaround applied 11 | - `tun` will no longer start without a configured service contract 12 | 13 | - API call retry interval changed to 150ms (was 100ms), total wait 14 | increased to 15s (was 10s) 15 | 16 | - Wireleap broker state logic improved wrt race conditions 17 | 18 | - git version now includes `+` instead of `-`, marking the extra 19 | information at the end of the version number as build info instead of 20 | pre-release version suffix as per the semver spec 21 | -------------------------------------------------------------------------------- /changelogs/0.6.2.md: -------------------------------------------------------------------------------- 1 | # 0.6.2 2 | 3 | - Depends on `wireleap/common` v0.3.6. 4 | 5 | - Experimental `httpget` forwarder added: issues a HTTP GET request 6 | through the Wireleap circuit. Feedback on failures is currently only 7 | logged and not returned to the CLI, which is planned to improve in a 8 | subsequent release. 9 | 10 | - Improvements to upgrade status handling code: 11 | - Upgrade status no longer assumed on start/stop. 12 | - If starting `wireleap` fails due to an upgrade being required, the 13 | information is passed to the user via the CLI (was previously only 14 | logged). 15 | 16 | - Improvements to `wireleap_tun` upgrade logic: 17 | - `wireleap_tun` binary will now be preserved on upgrades for 18 | potential rollback without having to re-setup permissions. 19 | - The user is no longer required to delete `wireleap_tun` manually. 20 | 21 | - Custom git versions enabled by using envvar `GITVERSION` in 22 | `contrib/build-bin.sh` if already set. 23 | -------------------------------------------------------------------------------- /changelogs/0.6.3.md: -------------------------------------------------------------------------------- 1 | # 0.6.3 2 | 3 | - Depends on `wireleap/common` v0.3.7. 4 | 5 | - Most of JSON files have indentation disabled, to reduce their size. 6 | -------------------------------------------------------------------------------- /circuit/circuit.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package circuit 4 | 5 | import ( 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | 10 | "github.com/wireleap/common/api/interfaces/clientrelay" 11 | "github.com/wireleap/common/api/relayentry" 12 | ) 13 | 14 | func init() { rand.Seed(time.Now().Unix()) } 15 | 16 | type T []*relayentry.T 17 | 18 | // Partition partitions an arbitrary circuit onto 3 parts: fronting, entropic 19 | // and backing relays. It also excludes all relays which have a version which 20 | // is incompatible with this wireleap. 21 | func (t T) Partition() (fronting T, entropic T, backing T) { 22 | for _, r := range t { 23 | // exclude older protocol & incompatible relays 24 | // TODO factor out this comparison? 25 | if r.Versions.ClientRelay == nil || r.Versions.ClientRelay.Minor != clientrelay.T.Version.Minor { 26 | continue 27 | } 28 | 29 | // separate 30 | switch r.Role { 31 | case "fronting": 32 | fronting = append(fronting, r) 33 | case "entropic": 34 | entropic = append(entropic, r) 35 | case "backing": 36 | backing = append(backing, r) 37 | } 38 | } 39 | 40 | return 41 | } 42 | 43 | // Join joins a partitioned circuit back. 44 | func Join(fronting T, entropic T, backing T) (t T) { 45 | t = append(t, fronting...) 46 | t = append(t, entropic...) 47 | t = append(t, backing...) 48 | return 49 | } 50 | 51 | // Make attempts to create a viable circuit given a type, number of requested 52 | // hops and a list of all relays to consider. 53 | func Make(hops int, all T) (t T, err error) { 54 | have := len(all) 55 | 56 | switch { 57 | case hops < 1: 58 | err = fmt.Errorf( 59 | "invalid number of hops requested: %d", 60 | hops, 61 | ) 62 | case hops > have: 63 | // not enough relays 64 | err = fmt.Errorf( 65 | "not enough relays to construct circuit: need %d hops, have %d suitable relays", 66 | hops, 67 | have, 68 | ) 69 | case hops <= have: 70 | // general case 71 | f, e, b := all.Partition() 72 | 73 | // always need at least a backing relay 74 | if len(b) < 1 { 75 | err = fmt.Errorf("cannot construct circuit: no backing relays") 76 | return 77 | } 78 | 79 | switch hops { 80 | case 1: 81 | // one random backing relay 82 | t = T{b[rand.Intn(len(b))]} 83 | case 2: 84 | // one random fronting and one random backing relay 85 | if len(f) < 1 { 86 | err = fmt.Errorf("cannot construct circuit: no fronting relays") 87 | return 88 | } 89 | 90 | t = T{f[rand.Intn(len(f))], b[rand.Intn(len(b))]} 91 | default: 92 | // one random fronting and one random backing relay and however 93 | // many random entropic relays 94 | if len(f) < 1 { 95 | err = fmt.Errorf("cannot construct circuit: no fronting relays") 96 | return 97 | } 98 | 99 | // shuffle entropic relays to break directory order 100 | rand.Shuffle(len(e), func(i, j int) { e[i], e[j] = e[j], e[i] }) 101 | 102 | // number of entropic relays needed 103 | need := hops - 2 104 | 105 | if len(e) < need { 106 | err = fmt.Errorf( 107 | "cannot construct circuit: not enough entropic relays; need %d for %d hops, have %d", 108 | need, 109 | hops, 110 | len(e), 111 | ) 112 | return 113 | } 114 | 115 | t = append(t, f[rand.Intn(len(f))]) 116 | t = append(t, e[:need]...) 117 | t = append(t, b[rand.Intn(len(b))]) 118 | return 119 | } 120 | } 121 | 122 | return 123 | } 124 | -------------------------------------------------------------------------------- /clientcfg/cfg.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | // Package clientcfg describes the configuration file format and data types 4 | // used by wireleap. 5 | package clientcfg 6 | 7 | import ( 8 | "time" 9 | 10 | "github.com/wireleap/common/api/duration" 11 | ) 12 | 13 | // C is the type of the config struct describing the config file format. 14 | type C struct { 15 | // Address describes the listening address of the api/controller. 16 | Address *string `json:"address,omitempty"` 17 | // Broker holds the settings specific to the wireleap broker. 18 | Broker Broker `json:"broker,omitempty"` 19 | // Forwarders holds the settings specific to the wireleap broker. 20 | Forwarders Forwarders `json:"forwarders,omitempty"` 21 | } 22 | 23 | type Broker struct { 24 | // Address describes the h2c listening address of the broker. 25 | Address *string `json:"address,omitempty"` 26 | // Accesskey is the section dealing with accesskey configuration. 27 | Accesskey Accesskey `json:"accesskey,omitempty"` 28 | // Circuit describes the configuration of the Wireleap connection circuit. 29 | Circuit Circuit `json:"circuit,omitempty"` 30 | } 31 | 32 | // Accesskey is the section dealing with accesskey configuration. 33 | type Accesskey struct { 34 | // UseOnDemand sets whether pofs are used to generate new servicekeys 35 | // automatically. 36 | UseOnDemand bool `json:"use_on_demand"` 37 | } 38 | 39 | // Circuit describes the configuration of the Wireleap connection circuit. 40 | type Circuit struct { 41 | // Timeout is the dial timeout for relay connections. 42 | Timeout duration.T `json:"timeout,omitempty"` 43 | // Whitelist is the optional user-defined list of relays to use exclusively. 44 | Whitelist []string `json:"whitelist"` 45 | // Hops is the desired number of hops to use for the circuit. 46 | Hops int `json:"hops,omitempty"` 47 | } 48 | 49 | // Forwarders describes the settings of the available forwarders. 50 | type Forwarders struct { 51 | // Socks is the SOCKSv5 TCP and UDP listening address. 52 | Socks Forwarder `json:"socks,omitempty"` 53 | // Tun is the listening address configuration for wireleap_tun. 54 | Tun Forwarder `json:"tun,omitempty"` 55 | } 56 | 57 | // Forwarder describes a single forwarder. 58 | type Forwarder struct { 59 | Address string `json:"address,omitempty"` 60 | } 61 | 62 | // Defaults provides a config with sane defaults whenever possible. 63 | func Defaults() C { 64 | var ( 65 | restaddr = "127.0.0.1:13490" 66 | brokaddr = "127.0.0.1:13490" 67 | sksaddr = "127.0.0.1:13491" 68 | tunaddr = "10.13.49.0:13492" 69 | ) 70 | return C{ 71 | Address: &restaddr, 72 | Broker: Broker{ 73 | Address: &brokaddr, 74 | Accesskey: Accesskey{UseOnDemand: true}, 75 | Circuit: Circuit{ 76 | Timeout: duration.T(time.Second * 5), 77 | Whitelist: []string{}, 78 | Hops: 1, 79 | }, 80 | }, 81 | Forwarders: Forwarders{ 82 | Socks: Forwarder{Address: sksaddr}, 83 | Tun: Forwarder{Address: tunaddr}, 84 | }, 85 | } 86 | } 87 | 88 | type Meta struct { 89 | // Option name 90 | Name string 91 | // Name of the "type' 92 | Type string 93 | // Description 94 | Desc string 95 | // Pointer to value to feed to Unmarshal() 96 | Val interface{} 97 | // Whether the input needs to be quoted before calling Unmarshal() 98 | Quote bool 99 | } 100 | 101 | func (c *C) Metadata() []*Meta { 102 | return []*Meta{ 103 | {"address", "str", "Controller address", &c.Address, true}, 104 | {"broker.address", "str", "Override default broker address", &c.Broker.Address, true}, 105 | {"broker.accesskey.use_on_demand", "bool", "Activate accesskeys as needed", &c.Broker.Accesskey.UseOnDemand, false}, 106 | {"broker.circuit.timeout", "str", "Dial timeout duration", &c.Broker.Circuit.Timeout, true}, 107 | {"broker.circuit.hops", "int", "Number of relays to use in a circuit", &c.Broker.Circuit.Hops, false}, 108 | {"broker.circuit.whitelist", "list", "Relay addresses to use in circuit", &c.Broker.Circuit.Whitelist, false}, 109 | {"forwarders.socks.address", "str", "SOCKSv5 proxy address", &c.Forwarders.Socks.Address, true}, 110 | {"forwarders.tun.address", "str", "TUN device address (not loopback)", &c.Forwarders.Tun.Address, true}, 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /clientlib/circuitdialer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package clientlib 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | 12 | "github.com/wireleap/common/api/interfaces/clientrelay" 13 | "github.com/wireleap/common/api/relayentry" 14 | "github.com/wireleap/common/api/servicekey" 15 | "github.com/wireleap/common/api/sharetoken" 16 | "github.com/wireleap/common/api/status" 17 | "github.com/wireleap/common/api/texturl" 18 | "github.com/wireleap/common/wlnet" 19 | ) 20 | 21 | func CircuitDialer( 22 | skf func() (*servicekey.T, error), 23 | circuitf func() ([]*relayentry.T, error), 24 | dialf func(net.Conn, string, *url.URL, *wlnet.Init) (net.Conn, error), 25 | ) func(string, string) (net.Conn, error) { 26 | return func(protocol, target string) (c net.Conn, err error) { 27 | sk, err := skf() 28 | if err != nil { 29 | err = fmt.Errorf("could not obtain fresh servicekey: %w", err) 30 | return 31 | } 32 | circuit, err := circuitf() 33 | if err != nil { 34 | err = fmt.Errorf("could not obtain circuit: %w", err) 35 | return 36 | } 37 | var st *sharetoken.T 38 | for i, link := range circuit { 39 | log.Println( 40 | "Connecting to circuit link:", 41 | link.Role, 42 | link.Addr.String(), 43 | link.Pubkey.String(), 44 | ) 45 | if i == 0 { 46 | continue 47 | } 48 | st, err = sharetoken.New(sk, circuit[i-1].Pubkey.T()) 49 | if err != nil { 50 | return 51 | } 52 | c, err = dialf(c, "tcp", &circuit[i-1].Addr.URL, &wlnet.Init{ 53 | Command: "CONNECT", 54 | Protocol: "tcp", 55 | Remote: link.Addr, 56 | Token: st, 57 | Version: &clientrelay.T.Version, 58 | }) 59 | if err != nil { 60 | // return circuit-specific error 61 | err = &status.T{ 62 | Code: http.StatusBadGateway, 63 | Desc: err.Error(), 64 | Origin: link.Pubkey.String(), 65 | } 66 | return 67 | } 68 | } 69 | log.Printf("Now connecting to target: %s", target) 70 | st, err = sharetoken.New(sk, circuit[len(circuit)-1].Pubkey.T()) 71 | if err != nil { 72 | return 73 | } 74 | u, err := url.Parse("target://" + target) 75 | if err != nil { 76 | return 77 | } 78 | c, err = dialf(c, "tcp", &circuit[len(circuit)-1].Addr.URL, &wlnet.Init{ 79 | Command: "CONNECT", 80 | Protocol: protocol, 81 | Remote: &texturl.URL{*u}, 82 | Token: st, 83 | Version: &clientrelay.T.Version, 84 | }) 85 | return 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /clientlib/cliutil.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package clientlib 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | "io" 9 | "log" 10 | "os" 11 | "time" 12 | 13 | "github.com/wireleap/common/api/client" 14 | "github.com/wireleap/common/api/status" 15 | ) 16 | 17 | var DefaultAPIClient = func() *client.Client { 18 | cl := client.New(nil) 19 | cl.RetryOpt.Tries = 100 20 | cl.RetryOpt.Interval = 150 * time.Millisecond 21 | cl.RetryOpt.Verbose = false 22 | return cl 23 | }() 24 | 25 | // NOTE: 26 | // the intended usage of this function is that it should never fail 27 | // if it does fail, that's an issue with the calling code 28 | func JSONOrDie(w io.Writer, x interface{}) { 29 | enc := json.NewEncoder(w) 30 | enc.SetEscapeHTML(false) 31 | enc.SetIndent("", " ") 32 | if err := enc.Encode(x); err != nil { 33 | log.Fatalf("could not marshal JSON output for %+v: %s", w, err) 34 | } 35 | } 36 | 37 | func APICallOrDie(method, url string, in interface{}, out interface{}) { 38 | if err := DefaultAPIClient.Perform(method, url, in, out); err != nil { 39 | st := &status.T{} 40 | if errors.As(err, &st) { 41 | // error can be jsonized 42 | JSONOrDie(os.Stdout, st) 43 | os.Exit(1) 44 | } else { 45 | log.Fatalf("error while executing API request: %s", err) 46 | } 47 | } else { 48 | JSONOrDie(os.Stdout, out) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /clientlib/contractinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package clientlib 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/wireleap/client/filenames" 9 | "github.com/wireleap/common/api/client" 10 | "github.com/wireleap/common/api/consume" 11 | "github.com/wireleap/common/api/contractinfo" 12 | "github.com/wireleap/common/api/relaylist" 13 | "github.com/wireleap/common/api/texturl" 14 | "github.com/wireleap/common/cli/fsdir" 15 | ) 16 | 17 | func GetContractInfo(cl *client.Client, sc *texturl.URL) (info *contractinfo.T, rl relaylist.T, err error) { 18 | if info, err = consume.ContractInfo(cl, sc); err != nil { 19 | err = fmt.Errorf( 20 | "could not get contract info for %s: %s", 21 | sc.String(), err, 22 | ) 23 | return 24 | } 25 | if rl, err = consume.ContractRelays(cl, sc); err != nil { 26 | err = fmt.Errorf( 27 | "could not get contract relays for %s: %s", 28 | sc.String(), err, 29 | ) 30 | } 31 | return 32 | } 33 | 34 | func SaveContractInfo(fm fsdir.T, ci *contractinfo.T, rl relaylist.T) (err error) { 35 | if err = fm.SetIndented(ci, filenames.Contract); err != nil { 36 | return fmt.Errorf("could not save contract info: %s", err) 37 | } 38 | if err = fm.SetIndented(rl, filenames.Relays); err != nil { 39 | return fmt.Errorf("could not save contract relays: %s", err) 40 | } 41 | return 42 | } 43 | 44 | func ContractInfo(fm fsdir.T) (ci *contractinfo.T, err error) { 45 | err = fm.Get(&ci, filenames.Contract) 46 | return 47 | } 48 | 49 | func ContractURL(fm fsdir.T) *texturl.URL { 50 | ci, err := ContractInfo(fm) 51 | if err == nil { 52 | return ci.Endpoint 53 | } else { 54 | return nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /clientlib/traceorigin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package clientlib 4 | 5 | import ( 6 | "errors" 7 | 8 | "github.com/wireleap/client/circuit" 9 | "github.com/wireleap/common/api/relayentry" 10 | "github.com/wireleap/common/api/status" 11 | ) 12 | 13 | func TraceOrigin(err error, circ circuit.T) (r *relayentry.T) { 14 | var e *status.T 15 | if errors.As(err, &e) { 16 | // this type of error can only be returned by relays 17 | // and will contain netstack origin info 18 | // so find responsible relay in circuit to provide 19 | // a more human-readable message (with url) 20 | if e.Origin != "" { 21 | for _, r0 := range circ { 22 | if e.Origin == r0.Pubkey.String() { 23 | r = r0 24 | break 25 | } 26 | } 27 | } 28 | } 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /contrib/build-bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SCRIPT_NAME="$(basename "$0")" 5 | 6 | fatal() { echo "FATAL [$SCRIPT_NAME]: $*" 1>&2; exit 1; } 7 | info() { echo "INFO [$SCRIPT_NAME]: $*"; } 8 | 9 | usage() { 10 | cat</dev/null || fatal "go not installed" 21 | command -v make >/dev/null || fatal "make not installed" 22 | 23 | OUTDIR="$(realpath "$1")" 24 | [ -d "$OUTDIR" ] || mkdir -p "$OUTDIR" 25 | 26 | SRCDIR="$(dirname "$(dirname "$(realpath "$0")")")" 27 | GITVERSION="${GITVERSION:-$($SRCDIR/contrib/gitversion.sh)}" 28 | GITCOMMIT="$(git rev-parse HEAD)" 29 | 30 | GOOS=${GOOS:-$(go env GOOS)} 31 | 32 | if [ "$GOOS" = 'linux' ]; then 33 | info "building wireleap_intercept (needed for wireleap on linux)" 34 | make -C "$SRCDIR/wireleap_intercept" 35 | cp "$SRCDIR/wireleap_intercept/wireleap_intercept.so" "$SRCDIR/sub/initcmd/embedded" 36 | make -C "$SRCDIR/wireleap_intercept" clean 37 | fi 38 | 39 | if [ "$GOOS" = 'linux' ] || [ "$GOOS" = 'darwin' ]; then 40 | info "building wireleap_tun" 41 | cd "$SRCDIR/wireleap_tun" 42 | go get -v -d ./... 43 | CGO_ENABLED=0 go build 44 | cd - 45 | mv "$SRCDIR/wireleap_tun/wireleap_tun" "$SRCDIR/sub/initcmd/embedded" 46 | fi 47 | 48 | info "building wireleap_socks" 49 | cd "$SRCDIR/wireleap_socks" 50 | go get -v -d ./... 51 | CGO_ENABLED=0 go build 52 | cd - 53 | if [ "$GOOS" = 'windows' ]; then 54 | mv "$SRCDIR/wireleap_socks/wireleap_socks.exe" "$SRCDIR/sub/initcmd/embedded/wireleap_socks.exe" 55 | else 56 | mv "$SRCDIR/wireleap_socks/wireleap_socks" "$SRCDIR/sub/initcmd/embedded/wireleap_socks" 57 | fi 58 | 59 | cp "$SRCDIR/LICENSE" "$SRCDIR/sub/initcmd/embedded/" 60 | 61 | info "building ..." 62 | CGO_ENABLED=0 go build -tags "$BUILD_TAGS" -o "$OUTDIR/wireleap" -ldflags " 63 | -X github.com/wireleap/client/version.GITREV=$GITVERSION \ 64 | -X github.com/wireleap/client/version.GIT_COMMIT=$GITCOMMIT \ 65 | -X github.com/wireleap/client/version.BUILD_TIME=$(date +%s) \ 66 | -X github.com/wireleap/client/version.BUILD_FLAGS=$BUILD_FLAGS \ 67 | " 68 | 69 | [ -z "$BUILD_USER" ] || chown -R "$BUILD_USER" "$OUTDIR" 70 | [ -z "$BUILD_GROUP" ] || chgrp -R "$BUILD_GROUP" "$OUTDIR" 71 | 72 | # defined in contrib/docker/build-bin.sh, change here if changed there 73 | DEPSDIR=/go/deps 74 | if [ -d "$DEPSDIR" ]; then 75 | [ -z "$BUILD_USER" ] || chown -R "$BUILD_USER" "$DEPSDIR" 76 | [ -z "$BUILD_GROUP" ] || chgrp -R "$BUILD_GROUP" "$DEPSDIR" 77 | fi 78 | -------------------------------------------------------------------------------- /contrib/docker/build-bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SCRIPT_NAME="$(basename "$0")" 5 | 6 | fatal() { echo "FATAL [$SCRIPT_NAME]: $*" 1>&2; exit 1; } 7 | info() { echo "INFO [$SCRIPT_NAME]: $*"; } 8 | 9 | usage() { 10 | cat</dev/null || fatal "docker not installed" 27 | 28 | SRCDIR="$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")" 29 | 30 | . "$SRCDIR/contrib/docker/goversion.sh" 31 | [ -n "$GO_VERSION" ] || fatal "go version is not defined" 32 | 33 | OUTDIR="$(realpath "$1")" 34 | [ -d "$OUTDIR" ] || mkdir -p "$OUTDIR" 35 | 36 | case "$TARGET_OS" in 37 | "") GOOS=""; UNAME="";; 38 | linux) GOOS="linux"; UNAME="Linux";; 39 | darwin) GOOS="darwin"; UNAME="Darwin";; 40 | windows) GOOS="windows"; UNAME="Windows";; 41 | *) fatal "TARGET_OS not supported: $TARGET_OS";; 42 | esac 43 | 44 | if [ -n "$DEPS_CACHE" ]; then 45 | DEPS_CACHE="$(realpath "$DEPS_CACHE")" 46 | [ -d "$DEPS_CACHE" ] || fatal "does not exist: $DEPS_CACHE" 47 | DEPS_CACHE_OPTS="-v $DEPS_CACHE:/go/deps -e GOPATH=/go/deps:/go" 48 | fi 49 | 50 | docker run --rm -i \ 51 | -v "$OUTDIR:/tmp/build" \ 52 | -v "$SRCDIR:/go/src/wireleap" \ 53 | -w /go/src/wireleap \ 54 | -e "GOOS=$GOOS" \ 55 | -e "UNAME=$UNAME" \ 56 | -e "BUILD_USER=$(id -u "$USER")" \ 57 | -e "BUILD_GROUP=$(id -u "$USER")" \ 58 | -e "BUILD_TAGS=$BUILD_TAGS" \ 59 | $DEPS_CACHE_OPTS \ 60 | $DOCKER_OPTS \ 61 | "golang:$GO_VERSION" /go/src/wireleap/contrib/build-bin.sh /tmp/build 62 | 63 | -------------------------------------------------------------------------------- /contrib/docker/goversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # this defines the Go image to use when this script is sourced 4 | export GO_VERSION='1.16' 5 | -------------------------------------------------------------------------------- /contrib/docker/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SCRIPT_NAME="$(basename "$0")" 5 | 6 | fatal() { echo "FATAL [$SCRIPT_NAME]: $*" 1>&2; exit 1; } 7 | 8 | command -v docker >/dev/null || fatal "docker not installed" 9 | 10 | SRCDIR="$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")" 11 | 12 | . "$SRCDIR/contrib/docker/goversion.sh" 13 | [ -n "$GO_VERSION" ] || fatal "go version is not defined" 14 | 15 | if [ -n "$DEPS_CACHE" ]; then 16 | DEPS_CACHE="$(realpath "$DEPS_CACHE")" 17 | [ -d "$DEPS_CACHE" ] || fatal "does not exist: $DEPS_CACHE" 18 | DEPS_CACHE_OPTS="-v $DEPS_CACHE:/go/deps -e GOPATH=/go/deps:/go" 19 | fi 20 | 21 | docker run --rm \ 22 | -v "$SRCDIR:/go/src/wireleap" \ 23 | -w /go/src/wireleap \ 24 | $DEPS_CACHE_OPTS \ 25 | $DOCKER_OPTS \ 26 | "golang:$GO_VERSION" /go/src/wireleap/contrib/run-tests.sh "$@" 27 | 28 | -------------------------------------------------------------------------------- /contrib/gen-cli-md.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | usage() { 7 | cat<&1)\n" 29 | printf '```\n\n' 30 | } 31 | 32 | _cmds() { 33 | $binary help "${1}" 2>&1 | awk '/^ [a-z\-]/ {print $1}' 34 | } 35 | 36 | main() { 37 | [ -n "$1" ] || usage 38 | binary="$1" 39 | command -v ${binary} >/dev/null || fatal "${binary} not found" 40 | 41 | component=$(echo ${binary} | cut -d- -f2) 42 | [ "${component}" = "wireleap" ] && component="client" 43 | printf "# Wireleap ${component} command line reference\n\n" 44 | 45 | printf "## Table of contents\n\n" 46 | _toc_link "${binary}" 47 | for c in $(_cmds | grep -v help); do _toc_link "${binary} ${c}"; done 48 | 49 | echo 50 | _help_entry "help" 51 | for c in $(_cmds | grep -v help); do _help_entry "help" "${c}"; done 52 | 53 | echo -n "Generated CLI reference using ${binary} v" 1>&2 54 | echo "$(${binary} version)" 1>&2 55 | } 56 | 57 | main "$@" 58 | -------------------------------------------------------------------------------- /contrib/get-darwin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | usage() { 7 | cat</dev/null 2>&1 || realpath() ( 83 | OURPWD="$PWD" 84 | cd "$(dirname "$1")" 85 | LINK="$(readlink "$(basename "$1")" || echo)" 86 | while [ "$LINK" ]; do 87 | cd "$(dirname "$LINK")" 88 | LINK="$(readlink "$(basename "$1")" || echo)" 89 | done 90 | REALPATH="$PWD/$(basename "$1")" 91 | cd "$OURPWD" 92 | echo "$REALPATH" 93 | ) 94 | 95 | _verify_environment() { 96 | [ "$(id -u)" = "0" ] && fatal "should not be run as root" 97 | 98 | case "$(uname -s):$(uname -m)" in 99 | Darwin:x86_64) ;; 100 | *) fatal "unsupported system: $(uname -a)" ;; 101 | esac 102 | 103 | if [ "$symlink_path" ]; then 104 | symdir="$(dirname "$symlink_path")" 105 | symfile="$(basename "$symlink_path")" 106 | [ -d "$symlink_path" ] && fatal "$symlink_path is a directory" 107 | [ -e "$symlink_path" ] && fatal "$symlink_path already exists" 108 | [ -w "$symdir" ] || fatal "$symdir is not writable" 109 | _in_path "$symdir" || fatal "$symdir is not in \$PATH" 110 | command -v "$symfile" >/dev/null && fatal "$symfile already in \$PATH" 111 | fi 112 | 113 | command -v curl >/dev/null || fatal "curl not found" 114 | 115 | e="gpg not found. install or specify --skip-gpg (not recommended)" 116 | [ "$skip_gpg" ] || command -v gpg >/dev/null || fatal "$e" 117 | 118 | if ! [ "$skip_checksum" ]; then 119 | if ! command -v shasum >/dev/null; then 120 | fatal "shasum not found. install or --skip-checksum (not recommended)" 121 | fi 122 | fi 123 | 124 | return 0 125 | } 126 | 127 | _download() { 128 | filename="$1" 129 | eval set -- "$2" 130 | dist="https://github.com/wireleap/client/releases/latest/download/" 131 | curl "$@" -LO "$dist/$filename" || fatal "$filename download failed" 132 | return 0 133 | } 134 | 135 | _verify_gpg() { 136 | [ "$skip_gpg" ] && echo " SKIPPED!" && return 0 137 | filename="$1" 138 | eval set -- "--no-default-keyring --keyring ./keyring.gpg" 139 | echo "$PUBKEY" | gpg "$@" --import > out.gpg 2>&1 || return 1 140 | gpg "$@" --verify "$filename" > out.gpg 2>&1 || return 1 141 | rm -f keyring.gpg keyring.gpg~ out.gpg 142 | return 0 143 | } 144 | 145 | _verify_checksum() { 146 | [ "$skip_checksum" ] && echo " SKIPPED!" && return 0 147 | filename="$1" 148 | eval set -- "--check --status" 149 | shasum -a512 "$@" "$filename" || fatal "shasum $filename check failed" 150 | return 0 151 | } 152 | 153 | _in_path() { 154 | echo "$PATH" | grep -q "^$1:" && return 0 155 | echo "$PATH" | grep -q ":$1:" && return 0 156 | echo "$PATH" | grep -q ":$1$" && return 0 157 | return 1 158 | } 159 | 160 | main() { 161 | while [ "$1" != "" ]; do 162 | case "$1" in 163 | --help|-h|help) usage ;; 164 | --symlink=*) symlink_path="${1##*=}" ;; 165 | --skip-gpg) skip_gpg="true" ;; 166 | --skip-checksum) skip_checksum="true" ;; 167 | *) if [ -n "$d" ]; then usage; else d="$1"; fi ;; 168 | esac 169 | shift 170 | done 171 | 172 | [ -n "$d" ] || usage 173 | [ -d "$d" ] && fatal "$d already exists" 174 | binary="wireleap" 175 | binarydir="$(realpath "$d")" 176 | 177 | echo "* verifying environment ..." 178 | _verify_environment 179 | 180 | echo "* preparing quarantine ..." 181 | mkdir -p "$binarydir/quarantine" 182 | cd "$binarydir/quarantine" 183 | 184 | os="$(uname -s | tr '[:upper:]' '[:lower:]')" 185 | echo "* downloading $binary ..." 186 | _download "${binary}_$os-amd64" 187 | 188 | echo "* downloading $binary.hash ..." 189 | _download "${binary}_$os-amd64.hash" "--silent --show-error" 190 | 191 | echo "* performing gpg verification on $binary.hash ..." 192 | _verify_gpg "${binary}_$os-amd64.hash" || fatal "gpg error\n$(cat out.gpg)" 193 | 194 | echo "* performing checksum verification on $binary ..." 195 | _verify_checksum "${binary}_$os-amd64.hash" 196 | 197 | echo "* releasing $binary from quarantine ..." 198 | cd "$binarydir" 199 | mv "$binarydir/quarantine/${binary}_$os-amd64" "$binarydir/$binary" 200 | rm "$binarydir/quarantine/${binary}_$os-amd64.hash" 201 | rmdir "$binarydir/quarantine" 202 | 203 | echo "* setting $binary executable flag ..." 204 | chmod +x "$binarydir/$binary" 205 | 206 | echo "* performing $binary initialization ..." 207 | "$binarydir/$binary" init > out.init 2>&1 || fatal "error\n$(cat out.init)" 208 | rm -f out.init 209 | 210 | if [ "$symlink_path" ]; then 211 | echo "* creating symlink $symlink_path ..." 212 | ln -sf "$binarydir/$binary" "$symlink_path" 213 | fi 214 | 215 | echo "* complete" 216 | 217 | if [ -e "$binarydir/wireleap_tun" ]; then 218 | echo 219 | echo "To enable TUN support, execute the following commands:" 220 | echo " $ sudo chown 0:0 $binarydir/wireleap_tun" 221 | echo " $ sudo chmod u+s $binarydir/wireleap_tun" 222 | fi 223 | 224 | return 0 225 | } 226 | 227 | # wrap in function for some protection against partial file if "curl | sh" 228 | main "$@" 229 | -------------------------------------------------------------------------------- /contrib/get-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | usage() { 7 | cat</dev/null && fatal "$symfile already in \$PATH" 97 | fi 98 | 99 | command -v curl >/dev/null || fatal "curl not found" 100 | 101 | e="gpg not found. install or specify --skip-gpg (not recommended)" 102 | [ "$skip_gpg" ] || command -v gpg >/dev/null || fatal "$e" 103 | 104 | if ! [ "$skip_checksum" ]; then 105 | if ! command -v sha512sum >/dev/null; then 106 | fatal "sha512sum not found. install or --skip-checksum (not recommended)" 107 | fi 108 | fi 109 | 110 | return 0 111 | } 112 | 113 | _download() { 114 | filename="$1" 115 | eval set -- "$2" 116 | dist="https://github.com/wireleap/client/releases/latest/download/" 117 | curl "$@" -LO "$dist/$filename" || fatal "$filename download failed" 118 | return 0 119 | } 120 | 121 | _verify_gpg() { 122 | [ "$skip_gpg" ] && echo " SKIPPED!" && return 0 123 | filename="$1" 124 | eval set -- "--no-default-keyring --keyring ./keyring.gpg" 125 | echo "$PUBKEY" | gpg "$@" --import > out.gpg 2>&1 || return 1 126 | gpg "$@" --verify "$filename" > out.gpg 2>&1 || return 1 127 | rm -f keyring.gpg keyring.gpg~ out.gpg 128 | return 0 129 | } 130 | 131 | _verify_checksum() { 132 | [ "$skip_checksum" ] && echo " SKIPPED!" && return 0 133 | filename="$1" 134 | eval set -- "--check --status" 135 | sha512sum "$@" "$filename" || fatal "sha512sum $filename check failed" 136 | return 0 137 | } 138 | 139 | _in_path() { 140 | echo "$PATH" | grep -q "^$1:" && return 0 141 | echo "$PATH" | grep -q ":$1:" && return 0 142 | echo "$PATH" | grep -q ":$1$" && return 0 143 | return 1 144 | } 145 | 146 | main() { 147 | while [ "$1" != "" ]; do 148 | case "$1" in 149 | --help|-h|help) usage ;; 150 | --symlink=*) symlink_path="${1##*=}" ;; 151 | --skip-gpg) skip_gpg="true" ;; 152 | --skip-checksum) skip_checksum="true" ;; 153 | *) if [ -n "$d" ]; then usage; else d="$1"; fi ;; 154 | esac 155 | shift 156 | done 157 | 158 | [ -n "$d" ] || usage 159 | [ -d "$d" ] && fatal "$d already exists" 160 | binary="wireleap" 161 | binarydir="$(realpath "$d")" 162 | 163 | echo "* verifying environment ..." 164 | _verify_environment 165 | 166 | echo "* preparing quarantine ..." 167 | mkdir -p "$binarydir/quarantine" 168 | cd "$binarydir/quarantine" 169 | 170 | os="$(uname -s | tr '[:upper:]' '[:lower:]')" 171 | echo "* downloading $binary ..." 172 | _download "${binary}_$os-amd64" 173 | 174 | echo "* downloading $binary.hash ..." 175 | _download "${binary}_$os-amd64.hash" "--silent --show-error" 176 | 177 | echo "* performing gpg verification on $binary.hash ..." 178 | _verify_gpg "${binary}_$os-amd64.hash" || fatal "gpg error\n$(cat out.gpg)" 179 | 180 | echo "* performing checksum verification on $binary ..." 181 | _verify_checksum "${binary}_$os-amd64.hash" 182 | 183 | echo "* releasing $binary from quarantine ..." 184 | cd "$binarydir" 185 | mv "$binarydir/quarantine/${binary}_$os-amd64" "$binarydir/$binary" 186 | rm "$binarydir/quarantine/${binary}_$os-amd64.hash" 187 | rmdir "$binarydir/quarantine" 188 | 189 | echo "* setting $binary executable flag ..." 190 | chmod +x "$binarydir/$binary" 191 | 192 | echo "* performing $binary initialization ..." 193 | "$binarydir/$binary" init > out.init 2>&1 || fatal "error\n$(cat out.init)" 194 | rm -f out.init 195 | 196 | if [ "$symlink_path" ]; then 197 | echo "* creating symlink $symlink_path ..." 198 | ln -sf "$binarydir/$binary" "$symlink_path" 199 | fi 200 | 201 | echo "* complete" 202 | 203 | if [ -e "$binarydir/wireleap_tun" ]; then 204 | echo 205 | echo "To enable TUN support, execute the following commands:" 206 | echo " $ sudo chown 0:0 $binarydir/wireleap_tun" 207 | echo " $ sudo chmod u+s $binarydir/wireleap_tun" 208 | fi 209 | 210 | return 0 211 | } 212 | 213 | # wrap in function for some protection against partial file if "curl | sh" 214 | main "$@" 215 | -------------------------------------------------------------------------------- /contrib/get-windows.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = 'Stop' 2 | # https://stackoverflow.com/questions/28682642/powershell-why-is-using-invoke-webrequest-much-slower-than-a-browser-download 3 | $ProgressPreference = 'SilentlyContinue' 4 | 5 | function Fatal($msg) { 6 | Write-Error "$msg" 7 | exit 1 8 | } 9 | 10 | function Download($filename) { 11 | $dist = "https://github.com/wireleap/client/releases/latest/download/" 12 | Invoke-WebRequest "$dist/$filename" -OutFile "$filename" 13 | } 14 | 15 | function Verify-Checksum($binary) { 16 | $want = (Select-String '[A-Fa-f0-9]{128}' -Path "$binary.hash").Matches.Value.ToUpper() 17 | $got = (Get-FileHash -Algorithm SHA512 "$binary").Hash 18 | if (-not ("$want" -eq "$got")) { 19 | Fatal "sha512 checksum verification failed: expected $want, got $got" 20 | } 21 | } 22 | 23 | <# 24 | .SYNOPSIS 25 | Download, verify and install wireleap client 26 | 27 | .DESCRIPTION 28 | This cmdlet will verify your environment's compatibility and download the 29 | latest client binary with the associated hash file to cryptographically verify 30 | its integrity via SHA-512 checksum. If all checks pass, it will release the 31 | binary from quarantine and initialize the client in the specified directory. 32 | 33 | .LINK 34 | https://wireleap.com/docs/client/ 35 | #> 36 | function Get-Wireleap { 37 | param( 38 | # Destination path (eg. $env:USERPROFILE\wireleap, mandatory) 39 | [Parameter(Mandatory)] 40 | [string]$Dir, 41 | 42 | # Skip checksum verification (not recommended) 43 | [Parameter()] 44 | [switch]$SkipChecksum 45 | ) 46 | 47 | if (Test-Path "$Dir") { Fatal "$Dir already exists" } 48 | 49 | echo "* preparing quarantine ..." 50 | if (Test-Path "$Dir\quarantine") { rm -r "$Dir\quarantine" } 51 | mkdir "$Dir\quarantine" > $null 52 | cd "$Dir\quarantine" 53 | 54 | $bin = "wireleap.exe" 55 | $fullbin = "wireleap_windows-amd64.exe" 56 | echo "* downloading $fullbin ..." 57 | Download "$fullbin" 58 | 59 | echo "* downloading $fullbin.hash ..." 60 | Download "$fullbin.hash" 61 | 62 | echo "* performing checksum verification on $fullbin ..." 63 | if (-not($SkipChecksum.IsPresent)) { 64 | Verify-Checksum("$fullbin") 65 | } else { 66 | echo " SKIPPED!" 67 | } 68 | 69 | echo "* releasing $fullbin from quarantine ..." 70 | cd "$Dir" 71 | Move-Item -Force "quarantine\$fullbin" "$bin" 72 | rm "quarantine\$fullbin.hash" 73 | rm -r 'quarantine' 74 | 75 | echo "* performing $binary initialization ..." 76 | $ErrorActionPreference = 'SilentlyContinue' 77 | # https://stackoverflow.com/questions/10666101/lastexitcode-0-but-false-in-powershell-redirecting-stderr-to-stdout-gives 78 | .\wireleap.exe init 2>&1 | %{ "$_" } > out.init 79 | if ($LASTEXITCODE -ne 0) { 80 | Fatal ("error\n" + (cat out.init)) 81 | } 82 | $ErrorActionPreference = 'Stop' 83 | rm out.init 84 | 85 | echo "* complete" 86 | } 87 | -------------------------------------------------------------------------------- /contrib/get.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | usage() { 7 | cat</dev/null 2>&1 || realpath() ( 83 | OURPWD="$PWD" 84 | cd "$(dirname "$1")" 85 | LINK="$(readlink "$(basename "$1")" || echo)" 86 | while [ "$LINK" ]; do 87 | cd "$(dirname "$LINK")" 88 | LINK="$(readlink "$(basename "$1")" || echo)" 89 | done 90 | REALPATH="$PWD/$(basename "$1")" 91 | cd "$OURPWD" 92 | echo "$REALPATH" 93 | ) 94 | 95 | _verify_environment() { 96 | [ "$(id -u)" = "0" ] && fatal "should not be run as root" 97 | 98 | case "$(uname -s):$(uname -m)" in 99 | Linux:x86_64) ;; 100 | Darwin:x86_64) ;; 101 | *) fatal "unsupported system: $(uname -a)" ;; 102 | esac 103 | 104 | if [ "$symlink_path" ]; then 105 | symdir="$(dirname "$symlink_path")" 106 | symfile="$(basename "$symlink_path")" 107 | [ -d "$symlink_path" ] && fatal "$symlink_path is a directory" 108 | [ -e "$symlink_path" ] && fatal "$symlink_path already exists" 109 | [ -w "$symdir" ] || fatal "$symdir is not writable" 110 | _in_path "$symdir" || fatal "$symdir is not in \$PATH" 111 | command -v "$symfile" >/dev/null && fatal "$symfile already in \$PATH" 112 | fi 113 | 114 | command -v curl >/dev/null || fatal "curl not found" 115 | 116 | e="gpg not found. install or specify --skip-gpg (not recommended)" 117 | [ "$skip_gpg" ] || command -v gpg >/dev/null || fatal "$e" 118 | 119 | SHASUM= 120 | if ! [ "$skip_checksum" ]; then 121 | if command -v sha512sum >/dev/null; then 122 | SHASUM='sha512sum' 123 | elif command -v shasum >/dev/null; then 124 | SHASUM='shasum -a512' 125 | else 126 | fatal "sha512sum or shasum not found. install or --skip-checksum (not recommended)" 127 | fi 128 | fi 129 | 130 | return 0 131 | } 132 | 133 | _download() { 134 | filename="$1" 135 | eval set -- "$2" 136 | dist="https://github.com/wireleap/client/releases/latest/download/" 137 | curl "$@" -LO "$dist/$filename" || fatal "$filename download failed" 138 | return 0 139 | } 140 | 141 | _verify_gpg() { 142 | [ "$skip_gpg" ] && echo " SKIPPED!" && return 0 143 | filename="$1" 144 | eval set -- "--no-default-keyring --keyring ./keyring.gpg" 145 | echo "$PUBKEY" | gpg "$@" --import > out.gpg 2>&1 || return 1 146 | gpg "$@" --verify "$filename" > out.gpg 2>&1 || return 1 147 | rm -f keyring.gpg keyring.gpg~ out.gpg 148 | return 0 149 | } 150 | 151 | _verify_checksum() { 152 | [ "$skip_checksum" ] && echo " SKIPPED!" && return 0 153 | filename="$1" 154 | eval set -- "--check --status" 155 | $SHASUM "$@" "$filename" || fatal "$SHASUM $filename check failed" 156 | return 0 157 | } 158 | 159 | _in_path() { 160 | echo "$PATH" | grep -q "^$1:" && return 0 161 | echo "$PATH" | grep -q ":$1:" && return 0 162 | echo "$PATH" | grep -q ":$1$" && return 0 163 | return 1 164 | } 165 | 166 | main() { 167 | while [ "$1" != "" ]; do 168 | case "$1" in 169 | --help|-h|help) usage ;; 170 | --symlink=*) symlink_path="${1##*=}" ;; 171 | --skip-gpg) skip_gpg="true" ;; 172 | --skip-checksum) skip_checksum="true" ;; 173 | *) if [ -n "$d" ]; then usage; else d="$1"; fi ;; 174 | esac 175 | shift 176 | done 177 | 178 | [ -n "$d" ] || usage 179 | [ -d "$d" ] && fatal "$d already exists" 180 | binary="wireleap" 181 | binarydir="$(realpath "$d")" 182 | 183 | echo "* verifying environment ..." 184 | _verify_environment 185 | 186 | echo "* preparing quarantine ..." 187 | mkdir -p "$binarydir/quarantine" 188 | cd "$binarydir/quarantine" 189 | 190 | os="$(uname -s | tr '[:upper:]' '[:lower:]')" 191 | echo "* downloading $binary ..." 192 | _download "${binary}_$os-amd64" 193 | 194 | echo "* downloading $binary.hash ..." 195 | _download "${binary}_$os-amd64.hash" "--silent --show-error" 196 | 197 | echo "* performing gpg verification on $binary.hash ..." 198 | _verify_gpg "${binary}_$os-amd64.hash" || fatal "gpg error\n$(cat out.gpg)" 199 | 200 | echo "* performing checksum verification on $binary ..." 201 | _verify_checksum "${binary}_$os-amd64.hash" 202 | 203 | echo "* releasing $binary from quarantine ..." 204 | cd "$binarydir" 205 | mv "$binarydir/quarantine/${binary}_$os-amd64" "$binarydir/$binary" 206 | rm "$binarydir/quarantine/${binary}_$os-amd64.hash" 207 | rmdir "$binarydir/quarantine" 208 | 209 | echo "* setting $binary executable flag ..." 210 | chmod +x "$binarydir/$binary" 211 | 212 | echo "* performing $binary initialization ..." 213 | "$binarydir/$binary" init > out.init 2>&1 || fatal "error\n$(cat out.init)" 214 | rm -f out.init 215 | 216 | if [ "$symlink_path" ]; then 217 | echo "* creating symlink $symlink_path ..." 218 | ln -sf "$binarydir/$binary" "$symlink_path" 219 | fi 220 | 221 | echo "* complete" 222 | 223 | if [ -e "$binarydir/wireleap_tun" ]; then 224 | echo 225 | echo "To enable TUN support, execute the following commands:" 226 | echo " $ sudo chown 0:0 $binarydir/wireleap_tun" 227 | echo " $ sudo chmod u+s $binarydir/wireleap_tun" 228 | fi 229 | 230 | return 0 231 | } 232 | 233 | # wrap in function for some protection against partial file if "curl | sh" 234 | main "$@" 235 | -------------------------------------------------------------------------------- /contrib/gitversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | SCRIPT_NAME="$(basename "$0")" 4 | fatal() { echo "FATAL [$SCRIPT_NAME]: $*" 1>&2; exit 1; } 5 | 6 | command -v git >/dev/null || fatal "git not installed" 7 | 8 | if git describe > /dev/null 2>&1; then 9 | git describe | sed 's/^v//; s/-/+/' 10 | else 11 | echo "0.0.0+$(git describe --always)" 12 | fi 13 | -------------------------------------------------------------------------------- /contrib/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | SCRIPT_NAME="$(basename "$0")" 5 | 6 | fatal() { echo "FATAL [$SCRIPT_NAME]: $*" 1>&2; exit 1; } 7 | info() { echo "INFO [$SCRIPT_NAME]: $*"; } 8 | 9 | command -v go >/dev/null || fatal "go not installed" 10 | 11 | SRCDIR="$(dirname "$(dirname "$(realpath "$0")")")" 12 | GITVERSION="$($SRCDIR/contrib/gitversion.sh)" 13 | 14 | NPROC= 15 | if command -v nproc >/dev/null; then 16 | NPROC="$( nproc )" 17 | elif command -v grep >/dev/null; then 18 | NPROC="$( grep -c processor /proc/cpuinfo )" 19 | fi 20 | 21 | if [ "$NPROC" -lt 2 ]; then 22 | NPROC=2 23 | fi 24 | 25 | info "running at most $NPROC tests in parallel" 26 | 27 | GOOS=${GOOS:-$(go env GOOS)} 28 | 29 | if [ "$GOOS" = 'linux' ]; then 30 | info "compiling wireleap_intercept (needed for wireleap on linux)" 31 | make -C wireleap_intercept 32 | cp wireleap_intercept/wireleap_intercept.so "$SRCDIR/sub/initcmd/embedded" 33 | fi 34 | 35 | if [ "$GOOS" = 'linux' ] || [ "$GOOS" = 'darwin' ]; then 36 | info "building wireleap_tun" 37 | cd wireleap_tun 38 | go get -v -d ./... 39 | CGO_ENABLED=0 go build 40 | cd - 41 | mv wireleap_tun/wireleap_tun "$SRCDIR/sub/initcmd/embedded" 42 | fi 43 | 44 | info "building wireleap_socks" 45 | cd "$SRCDIR/wireleap_socks" 46 | go get -v -d ./... 47 | CGO_ENABLED=0 go build 48 | cd - 49 | if [ "$GOOS" = 'windows' ]; then 50 | mv "$SRCDIR/wireleap_socks/wireleap_socks.exe" "$SRCDIR/sub/initcmd/embedded/wireleap_socks.exe" 51 | else 52 | mv "$SRCDIR/wireleap_socks/wireleap_socks" "$SRCDIR/sub/initcmd/embedded/wireleap_socks" 53 | fi 54 | 55 | VERSIONS= 56 | for c in common/api common/cli client; do 57 | VERSIONS="$VERSIONS -X github.com/wireleap/$c/version.GITREV=$GITVERSION" 58 | done 59 | 60 | cp "$SRCDIR/LICENSE" "$SRCDIR/sub/initcmd/embedded/" 61 | 62 | info "testing ..." 63 | go test \ 64 | -parallel "$NPROC" \ 65 | -ldflags " 66 | $VERSIONS 67 | " \ 68 | "$@" ./... 69 | -------------------------------------------------------------------------------- /contrib/speedtest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal () { echo "FATAL: $@"; exit 1; } 5 | 6 | for cmd in wireleap curl; do 7 | command -v "$cmd" >/dev/null || fatal "$cmd was not found in $PATH" 8 | done 9 | 10 | WIRELEAP="$(which wireleap)" 11 | CURL="curl" 12 | 13 | speed () { 14 | set -x 15 | $CURL -sS \ 16 | -o /dev/null \ 17 | -w 'scale=2;%{speed_download}/1024/1024\n' \ 18 | 'https://speed.hetzner.de/100MB.bin' | bc 19 | set +x 20 | } 21 | 22 | end () { 23 | set -x 24 | $WIRELEAP tun stop || true 25 | wireleap stop || true 26 | set +x 27 | echo "Results written to $OUT" 28 | exit 1 29 | } 30 | 31 | trap end ERR 32 | 33 | echo 'Warning, this test suite may consume several hundreds of MB of bandwidth.' 34 | echo 'Are you sure you want to proceed? (Return to continue, ^C to quit)' 35 | read 36 | 37 | echo 'Commands will be echoed before being ran. At any point, ^C to cancel and quit.' 38 | 39 | OUT="/tmp/speedtest-$(date +'%s').md" 40 | 41 | cat < "$OUT" 42 | # Speed test results for $(date +'%F %T %z') 43 | 44 | Avg speed vanilla: $(speed) MiB/s 45 | EOF 46 | 47 | set -x 48 | $WIRELEAP status >/dev/null || $WIRELEAP start 49 | set +x 50 | 51 | sleep 3 52 | echo "Avg speed via SOCKSv5: $(CURL="$WIRELEAP exec curl" speed) MiB/s" >> "$OUT" 53 | 54 | set -x 55 | $WIRELEAP tun start 56 | set +x 57 | 58 | echo "Avg speed via tun device: $(speed) MiB/s" >> "$OUT" 59 | 60 | set -x 61 | $WIRELEAP tun stop 62 | wireleap stop 63 | set +x 64 | 65 | echo "Done." 66 | echo "Results written to $OUT" 67 | -------------------------------------------------------------------------------- /dnscachedial/dnscachedial.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package dnscachedial 4 | 5 | import ( 6 | "context" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | // Control is the type of a cache controller. 12 | type Control struct { 13 | sync.Mutex 14 | cache map[string][]string 15 | } 16 | 17 | // New creates a new DNS cache. 18 | func New() *Control { return &Control{cache: map[string][]string{}} } 19 | 20 | // Cache explicitly adds an address to the DNS cache. 21 | func (c *Control) Cache(ctx context.Context, addr string) (err error) { 22 | c.Lock() 23 | c.cache[addr], err = net.DefaultResolver.LookupHost(ctx, addr) 24 | c.Unlock() 25 | return 26 | } 27 | 28 | // Get retrieves the cached resolved addresses of addr. 29 | func (c *Control) Get(addr string) (r []string) { 30 | c.Lock() 31 | r = c.cache[addr] 32 | c.Unlock() 33 | return 34 | } 35 | 36 | // Flush flushes the cache, removing all cached addresses. 37 | func (c *Control) Flush() { 38 | c.Lock() 39 | for k, _ := range c.cache { 40 | delete(c.cache, k) 41 | } 42 | c.Unlock() 43 | } 44 | 45 | type DialCtxFunc func(context.Context, string, string) (net.Conn, error) 46 | 47 | // Cover creates a new DNS caching DialCtxFunc from an original DialCtxFunc. 48 | func (c *Control) Cover(orig DialCtxFunc) DialCtxFunc { 49 | return func(ctx context.Context, network string, hostport string) (_ net.Conn, err error) { 50 | c.Lock() 51 | defer c.Unlock() 52 | // host:port given but only host needs to be looked up/stored 53 | host, port, err := net.SplitHostPort(hostport) 54 | if err != nil { 55 | return 56 | } 57 | // cache new addresses 58 | var addrs []string 59 | if addrs = c.cache[host]; addrs == nil { 60 | if addrs, err = net.DefaultResolver.LookupHost(ctx, host); err != nil { 61 | return 62 | } 63 | c.cache[host] = addrs 64 | } 65 | // rotate address list & dial 66 | if len(addrs) > 1 { 67 | c.cache[host] = append(addrs[1:], addrs[0]) 68 | } 69 | return orig(ctx, network, net.JoinHostPort(addrs[0], port)) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /filenames/filenames.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package filenames 4 | 5 | const ( 6 | Config = "config.json" 7 | Pid = "wireleap.pid" 8 | Servicekey = "servicekey.json" 9 | Pofs = "pofs.json" 10 | Log = "wireleap.log" 11 | Bypass = "bypass.json" 12 | Contract = "contract.json" 13 | Relays = "relays.json" 14 | ) 15 | 16 | var InitFiles = [...]string{Config, Servicekey, Pofs} 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wireleap/client 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/blang/semver v3.5.1+incompatible 7 | github.com/google/gopacket v1.1.19 8 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 9 | github.com/vishvananda/netlink v1.1.0 10 | github.com/wireleap/common v0.3.7 11 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b 12 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 2 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 3 | github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= 4 | github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 5 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= 6 | github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= 7 | github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= 8 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 9 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= 10 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 11 | github.com/wireleap/common v0.3.7 h1:Z2Z/bcVmcTxdzsTUtAAZLP+LwcpJ4oH0CXgEVRm58aI= 12 | github.com/wireleap/common v0.3.7/go.mod h1:bL+o0kyAOn+4ZCtAlWY3YvKhxztfXoA//BFOvqKOsgI= 13 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 14 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 15 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= 16 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 17 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 18 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 19 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 22 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A= 23 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 24 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 25 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= 31 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 34 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 35 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 36 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 37 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 38 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 39 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package main 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/wireleap/common/api/interfaces/clientcontract" 9 | "github.com/wireleap/common/api/interfaces/clientdir" 10 | "github.com/wireleap/common/api/interfaces/clientrelay" 11 | "github.com/wireleap/common/cli" 12 | "github.com/wireleap/common/cli/commonsub/commonlib" 13 | "github.com/wireleap/common/cli/commonsub/migratecmd" 14 | "github.com/wireleap/common/cli/commonsub/rollbackcmd" 15 | "github.com/wireleap/common/cli/commonsub/superviseupgradecmd" 16 | "github.com/wireleap/common/cli/commonsub/upgradecmd" 17 | "github.com/wireleap/common/cli/upgrade" 18 | 19 | "github.com/wireleap/client/sub/accesskeyscmd" 20 | "github.com/wireleap/client/sub/configcmd" 21 | "github.com/wireleap/client/sub/execcmd" 22 | "github.com/wireleap/client/sub/httpgetcmd" 23 | "github.com/wireleap/client/sub/initcmd" 24 | "github.com/wireleap/client/sub/interceptcmd" 25 | "github.com/wireleap/client/sub/logcmd" 26 | "github.com/wireleap/client/sub/reloadcmd" 27 | "github.com/wireleap/client/sub/restartcmd" 28 | "github.com/wireleap/client/sub/sockscmd" 29 | "github.com/wireleap/client/sub/startcmd" 30 | "github.com/wireleap/client/sub/statuscmd" 31 | "github.com/wireleap/client/sub/stopcmd" 32 | "github.com/wireleap/client/sub/tuncmd" 33 | "github.com/wireleap/client/sub/versioncmd" 34 | "github.com/wireleap/client/version" 35 | ) 36 | 37 | const binname = "wireleap" 38 | 39 | func main() { 40 | fm := cli.Home() 41 | 42 | cli.CLI{ 43 | Subcmds: []*cli.Subcmd{ 44 | initcmd.Cmd(), 45 | configcmd.Cmd(fm), 46 | accesskeyscmd.Cmd(), 47 | startcmd.Cmd(binname), 48 | statuscmd.Cmd(binname), 49 | reloadcmd.Cmd(binname), 50 | restartcmd.Cmd(binname, startcmd.Cmd(binname).Run, stopcmd.Cmd(binname).Run), 51 | stopcmd.Cmd(binname), 52 | logcmd.Cmd(binname), 53 | tuncmd.Cmd(), 54 | sockscmd.Cmd(), 55 | interceptcmd.Cmd(), 56 | httpgetcmd.Cmd(), 57 | execcmd.Cmd(), 58 | upgradecmd.Cmd( 59 | binname, 60 | upgrade.ExecutorSupervised, 61 | version.VERSION, 62 | version.LatestChannelVersion, 63 | ), 64 | rollbackcmd.Cmd(commonlib.Context{ 65 | BinName: binname, 66 | PostHook: version.PostRollbackHook, 67 | }), 68 | superviseupgradecmd.Cmd(commonlib.Context{ 69 | BinName: binname, 70 | NewVersion: version.VERSION, 71 | PostHook: version.PostUpgradeHook, 72 | }), 73 | migratecmd.Cmd(binname, version.MIGRATIONS, version.VERSION), 74 | versioncmd.Cmd( 75 | &version.VERSION, 76 | clientdir.T, 77 | clientcontract.T, 78 | clientrelay.T, 79 | ), 80 | }, 81 | }.Parse(os.Args).Run(fm) 82 | } 83 | -------------------------------------------------------------------------------- /restapi/accesskeys.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/wireleap/common/api/pof" 7 | "github.com/wireleap/common/api/servicekey" 8 | "github.com/wireleap/common/api/texturl" 9 | ) 10 | 11 | type AccesskeyImportRequest struct { 12 | URL *texturl.URL `json:"url"` 13 | } 14 | 15 | type AccesskeyReply struct { 16 | Contract *texturl.URL `json:"contract"` 17 | Duration int64 `json:"duration"` 18 | State string `json:"state"` 19 | Expiration int64 `json:"expiration"` 20 | } 21 | 22 | func (t *T) accesskeysFromSks(sks ...*servicekey.T) (rs []*AccesskeyReply) { 23 | ci := t.br.ContractInfo() 24 | for _, sk := range sks { 25 | if sk == nil { 26 | continue 27 | } 28 | state := "active" 29 | if sk.IsExpiredAt(time.Now().Unix()) { 30 | state = "expired" 31 | } 32 | rs = append(rs, &AccesskeyReply{ 33 | Contract: ci.Endpoint, 34 | Duration: int64(time.Duration(ci.Servicekey.Duration) / time.Second), 35 | State: state, 36 | Expiration: sk.Contract.SettlementOpen, 37 | }) 38 | } 39 | return 40 | } 41 | 42 | func (t *T) accesskeysFromPofs(pofs ...*pof.T) (rs []*AccesskeyReply) { 43 | ci := t.br.ContractInfo() 44 | for _, p := range pofs { 45 | if p == nil { 46 | continue 47 | } 48 | state := "inactive" 49 | if p.IsExpiredAt(time.Now().Unix()) { 50 | state = "expired" 51 | } 52 | rs = append(rs, &AccesskeyReply{ 53 | Contract: ci.Endpoint, 54 | Duration: int64(time.Duration(ci.Servicekey.Duration) / time.Second), 55 | State: state, 56 | Expiration: p.Expiration, 57 | }) 58 | } 59 | return 60 | } 61 | 62 | func (t *T) newAccesskeysReply() (rs []*AccesskeyReply) { 63 | rs = append(rs, t.accesskeysFromSks(t.br.CurrentSK())...) 64 | rs = append(rs, t.accesskeysFromPofs(t.br.CurrentPofs()...)...) 65 | // serve empty list instead of nil 66 | if rs == nil { 67 | rs = make([]*AccesskeyReply, 0) 68 | } 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /restapi/fwder.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io/fs" 8 | "io/ioutil" 9 | "log" 10 | "net" 11 | "net/http" 12 | "os" 13 | "os/exec" 14 | "runtime" 15 | "sync" 16 | "time" 17 | 18 | "github.com/wireleap/common/api/client" 19 | "github.com/wireleap/common/api/provide" 20 | "github.com/wireleap/common/api/status" 21 | "github.com/wireleap/common/cli/process" 22 | ) 23 | 24 | const fwderPrefix = "wireleap_" 25 | 26 | type FwderReply struct { 27 | Pid int `json:"pid"` 28 | State string `json:"state"` 29 | Address string `json:"address"` 30 | Binary binaryReply `json:"binary"` 31 | } 32 | 33 | type binaryReply struct { 34 | Ok bool `json:"ok"` 35 | State binaryState `json:"state"` 36 | } 37 | 38 | type binaryState struct { 39 | // those are sensible for any forwarder 40 | Exists bool `json:"exists"` 41 | ChmodX bool `json:"chmod_x"` 42 | // those are specific to (currently) only tun 43 | Chown0 *bool `json:"chown_0,omitempty"` 44 | ChmodUS *bool `json:"chmod_us,omitempty"` 45 | } 46 | 47 | type FwderState struct { 48 | State string `json:"state"` 49 | } 50 | 51 | func boolptr(x bool) *bool { return &x } 52 | 53 | // TODO clean up based on subcmd module-defined constant? 54 | func isImplemented(name string) (is bool) { 55 | switch runtime.GOOS { 56 | case "linux": 57 | switch name { 58 | case "socks": 59 | is = true 60 | case "tun": 61 | is = true 62 | } 63 | case "darwin": 64 | switch name { 65 | case "socks": 66 | is = true 67 | case "tun": 68 | is = true 69 | } 70 | case "windows": 71 | switch name { 72 | case "socks": 73 | is = true 74 | case "tun": 75 | is = false 76 | } 77 | default: 78 | is = false 79 | } 80 | return 81 | } 82 | 83 | func (t *T) registerForwarder(name string) { 84 | var ( 85 | bin = fwderPrefix + name 86 | fullbin = bin + fwderSuffix 87 | pidfile = bin + ".pid" 88 | o = FwderReply{ 89 | Pid: -1, 90 | State: "inactive", 91 | Address: "0.0.0.0:0", 92 | Binary: binaryReply{}, 93 | } 94 | mu sync.Mutex 95 | cl = client.New(nil) 96 | syncBinaryState = func() { 97 | // NOTE this does not do any locking. locking by the caller is expected 98 | st := t.getBinaryState(fullbin) 99 | // TODO can this be handled in a better way? 100 | switch name { 101 | case "socks": 102 | o.Address = t.br.Config().Forwarders.Socks.Address 103 | o.Binary.Ok = st.Exists && st.ChmodX 104 | case "tun": 105 | o.Address = t.br.Config().Forwarders.Tun.Address 106 | o.Binary.Ok = st.Exists && st.ChmodX && *st.Chown0 && *st.ChmodUS 107 | } 108 | o.Binary.State = st 109 | } 110 | ) 111 | cl.SetTransport(&http.Transport{ 112 | DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { 113 | return net.Dial("unix", t.br.Fd.Path(bin+".sock")) 114 | }, 115 | }) 116 | t.mux.Handle("/forwarders/"+name, provide.MethodGate(provide.Routes{http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 117 | mu.Lock() 118 | defer mu.Unlock() 119 | t.br.Fd.Get(&o.Pid, pidfile) 120 | var fst FwderState 121 | if o.Pid == -1 { 122 | o.State = "inactive" 123 | } else if o.Pid != -1 && !process.Exists(o.Pid) { 124 | o.State = "failed" 125 | } else { 126 | for i := 0; i < 10; i++ { 127 | if err := cl.PerformOnce(http.MethodGet, "http://localhost/state", nil, &fst); err == nil { 128 | o.State = fst.State 129 | break 130 | } 131 | time.Sleep(100 * time.Millisecond) 132 | } 133 | } 134 | syncBinaryState() 135 | t.reply(w, o) 136 | })})) 137 | t.mux.Handle("/forwarders/"+name+"/start", provide.MethodGate(provide.Routes{http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 138 | if !isImplemented(name) { 139 | status.ErrNotImplemented.WriteTo(w) 140 | return 141 | } 142 | var err error 143 | defer func() { 144 | if err == nil { 145 | t.reply(w, o) 146 | } else { 147 | status.ErrRequest.Wrap(err).WriteTo(w) 148 | } 149 | }() 150 | binpath := t.br.Fd.Path(fullbin) 151 | syncBinaryState() 152 | st := o.Binary.State 153 | switch { 154 | case !st.Exists: 155 | err = fmt.Errorf("forwarder binary %s does not exist", fullbin) 156 | return 157 | case !st.ChmodX: 158 | err = fmt.Errorf("could not execute %s: file is not executable (did you `chmod +x %s`?)", binpath, binpath) 159 | return 160 | case name == "tun" && !*st.Chown0: 161 | err = fmt.Errorf( 162 | "could not execute %s: file is not owned by root (did you `chown 0:0 %s && chmod u+s %s`?)", 163 | binpath, binpath, binpath, 164 | ) 165 | return 166 | case name == "tun" && !*st.ChmodUS: 167 | err = fmt.Errorf("could not execute %s: file is not setuid (did you `chmod u+s %s`?)", binpath, binpath) 168 | return 169 | } 170 | env := append( 171 | os.Environ(), 172 | "WIRELEAP_HOME="+t.br.Fd.Path(), 173 | "WIRELEAP_ADDR_H2C="+*t.br.Config().Broker.Address+"/broker", 174 | "WIRELEAP_ADDR_TUN="+t.br.Config().Forwarders.Tun.Address, 175 | "WIRELEAP_ADDR_SOCKS="+t.br.Config().Forwarders.Socks.Address, 176 | ) 177 | if err = t.br.Fd.Get(&o.Pid, pidfile); err == nil && process.Exists(o.Pid) { 178 | err = fmt.Errorf("%s daemon is already running!", fullbin) 179 | return 180 | } 181 | logpath := t.br.Fd.Path(bin + ".log") 182 | logfile, err := os.OpenFile(logpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 183 | if err != nil { 184 | err = fmt.Errorf("could not open %s logfile %s: %s", bin, logpath, err) 185 | return 186 | } 187 | defer logfile.Close() 188 | cmd := exec.Cmd{ 189 | Path: binpath, 190 | Args: []string{fullbin}, 191 | Env: env, 192 | Stdout: logfile, 193 | Stderr: logfile, 194 | } 195 | if err = cmd.Start(); err != nil { 196 | cmd.Wait() 197 | mu.Lock() 198 | o.State = "failed" 199 | mu.Unlock() 200 | err = fmt.Errorf("could not spawn background %s process: %s", fullbin, err) 201 | return 202 | } 203 | go func() { 204 | // reap process so it doesn't turn zombie 205 | if err = cmd.Wait(); err != nil { 206 | mu.Lock() 207 | o.Pid = -1 208 | o.State = "failed" 209 | mu.Unlock() 210 | } else { 211 | mu.Lock() 212 | o.Pid = -1 213 | o.State = "inactive" 214 | mu.Unlock() 215 | } 216 | }() 217 | log.Printf( 218 | "starting %s with pid %d, writing to %s...", 219 | fullbin, cmd.Process.Pid, logpath, 220 | ) 221 | t.br.Fd.Set(cmd.Process.Pid, pidfile) 222 | mu.Lock() 223 | o.Pid = cmd.Process.Pid 224 | mu.Unlock() 225 | // poll state until it's conclusive 226 | var fst FwderState 227 | for i := 0; i < 10; i++ { 228 | if err = cl.PerformOnce(http.MethodGet, "http://localhost/state", nil, &fst); err == nil && fst.State != "unknown" { 229 | mu.Lock() 230 | o.State = fst.State 231 | mu.Unlock() 232 | // TODO find a more elegant/general place for this 233 | if name == "tun" { 234 | _ = t.br.WriteBypass() 235 | } 236 | break 237 | } else { 238 | mu.Lock() 239 | o.State = "failed" 240 | mu.Unlock() 241 | } 242 | time.Sleep(100 * time.Millisecond) 243 | } 244 | })})) 245 | t.mux.Handle("/forwarders/"+name+"/stop", provide.MethodGate(provide.Routes{http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 246 | if !isImplemented(name) { 247 | status.ErrNotImplemented.WriteTo(w) 248 | return 249 | } 250 | var err error 251 | defer func() { 252 | if err == nil { 253 | mu.Lock() 254 | o.Pid = -1 255 | o.State = "inactive" 256 | mu.Unlock() 257 | t.reply(w, o) 258 | } else { 259 | status.ErrRequest.Wrap(err).WriteTo(w) 260 | } 261 | }() 262 | syncBinaryState() 263 | if err = t.br.Fd.Get(&o.Pid, pidfile); err != nil { 264 | err = fmt.Errorf( 265 | "could not get pid of %s from %s: %s", 266 | fullbin, t.br.Fd.Path(pidfile), err, 267 | ) 268 | return 269 | } 270 | if process.Exists(o.Pid) { 271 | if err = process.Term(o.Pid); err != nil { 272 | err = fmt.Errorf("could not terminate %s pid %d: %s", fullbin, o.Pid, err) 273 | return 274 | } 275 | } 276 | mu.Lock() 277 | o.State = "deactivating" 278 | mu.Unlock() 279 | for i := 0; i < 30; i++ { 280 | if !process.Exists(o.Pid) { 281 | log.Printf("stopped %s daemon (was pid %d)", fullbin, o.Pid) 282 | t.br.Fd.Del(pidfile) 283 | return 284 | } 285 | time.Sleep(100 * time.Millisecond) 286 | } 287 | process.Kill(o.Pid) 288 | time.Sleep(100 * time.Millisecond) 289 | if process.Exists(o.Pid) { 290 | err = fmt.Errorf("timed out waiting for %s (pid %d) to shut down -- process still alive!", fullbin, o.Pid) 291 | return 292 | } 293 | log.Printf("stopped %s daemon (was pid %d)", fullbin, o.Pid) 294 | t.br.Fd.Del(pidfile) 295 | })})) 296 | t.mux.Handle("/forwarders/"+name+"/log", provide.MethodGate(provide.Routes{http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 297 | if !isImplemented(name) { 298 | status.ErrNotImplemented.WriteTo(w) 299 | return 300 | } 301 | logfile := t.br.Fd.Path(bin + ".log") 302 | b, err := ioutil.ReadFile(logfile) 303 | if err != nil { 304 | if errors.Is(err, fs.ErrNotExist) { 305 | status.NoContent.Wrap(err).WriteTo(w) 306 | } else { 307 | status.ErrInternal.Wrap(err).WriteTo(w) 308 | } 309 | return 310 | } 311 | w.Write(b) 312 | })})) 313 | } 314 | -------------------------------------------------------------------------------- /restapi/fwder_binarystate_darwin.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | const fwderSuffix = "" 9 | 10 | func (t *T) getBinaryState(bin string) (st binaryState) { 11 | fi, err := os.Stat(t.br.Fd.Path(bin)) 12 | if err != nil { 13 | return 14 | } 15 | st.Exists = true 16 | st.ChmodX = fi.Mode()&0100 != 0 17 | if bin == fwderPrefix+"tun" { 18 | if stat, ok := fi.Sys().(*syscall.Stat_t); ok && stat.Uid == 0 { 19 | st.Chown0 = boolptr(true) 20 | } else { 21 | st.Chown0 = boolptr(false) 22 | } 23 | st.ChmodUS = boolptr(fi.Mode()&os.ModeSetuid != 0) 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /restapi/fwder_binarystate_linux.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | const fwderSuffix = "" 9 | 10 | func (t *T) getBinaryState(bin string) (st binaryState) { 11 | fi, err := os.Stat(t.br.Fd.Path(bin)) 12 | if err != nil { 13 | return 14 | } 15 | st.Exists = true 16 | st.ChmodX = fi.Mode()&0100 != 0 17 | if bin == fwderPrefix+"tun" { 18 | if stat, ok := fi.Sys().(*syscall.Stat_t); ok && stat.Uid == 0 { 19 | st.Chown0 = boolptr(true) 20 | } else { 21 | st.Chown0 = boolptr(false) 22 | } 23 | st.ChmodUS = boolptr(fi.Mode()&os.ModeSetuid != 0) 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /restapi/fwder_binarystate_windows.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import "os" 4 | 5 | const fwderSuffix = ".exe" 6 | 7 | func (t *T) getBinaryState(bin string) (st binaryState) { 8 | if _, err := os.Stat(t.br.Fd.Path(bin)); err != nil { 9 | return 10 | } 11 | st.Exists = true 12 | // NOTE not implemented on windows, just pretend everything is ok 13 | st.ChmodX = true 14 | if bin == fwderPrefix+"tun" { 15 | st.Chown0 = boolptr(true) 16 | st.ChmodUS = boolptr(true) 17 | } 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /restapi/restapi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package restapi 4 | 5 | import ( 6 | "encoding/json" 7 | "errors" 8 | "io" 9 | "io/fs" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "os" 14 | "time" 15 | 16 | "github.com/wireleap/client/broker" 17 | "github.com/wireleap/client/filenames" 18 | "github.com/wireleap/common/api/interfaces/clientrelay" 19 | "github.com/wireleap/common/api/provide" 20 | "github.com/wireleap/common/api/relayentry" 21 | "github.com/wireleap/common/api/status" 22 | ) 23 | 24 | // api server stub 25 | type T struct { 26 | http.Handler 27 | 28 | br *broker.T 29 | l *log.Logger 30 | mux *http.ServeMux 31 | } 32 | 33 | func New(br *broker.T, l *log.Logger) (t *T) { 34 | t = &T{br: br, l: l, mux: http.NewServeMux()} 35 | t.mux.Handle("/version", provide.MethodGate(provide.Routes{ 36 | http.MethodGet: t.replyHandler(Version{VERSION}), 37 | })) 38 | t.mux.Handle("/config", provide.MethodGate(provide.Routes{ 39 | http.MethodGet: t.replyHandler(t.br.Config()), 40 | http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 41 | b, err := io.ReadAll(r.Body) 42 | if err != nil { 43 | t.l.Printf("could not read POST /config request body: %s", err) 44 | status.ErrRequest.WriteTo(w) 45 | return 46 | } 47 | if err = json.Unmarshal(b, t.br.Config()); err != nil { 48 | t.l.Printf("could not unmarshal POST /config request body: %s", err) 49 | status.ErrRequest.WriteTo(w) 50 | return 51 | } 52 | if err = t.br.SaveConfig(); err != nil { 53 | t.l.Printf("could not save config changes: %s", err) 54 | status.ErrInternal.WriteTo(w) 55 | return 56 | } 57 | go t.br.Reload() 58 | t.reply(w, t.br.Config()) 59 | }), 60 | })) 61 | t.mux.Handle("/runtime", provide.MethodGate(provide.Routes{ 62 | http.MethodGet: t.replyHandler(RuntimeReply), 63 | })) 64 | t.mux.Handle("/contract", provide.MethodGate(provide.Routes{ 65 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 66 | if ci := t.br.ContractInfo(); ci != nil { 67 | t.reply(w, t.br.ContractInfo()) 68 | } else { 69 | status.NoContent.WriteTo(w) 70 | } 71 | }), 72 | })) 73 | t.mux.Handle("/relays", provide.MethodGate(provide.Routes{ 74 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 75 | rs, err := t.br.Relays() 76 | if err != nil { 77 | t.l.Printf("error %s while serving relays", err) 78 | if errors.Is(err, fs.ErrNotExist) { 79 | status.NoContent.Wrap(err).WriteTo(w) 80 | } else { 81 | status.ErrInternal.Wrap(err).WriteTo(w) 82 | } 83 | return 84 | } 85 | type selectableRelay struct { 86 | *relayentry.T 87 | Selectable bool `json:"selectable"` 88 | } 89 | var ors []selectableRelay 90 | // selectable by default 91 | sel := true 92 | wl := t.br.Config().Broker.Circuit.Whitelist 93 | hops := t.br.Config().Broker.Circuit.Hops 94 | for _, r := range rs { 95 | switch { 96 | case r.Versions.ClientRelay == nil: 97 | // weird case which should not happen 98 | fallthrough 99 | case r.Versions.ClientRelay.Minor != clientrelay.T.Version.Minor: 100 | // if version does not match it is not selectable 101 | // TODO factor out this comparison? 102 | fallthrough 103 | case hops == 1 && r.Role != "backing": 104 | // for hops=1 only backing is selectable 105 | fallthrough 106 | case hops == 2 && r.Role == "entropic": 107 | // for hops=2 only non-entropic is selectable 108 | sel = false 109 | case wl != nil && len(wl) > 0: 110 | // non-selectable by default if whitelist is set 111 | sel = false 112 | for _, wlr := range wl { 113 | if wlr == r.Addr.String() { 114 | // found in whitelist = selectable 115 | sel = true 116 | break 117 | } 118 | } 119 | default: 120 | // out of causes for unselectability 121 | } 122 | ors = append(ors, selectableRelay{ 123 | T: r, 124 | Selectable: sel, 125 | }) 126 | } 127 | t.reply(w, ors) 128 | }), 129 | })) 130 | t.mux.Handle("/accesskeys", provide.MethodGate(provide.Routes{ 131 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 132 | t.reply(w, t.newAccesskeysReply()) 133 | }), 134 | })) 135 | t.mux.Handle("/accesskeys/import", provide.MethodGate(provide.Routes{ 136 | http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 137 | b, err := io.ReadAll(r.Body) 138 | if err != nil { 139 | t.l.Printf("error when reading accesskeys import request body: %s", err) 140 | status.ErrRequest.Wrap(err).WriteTo(w) 141 | return 142 | } 143 | air := AccesskeyImportRequest{} 144 | if err = json.Unmarshal(b, &air); err != nil || air.URL == nil { 145 | t.l.Printf("error when unmarshaling accesskeys import request: %s", err) 146 | status.ErrRequest.WriteTo(w) 147 | return 148 | } 149 | aks, err := t.br.Import(air.URL.URL) 150 | if err != nil { 151 | t.l.Printf("error when importing accesskeys: %s", err) 152 | status.ErrRequest.Wrap(err).WriteTo(w) 153 | return 154 | } 155 | go t.br.Reload() 156 | t.reply(w, t.accesskeysFromPofs(aks.Pofs...)) 157 | }), 158 | })) 159 | t.mux.Handle("/accesskeys/activate", provide.MethodGate(provide.Routes{ 160 | http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 161 | if err := t.br.Activate(); err != nil { 162 | t.l.Printf("error when activating new accesskey: %s", err) 163 | status.ErrRequest.Wrap(err).WriteTo(w) 164 | return 165 | } 166 | t.reply(w, t.accesskeysFromSks(t.br.CurrentSK())) 167 | }), 168 | })) 169 | t.mux.Handle("/status", provide.MethodGate(provide.Routes{ 170 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 171 | circList := []string{} 172 | for _, r := range t.br.ActiveCircuit() { 173 | circList = append(circList, r.Addr.String()) 174 | } 175 | t.reply(w, StatusReply{ 176 | Home: t.br.Fd.Path(), 177 | Pid: os.Getpid(), 178 | State: "active", 179 | Broker: StatusBroker{ActiveCircuit: circList}, 180 | Upgrade: &StatusUpgrade{Required: t.br.IsUpgradeable()}, 181 | }) 182 | }), 183 | })) 184 | t.mux.Handle("/reload", provide.MethodGate(provide.Routes{ 185 | http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 186 | t.br.Reload() 187 | circList := []string{} 188 | for _, r := range t.br.ActiveCircuit() { 189 | circList = append(circList, r.Addr.String()) 190 | } 191 | t.reply(w, StatusReply{ 192 | Home: t.br.Fd.Path(), 193 | Pid: os.Getpid(), 194 | State: "active", 195 | Broker: StatusBroker{ActiveCircuit: circList}, 196 | Upgrade: &StatusUpgrade{Required: t.br.IsUpgradeable()}, 197 | }) 198 | }), 199 | })) 200 | t.mux.Handle("/log", provide.MethodGate(provide.Routes{ 201 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 202 | logfile := t.br.Fd.Path(filenames.Log) 203 | b, err := ioutil.ReadFile(logfile) 204 | if err != nil { 205 | if errors.Is(err, fs.ErrNotExist) { 206 | status.NoContent.Wrap(err).WriteTo(w) 207 | } else { 208 | status.ErrInternal.Wrap(err).WriteTo(w) 209 | } 210 | return 211 | } 212 | w.Write(b) 213 | }), 214 | })) 215 | t.registerForwarder("socks") 216 | t.registerForwarder("tun") 217 | t.Handler = http.TimeoutHandler(t.mux, 10*time.Second, "API call timed out!") 218 | return 219 | } 220 | 221 | func (t *T) reply(w http.ResponseWriter, x interface{}) { 222 | b, err := json.Marshal(x) 223 | if err != nil { 224 | t.l.Printf("error %s while serving reply", err) 225 | status.ErrInternal.WriteTo(w) 226 | return 227 | } 228 | w.Write(b) 229 | } 230 | 231 | func (t *T) replyHandler(x interface{}) http.HandlerFunc { 232 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 233 | t.reply(w, x) 234 | }) 235 | } 236 | -------------------------------------------------------------------------------- /restapi/runtime.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package restapi 4 | 5 | import ( 6 | "runtime" 7 | 8 | "github.com/blang/semver" 9 | "github.com/wireleap/client/version" 10 | "github.com/wireleap/common/api/interfaces/clientcontract" 11 | "github.com/wireleap/common/api/interfaces/clientdir" 12 | "github.com/wireleap/common/api/interfaces/clientrelay" 13 | "github.com/wireleap/common/cli/upgrade" 14 | ) 15 | 16 | type Versions struct { 17 | Software *semver.Version `json:"software"` 18 | Api *semver.Version `json:"api"` 19 | ClientRelay *semver.Version `json:"client-relay"` 20 | ClientDir *semver.Version `json:"client-dir"` 21 | ClientContract *semver.Version `json:"client-contract"` 22 | } 23 | 24 | type Upgrade struct { 25 | Supported bool `json:"supported"` 26 | } 27 | 28 | type Build struct { 29 | GitCommit string `json:"gitcommit"` 30 | GoVersion string `json:"goversion"` 31 | Time string `json:"time"` 32 | Flags string `json:"flags"` 33 | } 34 | 35 | type Platform struct { 36 | Os string `json:"os"` 37 | Arch string `json:"arch"` 38 | } 39 | 40 | type Runtime struct { 41 | Versions Versions `json:"versions"` 42 | Upgrade Upgrade `json:"upgrade"` 43 | Build Build `json:"build"` 44 | Platform Platform `json:"platform"` 45 | } 46 | 47 | var RuntimeReply = Runtime{ 48 | Versions: Versions{ 49 | Software: &version.VERSION, 50 | Api: &VERSION, 51 | ClientRelay: &clientrelay.T.Version, 52 | ClientDir: &clientdir.T.Version, 53 | ClientContract: &clientcontract.T.Version, 54 | }, 55 | Upgrade: Upgrade{Supported: upgrade.Supported}, 56 | Build: Build{ 57 | GitCommit: version.GIT_COMMIT, 58 | GoVersion: runtime.Version(), 59 | Time: version.BUILD_TIME, 60 | Flags: version.BUILD_FLAGS, 61 | }, 62 | Platform: Platform{ 63 | Os: runtime.GOOS, 64 | Arch: runtime.GOARCH, 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /restapi/status.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | type StatusReply struct { 4 | Home string `json:"home"` 5 | Pid int `json:"pid"` 6 | State string `json:"state"` 7 | Broker StatusBroker `json:"broker"` 8 | Upgrade *StatusUpgrade `json:"upgrade,omitempty"` 9 | } 10 | 11 | type StatusBroker struct { 12 | ActiveCircuit []string `json:"active_circuit"` 13 | } 14 | 15 | type StatusUpgrade struct { 16 | Required bool `json:"required"` 17 | } 18 | -------------------------------------------------------------------------------- /restapi/unixserver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package restapi 4 | 5 | import ( 6 | "log" 7 | "net" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/wireleap/common/api/provide" 12 | ) 13 | 14 | func UnixServer(p string, rts provide.Routes) error { 15 | if err := os.RemoveAll(p); err != nil { 16 | return err 17 | } 18 | l, err := net.Listen("unix", p) 19 | if err != nil { 20 | return err 21 | } 22 | mux := provide.NewMux(rts) 23 | h := &http.Server{Handler: mux} 24 | go func() { 25 | defer l.Close() 26 | if err = h.Serve(l); err != nil { 27 | log.Fatalf("error when serving unix socket: %s", err) 28 | } 29 | }() 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /restapi/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package restapi 4 | 5 | import "github.com/blang/semver" 6 | 7 | const VERSION_STRING = "0.1.0" 8 | 9 | var VERSION = semver.MustParse(VERSION_STRING) 10 | 11 | type Version struct { 12 | Version semver.Version `json:"version,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /socks/socks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | // Package socks provides a barebones SOCKSv5 server handshake protocol 4 | // implementation according to RFC1928. 5 | // https://datatracker.ietf.org/doc/html/rfc1928 6 | package socks 7 | 8 | import ( 9 | "bytes" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "net" 14 | "strconv" 15 | ) 16 | 17 | const ( 18 | SOCKSv5 = 0x05 19 | 20 | CONNECT = 0x01 21 | BIND = 0x02 22 | UDP_ASSOC = 0x03 23 | 24 | ADDR_IPV4 = 0x01 25 | ADDR_FQDN = 0x03 26 | ADDR_IPV6 = 0x04 27 | 28 | RSV = 0x00 29 | ) 30 | 31 | type SocksStatus byte 32 | 33 | func (e SocksStatus) Error() string { 34 | return [...]string{ 35 | "OK", 36 | "general failure", 37 | "not allowed", 38 | "network unreachable", 39 | "host unreachable", 40 | "connection refused", 41 | "TTL expired", 42 | "command not supported", 43 | "address type not supported", 44 | }[e] 45 | } 46 | 47 | const ( 48 | StatusOK SocksStatus = iota 49 | StatusGeneralFailure 50 | StatusNotAllowed 51 | StatusNetworkUnreachable 52 | StatusHostUnreachable 53 | StatusConnRefused 54 | StatusTTLExpired 55 | StatusCommandNotSupported 56 | StatusAddressNotSupported 57 | ) 58 | 59 | func WriteStatus(c net.Conn, status SocksStatus, addr Addr) (int, error) { 60 | return c.Write(append([]byte{SOCKSv5, byte(status), RSV}, addr...)) 61 | } 62 | 63 | type Addr []byte 64 | 65 | func AddrIPPort(ip net.IP, port int) (r Addr) { 66 | if ip4 := ip.To4(); ip4 == nil { 67 | r = append(r, ADDR_IPV6) 68 | r = append(r, ip...) 69 | } else { 70 | r = append(r, ADDR_IPV4) 71 | r = append(r, ip4...) 72 | } 73 | r = append(r, byte(port>>8), byte(port)) 74 | return 75 | } 76 | 77 | func AddrString(addr string) (r Addr, err error) { 78 | host, portstr, err := net.SplitHostPort(addr) 79 | if err != nil { 80 | return 81 | } 82 | ip := net.ParseIP(host) 83 | if ip == nil { 84 | // probably fqdn 85 | r = append(r, ADDR_FQDN) 86 | r = append(r, byte(len(host))) 87 | r = append(r, []byte(host)...) 88 | } else { 89 | if ip4 := ip.To4(); ip4 != nil { 90 | // v4 91 | r = append(r, ADDR_IPV4) 92 | r = append(r, []byte(ip4)...) 93 | } else { 94 | // v6 95 | r = append(r, ADDR_IPV6) 96 | r = append(r, []byte(ip)...) 97 | } 98 | } 99 | port, err := strconv.Atoi(portstr) 100 | if err != nil { 101 | return 102 | } 103 | r = append(r, []byte{byte(port >> 8), byte(port)}...) 104 | return 105 | } 106 | 107 | func AddrAddr(orig net.Addr) (r Addr) { 108 | switch v := orig.(type) { 109 | case *net.TCPAddr: 110 | r = AddrIPPort(v.IP, v.Port) 111 | case *net.UDPAddr: 112 | r = AddrIPPort(v.IP, v.Port) 113 | } 114 | return 115 | } 116 | 117 | func (t Addr) String() string { 118 | if ip, port := t.IPPort(); ip != nil { 119 | // ip:port 120 | return net.JoinHostPort(ip.String(), strconv.Itoa(port)) 121 | } 122 | if len(t) < 2 || len(t) < int(2+t[1]+2) { 123 | // ??? 124 | return "" 125 | } 126 | // fqdn:port 127 | port := int(t[1]+2)<<8 | int(t[1]+2+1) 128 | return net.JoinHostPort(string(t[2:t[1]+2]), strconv.Itoa(port)) 129 | } 130 | 131 | func (t Addr) IPPort() (ip net.IP, port int) { 132 | if len(t) < 1 { 133 | return 134 | } 135 | switch t[0] { 136 | case ADDR_IPV4, ADDR_IPV6: 137 | mult := int(t[0]) 138 | if len(t) < 1+mult*4+2 { 139 | return 140 | } 141 | ip = net.IP(t[1 : mult*4+1]) 142 | port = int(t[1+mult*4])<<8 | int(t[1+mult*4+1]) 143 | } 144 | return 145 | } 146 | 147 | func ComposeUDP(dstaddr Addr, p []byte) (r []byte, err error) { 148 | // RSV, RSV, FRAG 149 | r = []byte{0, 0, 0} 150 | // ATYP, ADDR, PORT 151 | r = append(r, dstaddr...) 152 | // DATA 153 | r = append(r, p...) 154 | return 155 | } 156 | 157 | var ErrFragment = errors.New("received UDP message is fragmented, fragmentation is not supported") 158 | 159 | func DissectUDP(p []byte) (dstaddr Addr, data []byte, err error) { 160 | // RSV, RSV ignored 161 | // FRAG -- do not process fragmentation 162 | if p[2] != 0 { 163 | err = ErrFragment 164 | return 165 | } 166 | // ATYP, ADDR, DATA 167 | switch p[3] { 168 | case ADDR_IPV4, ADDR_IPV6: 169 | mult := p[3] 170 | // ATYP + (1 or 4) * 4 + PORT 171 | dstaddr = make([]byte, 1+mult*4+2) 172 | copy(dstaddr, p[3:]) 173 | data = p[3+len(dstaddr):] 174 | case ADDR_FQDN: 175 | // SIZE 176 | size := p[4] 177 | dstaddr = make([]byte, 2+size+2) 178 | copy(dstaddr, p[3:]) 179 | data = p[2+len(dstaddr):] 180 | } 181 | return 182 | } 183 | 184 | func Handshake(c net.Conn) (cmd byte, address string, err error) { 185 | b := make([]byte, 1) 186 | // read auth methods 187 | // SOCKS version 188 | _, err = io.ReadFull(c, b) 189 | if err != nil { 190 | return 191 | } 192 | if b[0] != SOCKSv5 { 193 | WriteStatus(c, StatusGeneralFailure, AddrAddr(c.LocalAddr())) 194 | err = fmt.Errorf("unknown SOCKS auth version: 0x%x", b) 195 | return 196 | } 197 | // number of auth methods 198 | _, err = io.ReadFull(c, b) 199 | if err != nil { 200 | return 201 | } 202 | methods := make([]byte, b[0]) 203 | // auth methods -- don't care which ones as no auth is required 204 | _, err = io.ReadFull(c, methods) 205 | if err != nil { 206 | return 207 | } 208 | // tell the client no auth is needed 209 | _, err = c.Write([]byte{0x05, 0x00}) 210 | if err != nil { 211 | return 212 | } 213 | // read request 214 | // SOCKS version 215 | _, err = io.ReadFull(c, b) 216 | if err != nil { 217 | return 218 | } 219 | if b[0] != SOCKSv5 { 220 | WriteStatus(c, StatusGeneralFailure, AddrAddr(c.LocalAddr())) 221 | err = fmt.Errorf("unknown SOCKS request version: 0x%x", b) 222 | return 223 | } 224 | // command 225 | _, err = io.ReadFull(c, b) 226 | if err != nil { 227 | return 228 | } 229 | switch b[0] { 230 | case CONNECT, UDP_ASSOC: 231 | cmd = b[0] 232 | default: 233 | WriteStatus(c, StatusCommandNotSupported, AddrAddr(c.LocalAddr())) 234 | err = fmt.Errorf("unsupported SOCKS command %d", b) 235 | return 236 | } 237 | // RSV 0x0 238 | _, err = io.ReadFull(c, b) 239 | if err != nil { 240 | return 241 | } 242 | // address type 243 | _, err = io.ReadFull(c, b) 244 | if err != nil { 245 | return 246 | } 247 | var addr, nulladdr []byte 248 | switch b[0] { 249 | case ADDR_IPV4: 250 | // 4 bytes long 251 | nulladdr = make([]byte, net.IPv4len) 252 | addr = make([]byte, net.IPv4len) 253 | _, err = io.ReadFull(c, addr) 254 | if err != nil { 255 | return 256 | } 257 | address = net.IP(addr).String() 258 | case ADDR_IPV6: 259 | // 16 bytes long 260 | nulladdr = make([]byte, 16) 261 | addr = make([]byte, 16) 262 | _, err = io.ReadFull(c, addr) 263 | if err != nil { 264 | return 265 | } 266 | address = net.IP(addr).String() 267 | case ADDR_FQDN: 268 | // fqdn length in bytes 269 | _, err = io.ReadFull(c, b) 270 | if err != nil { 271 | return 272 | } 273 | // fqdn 274 | addr = make([]byte, b[0]) 275 | _, err = io.ReadFull(c, addr) 276 | if err != nil { 277 | return 278 | } 279 | address = string(addr) 280 | } 281 | 282 | // port number 283 | portb := make([]byte, 2) 284 | _, err = io.ReadFull(c, portb) 285 | if err != nil { 286 | return 287 | } 288 | port := int(portb[0])<<8 | int(portb[1]) 289 | if cmd == UDP_ASSOC && bytes.Equal(addr, nulladdr) && port == 0 { 290 | address = "" 291 | } else { 292 | address = net.JoinHostPort(address, strconv.Itoa(port)) 293 | } 294 | return 295 | } 296 | -------------------------------------------------------------------------------- /sub/accesskeyscmd/accesskeyscmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package accesskeyscmd 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | 12 | "github.com/wireleap/client/clientcfg" 13 | "github.com/wireleap/client/clientlib" 14 | "github.com/wireleap/client/filenames" 15 | "github.com/wireleap/client/restapi" 16 | "github.com/wireleap/common/api/texturl" 17 | "github.com/wireleap/common/cli" 18 | "github.com/wireleap/common/cli/fsdir" 19 | ) 20 | 21 | func Cmd() *cli.Subcmd { 22 | fs := flag.NewFlagSet("accesskeys", flag.ExitOnError) 23 | r := &cli.Subcmd{ 24 | FlagSet: fs, 25 | Desc: "Manage accesskeys", 26 | Sections: []cli.Section{{ 27 | Title: "Commands", 28 | Entries: []cli.Entry{ 29 | {Key: "list", Value: "List accesskeys"}, 30 | {Key: "import", Value: "Import accesskeys from URL and set up associated contract"}, 31 | {Key: "activate", Value: "Trigger accesskey activation (accesskey.use_on_demand=false)"}, 32 | }, 33 | }}, 34 | } 35 | r.Run = func(fm fsdir.T) { 36 | if (fs.Arg(0) == "import" && fs.NArg() != 2) || (fs.Arg(0) != "import" && fs.NArg() != 1) { 37 | r.Usage() 38 | os.Exit(1) 39 | } 40 | c := clientcfg.Defaults() 41 | err := fm.Get(&c, filenames.Config) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | var ( 46 | meth = http.MethodGet 47 | u = "http://" + *c.Address + "/api/accesskeys" 48 | param interface{} 49 | ak restapi.AccesskeyReply 50 | aks []restapi.AccesskeyReply 51 | out interface{} = aks 52 | ) 53 | switch fs.Arg(0) { 54 | case "list": 55 | // no changes needed to what's defined above 56 | case "import": 57 | u += "/import" 58 | meth = http.MethodPost 59 | from, err := url.Parse(fs.Arg(1)) 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | param = restapi.AccesskeyImportRequest{URL: &texturl.URL{*from}} 64 | case "activate": 65 | u += "/activate" 66 | meth = http.MethodPost 67 | out = ak 68 | default: 69 | log.Fatalf("unknown command %s", fs.Arg(0)) 70 | } 71 | clientlib.APICallOrDie(meth, u, param, &out) 72 | } 73 | r.SetMinimalUsage("COMMAND") 74 | return r 75 | } 76 | -------------------------------------------------------------------------------- /sub/configcmd/configcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package configcmd 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "flag" 9 | "fmt" 10 | "log" 11 | "net/http" 12 | "os" 13 | "text/tabwriter" 14 | 15 | "github.com/wireleap/client/clientcfg" 16 | "github.com/wireleap/client/clientlib" 17 | "github.com/wireleap/client/filenames" 18 | "github.com/wireleap/common/cli" 19 | "github.com/wireleap/common/cli/fsdir" 20 | ) 21 | 22 | func Cmd(fm0 fsdir.T) *cli.Subcmd { 23 | fs := flag.NewFlagSet("config", flag.ExitOnError) 24 | 25 | r := &cli.Subcmd{ 26 | FlagSet: fs, 27 | Desc: "Get or set wireleap configuration settings", 28 | } 29 | 30 | r.Run = func(fm fsdir.T) { 31 | if fs.NArg() < 1 { 32 | r.Usage() 33 | } 34 | 35 | key := fs.Arg(0) 36 | val := fs.Arg(1) 37 | vals := fs.Args()[1:] 38 | 39 | if key == "" { 40 | r.Usage() 41 | } 42 | 43 | // pre-processing val list for all "list" type config items to be in-line 44 | // with all other arguments 45 | var val_type string 46 | c := clientcfg.Defaults() 47 | for _, meta := range c.Metadata() { 48 | if meta.Name == key { 49 | val_type = meta.Type 50 | break 51 | } 52 | } 53 | 54 | if val_type != "list" && fs.NArg() > 2 { 55 | r.Usage() 56 | } 57 | 58 | if val_type == "list" && len(vals) > 0 { 59 | if vals[0] == "null" { 60 | val = "[]" 61 | } else { 62 | val_bytes, err := json.Marshal(&vals) 63 | if err != nil { 64 | log.Fatalf("could not marshal values for list: %s", err) 65 | } 66 | val = string(val_bytes) 67 | } 68 | } 69 | Run(fm, key, val) 70 | } 71 | 72 | r.Usage = func() { 73 | w := tabwriter.NewWriter(r.Output(), 0, 8, 1, ' ', 0) 74 | 75 | fmt.Fprintf(w, "Usage: wireleap %s [KEY [VALUE]]\n\n", r.Name()) 76 | fmt.Fprintf(w, "%s\n\n", r.Desc) 77 | fmt.Fprint(w, "Keys:\n") 78 | 79 | c := clientcfg.Defaults() 80 | fm0.Get(&c, filenames.Config) 81 | 82 | for _, meta := range c.Metadata() { 83 | fmt.Fprintf(w, " %s\t(%s)\t%s\n", meta.Name, meta.Type, meta.Desc) 84 | } 85 | 86 | fmt.Fprintln(w) 87 | fmt.Fprintln(w, "To unset a key, specify `null` as the value") 88 | 89 | w.Flush() 90 | os.Exit(2) 91 | } 92 | 93 | return r 94 | } 95 | 96 | func Run(fm fsdir.T, key, val string) { 97 | c := clientcfg.Defaults() 98 | if err := fm.Get(&c, filenames.Config); err != nil { 99 | log.Fatalf("error when loading config: %s", err) 100 | } 101 | var match *clientcfg.Meta 102 | for _, meta := range c.Metadata() { 103 | if meta.Name == key { 104 | match = meta 105 | break 106 | } 107 | } 108 | if match == nil { 109 | log.Fatalf("no such config key: %s", key) 110 | } 111 | if len(val) == 0 { 112 | // show defined value 113 | b, err := json.Marshal(match.Val) 114 | if err != nil { 115 | // this shouldn't really happen 116 | b = []byte("unknown") 117 | } else if match.Quote && !bytes.Equal(b, []byte("null")) { 118 | b = b[1 : len(b)-1] 119 | } 120 | fmt.Println(string(b)) 121 | return 122 | } 123 | if match.Quote { 124 | val = "\"" + val + "\"" 125 | } 126 | if err := json.Unmarshal([]byte(val), match.Val); err != nil { 127 | log.Fatalf("could not set %s value to %s: %s", key, val, err) 128 | } 129 | if key == "address.socks" { 130 | log.Printf("Note: address.socks changes will take effect on restart.") 131 | } 132 | clientlib.APICallOrDie(http.MethodPost, "http://"+*c.Address+"/api/config", &c, &c) 133 | } 134 | -------------------------------------------------------------------------------- /sub/execcmd/execcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package execcmd 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "net" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | "time" 13 | 14 | "github.com/wireleap/client/clientcfg" 15 | "github.com/wireleap/client/filenames" 16 | "github.com/wireleap/common/cli" 17 | "github.com/wireleap/common/cli/fsdir" 18 | "github.com/wireleap/common/cli/process" 19 | ) 20 | 21 | func Cmd() *cli.Subcmd { 22 | fs := flag.NewFlagSet("exec", flag.ExitOnError) 23 | r := &cli.Subcmd{ 24 | FlagSet: fs, 25 | Desc: "Execute script from scripts directory (req. SOCKS forwarder)", 26 | Sections: []cli.Section{{ 27 | Title: "Commands", 28 | Entries: []cli.Entry{ 29 | {Key: "list", Value: "List available scripts in scripts directory"}, 30 | }, 31 | }}, 32 | } 33 | r.SetMinimalUsage("COMMAND|FILENAME") 34 | r.Run = func(fm fsdir.T) { 35 | c := clientcfg.Defaults() 36 | err := fm.Get(&c, filenames.Config) 37 | 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | if fs.NArg() < 1 { 43 | r.Usage() 44 | } 45 | 46 | arg0 := fs.Arg(0) 47 | if runtime.GOOS == "windows" { 48 | arg0 += ".bat" 49 | } 50 | 51 | p := fm.Path("scripts", arg0) 52 | 53 | fi, err := os.Stat(p) 54 | 55 | if err != nil { 56 | p0 := fm.Path("scripts", "default", arg0) 57 | fi, err = os.Stat(p0) 58 | if err != nil { 59 | log.Fatalf("could not stat %s or %s: %s", p, p0, err) 60 | } 61 | p = p0 62 | } 63 | 64 | if runtime.GOOS != "windows" && fi.Mode()&0111 == 0 { 65 | log.Fatalf("could not execute %s: file is not executable (did you `chmod +x`?)", p) 66 | } 67 | 68 | var pid int 69 | err = fm.Get(&pid, filenames.Pid) 70 | 71 | if err != nil { 72 | log.Fatalf("it appears wireleap is not running: could not get wireleap PID from %s: %s", fm.Path(filenames.Pid), err) 73 | } 74 | 75 | if !process.Exists(pid) { 76 | log.Fatalf("it appears wireleap (pid %d) is not running!", pid) 77 | } 78 | 79 | conn, err := net.DialTimeout("tcp", c.Forwarders.Socks.Address, time.Second) 80 | 81 | if err != nil { 82 | log.Fatalf("could not connect to wireleap at address.socks %s: %s", c.Forwarders.Socks.Address, err) 83 | } 84 | 85 | conn.Close() 86 | 87 | host, port, err := net.SplitHostPort(c.Forwarders.Socks.Address) 88 | if err != nil { 89 | log.Fatalf("could not parse wireleap address.socks %s: %s", c.Forwarders.Socks.Address, err) 90 | } 91 | 92 | cmd := exec.Command(p, fs.Args()[1:]...) 93 | cmd.Env = append(os.Environ(), 94 | "WIRELEAP_HOME="+fm.Path(), 95 | "WIRELEAP_SOCKS="+c.Forwarders.Socks.Address, 96 | "WIRELEAP_SOCKS_HOST="+host, 97 | "WIRELEAP_SOCKS_PORT="+port, 98 | ) 99 | cmd.Stdin = os.Stdin 100 | cmd.Stderr = os.Stderr 101 | cmd.Stdout = os.Stdout 102 | 103 | err = cmd.Run() 104 | 105 | hint := "" 106 | 107 | if os.IsPermission(err) { 108 | hint = ", check permissions (ownership and executable bit/+x)?" 109 | } 110 | 111 | if err != nil { 112 | log.Fatalf("could not execute %s: %s%s", p, err, hint) 113 | } 114 | } 115 | return r 116 | } 117 | -------------------------------------------------------------------------------- /sub/httpgetcmd/httpget.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package httpgetcmd 4 | 5 | import ( 6 | "crypto/tls" 7 | "flag" 8 | "io" 9 | "log" 10 | "net" 11 | "net/http" 12 | "os" 13 | "time" 14 | 15 | "github.com/wireleap/client/clientcfg" 16 | "github.com/wireleap/client/filenames" 17 | "github.com/wireleap/common/cli" 18 | "github.com/wireleap/common/cli/fsdir" 19 | "github.com/wireleap/common/wlnet/h2conn" 20 | "golang.org/x/net/http2" 21 | ) 22 | 23 | func Cmd() *cli.Subcmd { 24 | fs := flag.NewFlagSet("httpget", flag.ExitOnError) 25 | r := &cli.Subcmd{ 26 | FlagSet: fs, 27 | Desc: "Perform a HTTP GET through the circuit (experimental)", 28 | } 29 | r.SetMinimalUsage("[URL]") 30 | r.Run = func(fm fsdir.T) { 31 | c := clientcfg.Defaults() 32 | err := fm.Get(&c, filenames.Config) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | if fs.NArg() == 0 || fs.NArg() > 1 { 37 | r.Usage() 38 | os.Exit(1) 39 | } 40 | conn, err := net.DialTimeout("tcp", *c.Broker.Address, time.Second) 41 | if err != nil { 42 | log.Fatalf("could not connect to wireleap broker at address %s: %s", *c.Broker.Address, err) 43 | } 44 | conn.Close() 45 | // h2c transport for non-TLS dial to broker 46 | var h2ct = &http2.Transport{ 47 | AllowHTTP: true, 48 | DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { 49 | return net.Dial(network, addr) 50 | }, 51 | ReadIdleTimeout: 10 * time.Second, 52 | PingTimeout: 10 * time.Second, 53 | } 54 | // high-level transport which connects through the broker 55 | var wlt = &http.Transport{ 56 | Dial: func(proto string, addr string) (net.Conn, error) { 57 | c, err := h2conn.New(h2ct, "https://"+*c.Broker.Address+"/broker", map[string]string{ 58 | "Wl-Dial-Protocol": proto, 59 | "Wl-Dial-Target": addr, 60 | "Wl-Forwarder": "httpget", 61 | }) 62 | 63 | if err != nil { 64 | log.Fatalf("error when h2conn: %s", err) 65 | } 66 | return c, nil 67 | }, 68 | } 69 | cl := &http.Client{Transport: wlt} 70 | res, err := cl.Get(fs.Arg(0)) 71 | if err != nil { 72 | log.Fatalf("error while performing get request: %s", err) 73 | } 74 | defer res.Body.Close() 75 | if _, err = io.Copy(os.Stdout, res.Body); err != nil { 76 | log.Fatalf("error while reading response body: %s", err) 77 | } 78 | } 79 | return r 80 | } 81 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/completion.bash: -------------------------------------------------------------------------------- 1 | # bashrc: source path/to/completion.bash 2 | # zshrc: autoload compinit && compinit && autoload bashcompinit && bashcompinit && source path/to/completion.bash 3 | # requires: awk sed find basename uniq (should all be included in coreutils) 4 | 5 | __wireleap_cmds() { 6 | wireleap help "$1" 2>&1 | awk '/^ [a-z\-]/ {print $1}' 7 | } 8 | 9 | __wireleap_home() { 10 | wireleap status | sed -n 's/^.*\"home\": \"\(.*\)\",$/\1/p' 11 | } 12 | 13 | __wireleap_scripts() { 14 | local wlhome 15 | wlhome="$(__wireleap_home)" 16 | [ -d "${wlhome}/scripts" ] || return 1 17 | find "${wlhome}/scripts" -perm -100 -type f -exec basename '{}' ';' | uniq 18 | } 19 | 20 | __wireleap_relays() { 21 | local wlhome 22 | wlhome="$(__wireleap_home)" 23 | [ -f "${wlhome}/relays.json" ] || return 1 24 | sed -n 's/^.*\"address\": \"\(.*\)\",$/\1/p' < "${wlhome}/relays.json" 25 | } 26 | 27 | __wireleap_comp() { 28 | local words 29 | case "${#COMP_WORDS[@]}" in 30 | 1) 31 | words="$(__wireleap_cmds)";; 32 | *) 33 | case "${COMP_WORDS[1]}" in 34 | exec) words="$(__wireleap_scripts)";; 35 | config) 36 | case "${COMP_WORDS[2]}" in 37 | broker.circuit.whitelist) 38 | local cur="${COMP_WORDS[COMP_CWORD]}" 39 | words="$(__wireleap_relays)" 40 | # this is a hack but another way was not to be found 41 | if [ "$(basename "$SHELL")" = 'zsh' ]; then 42 | COMPREPLY=($(compgen -W "$words" -- "$cur")) 43 | elif [ "$(basename "$SHELL")" = 'bash' ]; then 44 | COMPREPLY=($(compgen -P \" -S \" -W "$words" -- "$cur")) 45 | fi 46 | return 0 47 | ;; 48 | *) words="$(__wireleap_cmds "${COMP_WORDS[1]}")";; 49 | esac 50 | ;; 51 | *) words="$(__wireleap_cmds "${COMP_WORDS[1]}")";; 52 | esac 53 | ;; 54 | esac 55 | COMPREPLY=($(compgen -W "$words" -- "${COMP_WORDS[COMP_CWORD]}")) 56 | } 57 | 58 | complete -o default -F __wireleap_comp wireleap 59 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/embedded_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package embedded 4 | 5 | import "embed" 6 | 7 | //go:embed wireleap_tun wireleap_socks scripts_darwin LICENSE completion.bash 8 | var FS embed.FS 9 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/embedded_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package embedded 4 | 5 | import "embed" 6 | 7 | //go:embed wireleap_intercept.so wireleap_tun wireleap_socks scripts_linux LICENSE completion.bash 8 | var FS embed.FS 9 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/embedded_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package embedded 4 | 5 | import "embed" 6 | 7 | //go:embed scripts_windows wireleap_socks.exe LICENSE 8 | var FS embed.FS 9 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/README: -------------------------------------------------------------------------------- 1 | This folder contains user-defined wireleap scripts and may include 2 | locally-modified versions of default scripts. 3 | 4 | If identically named scripts exist both here and in the "default" 5 | folder, this folder is given precedence when `wireleap exec` is ran. 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/README: -------------------------------------------------------------------------------- 1 | This folder contains default scripts. They will be overwritten on 2 | upgrade; do not modify. 3 | 4 | If adjustments are required, create a script in the parent directory, 5 | "scripts". 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/brave-browser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" 9 | [ -e "$cmd" ] || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/brave-browser-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/chromium-browser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="/Applications/Chromium Browser.app/Contents/MacOS/Chromium Browser" 9 | [ -e "$cmd" ] || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/chromium-browser-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/curl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | export ALL_PROXY="socks5h://$WIRELEAP_SOCKS" 11 | exec "$cmd" "$@" 12 | 13 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/firefox: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | WIRELEAP_SOCKS_HOST="$(echo "$WIRELEAP_SOCKS" | cut -d: -f1)" 8 | WIRELEAP_SOCKS_PORT="$(echo "$WIRELEAP_SOCKS" | cut -d: -f2)" 9 | 10 | cmd="/Applications/Firefox.app/Contents/MacOS/firefox-bin" 11 | [ -e "$cmd" ] || fatal "$cmd not found" 12 | 13 | PROFILE="$HOME/.mozilla/firefox/wireleap" 14 | mkdir -p "$PROFILE" 15 | 16 | if [ -e "$PROFILE/prefs.js" ]; then 17 | sed -i '' '/network.proxy/d' "$PROFILE/prefs.js" 18 | fi 19 | 20 | cat<> "$PROFILE/prefs.js" 21 | user_pref("network.proxy.socks", "$WIRELEAP_SOCKS_HOST"); 22 | user_pref("network.proxy.socks_port", $WIRELEAP_SOCKS_PORT); 23 | user_pref("network.proxy.socks_remote_dns", true); 24 | user_pref("network.proxy.type", 1); 25 | EOF 26 | 27 | exec "$cmd" \ 28 | --profile "$PROFILE" \ 29 | --new-instance \ 30 | --private-window \ 31 | "$@" 32 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/git: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | exec "$cmd" -c http.proxy=socks5h://$WIRELEAP_SOCKS "$@" 11 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/google-chrome: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" 9 | [ -e "$cmd" ] || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/google-chrome-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/list: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | find "$WIRELEAP_HOME/scripts" -type f -perm -100 -exec basename '{}' ';' | uniq 3 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_darwin/default/surf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | export HTTP_PROXY="socks5://$WIRELEAP_SOCKS" 11 | exec "$cmd" "$@" 12 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/README: -------------------------------------------------------------------------------- 1 | This folder contains user-defined wireleap scripts and may include 2 | locally-modified versions of default scripts. 3 | 4 | If identically named scripts exist both here and in the "default" 5 | folder, this folder is given precedence when `wireleap exec` is ran. 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/README: -------------------------------------------------------------------------------- 1 | This folder contains default scripts. They will be overwritten on 2 | upgrade; do not modify. 3 | 4 | If adjustments are required, create a script in the parent directory, 5 | "scripts". 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/brave-browser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="$(basename "$0")" 9 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/brave-browser-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/chromium-browser: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="$(basename "$0")" 9 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/chromium-browser-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/curl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | export ALL_PROXY="socks5h://$WIRELEAP_SOCKS" 11 | exec "$cmd" "$@" 12 | 13 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/firefox: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | WIRELEAP_SOCKS_HOST="$(echo "$WIRELEAP_SOCKS" | cut -d: -f1)" 8 | WIRELEAP_SOCKS_PORT="$(echo "$WIRELEAP_SOCKS" | cut -d: -f2)" 9 | 10 | cmd="$(basename "$0")" 11 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 12 | 13 | PROFILE="$HOME/.mozilla/firefox/wireleap" 14 | mkdir -p "$PROFILE" 15 | 16 | if [ -e "$PROFILE/prefs.js" ]; then 17 | sed -i '/network.proxy/d' "$PROFILE/prefs.js" 18 | fi 19 | 20 | cat<> "$PROFILE/prefs.js" 21 | user_pref("network.proxy.socks", "$WIRELEAP_SOCKS_HOST"); 22 | user_pref("network.proxy.socks_port", $WIRELEAP_SOCKS_PORT); 23 | user_pref("network.proxy.socks_remote_dns", true); 24 | user_pref("network.proxy.type", 1); 25 | EOF 26 | 27 | exec "$cmd" \ 28 | --profile "$PROFILE" \ 29 | --new-instance \ 30 | --private-window \ 31 | "$@" 32 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/git: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | exec "$cmd" -c http.proxy=socks5h://$WIRELEAP_SOCKS "$@" 11 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/google-chrome: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 7 | 8 | cmd="$(basename "$0")" 9 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 10 | 11 | exec "$cmd" \ 12 | --proxy-server="socks5://$WIRELEAP_SOCKS" \ 13 | --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \ 14 | --user-data-dir="$HOME/.config/google-chrome-wireleap" \ 15 | --incognito \ 16 | "$@" 17 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/list: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | find "$WIRELEAP_HOME/scripts" -type f -perm -100 -exec basename '{}' ';' | uniq 3 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_linux/default/surf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | fatal() { echo "fatal: $*" 1>&2; exit 1; } 5 | 6 | cmd="$(basename "$0")" 7 | command -v "$cmd" >/dev/null || fatal "$cmd not found" 8 | [ "$WIRELEAP_SOCKS" ] || fatal "WIRELEAP_SOCKS not set" 9 | 10 | export HTTP_PROXY="socks5://$WIRELEAP_SOCKS" 11 | exec "$cmd" "$@" 12 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/README: -------------------------------------------------------------------------------- 1 | This folder contains user-defined wireleap scripts and may include 2 | locally-modified versions of default scripts. 3 | 4 | If identically named scripts exist both here and in the "default" 5 | folder, this folder is given precedence when `wireleap exec` is ran. 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/README: -------------------------------------------------------------------------------- 1 | This folder contains default scripts. They will be overwritten on 2 | upgrade; do not modify. 3 | 4 | If adjustments are required, create a script in the parent directory, 5 | "scripts". 6 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/brave-browser.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set bin64=%ProgramFiles%\BraveSoftware\Brave-Browser\Application\brave.exe 4 | set bin32=%ProgramFiles(x86)%\BraveSoftware\Brave-Browser\Application\brave.exe 5 | 6 | set bin="%bin64%" 7 | if not exist "%bin64%" ( 8 | if not exist "%bin32%" ( 9 | echo "The executable file [%bin32%] does not exist." 10 | exit /b 1 11 | ) 12 | set bin="%bin32%" 13 | ) 14 | 15 | %bin% --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" --proxy-server="socks5://%WIRELEAP_SOCKS%" --user-data-dir="%LOCALAPPDATA%\BraveSoftware\brave-wireleap" --incognito %* 16 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/chromium-browser.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set bin64=%ProgramFiles%\Chromium\Application\chrome.exe 4 | set bin32=%ProgramFiles(x86)%\Chromium\Application\chrome.exe 5 | 6 | set bin="%bin64%" 7 | if not exist "%bin64%" ( 8 | if not exist "%bin32%" ( 9 | echo "The executable file [%bin32%] does not exist." 10 | exit /b 1 11 | ) 12 | set bin="%bin32%" 13 | ) 14 | 15 | %bin% --proxy-server="socks5://%WIRELEAP_SOCKS%" --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" --user-data-dir="%LOCALAPPDATA%\Chromium\chromium-wireleap" --incognito %* 16 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/curl.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set ALL_PROXY=socks5h://%WIRELEAP_SOCKS% 3 | curl %* 4 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/firefox.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set bin64=%ProgramFiles%\Mozilla Firefox\firefox.exe 4 | set bin32=%ProgramFiles(x86)%\Mozilla Firefox\firefox.exe 5 | 6 | set bin="%bin64%" 7 | if not exist "%bin64%" ( 8 | if not exist "%bin32%" ( 9 | echo "The executable file [%bin32%] does not exist." 10 | exit /b 1 11 | ) 12 | set bin="%bin32%" 13 | ) 14 | 15 | set profile=%LOCALAPPDATA%/Mozilla/Firefox/wireleap 16 | md "%profile%" 17 | 18 | > "%profile%/prefs.js" ( 19 | echo.user_pref("network.proxy.socks", "%WIRELEAP_SOCKS_HOST%"^); 20 | echo.user_pref("network.proxy.socks_port", %WIRELEAP_SOCKS_PORT%^); 21 | echo.user_pref("network.proxy.socks_remote_dns", true^); 22 | echo.user_pref("network.proxy.type", 1^); 23 | ) 24 | 25 | %bin% --profile "%profile%" --new-instance --private-window %* 26 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/git.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | git -c http.proxy=socks5h://%WIRELEAP_SOCKS% %* 3 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/google-chrome.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set bin64=%ProgramFiles%\Google\Chrome\Application\chrome.exe 4 | set bin32=%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe 5 | 6 | set bin="%bin64%" 7 | if not exist "%bin64%" ( 8 | if not exist "%bin32%" ( 9 | echo "The executable file [%bin%] does not exist." 10 | exit /b 1 11 | ) 12 | set bin="%bin32%" 13 | ) 14 | 15 | %bin% --proxy-server="socks5://%WIRELEAP_SOCKS%" --host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" --user-data-dir="%LOCALAPPDATA%\Google\Chrome\chrome-wireleap" --incognito %* 16 | -------------------------------------------------------------------------------- /sub/initcmd/embedded/scripts_windows/default/list.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for %%f in (%WIRELEAP_HOME%\scripts\*.bat %WIRELEAP_HOME%\scripts\default\*.bat) do echo %%~nf | sort /unique 3 | -------------------------------------------------------------------------------- /sub/initcmd/initcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package initcmd 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "text/tabwriter" 9 | 10 | "github.com/wireleap/client/clientcfg" 11 | "github.com/wireleap/client/filenames" 12 | "github.com/wireleap/client/sub/initcmd/embedded" 13 | "github.com/wireleap/common/cli" 14 | "github.com/wireleap/common/cli/fsdir" 15 | ) 16 | 17 | func Cmd() *cli.Subcmd { 18 | fs := flag.NewFlagSet("init", flag.ExitOnError) 19 | force := fs.Bool("force-unpack-only", false, "Overwrite embedded files only") 20 | r := &cli.Subcmd{ 21 | FlagSet: fs, 22 | Desc: "Initialize wireleap home directory", 23 | Run: func(fm fsdir.T) { 24 | if err := cli.UnpackEmbedded(embedded.FS, fm, *force); err != nil { 25 | log.Fatalf("error while unpacking embedded files: %s", err) 26 | } 27 | if !*force { 28 | if err := fm.SetIndented(clientcfg.Defaults(), filenames.Config); err != nil { 29 | log.Fatalf("could not write initial config.json: %s", err) 30 | } 31 | } 32 | }, 33 | } 34 | r.Writer = tabwriter.NewWriter(r.FlagSet.Output(), 0, 8, 6, ' ', 0) 35 | return r 36 | } 37 | -------------------------------------------------------------------------------- /sub/initcmd/initcmd_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package initcmd 4 | 5 | import ( 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | 10 | "github.com/wireleap/common/cli/fsdir" 11 | ) 12 | 13 | func TestInitRun(t *testing.T) { 14 | tmpd, err := ioutil.TempDir("", "wltest.*") 15 | 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | t.Cleanup(func() { os.RemoveAll(tmpd) }) 21 | 22 | fm, err := fsdir.New(tmpd) 23 | 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | Cmd().Run(fm) 29 | 30 | f := fm.Path("config.json") 31 | _, err = os.Stat(f) 32 | 33 | if err != nil { 34 | t.Errorf( 35 | "error while looking for file %s: %s", 36 | f, 37 | err, 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sub/interceptcmd/available_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | // intercept is unsupported on Darwin 6 | const Available bool = false 7 | -------------------------------------------------------------------------------- /sub/interceptcmd/available_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | // intercept is supported on Linux 6 | const Available bool = true 7 | -------------------------------------------------------------------------------- /sub/interceptcmd/available_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | // intercept is unsupported on windows 6 | const Available bool = false 7 | -------------------------------------------------------------------------------- /sub/interceptcmd/interceptcmd_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | import ( 6 | "github.com/wireleap/common/cli" 7 | ) 8 | 9 | func Cmd() *cli.Subcmd { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /sub/interceptcmd/interceptcmd_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "net" 9 | "os" 10 | "os/exec" 11 | "runtime" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/wireleap/client/clientcfg" 16 | "github.com/wireleap/client/filenames" 17 | "github.com/wireleap/common/cli" 18 | "github.com/wireleap/common/cli/fsdir" 19 | ) 20 | 21 | func Cmd() *cli.Subcmd { 22 | fs := flag.NewFlagSet("intercept", flag.ExitOnError) 23 | r := &cli.Subcmd{ 24 | FlagSet: fs, 25 | Desc: "Run executable and redirect connections (req. SOCKS forwarder)", 26 | } 27 | r.SetMinimalUsage("[ARGS]") 28 | r.Run = func(fm fsdir.T) { 29 | c := clientcfg.Defaults() 30 | err := fm.Get(&c, filenames.Config) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | if fs.NArg() == 0 { 35 | r.Usage() 36 | os.Exit(1) 37 | } 38 | switch runtime.GOOS { 39 | case "linux": 40 | conn, err := net.DialTimeout("tcp", *c.Broker.Address, time.Second) 41 | if err != nil { 42 | log.Fatalf("could not connect to wireleap broker at address %s: %s", *c.Broker.Address, err) 43 | } 44 | conn.Close() 45 | lib := fm.Path("wireleap_intercept.so") 46 | args := fs.Args() 47 | 48 | bin, err := exec.LookPath(args[0]) 49 | 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | 54 | err = syscall.Exec( 55 | bin, 56 | args, 57 | append([]string{ 58 | "LD_PRELOAD=" + lib, 59 | "SOCKS5_PROXY=" + c.Forwarders.Socks.Address, 60 | }, os.Environ()...), 61 | ) 62 | 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | default: 67 | log.Fatal("unsupported OS:", runtime.GOOS) 68 | } 69 | } 70 | return r 71 | } 72 | -------------------------------------------------------------------------------- /sub/interceptcmd/interceptcmd_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package interceptcmd 4 | 5 | import ( 6 | "github.com/wireleap/common/cli" 7 | ) 8 | 9 | func Cmd() *cli.Subcmd { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /sub/logcmd/logcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package logcmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | 13 | "github.com/wireleap/client/clientcfg" 14 | "github.com/wireleap/client/filenames" 15 | "github.com/wireleap/common/api/client" 16 | "github.com/wireleap/common/cli" 17 | "github.com/wireleap/common/cli/fsdir" 18 | ) 19 | 20 | func Cmd(arg0 string) *cli.Subcmd { 21 | return &cli.Subcmd{ 22 | FlagSet: flag.NewFlagSet("log", flag.ExitOnError), 23 | Desc: fmt.Sprintf("Show %s controller daemon logs", arg0), 24 | Run: func(fm fsdir.T) { 25 | c := clientcfg.Defaults() 26 | err := fm.Get(&c, filenames.Config) 27 | if err != nil { 28 | log.Fatalf("could not read config: %s", err) 29 | } 30 | cl := client.New(nil) 31 | url := "http://" + *c.Address + "/api/log" 32 | req, err := cl.NewRequest(http.MethodGet, url, nil) 33 | if err != nil { 34 | log.Fatalf("could not create request to %s: %s", url, err) 35 | } 36 | res, err := cl.PerformRequestNoParse(req) 37 | if err != nil { 38 | log.Fatalf("could not perform request to %s: %s", url, err) 39 | } 40 | b, err := io.ReadAll(res.Body) 41 | if err != nil { 42 | log.Fatalf("could not read %s request body: %s", url, err) 43 | } 44 | os.Stdout.Write(b) 45 | return 46 | }, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sub/reloadcmd/reloadcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package reloadcmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/wireleap/client/clientcfg" 12 | "github.com/wireleap/client/clientlib" 13 | "github.com/wireleap/client/filenames" 14 | "github.com/wireleap/client/restapi" 15 | "github.com/wireleap/common/cli" 16 | "github.com/wireleap/common/cli/fsdir" 17 | ) 18 | 19 | func Cmd(arg0 string) *cli.Subcmd { 20 | return &cli.Subcmd{ 21 | FlagSet: flag.NewFlagSet("reload", flag.ExitOnError), 22 | Desc: fmt.Sprintf("Reload %s controller daemon configuration", arg0), 23 | Run: func(fm fsdir.T) { 24 | c := clientcfg.Defaults() 25 | err := fm.Get(&c, filenames.Config) 26 | if err != nil { 27 | log.Fatalf("could not read config: %s", err) 28 | } 29 | var st restapi.StatusReply 30 | clientlib.APICallOrDie(http.MethodPost, "http://"+*c.Address+"/api/reload", nil, &st) 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sub/restartcmd/restartcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package restartcmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "time" 10 | 11 | "github.com/wireleap/common/cli" 12 | "github.com/wireleap/common/cli/fsdir" 13 | "github.com/wireleap/common/cli/process" 14 | ) 15 | 16 | func Cmd(arg0 string, start func(fsdir.T), stop func(fsdir.T)) *cli.Subcmd { 17 | return &cli.Subcmd{ 18 | FlagSet: flag.NewFlagSet("restart", flag.ExitOnError), 19 | Desc: fmt.Sprintf("Restart %s controller daemon", arg0), 20 | Run: func(fm fsdir.T) { 21 | var pid int 22 | if fm.Get(&pid, arg0+".pid") == nil { 23 | stop(fm) 24 | i := 0 25 | for ; i < 10 && process.Exists(pid); i++ { 26 | time.Sleep(500 * time.Millisecond) 27 | } 28 | if i == 10 { 29 | log.Fatalf("timed out waiting for %s to stop", arg0) 30 | } 31 | } 32 | start(fm) 33 | }, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sub/sockscmd/sockscmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package sockscmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | "text/tabwriter" 13 | "time" 14 | 15 | "github.com/wireleap/client/clientcfg" 16 | "github.com/wireleap/client/clientlib" 17 | "github.com/wireleap/client/filenames" 18 | "github.com/wireleap/client/restapi" 19 | "github.com/wireleap/common/api/client" 20 | "github.com/wireleap/common/cli" 21 | "github.com/wireleap/common/cli/fsdir" 22 | "github.com/wireleap/common/cli/process" 23 | ) 24 | 25 | const Available = true 26 | 27 | const name = "socks" 28 | 29 | const bin = "wireleap_" + name 30 | 31 | func Cmd() (r *cli.Subcmd) { 32 | r = &cli.Subcmd{ 33 | FlagSet: flag.NewFlagSet(name, flag.ExitOnError), 34 | Desc: "Control SOCKSv5 proxy forwarder", 35 | Sections: []cli.Section{{ 36 | Title: "Commands", 37 | Entries: []cli.Entry{ 38 | {"start", fmt.Sprintf("Start %s daemon", bin)}, 39 | {"stop", fmt.Sprintf("Stop %s daemon", bin)}, 40 | {"status", fmt.Sprintf("Report %s daemon status", bin)}, 41 | {"restart", fmt.Sprintf("Restart %s daemon", bin)}, 42 | {"log", fmt.Sprintf("Show %s logs", bin)}, 43 | }, 44 | }}, 45 | } 46 | r.Writer = tabwriter.NewWriter(r.FlagSet.Output(), 0, 8, 7, ' ', 0) 47 | r.SetMinimalUsage("COMMAND [OPTIONS]") 48 | force := r.FlagSet.Bool("force", false, "Force shutdown (when using `stop`)") 49 | r.Run = func(fm fsdir.T) { 50 | if r.FlagSet.NArg() < 1 { 51 | r.Usage() 52 | } 53 | cmd := r.FlagSet.Arg(0) 54 | c := clientcfg.Defaults() 55 | err := fm.Get(&c, filenames.Config) 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | cl := client.New(nil) 60 | var ( 61 | st restapi.FwderReply 62 | meth = http.MethodGet 63 | url = "http://" + *c.Address + "/api/forwarders/" + name 64 | ) 65 | switch cmd { 66 | case "status": 67 | // url defined above is usable as-is 68 | case "start": 69 | meth = http.MethodPost 70 | url += "/start" 71 | case "stop": 72 | if args := r.FlagSet.Args(); *force || args[len(args)-1] == "--force" { 73 | pidfile := bin + ".pid" 74 | var pid int 75 | if err := fm.Get(&pid, pidfile); err != nil { 76 | log.Fatalf("could not read %s pidfile %s: %s", bin, pidfile, err) 77 | } 78 | process.Term(pid) 79 | time.Sleep(500 * time.Millisecond) 80 | process.Kill(pid) 81 | log.Printf("successfully killed %s, pid %d", bin, pid) 82 | return 83 | } 84 | meth = http.MethodPost 85 | url += "/stop" 86 | case "restart": 87 | meth = http.MethodPost 88 | url += "/stop" 89 | // specially handled below 90 | case "log": 91 | url += "/log" 92 | req, err := cl.NewRequest(meth, url, nil) 93 | if err != nil { 94 | log.Fatalf("could not create request to %s: %s", url, err) 95 | } 96 | res, err := cl.PerformRequestNoParse(req) 97 | if err != nil { 98 | log.Fatalf("could not perform request to %s: %s", url, err) 99 | } 100 | b, err := io.ReadAll(res.Body) 101 | if err != nil { 102 | log.Fatalf("could not read %s request body: %s", url, err) 103 | } 104 | os.Stdout.Write(b) 105 | return 106 | default: 107 | log.Fatalf("unknown %s subcommand: %s", name, cmd) 108 | } 109 | time.AfterFunc(3*time.Second, func() { 110 | // if 3 seconds elapsed waiting for API call to finish 111 | // it is probable that the API is down 112 | if cmd == "stop" { 113 | log.Println("this is taking a long time, consider using `stop --force`") 114 | } 115 | }) 116 | clientlib.APICallOrDie(meth, url, nil, &st) 117 | switch cmd { 118 | case "restart": 119 | url = "http://" + *c.Address + "/api/forwarders/" + name + "/start" 120 | clientlib.APICallOrDie(meth, url, nil, &st) 121 | case "status": 122 | switch st.State { 123 | case "failed", "inactive", "unknown": 124 | os.Exit(1) 125 | } 126 | } 127 | } 128 | return 129 | } 130 | -------------------------------------------------------------------------------- /sub/startcmd/startcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package startcmd 4 | 5 | import ( 6 | "crypto/tls" 7 | "errors" 8 | "flag" 9 | "fmt" 10 | "log" 11 | "net" 12 | "net/http" 13 | "os" 14 | "os/exec" 15 | "strings" 16 | "syscall" 17 | "text/tabwriter" 18 | "time" 19 | 20 | "github.com/wireleap/client/broker" 21 | "github.com/wireleap/client/clientcfg" 22 | "github.com/wireleap/client/clientlib" 23 | "github.com/wireleap/client/filenames" 24 | "github.com/wireleap/client/restapi" 25 | "github.com/wireleap/common/api/status" 26 | "github.com/wireleap/common/cli" 27 | "github.com/wireleap/common/cli/fsdir" 28 | "github.com/wireleap/common/cli/process" 29 | "golang.org/x/net/http2" 30 | "golang.org/x/net/http2/h2c" 31 | ) 32 | 33 | var reloads, shutdowns []func() 34 | 35 | func reload() bool { 36 | for _, f := range reloads { 37 | f() 38 | } 39 | return false 40 | } 41 | 42 | func shutdown() bool { 43 | for _, f := range shutdowns { 44 | f() 45 | } 46 | return true 47 | } 48 | 49 | func setupServer(l net.Listener, h http.Handler, tc *tls.Config) { 50 | h1s := &http.Server{ 51 | Handler: h, 52 | TLSConfig: tc, 53 | ReadTimeout: 10 * time.Second, 54 | WriteTimeout: 10 * time.Second, 55 | IdleTimeout: 30 * time.Second, 56 | ReadHeaderTimeout: 5 * time.Second, 57 | } 58 | h2s := &http2.Server{MaxHandlers: 0, MaxConcurrentStreams: 0} 59 | if err := http2.ConfigureServer(h1s, h2s); err != nil { 60 | log.Fatalf("could not configure h2 server: %s", err) 61 | } 62 | h1s.Handler = h2c.NewHandler(h1s.Handler, h2s) 63 | go func() { 64 | if err := h1s.Serve(l); err != nil { 65 | log.Fatalf("serving on h2c://%s failed: %s", l.Addr(), err) 66 | } 67 | }() 68 | } 69 | 70 | var ( 71 | broklog = log.New(os.Stderr, "[broker] ", log.LstdFlags|log.Lmsgprefix) 72 | restlog = log.New(os.Stderr, "[restapi] ", log.LstdFlags|log.Lmsgprefix) 73 | ) 74 | 75 | func Cmd(arg0 string) *cli.Subcmd { 76 | fs := flag.NewFlagSet("start", flag.ExitOnError) 77 | fg := fs.Bool("fg", false, "Run in foreground, don't detach") 78 | r := &cli.Subcmd{ 79 | FlagSet: fs, 80 | Desc: fmt.Sprintf("Start %s controller daemon", arg0), 81 | Run: func(fm fsdir.T) { 82 | var err error 83 | c := clientcfg.Defaults() 84 | // try versioned config first 85 | if err := fm.Get(&c, filenames.Config+".next"); err != nil { 86 | if err = fm.Get(&c, filenames.Config); err != nil { 87 | log.Fatalf("could not load config file %s: %s", fm.Path(filenames.Config), err) 88 | } 89 | } 90 | if *fg == false { 91 | var pid int 92 | if err = fm.Get(&pid, arg0+".pid"); err == nil { 93 | if process.Exists(pid) { 94 | log.Fatalf("%s daemon is already running!", arg0) 95 | } 96 | } 97 | 98 | binary, err := exec.LookPath(os.Args[0]) 99 | if err != nil { 100 | log.Fatalf("could not find own binary path: %s", err) 101 | } 102 | 103 | logpath := fm.Path(arg0 + ".log") 104 | logfile, err := os.OpenFile(logpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 105 | 106 | if err != nil { 107 | log.Fatalf("could not open logfile %s: %s", logpath, err) 108 | } 109 | defer logfile.Close() 110 | 111 | cmd := exec.Cmd{ 112 | Path: binary, 113 | Args: []string{binary, "start", "--fg"}, 114 | Stdout: logfile, 115 | Stderr: logfile, 116 | } 117 | if err = cmd.Start(); err != nil { 118 | log.Fatalf("could not spawn background %s process: %s", arg0, err) 119 | } 120 | // assuming failed by default 121 | str := restapi.StatusReply{ 122 | Home: fm.Path(), 123 | Pid: -1, 124 | State: "failed", 125 | Broker: restapi.StatusBroker{}, 126 | } 127 | if err = clientlib.DefaultAPIClient.Perform(http.MethodGet, "http://"+*c.Address+"/api/status", nil, &str); err != nil { 128 | st := &status.T{} 129 | if errors.As(err, &st) { 130 | // error returned from API can be jsonized 131 | clientlib.JSONOrDie(os.Stdout, st) 132 | os.Exit(1) 133 | } else { 134 | // NOTE: special exit code for fail due to upgrade requirement. 135 | // TODO FIXME needs a better way to signal this 136 | if _ = cmd.Wait(); cmd.ProcessState.ExitCode() == 26 { 137 | str.Upgrade = &restapi.StatusUpgrade{Required: true} 138 | clientlib.JSONOrDie(os.Stdout, str) 139 | os.Exit(1) 140 | } 141 | log.Fatalf("error while executing API request: %s", err) 142 | } 143 | } else { 144 | clientlib.JSONOrDie(os.Stdout, str) 145 | } 146 | return 147 | } 148 | if c.Broker.Address == nil { 149 | log.Fatalf("broker.address not provided, refusing to start") 150 | } 151 | log.Default().SetFlags(log.LstdFlags | log.Lmsgprefix) 152 | log.Default().SetPrefix("[controller] ") 153 | brok := broker.New(fm, &c, broklog) 154 | shutdowns = append(shutdowns, brok.Shutdown) 155 | reloads = append(reloads, brok.Reload) 156 | defer brok.Shutdown() 157 | os.Mkdir(fm.Path("webroot"), 0755) 158 | 159 | mux := http.NewServeMux() 160 | mux.Handle("/broker", brok) 161 | mux.Handle("/broker/", http.NotFoundHandler()) 162 | 163 | // combo socket? 164 | if *c.Address == *c.Broker.Address { 165 | mux.Handle("/api/", http.StripPrefix("/api", restapi.New(brok, restlog))) 166 | mux.Handle("/", http.FileServer(http.Dir(fm.Path("webroot")))) 167 | restlog.Printf("listening h2c on %s", *c.Address) 168 | } else { 169 | restmux := http.NewServeMux() 170 | restmux.Handle("/api/", http.StripPrefix("/api", restapi.New(brok, restlog))) 171 | restmux.Handle("/", http.FileServer(http.Dir(fm.Path("webroot")))) 172 | 173 | var restl net.Listener 174 | if strings.HasPrefix(*c.Address, "/") { 175 | if err = os.RemoveAll(*c.Address); err != nil { 176 | restlog.Fatalf("could not remove unix socket %s: %s", *c.Address, err) 177 | } 178 | restl, err = net.Listen("unix", *c.Address) 179 | if err != nil { 180 | restlog.Fatalf("listening h2c on unix:%s failed: %s", *c.Address, err) 181 | } 182 | restlog.Printf("listening h2c on unix:%s", *c.Address) 183 | } else { 184 | restl, err = net.Listen("tcp", *c.Address) 185 | if err != nil { 186 | restlog.Fatalf("listening h2c on http://%s failed: %s", *c.Address, err) 187 | } 188 | restlog.Printf("listening h2c on http://%s", *c.Address) 189 | } 190 | defer restl.Close() 191 | setupServer(restl, restmux, brok.T.TLSClientConfig) 192 | } 193 | 194 | var brokl net.Listener 195 | if strings.HasPrefix(*c.Broker.Address, "/") { 196 | if err = os.RemoveAll(*c.Broker.Address); err != nil { 197 | restlog.Fatalf("could not remove unix socket %s: %s", *c.Broker.Address, err) 198 | } 199 | brokl, err = net.Listen("unix", *c.Broker.Address) 200 | if err != nil { 201 | broklog.Fatalf("listening h2c on unix:%s failed: %s", *c.Broker.Address, err) 202 | } 203 | broklog.Printf("listening h2c on unix:%s, waiting for forwarders", *c.Broker.Address) 204 | } else { 205 | brokl, err = net.Listen("tcp", *c.Broker.Address) 206 | if err != nil { 207 | broklog.Fatalf("listening h2c on http://%s failed: %s", *c.Broker.Address, err) 208 | } 209 | broklog.Printf("listening h2c on http://%s, waiting for forwarders", *c.Broker.Address) 210 | } 211 | defer brokl.Close() 212 | setupServer(brokl, mux, brok.T.TLSClientConfig) 213 | if err = fm.Set(os.Getpid(), arg0+".pid"); err != nil { 214 | log.Fatalf("could not write pid: %s", err) 215 | } 216 | cli.SignalLoop(cli.SignalMap{ 217 | process.ReloadSignal: reload, 218 | syscall.SIGINT: shutdown, 219 | syscall.SIGTERM: shutdown, 220 | syscall.SIGQUIT: shutdown, 221 | }) 222 | }, 223 | } 224 | r.Writer = tabwriter.NewWriter(r.FlagSet.Output(), 6, 8, 1, ' ', 0) 225 | r.Sections = []cli.Section{ 226 | { 227 | Title: "Signals", 228 | Entries: []cli.Entry{ 229 | { 230 | Key: "SIGUSR1\t(10)", 231 | Value: "Reload configuration, contract information and circuit", 232 | }, 233 | { 234 | Key: "SIGTERM\t(15)", 235 | Value: "Gracefully stop wireleap daemon and exit", 236 | }, 237 | { 238 | Key: "SIGQUIT\t(3)", 239 | Value: "Gracefully stop wireleap daemon and exit", 240 | }, 241 | { 242 | Key: "SIGINT\t(2)", 243 | Value: "Gracefully stop wireleap daemon and exit", 244 | }, 245 | }, 246 | }, 247 | { 248 | Title: "Environment", 249 | Entries: []cli.Entry{{ 250 | Key: "WIRELEAP_TARGET_PROTOCOL", 251 | Value: "Resolve target IP via tcp4, tcp6 or tcp (default)", 252 | }}, 253 | }, 254 | } 255 | return r 256 | } 257 | -------------------------------------------------------------------------------- /sub/statuscmd/statuscmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package statuscmd 4 | 5 | import ( 6 | "errors" 7 | "flag" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "text/tabwriter" 13 | 14 | "github.com/wireleap/client/clientcfg" 15 | "github.com/wireleap/client/clientlib" 16 | "github.com/wireleap/client/filenames" 17 | "github.com/wireleap/client/restapi" 18 | "github.com/wireleap/common/cli" 19 | "github.com/wireleap/common/cli/fsdir" 20 | "github.com/wireleap/common/cli/process" 21 | ) 22 | 23 | func Cmd(arg0 string) *cli.Subcmd { 24 | r := &cli.Subcmd{ 25 | FlagSet: flag.NewFlagSet("status", flag.ExitOnError), 26 | Desc: fmt.Sprintf("Report %s controller daemon status", arg0), 27 | Sections: []cli.Section{{ 28 | Title: "Exit codes", 29 | Entries: []cli.Entry{ 30 | { 31 | Key: "0", 32 | Value: fmt.Sprintf("%s controller is active", arg0), 33 | }, 34 | { 35 | Key: "1", 36 | Value: fmt.Sprintf("%s controller is inactive", arg0), 37 | }, 38 | { 39 | Key: "2", 40 | Value: fmt.Sprintf("%s controller is activating or deactivating", arg0), 41 | }, 42 | { 43 | Key: "3", 44 | Value: fmt.Sprintf("%s controller failed or state is unknown", arg0), 45 | }, 46 | }, 47 | }}, 48 | Run: func(fm fsdir.T) { 49 | var ( 50 | o = restapi.StatusReply{ 51 | Home: fm.Path(), 52 | Pid: -1, 53 | State: "unknown", 54 | } 55 | exit = 3 56 | ) 57 | // set both state string & exit code 58 | setState := func(s string) { 59 | switch s { 60 | case "active": 61 | exit = 0 62 | case "inactive": 63 | exit = 1 64 | case "activating", "deactivating": 65 | exit = 2 66 | case "failed", "unknown": 67 | exit = 3 68 | default: 69 | panic(fmt.Errorf("unknown state: %s", s)) 70 | } 71 | o.State = s 72 | } 73 | if err := fm.Get(&o.Pid, arg0+".pid"); err != nil { 74 | if errors.Is(err, os.ErrNotExist) { 75 | setState("inactive") 76 | } else { 77 | setState("unknown") 78 | } 79 | } else { 80 | if process.Exists(o.Pid) { 81 | c := clientcfg.Defaults() 82 | err := fm.Get(&c, filenames.Config) 83 | if err != nil { 84 | log.Fatalf("could not read config: %s", err) 85 | } 86 | clientlib.APICallOrDie(http.MethodGet, "http://"+*c.Address+"/api/status", nil, &o) 87 | setState(o.State) 88 | return 89 | } else { 90 | // pidfile was not cleaned up ... 91 | setState("failed") 92 | } 93 | } 94 | clientlib.JSONOrDie(os.Stdout, o) 95 | os.Exit(exit) 96 | }, 97 | } 98 | r.Writer = tabwriter.NewWriter(r.FlagSet.Output(), 0, 8, 5, ' ', 0) 99 | return r 100 | } 101 | -------------------------------------------------------------------------------- /sub/stopcmd/stopcmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package stopcmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "time" 11 | 12 | "github.com/wireleap/client/clientlib" 13 | "github.com/wireleap/client/restapi" 14 | "github.com/wireleap/common/cli" 15 | "github.com/wireleap/common/cli/fsdir" 16 | "github.com/wireleap/common/cli/process" 17 | ) 18 | 19 | func Cmd(arg0 string) *cli.Subcmd { 20 | return &cli.Subcmd{ 21 | FlagSet: flag.NewFlagSet("stop", flag.ExitOnError), 22 | Desc: fmt.Sprintf("Stop %s controller daemon", arg0), 23 | Run: func(fm fsdir.T) { 24 | var ( 25 | pid int 26 | err error 27 | pidfile = arg0 + ".pid" 28 | sockspid = "wireleap_socks.pid" 29 | tunpid = "wireleap_tun.pid" 30 | ) 31 | if err = fm.Get(&pid, sockspid); err == nil && process.Exists(pid) { 32 | log.Fatalf("`wireleap_socks` appears to be running, stop it before stopping `wireleap`") 33 | } 34 | if err = fm.Get(&pid, tunpid); err == nil && process.Exists(pid) { 35 | log.Fatalf("`wireleap_tun` appears to be running, stop it before stopping `wireleap`") 36 | } 37 | if err = fm.Get(&pid, pidfile); err != nil { 38 | log.Fatalf( 39 | "could not get pid of %s from %s: %s", 40 | arg0, fm.Path(pidfile), err, 41 | ) 42 | } 43 | if process.Exists(pid) { 44 | if err = process.Term(pid); err != nil { 45 | log.Fatalf("could not terminate %s pid %d: %s", arg0, pid, err) 46 | } 47 | } 48 | o := restapi.StatusReply{ 49 | Home: fm.Path(), 50 | Pid: -1, 51 | State: "inactive", 52 | Broker: restapi.StatusBroker{}, 53 | } 54 | for i := 0; i < 30; i++ { 55 | if !process.Exists(pid) { 56 | clientlib.JSONOrDie(os.Stdout, o) 57 | fm.Del(pidfile) 58 | return 59 | } 60 | time.Sleep(100 * time.Millisecond) 61 | } 62 | process.Kill(pid) 63 | time.Sleep(100 * time.Millisecond) 64 | if process.Exists(pid) { 65 | log.Fatalf("timed out waiting for %s (pid %d) to shut down -- process still alive!", arg0, pid) 66 | } 67 | clientlib.JSONOrDie(os.Stdout, o) 68 | fm.Del(pidfile) 69 | }, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sub/tuncmd/tuncmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tuncmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | "text/tabwriter" 13 | "time" 14 | 15 | "github.com/wireleap/client/clientcfg" 16 | "github.com/wireleap/client/clientlib" 17 | "github.com/wireleap/client/filenames" 18 | "github.com/wireleap/client/restapi" 19 | "github.com/wireleap/common/api/client" 20 | "github.com/wireleap/common/cli" 21 | "github.com/wireleap/common/cli/fsdir" 22 | "github.com/wireleap/common/cli/process" 23 | ) 24 | 25 | const name = "tun" 26 | 27 | const bin = "wireleap_" + name 28 | 29 | func Cmd() (r *cli.Subcmd) { 30 | r = &cli.Subcmd{ 31 | FlagSet: flag.NewFlagSet(name, flag.ExitOnError), 32 | Desc: "Control TUN device forwarder", 33 | Sections: []cli.Section{{ 34 | Title: "Commands", 35 | Entries: []cli.Entry{ 36 | {"start", fmt.Sprintf("Start %s daemon", bin)}, 37 | {"stop", fmt.Sprintf("Stop %s daemon", bin)}, 38 | {"status", fmt.Sprintf("Report %s daemon status", bin)}, 39 | {"restart", fmt.Sprintf("Restart %s daemon", bin)}, 40 | {"log", fmt.Sprintf("Show %s logs", bin)}, 41 | }, 42 | }}, 43 | } 44 | r.Writer = tabwriter.NewWriter(r.FlagSet.Output(), 0, 8, 7, ' ', 0) 45 | r.SetMinimalUsage("COMMAND [OPTIONS]") 46 | force := r.FlagSet.Bool("force", false, "Force shutdown (when using `stop`)") 47 | r.Run = func(fm fsdir.T) { 48 | if r.FlagSet.NArg() < 1 { 49 | r.Usage() 50 | } 51 | cmd := r.FlagSet.Arg(0) 52 | c := clientcfg.Defaults() 53 | err := fm.Get(&c, filenames.Config) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | cl := client.New(nil) 58 | var ( 59 | st restapi.FwderReply 60 | meth = http.MethodGet 61 | url = "http://" + *c.Address + "/api/forwarders/" + name 62 | ) 63 | switch cmd { 64 | case "status": 65 | // url defined above is usable as-is 66 | case "start": 67 | if clientlib.ContractURL(fm) == nil { 68 | log.Fatalf("no contract configured; import accesskey before starting tun") 69 | } 70 | meth = http.MethodPost 71 | url += "/start" 72 | case "stop": 73 | if args := r.FlagSet.Args(); *force || args[len(args)-1] == "--force" { 74 | pidfile := bin + ".pid" 75 | var pid int 76 | if err := fm.Get(&pid, pidfile); err != nil { 77 | log.Fatalf("could not read %s pidfile %s: %s", bin, pidfile, err) 78 | } 79 | process.Term(pid) 80 | time.Sleep(500 * time.Millisecond) 81 | process.Kill(pid) 82 | log.Printf("successfully killed %s, pid %d", bin, pid) 83 | return 84 | } 85 | meth = http.MethodPost 86 | url += "/stop" 87 | case "restart": 88 | meth = http.MethodPost 89 | url += "/stop" 90 | // specially handled below 91 | case "log": 92 | url += "/log" 93 | req, err := cl.NewRequest(meth, url, nil) 94 | if err != nil { 95 | log.Fatalf("could not create request to %s: %s", url, err) 96 | } 97 | res, err := cl.PerformRequestNoParse(req) 98 | if err != nil { 99 | log.Fatalf("could not perform request to %s: %s", url, err) 100 | } 101 | b, err := io.ReadAll(res.Body) 102 | if err != nil { 103 | log.Fatalf("could not read %s request body: %s", url, err) 104 | } 105 | os.Stdout.Write(b) 106 | return 107 | default: 108 | log.Fatalf("unknown %s subcommand: %s", name, cmd) 109 | } 110 | time.AfterFunc(3*time.Second, func() { 111 | // if 3 seconds elapsed waiting for API call to finish 112 | // it is probable that the API is down 113 | if cmd == "stop" { 114 | log.Println("this is taking a long time, consider using `stop --force`") 115 | } 116 | }) 117 | clientlib.APICallOrDie(meth, url, nil, &st) 118 | switch cmd { 119 | case "restart": 120 | url = "http://" + *c.Address + "/api/forwarders/" + name + "/start" 121 | clientlib.APICallOrDie(meth, url, nil, &st) 122 | case "status": 123 | switch st.State { 124 | case "failed", "inactive", "unknown": 125 | os.Exit(1) 126 | } 127 | } 128 | } 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /sub/tuncmd/tuncmd_platform/tuncmd_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tuncmd_platform 4 | 5 | const Available = true 6 | -------------------------------------------------------------------------------- /sub/tuncmd/tuncmd_platform/tuncmd_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tuncmd_platform 4 | 5 | const Available = true 6 | -------------------------------------------------------------------------------- /sub/tuncmd/tuncmd_platform/tuncmd_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tuncmd_platform 4 | 5 | const Available = false 6 | -------------------------------------------------------------------------------- /sub/versioncmd/versioncmd.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package versioncmd 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/blang/semver" 11 | "github.com/wireleap/client/clientlib" 12 | "github.com/wireleap/client/restapi" 13 | "github.com/wireleap/common/api/interfaces" 14 | "github.com/wireleap/common/cli" 15 | "github.com/wireleap/common/cli/fsdir" 16 | ) 17 | 18 | func Cmd(swversion *semver.Version, is ...interfaces.T) *cli.Subcmd { 19 | out := swversion.String() 20 | fs := flag.NewFlagSet("version", flag.ExitOnError) 21 | verbose := fs.Bool("v", false, "show verbose output") 22 | 23 | return &cli.Subcmd{ 24 | FlagSet: fs, 25 | Desc: "Show version and exit", 26 | Run: func(_ fsdir.T) { 27 | if *verbose { 28 | clientlib.JSONOrDie(os.Stdout, restapi.RuntimeReply) 29 | } else { 30 | fmt.Println(out) 31 | } 32 | }, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | // The release version is defined here. 4 | package version 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io/fs" 10 | "log" 11 | "os" 12 | 13 | "github.com/blang/semver" 14 | "github.com/wireleap/client/clientcfg" 15 | "github.com/wireleap/client/clientlib" 16 | "github.com/wireleap/client/filenames" 17 | "github.com/wireleap/client/sub/tuncmd/tuncmd_platform" 18 | "github.com/wireleap/common/api/client" 19 | "github.com/wireleap/common/api/consume" 20 | "github.com/wireleap/common/api/duration" 21 | "github.com/wireleap/common/api/interfaces/clientdir" 22 | "github.com/wireleap/common/cli" 23 | "github.com/wireleap/common/cli/fsdir" 24 | "github.com/wireleap/common/cli/process" 25 | "github.com/wireleap/common/cli/upgrade" 26 | ) 27 | 28 | var BUILD_FLAGS string 29 | 30 | var BUILD_TIME string 31 | 32 | var GIT_COMMIT string 33 | 34 | // old name compat 35 | var GITREV string = "0.0.0-UNSET-VERSION" 36 | 37 | // VERSION_STRING is the current version string, set by the linker via go build 38 | // -X flag. 39 | var VERSION_STRING = GITREV 40 | 41 | // VERSION is the semver version struct of VERSION_STRING. 42 | var VERSION = semver.MustParse(VERSION_STRING) 43 | 44 | // Hardcoded (for now) channel value for wireleap client. 45 | const Channel = "default" 46 | 47 | // Post-upgrade hook for superviseupgradecmd. 48 | func PostUpgradeHook(f fsdir.T) (err error) { 49 | if tuncmd_platform.Available { 50 | log.Println("moving wireleap_tun to wireleap_tun.prev for potential rollback...") 51 | if err = os.Rename(f.Path("wireleap_tun"), f.Path("wireleap_tun.prev")); err != nil && !errors.Is(err, fs.ErrNotExist) { 52 | err = fmt.Errorf("error while attempting to move wireleap_tun to wireleap_tun.prev: %s", err) 53 | return 54 | } else { 55 | // if it does not exist, that is fine 56 | err = nil 57 | } 58 | } 59 | // force unpacking of files 60 | log.Println("unpacking new embedded files...") 61 | if err = cli.RunChild(f.Path("wireleap"), "init", "--force-unpack-only"); err != nil { 62 | return 63 | } 64 | log.Println("stopping running wireleap...") 65 | if err = cli.RunChild(f.Path("wireleap"), "stop"); err != nil { 66 | return 67 | } 68 | if tuncmd_platform.Available { 69 | fp := f.Path("wireleap_tun") 70 | fmt.Println("===================================") 71 | fmt.Println("NOTE: to enable wireleap_tun again:") 72 | fmt.Println("$ sudo chown 0:0", fp) 73 | fmt.Println("$ sudo chmod u+s", fp) 74 | fmt.Println("===================================") 75 | fmt.Println("(to return to your shell prompt just press Return)") 76 | } 77 | return 78 | } 79 | 80 | // Post-rollback hook for rollbackcmd. 81 | func PostRollbackHook(f fsdir.T) (err error) { 82 | // do the same thing but with the old binary on rollback 83 | log.Println("unpacking old embedded files...") 84 | if err = cli.RunChild(f.Path("wireleap"), "init", "--force-unpack-only"); err != nil { 85 | return 86 | } 87 | if tuncmd_platform.Available { 88 | log.Println("moving wireleap_tun.prev to wireleap_tun...") 89 | if err = os.Rename(f.Path("wireleap_tun.prev"), f.Path("wireleap_tun")); err != nil && !errors.Is(err, fs.ErrNotExist) { 90 | fp := f.Path("wireleap_tun") 91 | fmt.Println("===================================") 92 | fmt.Println("no wireleap_tun.prev found") 93 | fmt.Println("NOTE: to enable wireleap_tun again:") 94 | fmt.Println("$ sudo chown 0:0", fp) 95 | fmt.Println("$ sudo chmod u+s", fp) 96 | fmt.Println("===================================") 97 | fmt.Println("(to return to your shell prompt just press Return)") 98 | } else { 99 | // if it does not exist, that is fine 100 | err = nil 101 | } 102 | } 103 | return 104 | } 105 | 106 | // MIGRATIONS is the slice of versioned migrations. 107 | var MIGRATIONS = []*upgrade.Migration{ 108 | { 109 | Name: "restructuring_config", 110 | Version: semver.Version{Major: 0, Minor: 6, Patch: 0}, 111 | Apply: func(f fsdir.T) error { 112 | var oldc map[string]interface{} 113 | if err := f.Get(&oldc, "config.json.next"); err != nil { 114 | return fmt.Errorf("could not load config.json.next: %s", err) 115 | } 116 | c := clientcfg.Defaults() 117 | // old contract field is ignored/obsolete 118 | // old accesskey field 119 | if ak, ok := oldc["accesskey"].(clientcfg.Accesskey); ok { 120 | c.Broker.Accesskey = ak 121 | } 122 | // old circuit field 123 | if circ, ok := oldc["circuit"].(clientcfg.Circuit); ok { 124 | c.Broker.Circuit = circ 125 | } 126 | // old timeout field 127 | if timeout, ok := oldc["timeout"].(duration.T); ok { 128 | c.Broker.Circuit.Timeout = timeout 129 | } 130 | // replace nil with empty list 131 | if c.Broker.Circuit.Whitelist == nil { 132 | c.Broker.Circuit.Whitelist = make([]string, 0) 133 | } 134 | // address/port changes are merged automatically 135 | if err := f.SetIndented(&c, "config.json.next"); err != nil { 136 | return fmt.Errorf("could not save config.json.next: %s", err) 137 | } 138 | log.Println("NOTE: ports used by Wireleap client have changed as follows:") 139 | log.Println("h2c: 13492 -> 13490") 140 | log.Println("socks5: 13491 (no change)") 141 | log.Println("tun: 13493 -> 13492") 142 | log.Println("If you have been depending on the old values please change the configuration accordingly.") 143 | return nil 144 | }, 145 | Rollback: func(fsdir.T) error { 146 | // since we only modify config.next there is no rollback 147 | return nil 148 | }, 149 | }, 150 | } 151 | 152 | // LatestChannelVersion is a special function for wireleap which will obtain 153 | // the latest version supported by the currently configured update channel from 154 | // the directory. 155 | // NOTE: since wireleap is not running, we need to check status out of band. 156 | func LatestChannelVersion(f fsdir.T) (_ semver.Version, err error) { 157 | var pid int 158 | // check if running wireleap or wireleap_tun 159 | if tuncmd_platform.Available { 160 | if err = f.Get(&pid, "wireleap_tun.pid"); err == nil && process.Exists(pid) { 161 | err = fmt.Errorf("wireleap_tun appears to be running, please stop it to upgrade") 162 | return 163 | } 164 | } 165 | if err = f.Get(&pid, "wireleap.pid"); err == nil && process.Exists(pid) { 166 | err = fmt.Errorf("wireleap appears to be running, please stop it to upgrade") 167 | return 168 | } 169 | c := clientcfg.Defaults() 170 | if err = f.Get(&c, filenames.Config); err != nil { 171 | return 172 | } 173 | if clientlib.ContractURL(f) == nil { 174 | err = fmt.Errorf("`contract` field in config is empty, setup a contract with `wireleap import`") 175 | return 176 | } 177 | cl := client.New(nil, clientdir.T) 178 | dinfo, err := consume.DirectoryInfo(cl, clientlib.ContractURL(f)) 179 | if err != nil { 180 | return 181 | } 182 | v, ok := dinfo.UpgradeChannels.Client[Channel] 183 | if !ok { 184 | err = fmt.Errorf("no version for client channel '%s' is provided by directory", Channel) 185 | return 186 | } 187 | return v, nil 188 | } 189 | -------------------------------------------------------------------------------- /wireleap_intercept/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Wireleap 2 | 3 | CC = gcc 4 | CFLAGS = -D_GNU_SOURCE -O2 -fPIC 5 | LDFLAGS = -shared -ldl 6 | 7 | PREFIX = /usr/local 8 | LIBDIR = $(PREFIX)/lib 9 | 10 | LIB = wireleap_intercept.so 11 | OBJ = $(LIB:.so=.o) 12 | 13 | all: $(LIB) 14 | 15 | $(LIB): $(OBJ) 16 | $(CC) $(OBJ) $(LDFLAGS) -o $@ 17 | 18 | clean: 19 | rm -f $(LIB) $(OBJ) 20 | 21 | install: all 22 | cp -f $(LIB) $(DESTDIR)$(LIBDIR) 23 | chmod 755 $(DESTDIR)$(LIBDIR)/$(LIB) 24 | 25 | uninstall: 26 | rm -f $(DESTDIR)$(LIBDIR)/$(LIB) 27 | 28 | .c.o: 29 | $(CC) $(CFLAGS) -c $< 30 | -------------------------------------------------------------------------------- /wireleap_intercept/wireleap_intercept.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Wireleap */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define WL_ADDR "localhost" 13 | #define WL_PORT "13491" 14 | 15 | static struct hostent *(*o_gethostbyname)(const char *name); 16 | static int (*o_getaddrinfo)(const char *node, const char *service, 17 | const struct addrinfo *hints, struct addrinfo **res); 18 | static int (*o_connect)(int sockfd, const struct sockaddr *addr, 19 | socklen_t addrlen); 20 | 21 | static struct addrinfo *proxy_info; 22 | static char saved_node[256]; 23 | 24 | /* Initializer, intercept functions and initialize the SOCKS proxy */ 25 | static void __attribute__((constructor)) 26 | init(void) 27 | { 28 | char *env_socks_proxy, *proxy_addr, *proxy_port; 29 | struct addrinfo hints; 30 | 31 | *(void **) (&o_connect) = dlsym(RTLD_NEXT, "connect"); 32 | *(void **) (&o_getaddrinfo) = dlsym(RTLD_NEXT, "getaddrinfo"); 33 | *(void **) (&o_gethostbyname) = dlsym(RTLD_NEXT, "gethostbyname"); 34 | 35 | memset(&hints, 0, sizeof(struct addrinfo)); 36 | hints.ai_family = AF_INET; 37 | hints.ai_socktype = SOCK_STREAM; 38 | hints.ai_flags = 0; 39 | hints.ai_protocol = 0; 40 | 41 | env_socks_proxy = getenv("SOCKS5_PROXY"); 42 | if (env_socks_proxy) { 43 | proxy_addr = strtok(env_socks_proxy, ":"); 44 | proxy_port = strtok(NULL, ":"); 45 | } else { 46 | proxy_addr = WL_ADDR; 47 | proxy_port = WL_PORT; 48 | } 49 | o_getaddrinfo(proxy_addr, proxy_port, &hints, &proxy_info); 50 | } 51 | 52 | /* Intercepted getaddrinfo(3) */ 53 | int 54 | getaddrinfo(const char *node, const char *service, 55 | const struct addrinfo *hints, struct addrinfo **res) 56 | { 57 | strncpy(saved_node, node, sizeof(saved_node)-1); 58 | return (*o_getaddrinfo)("0.0.0.1", service, hints, res); 59 | } 60 | 61 | /* Intercepted gethostbyname(3) */ 62 | struct hostent 63 | *gethostbyname(const char *name) 64 | { 65 | strncpy(saved_node, name, sizeof(saved_node)-1); 66 | return (*o_gethostbyname)("0.0.0.1"); 67 | } 68 | 69 | 70 | /* SOCKS proxy */ 71 | int 72 | connect_proxy(int sockfd, const struct sockaddr_in *addr, socklen_t addrlen) 73 | { 74 | int ret, fd_flags; 75 | struct sockaddr_in *proxy_addr; 76 | char buf[512]; 77 | 78 | fd_flags = fcntl(sockfd, F_GETFL, 0); 79 | fcntl(sockfd, F_SETFL, fd_flags & ~O_NONBLOCK); 80 | 81 | ret = (*o_connect)(sockfd, proxy_info->ai_addr, sizeof(struct sockaddr)); 82 | if (ret != 0) { 83 | proxy_addr = (struct sockaddr_in *)(proxy_info->ai_addr); 84 | inet_ntop(proxy_addr->sin_family, &proxy_addr->sin_addr, buf, 85 | sizeof(buf)); 86 | return ret; 87 | } 88 | 89 | send(sockfd, "\x05\x01\x00", 3, 0); 90 | recv(sockfd, buf, 2, 0); 91 | if (memcmp(buf, "\x05\x00", 2) != 0) { 92 | errno = ECONNREFUSED; 93 | return -1; 94 | } 95 | 96 | if (memcmp(&addr->sin_addr, "\x00\x00\x00\x01", 4) == 0) { 97 | int nodelen = strlen(saved_node); 98 | if (nodelen > sizeof(saved_node) || 7+nodelen > sizeof(buf)) { 99 | errno = ECONNREFUSED; 100 | return -1; 101 | } 102 | memcpy(buf, "\x05\x01\x00\x03", 4); 103 | buf[4] = (unsigned char)nodelen; 104 | memcpy(buf+5, saved_node, nodelen); 105 | memcpy(buf+5+nodelen, &addr->sin_port, 2); 106 | send(sockfd, buf, 7+nodelen, 0); 107 | } else if (addr->sin_family == AF_INET) { 108 | memcpy(buf, "\x05\x01\x00\x01", 4); 109 | memcpy(buf+4, &addr->sin_addr, 4); 110 | memcpy(buf+8, &addr->sin_port, 2); 111 | send(sockfd, buf, 10, 0); 112 | } else if (addr->sin_family == AF_INET6) { 113 | memcpy(buf, "\x05\x01\x00\x04", 4); 114 | memcpy(buf+4, &addr->sin_addr, 16); 115 | memcpy(buf+20, &addr->sin_port, 2); 116 | send(sockfd, buf, 22, 0); 117 | } else { 118 | exit(1); 119 | } 120 | 121 | recv(sockfd, buf, 10, 0); 122 | if (memcmp(buf, "\x05\x00", 2) != 0) { 123 | errno = ECONNREFUSED; 124 | return -1; 125 | } 126 | 127 | fcntl(sockfd, F_SETFL, fd_flags); 128 | return ret; 129 | } 130 | 131 | /* Intercepted connect(3) function */ 132 | int 133 | connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 134 | { 135 | int so_type; 136 | socklen_t optlen; 137 | struct sockaddr_in *addr_in; 138 | 139 | if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) { 140 | char str_address[64]; 141 | optlen = sizeof(so_type); 142 | getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &so_type, &optlen); 143 | 144 | addr_in = (struct sockaddr_in *)addr; 145 | inet_ntop(addr_in->sin_family, &addr_in->sin_addr, str_address, 146 | sizeof(str_address)); 147 | if (so_type & SOCK_STREAM) { 148 | return connect_proxy(sockfd, addr_in, addrlen); 149 | } 150 | } 151 | return (*o_connect)(sockfd, addr, addrlen); 152 | } 153 | -------------------------------------------------------------------------------- /wireleap_socks/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "crypto/tls" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "log" 12 | "net" 13 | "net/http" 14 | "os" 15 | "os/signal" 16 | "strings" 17 | "syscall" 18 | "time" 19 | 20 | "github.com/wireleap/client/restapi" 21 | "github.com/wireleap/client/socks" 22 | "github.com/wireleap/common/api/provide" 23 | "github.com/wireleap/common/api/status" 24 | "github.com/wireleap/common/wlnet" 25 | "github.com/wireleap/common/wlnet/h2conn" 26 | "golang.org/x/net/http2" 27 | ) 28 | 29 | const udpbufsize = 4096 // change if bigger datagrams are expected 30 | 31 | var tt = &http2.Transport{ 32 | AllowHTTP: true, 33 | DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { 34 | return net.Dial(network, addr) 35 | }, 36 | ReadIdleTimeout: 10 * time.Second, 37 | PingTimeout: 10 * time.Second, 38 | } 39 | 40 | type DialFunc func(string, string) (*h2conn.T, error) 41 | 42 | func dialFuncTo(h2caddr string) DialFunc { 43 | return func(proto, addr string) (*h2conn.T, error) { 44 | return h2conn.New(tt, h2caddr, map[string]string{ 45 | "Wl-Dial-Protocol": proto, 46 | "Wl-Dial-Target": addr, 47 | "Wl-Forwarder": "socks", 48 | }) 49 | } 50 | } 51 | 52 | // handle everything SOCKSv5-related on the same address 53 | func ListenSOCKS(addr string, dialer DialFunc) (err error) { 54 | var udpl net.PacketConn 55 | var tcpl net.Listener 56 | udpl, err = net.ListenPacket("udp", addr) 57 | if err != nil { 58 | err = fmt.Errorf("could not listen on requested udp address %s: %w", addr, err) 59 | return 60 | } 61 | tcpl, err = net.Listen("tcp", addr) 62 | if err != nil { 63 | err = fmt.Errorf("could not listen on requested tcp address %s: %w", addr, err) 64 | return 65 | } 66 | go ProxyUDP(udpl, dialer) 67 | go ProxyTCP(tcpl, dialer, udpl.LocalAddr()) 68 | return 69 | } 70 | 71 | // handle TCP socks connections 72 | func ProxyTCP(l net.Listener, dialer DialFunc, udpaddr net.Addr) { 73 | pause := 1 * time.Second 74 | for { 75 | c0, err := l.Accept() 76 | if err != nil { 77 | log.Printf("SOCKSv5 tcp socket accept error: %s, pausing for %s", err, pause) 78 | time.Sleep(pause) 79 | continue 80 | } 81 | go func() { 82 | log.Printf("SOCKSv5 tcp socket accepted: %s -> %s", c0.RemoteAddr(), c0.LocalAddr()) 83 | cmd, addr, err := socks.Handshake(c0) 84 | if err != nil { 85 | log.Printf("SOCKSv5 tcp socket handshake error: %s", err) 86 | c0.Close() 87 | return 88 | } 89 | switch cmd { 90 | case socks.CONNECT: 91 | defer c0.Close() 92 | c1, err := dialer("tcp", addr) 93 | if err != nil { 94 | log.Printf("error dialing tcp through the circuit: %s", err) 95 | socks.WriteStatus(c0, socks.StatusGeneralFailure, socks.AddrAddr(c0.LocalAddr())) 96 | return 97 | } 98 | socks.WriteStatus(c0, socks.StatusOK, socks.AddrAddr(c0.LocalAddr())) 99 | if err = wlnet.Splice(context.Background(), c0, c1, 0, 32768); err != nil { 100 | log.Printf("error splicing initial connection: %s", err) 101 | } 102 | case socks.UDP_ASSOC: 103 | socks.WriteStatus(c0, socks.StatusOK, socks.AddrAddr(udpaddr)) 104 | default: 105 | socks.WriteStatus(c0, socks.StatusCommandNotSupported, socks.AddrAddr(l.Addr())) 106 | c0.Close() 107 | } 108 | }() 109 | } 110 | } 111 | 112 | // handle UDP packets 113 | func ProxyUDP(l net.PacketConn, dialer DialFunc) { 114 | l.(*net.UDPConn).SetWriteBuffer(2147483647) 115 | l.(*net.UDPConn).SetReadBuffer(2147483647) 116 | for { 117 | ibuf, obuf := make([]byte, udpbufsize), make([]byte, udpbufsize) 118 | n, laddr, err := l.ReadFrom(ibuf) 119 | if err != nil { 120 | log.Printf("error while reading udp packet from %s: %s", laddr, err) 121 | continue 122 | } 123 | go func() { 124 | dstaddr, data, err := socks.DissectUDP(ibuf[:n]) 125 | if err != nil { 126 | log.Printf("SOCKSv5 failed dissecting UDP packet: %s", err) 127 | return 128 | } 129 | conn, err := dialer("udp", dstaddr.String()) 130 | if err != nil { 131 | log.Printf( 132 | "error dialing udp %s->%s->%s through the circuit: %s", 133 | laddr, l.LocalAddr(), dstaddr, err, 134 | ) 135 | return 136 | } 137 | _, err = conn.Write(data) 138 | if err != nil { 139 | log.Printf( 140 | "error when writing initial data to %s->%s->%s udp tunnel: %s", 141 | laddr, l.LocalAddr(), dstaddr, err, 142 | ) 143 | return 144 | } 145 | for { 146 | n, err := conn.Read(obuf) 147 | if err != nil { 148 | if err != io.EOF { 149 | log.Printf("error reading %s<-%s<-%s via udp: %s", laddr, l.LocalAddr(), dstaddr, err) 150 | } 151 | break 152 | } 153 | b, err := socks.ComposeUDP(dstaddr, obuf[:n]) 154 | if err != nil { 155 | log.Printf("error writing %s<-%s<-%s via udp: %s", laddr, l.LocalAddr(), dstaddr, err) 156 | break 157 | } 158 | _, err = l.WriteTo(b, laddr) 159 | if err != nil { 160 | log.Printf("error writing %s<-%s<-%s via udp: %s", laddr, l.LocalAddr(), dstaddr, err) 161 | break 162 | } 163 | } 164 | }() 165 | } 166 | } 167 | 168 | func main() { 169 | // set up state API 170 | state := "activating" 171 | exe, err := os.Executable() 172 | if err != nil { 173 | log.Fatalf("could not find own executable path: %s", err) 174 | } 175 | // windows... 176 | exe = strings.TrimSuffix(exe, ".exe") 177 | err = restapi.UnixServer(exe+".sock", provide.Routes{"/state": provide.MethodGate(provide.Routes{ 178 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 179 | st := restapi.FwderState{State: state} 180 | b, err := json.Marshal(st) 181 | if err != nil { 182 | log.Printf("error while serving /state reply: %s", err) 183 | status.ErrInternal.WriteTo(w) 184 | return 185 | } 186 | w.Write(b) 187 | }), 188 | })}) 189 | if err != nil { 190 | log.Fatal(err) 191 | } 192 | // set up graceful signal handling 193 | sigs := make(chan os.Signal, 1) 194 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 195 | 196 | var ok bool 197 | var h2caddr, socksaddr string 198 | 199 | if h2caddr, ok = os.LookupEnv("WIRELEAP_ADDR_H2C"); !ok { 200 | log.Fatal("WIRELEAP_ADDR_H2C is not defined") 201 | } 202 | if socksaddr, ok = os.LookupEnv("WIRELEAP_ADDR_SOCKS"); !ok { 203 | log.Fatal("WIRELEAP_ADDR_SOCKS is not defined") 204 | } 205 | h2caddr = "http://" + h2caddr 206 | if err := ListenSOCKS(socksaddr, dialFuncTo(h2caddr)); err != nil { 207 | log.Fatalf("listening on socks5://%s failed: %s", socksaddr, err) 208 | } 209 | log.Printf("listening for SOCKSv5 connections on %s, state queries on %s", socksaddr, exe+".sock") 210 | state = "active" 211 | select { 212 | case <-sigs: 213 | state = "deactivating" 214 | log.Printf("shutting down gracefully...") 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /wireleap_tun/bypasslist.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package main 4 | 5 | import ( 6 | "net" 7 | "sync" 8 | 9 | "github.com/wireleap/client/wireleap_tun/netsetup" 10 | ) 11 | 12 | // a bypassList holds the bypassed IPs and routes 13 | type bypassList struct { 14 | m []net.IP 15 | mu sync.RWMutex 16 | rts netsetup.Routes 17 | } 18 | 19 | func (t *bypassList) Set(ips ...net.IP) (err error) { 20 | t.mu.Lock() 21 | t.m = ips 22 | if t.rts != nil { 23 | t.rts.Down() 24 | t.rts = nil 25 | } 26 | t.rts, err = netsetup.RoutesUp(ips...) 27 | t.mu.Unlock() 28 | return 29 | } 30 | 31 | func (t *bypassList) Get() []net.IP { 32 | t.mu.RLock() 33 | r := make([]net.IP, len(t.m)) 34 | copy(r, t.m) 35 | t.mu.RUnlock() 36 | return r 37 | } 38 | 39 | func (t *bypassList) Clear() { 40 | t.mu.Lock() 41 | t.m = []net.IP{} 42 | if t.rts != nil { 43 | t.rts.Down() 44 | t.rts = nil 45 | } 46 | t.mu.Unlock() 47 | } 48 | -------------------------------------------------------------------------------- /wireleap_tun/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "io" 8 | "io/ioutil" 9 | "log" 10 | "net" 11 | "os" 12 | "os/signal" 13 | "path" 14 | "runtime" 15 | "strconv" 16 | "syscall" 17 | 18 | "github.com/wireleap/client/restapi" 19 | "github.com/wireleap/client/wireleap_tun/netsetup" 20 | "github.com/wireleap/client/wireleap_tun/tun" 21 | "github.com/wireleap/common/api/provide" 22 | "github.com/wireleap/common/api/status" 23 | 24 | "net/http" 25 | _ "net/http/pprof" 26 | ) 27 | 28 | func main() { 29 | // set up state API 30 | state := "activating" 31 | exe, err := os.Executable() 32 | if err != nil { 33 | log.Fatalf("could not find own executable path: %s", err) 34 | } 35 | bypass := bypassList{} 36 | err = restapi.UnixServer(exe+".sock", provide.Routes{ 37 | "/state": provide.MethodGate(provide.Routes{ 38 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 | st := restapi.FwderState{State: state} 40 | b, err := json.Marshal(st) 41 | if err != nil { 42 | log.Printf("error while serving /state reply: %s", err) 43 | status.ErrInternal.WriteTo(w) 44 | return 45 | } 46 | w.Write(b) 47 | }), 48 | }), 49 | "/bypass": provide.MethodGate(provide.Routes{ 50 | http.MethodGet: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 | b, err := json.Marshal(bypass.Get()) 52 | if err != nil { 53 | log.Printf("error while serving /bypass GET reply: %s", err) 54 | status.ErrInternal.WriteTo(w) 55 | return 56 | } 57 | w.Write(b) 58 | }), 59 | http.MethodPost: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 | ips := []net.IP{} 61 | b, err := io.ReadAll(r.Body) 62 | if err != nil { 63 | log.Printf("error while reading /bypass POST request body: %s", err) 64 | status.ErrRequest.WriteTo(w) 65 | return 66 | } 67 | err = json.Unmarshal(b, &ips) 68 | if err != nil { 69 | log.Printf("error while unmarshaling /bypass POST request body: %s", err) 70 | status.ErrRequest.WriteTo(w) 71 | return 72 | } 73 | if err = bypass.Set(ips...); err != nil { 74 | // hard fail here to catch bugs & avoid inadvertent leaks 75 | // TODO maybe soft fail in the future 76 | log.Fatalf("could not configure routes: %s", err) 77 | } 78 | status.OK.WriteTo(w) 79 | }), 80 | http.MethodDelete: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 81 | bypass.Clear() 82 | status.OK.WriteTo(w) 83 | }), 84 | }), 85 | }) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | if err := syscall.Seteuid(0); err != nil { 90 | log.Fatal("could not gain privileges; check if setuid flag is set?") 91 | } 92 | os.Chmod(exe+".sock", 0660) 93 | sig := make(chan os.Signal) 94 | signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 95 | sh := os.Getenv("WIRELEAP_HOME") 96 | h2caddr := os.Getenv("WIRELEAP_ADDR_H2C") 97 | tunaddr := os.Getenv("WIRELEAP_ADDR_TUN") 98 | if sh == "" || h2caddr == "" || tunaddr == "" { 99 | log.Fatal("Running wireleap_tun separately from wireleap is not supported. Please use `sudo wireleap tun start`.") 100 | } 101 | t, err := tun.New() 102 | if err != nil { 103 | log.Fatalf("could not create tun device: %s", err) 104 | } 105 | rlim := syscall.Rlimit{Cur: 65535, Max: 65535} 106 | if err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim); err != nil { 107 | log.Fatalf("could not set RLIMIT_NOFILE to %+v", rlim) 108 | } 109 | if err = netsetup.Init(t, tunaddr); err != nil { 110 | log.Fatalf("could not configure tun device %s as %s: %s", t.Name(), tunaddr, err) 111 | } 112 | pidfile := path.Join(sh, "wireleap_tun.pid") 113 | finalize := func() { 114 | // don't need to delete catch-all routes via tun dev as they will be 115 | // removed when the device is down 116 | bypass.Clear() 117 | os.Remove(pidfile) 118 | } 119 | defer finalize() 120 | os.Remove(pidfile) 121 | pidtext := []byte(strconv.Itoa(os.Getpid())) 122 | err = ioutil.WriteFile(pidfile, pidtext, 0644) 123 | if err != nil { 124 | finalize() 125 | log.Fatalf("could not write pidfile %s: %s", pidfile, err) 126 | } 127 | defer os.Remove(pidfile) 128 | // setup debugging & profiling 129 | if os.Getenv("WIRELEAP_TUN_DEBUG") != "" { 130 | DEBUG = true 131 | } 132 | if os.Getenv("WIRELEAP_TUN_PPROF") != "" { 133 | go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() 134 | } 135 | if bpr := os.Getenv("WIRELEAP_TUN_BLOCK_PROFILE_RATE"); bpr != "" { 136 | n, err := strconv.Atoi(bpr) 137 | if err != nil { 138 | log.Fatalf("invalid WIRELEAP_TUN_BLOCK_PROFILE_RATE value: %s", bpr) 139 | } 140 | runtime.SetBlockProfileRate(n) 141 | } 142 | if mpf := os.Getenv("WIRELEAP_TUN_MUTEX_PROFILE_FRACTION"); mpf != "" { 143 | n, err := strconv.Atoi(mpf) 144 | if err != nil { 145 | log.Fatalf("invalid WIRELEAP_TUN_MUTEX_PROFILE_FRACTION value: %s", mpf) 146 | } 147 | runtime.SetMutexProfileFraction(n) 148 | } 149 | log.Printf("listening for state queries on %s", exe+".sock") 150 | if err = tunsplice(t, h2caddr, tunaddr); err != nil { 151 | log.Fatal("tunsplice returned error:", err) 152 | } 153 | state = "active" 154 | for { 155 | select { 156 | case s := <-sig: 157 | state = "deactivating" 158 | log.Printf("terminating on signal %s", s) 159 | return 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /wireleap_tun/netsetup/netsetup.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package netsetup 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | // CopyIP copies an IP and returns the copy. 10 | func CopyIP(i1 net.IP) (i2 net.IP) { 11 | i2 = make([]byte, len(i1)) 12 | copy(i2, i1) 13 | return 14 | } 15 | 16 | // NextIP returns a new IP from the passed one with the last octet incremented 17 | // by 1. Normally, this should be its /31 "neighbor" if the original IP is the 18 | // lowest /31 address. 19 | func NextIP(i1 net.IP) (i2 net.IP) { 20 | i2 = CopyIP(i1) 21 | i2[len(i2)-1]++ 22 | return 23 | } 24 | 25 | // route table storage to keep track of bypass routes 26 | type Routes interface{ Down() error } 27 | -------------------------------------------------------------------------------- /wireleap_tun/netsetup/route_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package netsetup 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "log" 9 | "net" 10 | "os" 11 | "os/exec" 12 | "strconv" 13 | "syscall" 14 | 15 | "github.com/wireleap/client/wireleap_tun/tun" 16 | "golang.org/x/net/route" 17 | "golang.org/x/sys/unix" 18 | ) 19 | 20 | func sockwrite(rms []*route.RouteMessage) error { 21 | fd, err := syscall.Socket(syscall.AF_ROUTE, syscall.SOCK_RAW, syscall.AF_UNSPEC) 22 | if err != nil { 23 | return fmt.Errorf("could not create raw routing socket: %s", err) 24 | } 25 | defer syscall.Close(fd) 26 | for _, rm := range rms { 27 | // generate human-readable debug output 28 | o := []string{"writing route message:"} 29 | switch rm.Type { 30 | case syscall.RTM_ADD: 31 | o = append(o, "add") 32 | case syscall.RTM_DELETE: 33 | o = append(o, "delete") 34 | } 35 | if aa := rm.Addrs[syscall.RTAX_DST]; aa != nil { 36 | o = append(o, "dst") 37 | switch a := aa.(type) { 38 | case *route.Inet4Addr: 39 | o = append(o, "v4", net.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3]).String()) 40 | case *route.Inet6Addr: 41 | o = append(o, "v6", net.IP(a.IP[:]).String()) 42 | case *route.LinkAddr: 43 | o = append(o, "link#"+strconv.Itoa(a.Index)) 44 | default: 45 | o = append(o, "") 46 | } 47 | } 48 | if aa := rm.Addrs[syscall.RTAX_GATEWAY]; aa != nil { 49 | o = append(o, "gw") 50 | switch a := aa.(type) { 51 | case *route.Inet4Addr: 52 | o = append(o, "v4", net.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3]).String()) 53 | case *route.Inet6Addr: 54 | o = append(o, "v6", net.IP(a.IP[:]).String()) 55 | case *route.LinkAddr: 56 | o = append(o, "link#"+strconv.Itoa(a.Index)) 57 | default: 58 | o = append(o, "") 59 | } 60 | } 61 | log.Println(o) 62 | 63 | b, err := rm.Marshal() 64 | if err != nil { 65 | return fmt.Errorf("could not marshal routemessage %+v: %s", rm, err) 66 | } 67 | n, err := syscall.Write(fd, b) 68 | // if route being added already exists or route being deleted is already gone 69 | if errors.Is(err, syscall.EEXIST) || errors.Is(err, syscall.ENOENT) { 70 | // do nothing 71 | } else { 72 | // only log failures here for now for debugging 73 | log.Printf("%d bytes written to AF_ROUTE, error is %s", n, err) 74 | } 75 | } 76 | return nil 77 | } 78 | 79 | func mkrms(t int, rts [][]route.Addr) (r []*route.RouteMessage) { 80 | for i, addrs := range rts { 81 | r = append(r, &route.RouteMessage{ 82 | Version: syscall.RTM_VERSION, 83 | Seq: i, 84 | Type: t, 85 | Flags: syscall.RTF_STATIC | 86 | syscall.RTF_UP | 87 | syscall.RTF_GATEWAY | 88 | unix.RTF_GLOBAL, 89 | ID: uintptr(os.Getpid()), 90 | Addrs: addrs, 91 | }) 92 | } 93 | return 94 | } 95 | 96 | func getgws() (gw4 route.Addr, gw6 route.Addr, err error) { 97 | // get default route(s) 98 | rib, err := route.FetchRIB(syscall.AF_UNSPEC, route.RIBTypeRoute, 0) 99 | if err != nil { 100 | return nil, nil, err 101 | } 102 | msgs, err := route.ParseRIB(route.RIBTypeRoute, rib) 103 | if err != nil { 104 | return nil, nil, err 105 | } 106 | for _, m := range msgs { 107 | switch m := m.(type) { 108 | case *route.RouteMessage: 109 | // looking for a destination of all zeroes (default route sign) 110 | switch a := m.Addrs[syscall.RTAX_DST].(type) { 111 | case *route.Inet4Addr: 112 | if !net.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3]).Equal(net.IPv4zero) { 113 | // not default route 114 | continue 115 | } 116 | case *route.Inet6Addr: 117 | if !net.IP(a.IP[:]).Equal(net.IPv6zero) { 118 | // not default route 119 | continue 120 | } 121 | default: 122 | continue 123 | } 124 | 125 | // getting the gateway to use for v4/v6 bypass routes 126 | switch a := m.Addrs[syscall.RTAX_GATEWAY].(type) { 127 | case *route.Inet4Addr: 128 | if gw4 != nil { 129 | // already have one 130 | continue 131 | } 132 | if ip := net.IPv4(a.IP[0], a.IP[1], a.IP[2], a.IP[3]); ip.IsLinkLocalUnicast() { 133 | // skip link-local gateways present on darwin 134 | continue 135 | } else { 136 | log.Printf("found default v4 route, gateway %s", ip) 137 | } 138 | gw4 = a 139 | case *route.Inet6Addr: 140 | if gw6 != nil { 141 | // already have one 142 | continue 143 | } 144 | if ip := net.IP(a.IP[:]); ip.IsLinkLocalUnicast() { 145 | // skip link-local gateways present on darwin 146 | continue 147 | } else { 148 | log.Printf("found default v6 route, gateway %s", ip) 149 | } 150 | gw6 = a 151 | default: 152 | continue 153 | } 154 | } 155 | } 156 | if gw4 == nil && gw6 == nil { 157 | return nil, nil, fmt.Errorf("could not obtain any default v4/v6 gateways") 158 | } 159 | return 160 | } 161 | 162 | // mkroutes returns the routes we need for wireleap to function (contract, 163 | // directory, fronting relay). 164 | func mkroutes(ips []net.IP) (routes []*route.RouteMessage, err error) { 165 | for _, ip := range ips { 166 | if ip.IsLoopback() || ip.IsUnspecified() { 167 | // don't need routes for these... 168 | continue 169 | } 170 | gw4, gw6, err := getgws() 171 | if err != nil { 172 | return nil, err 173 | } 174 | // route bypass ips as default route using default gateway 175 | var addrs [][]route.Addr 176 | for _, ip := range ips { 177 | if ip4 := ip.To4(); ip4 != nil && gw4 != nil { 178 | // v4 179 | addrs = append(addrs, []route.Addr{ 180 | syscall.RTAX_DST: &route.Inet4Addr{IP: [4]byte{ip4[0], ip4[1], ip4[2], ip4[3]}}, 181 | syscall.RTAX_NETMASK: &route.Inet4Addr{IP: [4]byte{255, 255, 255, 255}}, 182 | syscall.RTAX_GATEWAY: gw4, 183 | }) 184 | } else if gw6 != nil { 185 | // v6 186 | // TODO go 1.17: 187 | // use https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer 188 | ip6 := [16]byte{} 189 | copy(ip6[:], ip) 190 | addrs = append(addrs, []route.Addr{ 191 | syscall.RTAX_DST: &route.Inet6Addr{IP: ip6}, 192 | syscall.RTAX_NETMASK: &route.Inet4Addr{IP: [4]byte{255, 255, 255, 255}}, 193 | syscall.RTAX_GATEWAY: gw6, 194 | }) 195 | } 196 | } 197 | routes = mkrms(syscall.RTM_ADD, addrs) 198 | } 199 | return 200 | } 201 | 202 | func Init(t *tun.T, tunaddr string) error { 203 | tunhost, _, err := net.SplitHostPort(tunaddr) 204 | if err != nil { 205 | return fmt.Errorf("could not parse WIRELEAP_ADDR_TUN `%s`: %s", tunaddr, err) 206 | } 207 | ip, _, err := net.ParseCIDR(tunhost + "/31") 208 | if err != nil { 209 | return fmt.Errorf("could not parse WIRELEAP_ADDR_TUN subnet `%s`: %s", tunhost+"/31", err) 210 | } 211 | if err = exec.Command("ifconfig", t.Name(), tunhost, NextIP(ip).String(), "netmask", "0xffffffff").Run(); err != nil { 212 | return fmt.Errorf("tun device %s configuration failed: %s", t.Name(), err) 213 | } 214 | gw4, gw6, err := getgws() 215 | if err != nil { 216 | return err 217 | } 218 | var addrs [][]route.Addr 219 | if gw4 != nil { 220 | addrs = append(addrs, []route.Addr{ 221 | // lower half of all ipv4 addresses 222 | syscall.RTAX_DST: &route.Inet4Addr{IP: [4]byte{}}, // 0.0.0.0 223 | syscall.RTAX_NETMASK: &route.Inet4Addr{IP: [4]byte{128}}, // /1 224 | syscall.RTAX_GATEWAY: &route.LinkAddr{Index: t.NetIf.Index}, // via utunX 225 | }, []route.Addr{ 226 | // upper half of all ipv4 addresses 227 | syscall.RTAX_DST: &route.Inet4Addr{IP: [4]byte{128}}, // 128.0.0.0 228 | syscall.RTAX_NETMASK: &route.Inet4Addr{IP: [4]byte{128}}, // /1 229 | syscall.RTAX_GATEWAY: &route.LinkAddr{Index: t.NetIf.Index}, // via utunX 230 | }) 231 | } 232 | if gw6 != nil { 233 | addrs = append(addrs, []route.Addr{ 234 | // global-adressable ipv6 235 | syscall.RTAX_DST: &route.Inet6Addr{IP: [16]byte{32}}, // 2000:: 236 | syscall.RTAX_NETMASK: &route.Inet6Addr{IP: [16]byte{224}}, // /3 237 | syscall.RTAX_GATEWAY: &route.LinkAddr{Index: t.NetIf.Index}, // via utunX 238 | }) 239 | } 240 | return sockwrite(mkrms(syscall.RTM_ADD, addrs)) 241 | } 242 | 243 | type darwinRoutes struct{ rts []*route.RouteMessage } 244 | 245 | func RoutesUp(ips ...net.IP) (Routes, error) { 246 | log.Printf("bringing up bypass routes...") 247 | bypassrts, err := mkroutes(ips) 248 | if err != nil { 249 | return nil, fmt.Errorf("could not create routes to bypass IPs: %s", err) 250 | } 251 | if err = sockwrite(bypassrts); err != nil { 252 | return nil, fmt.Errorf("could not setup bypass routes: %s", err) 253 | } 254 | return darwinRoutes{bypassrts}, nil 255 | } 256 | 257 | func (t darwinRoutes) Down() error { 258 | log.Printf("bringing down bypass routes...") 259 | for _, rt := range t.rts { 260 | // mutate in place, this struct is being discarded anyway 261 | rt.Type = syscall.RTM_DELETE 262 | } 263 | return sockwrite(t.rts) 264 | } 265 | -------------------------------------------------------------------------------- /wireleap_tun/netsetup/route_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package netsetup 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | 10 | "github.com/vishvananda/netlink" 11 | "github.com/wireleap/client/wireleap_tun/tun" 12 | ) 13 | 14 | // default route filter 15 | var filter = &netlink.Route{Dst: nil} 16 | 17 | // mkroutes returns the routes we need for wireleap to function (contract, 18 | // directory, fronting relay). 19 | // NOTE: routes returned by filter can be duplicate. therefore, when iterating 20 | // do not add but replace 21 | func mkroutes(ips []net.IP) (routes []netlink.Route, err error) { 22 | for _, ip := range ips { 23 | if ip.IsLoopback() || ip.IsUnspecified() { 24 | // don't need routes for these... 25 | continue 26 | } 27 | var tmp []netlink.Route 28 | // get default route(s) 29 | v6 := ip.To4() == nil 30 | if v6 { 31 | tmp, err = netlink.RouteListFiltered(netlink.FAMILY_V6, filter, netlink.RT_FILTER_DST) 32 | } else { 33 | tmp, err = netlink.RouteListFiltered(netlink.FAMILY_V4, filter, netlink.RT_FILTER_DST) 34 | } 35 | if err != nil { 36 | err = fmt.Errorf("could not get route(s) to %s: %s", ip, err) 37 | return 38 | } 39 | // route bypass ips as default route 40 | for _, r := range tmp { 41 | if r.Gw != nil { 42 | var mask net.IPMask 43 | if v6 { 44 | mask = net.CIDRMask(128, 128) 45 | } else { 46 | mask = net.CIDRMask(32, 32) 47 | } 48 | r.Dst = &net.IPNet{IP: ip, Mask: mask} 49 | routes = append(routes, r) 50 | } 51 | } 52 | } 53 | return 54 | } 55 | 56 | func Init(t *tun.T, tunaddr string) error { 57 | // set tun device up & add defined address 58 | tunhost, _, err := net.SplitHostPort(tunaddr) 59 | if err != nil { 60 | return fmt.Errorf("could not parse WIRELEAP_ADDR_TUN `%s`: %s", tunaddr, err) 61 | } 62 | addr, err := netlink.ParseAddr(tunhost + "/31") 63 | if err != nil { 64 | return fmt.Errorf("could not parse address of %s: %s", tunaddr, err) 65 | } 66 | link, err := netlink.LinkByName(t.Name()) 67 | if err != nil { 68 | return fmt.Errorf("could not get link for %s: %s", t.Name(), err) 69 | } 70 | err = netlink.AddrAdd(link, addr) 71 | if err != nil { 72 | return fmt.Errorf("could not set address of %s to %s: %s", link, addr, err) 73 | } 74 | err = netlink.LinkSetTxQLen(link, 1000) 75 | if err != nil { 76 | return fmt.Errorf("could not set link txqueue length for %s to %d: %s", t.Name(), 1000, err) 77 | } 78 | err = netlink.LinkSetUp(link) 79 | if err != nil { 80 | return fmt.Errorf("could not set %s up: %s", link, err) 81 | } 82 | // avoid clobbering the default route by being just a _little_ bit more specific 83 | initrts := []netlink.Route{{ 84 | // lower half of all v4 addresses 85 | LinkIndex: t.NetIf.Index, 86 | Dst: &net.IPNet{IP: net.IPv4(0, 0, 0, 0), Mask: net.CIDRMask(1, net.IPv4len*8)}, 87 | }, { 88 | // upper half of all v4 addresses 89 | LinkIndex: t.NetIf.Index, 90 | Dst: &net.IPNet{IP: net.IPv4(128, 0, 0, 0), Mask: net.CIDRMask(1, net.IPv4len*8)}, 91 | }, { 92 | // v6 global-adressable range 93 | LinkIndex: t.NetIf.Index, 94 | Dst: &net.IPNet{IP: net.ParseIP("2000::"), Mask: net.CIDRMask(3, net.IPv6len*8)}, 95 | }} 96 | for _, rt := range initrts { 97 | log.Printf("adding catch-all route: %+v", rt) 98 | if err = netlink.RouteReplace(&rt); err != nil { 99 | return fmt.Errorf("could not add catch-all route to %s: %s", rt.Dst, err) 100 | } 101 | log.Printf("added catch-all route to %s via %s", rt.Dst, rt.Gw) 102 | } 103 | return nil 104 | } 105 | 106 | type linuxRoutes struct{ rts []netlink.Route } 107 | 108 | func RoutesUp(ips ...net.IP) (Routes, error) { 109 | bypassrts, err := mkroutes(ips) 110 | if err != nil { 111 | return nil, fmt.Errorf("could not create routes to bypass IPs: %s", err) 112 | } 113 | for _, rt := range bypassrts { 114 | log.Printf("adding bypass route: %+v", rt) 115 | if err = netlink.RouteReplace(&rt); err != nil { 116 | return nil, fmt.Errorf("could not add bypass route to %s: %s", rt.Dst, err) 117 | } 118 | log.Printf("added bypass route to %s via %s", rt.Dst, rt.Gw) 119 | } 120 | return linuxRoutes{bypassrts}, nil 121 | } 122 | 123 | func (t linuxRoutes) Down() error { 124 | for _, rt := range t.rts { 125 | if err := netlink.RouteDel(&rt); err != nil { 126 | return fmt.Errorf("error bringing route %s via %s down: %s", rt.Dst, rt.Gw, err) 127 | } 128 | } 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /wireleap_tun/ptable/ptable.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package ptable 4 | 5 | import ( 6 | "net" 7 | "sync" 8 | "sync/atomic" 9 | ) 10 | 11 | type Family int 12 | 13 | const ( 14 | TCP Family = iota 15 | UDP 16 | nfamilies 17 | nports = 65535 18 | ) 19 | 20 | type Entry struct { 21 | SrcIP, DstIP net.IP 22 | SrcPort, DstPort int 23 | 24 | mu sync.Mutex // for initializing conn 25 | conn net.Conn 26 | } 27 | 28 | type T [nfamilies * nports]atomic.Value 29 | 30 | func (t *T) Get(f Family, port int) (e *Entry) { 31 | v := t[int(f*nports)+port].Load() 32 | if v == nil || v.(*Entry) == nil { 33 | return (*Entry)(nil) 34 | } 35 | e = v.(*Entry) 36 | return 37 | } 38 | 39 | func (t *T) Set(f Family, port int, e *Entry, init func() (net.Conn, error)) { 40 | e.mu.Lock() 41 | t[int(f*nports)+port].Store(e) 42 | go func() { 43 | defer e.mu.Unlock() 44 | if c, err := init(); err == nil { 45 | e.conn = c 46 | } else { 47 | t.Del(f, port) 48 | } 49 | }() 50 | } 51 | 52 | func (t *T) Del(f Family, port int) { 53 | // remove reference 54 | // it will get garbage collected after it goes out of scope 55 | t[int(f*nports)+port].Store((*Entry)(nil)) 56 | } 57 | 58 | func (e *Entry) Conn() net.Conn { 59 | // wait until init unlocks the connection 60 | e.mu.Lock() 61 | e.mu.Unlock() 62 | return e.conn 63 | } 64 | -------------------------------------------------------------------------------- /wireleap_tun/tun/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tun 4 | 5 | import "log" 6 | 7 | type Reader struct{ queue chan []byte } 8 | 9 | func NewReader(tunif *T) *Reader { 10 | t := &Reader{queue: make(chan []byte, 1024)} 11 | buf := make([]byte, 65535) // max IP packet size 12 | go func() { 13 | for { 14 | n, err := tunif.Read(buf) 15 | if err != nil { 16 | log.Println("error reading packet data:", err) 17 | continue 18 | } 19 | // raw packet data copied here 20 | data := make([]byte, n) 21 | copy(data, buf[:n]) 22 | t.queue <- data 23 | } 24 | }() 25 | return t 26 | } 27 | 28 | func (t *Reader) Recv() []byte { return <-t.queue } 29 | -------------------------------------------------------------------------------- /wireleap_tun/tun/tun.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tun 4 | 5 | import ( 6 | "net" 7 | 8 | "github.com/songgao/water" 9 | ) 10 | 11 | // T is the type of a tun device. 12 | type T struct { 13 | *water.Interface 14 | NetIf *net.Interface 15 | } 16 | 17 | // New() creates a new tun device. 18 | func New() (s *T, err error) { 19 | var ifc *water.Interface 20 | ifc, err = water.New(water.Config{DeviceType: water.TUN}) 21 | if err != nil { 22 | return 23 | } 24 | var nif *net.Interface 25 | nif, err = net.InterfaceByName(ifc.Name()) 26 | if err != nil { 27 | return 28 | } 29 | s = &T{ 30 | Interface: ifc, 31 | NetIf: nif, 32 | } 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /wireleap_tun/tun/writer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Wireleap 2 | 3 | package tun 4 | 5 | import "log" 6 | 7 | type Writer struct{ queue chan []byte } 8 | 9 | func NewWriter(tunif *T) *Writer { 10 | t := &Writer{queue: make(chan []byte, 1024)} 11 | go func() { 12 | for data := range t.queue { 13 | if _, err := tunif.Write(data); err != nil { 14 | log.Println("error writing packet data:", err) 15 | continue 16 | } 17 | } 18 | }() 19 | return t 20 | } 21 | 22 | func (t *Writer) Send(data []byte) { t.queue <- data } 23 | --------------------------------------------------------------------------------