├── .devcontainer ├── Dockerfile ├── devcontainer.json └── library-scripts │ └── sshd-debian.sh ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── ips-to-proxy.conf ├── src ├── proxy-connect ├── proxy-reset ├── proxy │ ├── Dockerfile │ └── danted.conf └── redsocks.conf ├── ssh-proxy ├── ssh-proxy.cmd └── ssh-proxy.ps1 /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | ARG VARIANT="buster" 7 | FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} 8 | 9 | COPY library-scripts/sshd-debian.sh /tmp/library-scripts/ 10 | RUN export DEBIAN_FRONTEND=noninteractive && apt-get update \ 11 | && bash /tmp/library-scripts/sshd-debian.sh \ 12 | && apt-get install -y redsocks iptables \ 13 | && rm -rf /tmp/library-scripts 14 | 15 | ENTRYPOINT ["/usr/local/share/ssh-init.sh"] 16 | CMD ["sleep", "infinity"] 17 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Codespaces Proxy Example", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { "VARIANT": "buster" } 6 | }, 7 | // Just adding capabilities doesn't work in Codespaces currently 8 | // "runArgs": [ "--cap-add=NET_ADMIN", "--cap-add=NET_RAW" ], 9 | "runArgs": [ "--init", "--privileged" ], 10 | "settings": { 11 | "terminal.integrated.shell.linux": "/bin/bash" 12 | }, 13 | "extensions": [], 14 | "forwardPorts": [2222], 15 | "overrideCommand": false, 16 | "remoteUser": "vscode" 17 | } 18 | -------------------------------------------------------------------------------- /.devcontainer/library-scripts/sshd-debian.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #------------------------------------------------------------------------------------------------------------- 3 | # Copyright (c) Microsoft Corporation. All rights reserved. 4 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 5 | #------------------------------------------------------------------------------------------------------------- 6 | # 7 | # Docs: https://github.com/microsoft/vscode-dev-containers/blob/master/script-library/docs/sshd.md 8 | # Maintainer: The VS Code and Codespaces Teams 9 | # 10 | # Syntax: ./sshd-debian.sh [SSH Port (don't use 22)] [non-root user] [start sshd now flag] [new password for user] 11 | # 12 | # Note: You can change your user's password with "sudo passwd $(whoami)" (or just "passwd" if running as root). 13 | 14 | SSHD_PORT=${1:-"2222"} 15 | USERNAME=${2:-"automatic"} 16 | START_SSHD=${3:-"false"} 17 | NEW_PASSWORD=${4:-"skip"} 18 | 19 | set -e 20 | 21 | if [ "$(id -u)" -ne 0 ]; then 22 | echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' 23 | exit 1 24 | fi 25 | 26 | # SSH uses a login shells, so we need to ensure these get the same initial PATH as non-login shells. 27 | # /etc/profile wipes out the path which is a problem when the PATH was modified using the ENV directive in a Dockerfile. 28 | rm -f /etc/profile.d/00-restore-env.sh 29 | echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh 30 | chmod +x /etc/profile.d/00-restore-env.sh 31 | 32 | # Determine the appropriate non-root user 33 | if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then 34 | USERNAME="" 35 | POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") 36 | for CURRENT_USER in ${POSSIBLE_USERS[@]}; do 37 | if id -u ${CURRENT_USER} > /dev/null 2>&1; then 38 | USERNAME=${CURRENT_USER} 39 | break 40 | fi 41 | done 42 | if [ "${USERNAME}" = "" ]; then 43 | USERNAME=root 44 | fi 45 | elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then 46 | USERNAME=root 47 | fi 48 | 49 | # Function to run apt-get if needed 50 | apt-get-update-if-needed() 51 | { 52 | if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then 53 | echo "Running apt-get update..." 54 | apt-get update 55 | else 56 | echo "Skipping apt-get update." 57 | fi 58 | } 59 | 60 | # Ensure apt is in non-interactive to avoid prompts 61 | export DEBIAN_FRONTEND=noninteractive 62 | 63 | # Install openssh-server openssh-client 64 | if ! dpkg -s openssh-server openssh-client > /dev/null 2>&1; then 65 | apt-get-update-if-needed 66 | apt-get -y install --no-install-recommends openssh-server openssh-client 67 | fi 68 | 69 | # Generate password if new password set to the word "random" 70 | if [ "${NEW_PASSWORD}" = "random" ]; then 71 | NEW_PASSWORD="$(openssl rand -hex 16)" 72 | EMIT_PASSWORD="true" 73 | fi 74 | 75 | # If new password not set to skip, set it for the specified user 76 | if [ "${NEW_PASSWORD}" != "skip" ]; then 77 | echo "${USERNAME}:${NEW_PASSWORD}" | chpasswd 78 | if [ "${NEW_PASSWORD}" != "root" ]; then 79 | usermod -aG ssh ${USERNAME} 80 | fi 81 | fi 82 | 83 | # Setup sshd 84 | mkdir -p /var/run/sshd 85 | sed -i 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' /etc/pam.d/sshd 86 | sed -i 's/#*PermitRootLogin prohibit-password/PermitRootLogin yes/g' /etc/ssh/sshd_config 87 | sed -i -E "s/#*\s*Port\s+.+/Port ${SSHD_PORT}/g" /etc/ssh/sshd_config 88 | 89 | # Write out a script that can be referenced as an ENTRYPOINT to auto-start sshd 90 | tee /usr/local/share/ssh-init.sh > /dev/null \ 91 | << EOF 92 | #!/usr/bin/env bash 93 | set -e 94 | 95 | if [ "\$(id -u)" -ne 0 ]; then 96 | sudo /etc/init.d/ssh start > /tmp/sshd.log 2>&1 97 | else 98 | /etc/init.d/ssh start > /tmp/sshd.log 2>&1 99 | fi 100 | 101 | set +e 102 | exec "\$@" 103 | EOF 104 | chmod +x /usr/local/share/ssh-init.sh 105 | chown ${USERNAME}:ssh /usr/local/share/ssh-init.sh 106 | 107 | # If we should start sshd now, do so 108 | if [ "${START_SSHD}" = "true" ]; then 109 | /usr/local/share/ssh-init.sh 110 | fi 111 | 112 | # Write out result 113 | echo -e "Done!\n\n- Port: ${SSHD_PORT}\n- User: ${USERNAME}" 114 | if [ "${EMIT_PASSWORD}" = "true" ]; then 115 | echo "- Password: ${NEW_PASSWORD}" 116 | fi 117 | echo -e "\nForward port ${SSHD_PORT} to your local machine and run:\n\n ssh -p ${SSHD_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${USERNAME}@localhost\n" 118 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.{cmd,[cC][mM][dD]} text eol=crlf 3 | *.{bat,[bB][aA][tT]} text eol=crlf 4 | *.gif binary 5 | *.jpeg binary 6 | *.png binary 7 | *.gz binary 8 | *.jar binary 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/.build 3 | *.DS_Store 4 | Thumbs.db 5 | **/out 6 | *.out 7 | *.class 8 | *.tgz 9 | **/.ionide 10 | 11 | containers-readmes 12 | 13 | ## Ignore Visual Studio temporary files, build results, and 14 | ## files generated by popular Visual Studio add-ons. 15 | ## 16 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 17 | 18 | # User-specific files 19 | *.suo 20 | *.user 21 | *.userosscache 22 | *.sln.docstates 23 | 24 | # User-specific files (MonoDevelop/Xamarin Studio) 25 | *.userprefs 26 | 27 | # Build results 28 | [Dd]ebug/ 29 | [Dd]ebugPublic/ 30 | [Rr]elease/ 31 | [Rr]eleases/ 32 | x64/ 33 | x86/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Ll]og/ 38 | 39 | # Visual Studio 2015/2017 cache/options directory 40 | .vs/ 41 | # Uncomment if you have tasks that create the project's static files in wwwroot 42 | #wwwroot/ 43 | 44 | # Visual Studio 2017 auto generated files 45 | Generated\ Files/ 46 | 47 | # MSTest test Results 48 | [Tt]est[Rr]esult*/ 49 | [Bb]uild[Ll]og.* 50 | 51 | # NUNIT 52 | *.VisualState.xml 53 | TestResult.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | **/Properties/launchSettings.json 68 | 69 | # Bazel 70 | bazel-* 71 | 72 | # StyleCop 73 | StyleCopReport.xml 74 | 75 | # Files built by Visual Studio 76 | *_i.c 77 | *_p.c 78 | *_i.h 79 | *.ilk 80 | *.meta 81 | *.obj 82 | *.iobj 83 | *.pch 84 | *.pdb 85 | *.ipdb 86 | *.pgc 87 | *.pgd 88 | *.rsp 89 | *.sbr 90 | *.tlb 91 | *.tli 92 | *.tlh 93 | *.tmp 94 | *.tmp_proj 95 | *.log 96 | *.vspscc 97 | *.vssscc 98 | .builds 99 | *.pidb 100 | *.svclog 101 | *.scc 102 | 103 | # Chutzpah Test files 104 | _Chutzpah* 105 | 106 | # Visual C++ cache files 107 | ipch/ 108 | *.aps 109 | *.ncb 110 | *.opendb 111 | *.opensdf 112 | *.sdf 113 | *.cachefile 114 | *.VC.db 115 | *.VC.VC.opendb 116 | 117 | # Visual Studio profiler 118 | *.psess 119 | *.vsp 120 | *.vspx 121 | *.sap 122 | 123 | # Visual Studio Trace Files 124 | *.e2e 125 | 126 | # TFS 2012 Local Workspace 127 | $tf/ 128 | 129 | # Guidance Automation Toolkit 130 | *.gpState 131 | 132 | # ReSharper is a .NET coding add-in 133 | _ReSharper*/ 134 | *.[Rr]e[Ss]harper 135 | *.DotSettings.user 136 | 137 | # JustCode is a .NET coding add-in 138 | .JustCode 139 | 140 | # TeamCity is a build add-in 141 | _TeamCity* 142 | 143 | # DotCover is a Code Coverage Tool 144 | *.dotCover 145 | 146 | # AxoCover is a Code Coverage Tool 147 | .axoCover/* 148 | !.axoCover/settings.json 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # The packages folder can be ignored because of Package Restore 198 | **/[Pp]ackages/* 199 | # except build/, which is used as an MSBuild target. 200 | !**/[Pp]ackages/build/ 201 | # Uncomment if necessary however generally it will be regenerated when needed 202 | #!**/[Pp]ackages/repositories.config 203 | # NuGet v3's project.json files produces more ignorable files 204 | *.nuget.props 205 | *.nuget.targets 206 | 207 | # Microsoft Azure Build Output 208 | csx/ 209 | *.build.csdef 210 | 211 | # Microsoft Azure Emulator 212 | ecf/ 213 | rcf/ 214 | 215 | # Windows Store app package directories and files 216 | AppPackages/ 217 | BundleArtifacts/ 218 | Package.StoreAssociation.xml 219 | _pkginfo.txt 220 | *.appx 221 | 222 | # Visual Studio cache files 223 | # files ending in .cache can be ignored 224 | *.[Cc]ache 225 | # but keep track of directories ending in .cache 226 | !*.[Cc]ache/ 227 | 228 | # Others 229 | ClientBin/ 230 | ~$* 231 | *~ 232 | *.dbmdl 233 | *.dbproj.schemaview 234 | *.jfm 235 | *.pfx 236 | *.publishsettings 237 | orleans.codegen.cs 238 | 239 | # Including strong name files can present a security risk 240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 241 | #*.snk 242 | 243 | # Since there are multiple workflows, uncomment next line to ignore bower_components 244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 245 | #bower_components/ 246 | 247 | # RIA/Silverlight projects 248 | Generated_Code/ 249 | 250 | # Backup & report files from converting an old project file 251 | # to a newer Visual Studio version. Backup files are not needed, 252 | # because we have git ;-) 253 | _UpgradeReport_Files/ 254 | Backup*/ 255 | UpgradeLog*.XML 256 | UpgradeLog*.htm 257 | ServiceFabricBackup/ 258 | *.rptproj.bak 259 | 260 | # SQL Server files 261 | *.mdf 262 | *.ldf 263 | *.ndf 264 | 265 | # Business Intelligence projects 266 | *.rdl.data 267 | *.bim.layout 268 | *.bim_*.settings 269 | *.rptproj.rsuser 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # JetBrains Rider 306 | .idea/ 307 | *.sln.iml 308 | 309 | # CodeRush 310 | .cr/ 311 | 312 | # Python Tools for Visual Studio (PTVS) 313 | __pycache__/ 314 | *.pyc 315 | 316 | # Cake - Uncomment if you are using it 317 | # tools/** 318 | # !tools/packages.config 319 | 320 | # Tabs Studio 321 | *.tss 322 | 323 | # Telerik's JustMock configuration file 324 | *.jmconfig 325 | 326 | # BizTalk build output 327 | *.btp.cs 328 | *.btm.cs 329 | *.odx.cs 330 | *.xsd.cs 331 | 332 | # OpenCover UI analysis results 333 | OpenCover/ 334 | 335 | # Azure Stream Analytics local run output 336 | ASALocalRun/ 337 | 338 | # MSBuild Binary and Structured Log 339 | *.binlog 340 | 341 | # NVidia Nsight GPU debugger configuration file 342 | *.nvuser 343 | 344 | # MFractors (Xamarin productivity tool) working folder 345 | .mfractor/ 346 | 347 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Codespaces SOCKS proxy demo 2 | 3 | This demonstrates how to use SSH and a local Docker container with a SOCKS proxy in it to allow a Codespace to access IPs on your local network. 4 | 5 | > **Note:** This is a proof of concept rather than an offical implementation. Any offical implementation here would be more transparent than this illustrates. 6 | > 7 | > In addition, DNS or UDP forwarding are not wired in yet, though this is possible. 8 | 9 | ## Usage 10 | 11 | ### Codespace setup 12 | 1. Create a codespace from this repository 13 | 2. Connect to the codespace from VS Code client (not the web) 14 | 4. Open a terminal and run `sudo passwd $(whoami)` and set a SSH password 15 | 5. Take note of the username in the terminal - this image uses `vscode`, but it could be `codespaces` or `node` if you pick a different one. 16 | 6. Click on the "Ports" tab to see what port SSH ended up on locally - by default this would be port `2222`. 17 | 18 | ### Local setup 19 | 1. Install Docker Desktop (macOS/Windows) or Docker CE (on Linux) locally 20 | 2. Clone this repository locally 21 | 3. **[Recommended]** If you only want to proxy certain IPs to your local network, update `ips-to-proxy.conf` with a list (e.g. `10.130.128.0/8`). The default will proxy **everything**... including calls to github. 22 | 23 | ### Connecting 24 | 1. Connect to the codespace in VS Code client if you are not already 25 | 2. Open a *local* terminal and go to where you have cloned this repository 26 | 3. Run `./ssh-proxy vscode 2222` replacing `vscode` with the username for the image and `2222` with the local SSH port 27 | 4. When prompted, enter the password you configured 28 | 29 | At this point, you can go into the terminal inside the codespace and hit local IPs you've configured. 30 | 31 | ### Troubleshooting 32 | If something went wrong and the codespace stops working, just stop it and start it again. That will wipe out all config. While connected to the codespace you can also run `sudo proxy-reset` from the integrated terminal to reset. 33 | 34 | ## How it works 35 | 36 | Here's what happens: 37 | 38 | 1. Locally, a SOCKS5 capabile proxy is spun up in a Docker container (see the [Dockerfile here](https://github.com/Chuxel/codespaces-proxy/blob/master/src/proxy/Dockerfile)). Technically any SOCKS capable proxy could be used - this is just easy to get up and running. By default, the container makes the proxy available on port 4040. 39 | 40 | 2. In the codespace, a SSH server is started when the container starts. 41 | 42 | 3. When you connect to the codespace from VS Code, the SSH port (running on 2222) is forwarded to your local machine (via forwardPorts in [devcontainer.json](https://github.com/Chuxel/codespaces-proxy/blob/master/.devcontainer/devcontainer.json)). 43 | 44 | 4. Next, the local proxy's port (4040 by default) is reverse forwarded into the codespace using SSH (via `ssh -R`). This makes the SOCKS proxy available inside the container on a port (1080 by default). 45 | 46 | 5. Finally, a script is run via SSH to configure the codespace to use the forwarded SOCKS proxy. It: 47 | 1. Installs the `redsocks` and `iptables` packages if missing - The `redsocks` package will allow the script to wire the proxy directly into the network stack via `iptables`. 48 | 2. Uses `iptables` to redirect certain IP destinations to `redsocks`. 49 | 3. Tweaks a `redsocks` configuration file so it connects to the port that SSH forwaded the local SOCKS proxy was forwaded to (1080 by default). 50 | 2. Starts the `redsocks` daemon so it can start processing. 51 | 52 | # License 53 | Copyright (c) Microsoft Corporation. All rights reserved.
54 | Licensed under the MIT License. See [LICENSE](./LICENSE). 55 | -------------------------------------------------------------------------------- /ips-to-proxy.conf: -------------------------------------------------------------------------------- 1 | all -------------------------------------------------------------------------------- /src/proxy-connect: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | PROXY_HOST=${1:-127.0.0.1} 4 | PROXY_PORT=${2:-1080} 5 | shift 2 6 | IPS_TO_PROXY=("${@:-all}") 7 | 8 | IPTABLES_TCP_PORT=17767 9 | IPTABLES_UDP_PORT=17668 10 | IPTABLES_DNS_PORT=17669 11 | 12 | if [ "$(id -u)" != "0" ]; then 13 | echo -e "\nScript must run as root!\n" 14 | exit 1 15 | fi 16 | 17 | # Check for dependencies and install if missing - TODO: support Alpine, Redhat containers 18 | if ! dpkg -s iptables redsocks > /dev/null 2>&1; then 19 | echo 'Installing needed depdendencies...' 20 | export DEBIAN_FRONTEND=noninteractive 21 | apt-get update 22 | apt-get install -y iptables redsocks 23 | else 24 | # Otherwise stop redsocks and remove any existing rules 25 | /etc/init.d/redsocks stop > /dev/null 2>&1 26 | if ( iptables -t nat --list | grep REDSOCKS ) > /dev/null 2>&1; then 27 | echo "Removing existing redsocks routing rules..." 28 | iptables -t nat -F > /dev/null 2>&1 29 | iptables -t nat -X REDSOCKS > /dev/null 2>&1 30 | fi 31 | fi 32 | 33 | echo -e "\nSetting SOCKS proxy to ${PROXY_HOST}:${PROXY_PORT}..." 34 | iptables -t nat -N REDSOCKS 35 | iptables -t nat -A REDSOCKS -d "${PROXY_HOST}" -j RETURN 36 | 37 | # In "all" mode, everything but key things should be re-routed through the proxy 38 | if [ "${IPS_TO_PROXY[0]}" = "all" ]; then 39 | # If running in host mode, check the docker0 adapter 40 | DOCKER_IP_RANGE=$(ip address | awk '/docker[0-9]/,/inet/brd {print $2}' | awk 'NR==2') 41 | if [ "${DOCKER_IP_RANGE}" = "" ]; then 42 | # Look at the 172.17 range if no docker adapter exists 43 | DOCKER_IP_RANGE=$(ip address | awk '/inet 172.17.0./ {print $2}') 44 | fi 45 | if [ "${DDOCKER_IP_RANGE}" != "" ]; then 46 | iptables -t nat -A REDSOCKS -d ${DOCKER_IP_RANGE} -j RETURN 47 | fi 48 | if ping -c 1 host.docker.internal > /dev/null 2>&1; then 49 | iptables -t nat -A REDSOCKS -d host.docker.internal -j RETURN 50 | fi 51 | 52 | # Make sure the local IP range is skips REDSOCKS proxy 53 | ETH0_IP_RANGE=$(ip address | awk '/eth0/,/inet/brd {print $2}' | awk 'NR==2') 54 | iptables -t nat -A REDSOCKS -d "${ETH0_IP_RANGE}" -j RETURN 55 | 56 | # [Optional] Skip internal network ranges 57 | #iptables -t nat -A REDSOCKS -d 10.0.0.0/8 -j RETURN 58 | #iptables -t nat -A REDSOCKS -d 192.168.0.0/16 -j RETURN 59 | #iptables -t nat -A REDSOCKS -d 172.16.0.0/12 -j RETURN 60 | 61 | # See here for info about special IP ranges that are set to skip the proxy: https://tools.ietf.org/html/rfc5735 62 | iptables -t nat -A REDSOCKS -d 0.0.0.0/8 -j RETURN 63 | iptables -t nat -A REDSOCKS -d 127.0.0.0/8 -j RETURN 64 | iptables -t nat -A REDSOCKS -d 169.254.0.0/16 -j RETURN 65 | iptables -t nat -A REDSOCKS -d 224.0.0.0/4 -j RETURN 66 | iptables -t nat -A REDSOCKS -d 240.0.0.0/4 -j RETURN 67 | iptables -t nat -A REDSOCKS -p tcp -j REDIRECT --to-ports ${IPTABLES_TCP_PORT} 68 | #iptables -t nat -A REDSOCKS -p udp -j REDIRECT --to-ports ${IPTABLES_UDP_PORT} 69 | else 70 | # Otherwise just forward the specific range specified 71 | for range in ${IPS_TO_PROXY[@]}; do 72 | iptables -t nat -A REDSOCKS -p tcp -d $range -j REDIRECT --to-ports ${IPTABLES_TCP_PORT} 73 | #iptables -t nat -A REDSOCKS -p udp -d $range -j REDIRECT --to-ports ${IPTABLES_UDP_PORT} 74 | done 75 | fi 76 | 77 | # Setup proxy 78 | iptables -t nat -A PREROUTING -p tcp --dport 1:65535 -j REDSOCKS 79 | iptables -t nat -A OUTPUT -p tcp --dport 1:65535 -j REDSOCKS 80 | #iptables -t nat -A PREROUTING -p udp --dport 1:65535 -j REDSOCKS 81 | #iptables -t nat -A OUTPUT -p udp --dport 1:65535 -j REDSOCKS 82 | 83 | cp -f /etc/redsocks.conf.orig /etc/redsocks.conf 84 | sed -i "s/host.docker.internal/${PROXY_HOST}/g" /etc/redsocks.conf 85 | sed -i "s/1080/${PROXY_PORT}/g" /etc/redsocks.conf 86 | sed -i "s/17667/${IPTABLES_TCP_PORT}/g" /etc/redsocks.conf 87 | sed -i "s/17668/${IPTABLES_UDP_PORT}/g" /etc/redsocks.conf 88 | sed -i "s/17669/${IPTABLES_DNS_PORT}/g" /etc/redsocks.conf 89 | /etc/init.d/redsocks start 90 | -------------------------------------------------------------------------------- /src/proxy-reset: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | if [ "$(id -u)" != "0" ]; then 4 | echo -e "\nScript must run as root!\n" 5 | exit 1 6 | fi 7 | 8 | echo "Removing existing rules if present..." 9 | /etc/init.d/redsocks stop > /dev/null 2>&1 10 | if ( iptables -t nat --list | grep REDSOCKS ) > /dev/null 2>&1; then 11 | iptables -t nat -F > /dev/null 2>&1 12 | iptables -t nat -X REDSOCKS > /dev/null 2>&1 13 | fi -------------------------------------------------------------------------------- /src/proxy/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | FROM debian:stable-slim 7 | 8 | ARG DANTED_PORT=1080 9 | COPY ./danted.conf /tmp/danted.conf 10 | RUN export DEBIAN_FRONTEND=noninteractive && apt-get update \ 11 | && apt-get install -y dante-server \ 12 | && sed -i "s/1080/${DANTED_PORT}/g" /tmp/danted.conf \ 13 | && cp -f /tmp/danted.conf /etc/danted.conf \ 14 | && echo '#!/bin/sh\n/etc/init.d/danted start\nexec "$@"' > /usr/local/bin/danted-init \ 15 | && chmod +x /usr/local/bin/danted-init 16 | 17 | ENTRYPOINT [ "/usr/local/bin/danted-init" ] 18 | CMD [ "/bin/sh", "-c", "while sleep 1000; do :; done" ] -------------------------------------------------------------------------------- /src/proxy/danted.conf: -------------------------------------------------------------------------------- 1 | internal: eth0 port = 1080 2 | internal: 127.0.0.1 port = 1080 3 | 4 | external: eth0 5 | 6 | logoutput: stderr 7 | 8 | socksmethod: username none 9 | 10 | user.notprivileged: nobody 11 | 12 | client pass { 13 | from: 0.0.0.0/0 port 1-65535 to: 0.0.0.0/0 14 | } 15 | 16 | socks pass { 17 | from: 0.0.0.0/0 to: 0.0.0.0/0 18 | protocol: tcp udp 19 | } 20 | -------------------------------------------------------------------------------- /src/redsocks.conf: -------------------------------------------------------------------------------- 1 | base { 2 | log_debug = off; 3 | log_info = on; 4 | log = "file:/var/log/redsocks"; 5 | daemon = on; 6 | redirector = iptables; 7 | } 8 | 9 | redsocks { 10 | local_ip = 127.0.0.1; 11 | local_port = 17667; 12 | ip = "host.docker.internal"; 13 | port = 1080; 14 | type = socks5; 15 | } 16 | 17 | redudp { 18 | local_ip = 127.0.0.1; 19 | local_port = 17668; 20 | ip = "host.docker.internal"; 21 | port = 1080; 22 | } 23 | -------------------------------------------------------------------------------- /ssh-proxy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | cd $(dirname $0) 4 | 5 | REMOTE_USERNAME=${1:-vscode} 6 | SSH_PORT=${2:-2222} 7 | SSH_HOST=${3:-127.0.0.1} 8 | LOCAL_PROXY_PORT=${4:-4040} 9 | REMOTE_PROXY_PORT=${5:-1080} 10 | 11 | if [ -f "ips-to-proxy.conf" ]; then 12 | IP_RANGES_TO_PROXY="$(cat ips-to-proxy.conf | tr '\n' ' ')" 13 | else 14 | IP_RANGES_TO_PROXY="all" 15 | fi 16 | 17 | if [ "$(docker ps -q -f 'name=codespaces-local-proxy-server')" != "" ]; then 18 | docker rm --force codespaces-local-proxy-server > /dev/null 19 | fi 20 | 21 | cd src/proxy 22 | echo 'Building proxy image...' 23 | docker build --build-arg "DANTED_PORT=${LOCAL_PROXY_PORT}" -t codespaces-local-proxy-server . 24 | echo 'Starting proxy...' 25 | docker run -d --rm --name codespaces-local-proxy-server -p 127.0.0.1:${LOCAL_PROXY_PORT}:${LOCAL_PROXY_PORT} -p 127.0.0.1:${LOCAL_PROXY_PORT}:${LOCAL_PROXY_PORT}/udp codespaces-local-proxy-server 26 | cd .. 27 | 28 | echo 'Wiring up codespace...' 29 | ssh -tt -R 127.0.0.1:${REMOTE_PROXY_PORT}:127.0.0.1:${LOCAL_PROXY_PORT} -p ${SSH_PORT} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${REMOTE_USERNAME}@${SSH_HOST} bash -s << EOF 30 | cat << 'SCRIPTEOF' | sudo tee /etc/redsocks.conf.orig > /dev/null 31 | $(cat redsocks.conf) 32 | SCRIPTEOF 33 | cat << 'SCRIPTEOF' | sudo tee /usr/local/bin/proxy-connect > /dev/null 34 | $(cat proxy-connect) 35 | SCRIPTEOF 36 | cat << 'SCRIPTEOF' | sudo tee /usr/local/bin/proxy-reset > /dev/null 37 | $(cat proxy-reset) 38 | SCRIPTEOF 39 | clear && sudo proxy-connect 127.0.0.1 ${REMOTE_PROXY_PORT} ${IP_RANGES_TO_PROXY} && echo -e "\nPress Ctrl+C to disconnect!" && sleep infinity 40 | EOF 41 | 42 | echo -e '\nShutting down local proxy...' 43 | docker stop codespaces-local-proxy-server > /dev/null 44 | -------------------------------------------------------------------------------- /ssh-proxy.cmd: -------------------------------------------------------------------------------- 1 | @powershell -ExecutionPolicy remotesigned "%~dp0\ssh-proxy.ps1" %* -------------------------------------------------------------------------------- /ssh-proxy.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | $remoteUserName='vscode', 3 | $sshPort='2222', 4 | $sshHost='127.0.0.1', 5 | $localProxyPort='4040', 6 | $remoteProxyPort='1080' 7 | ) 8 | 9 | $ErrorActionPreference = "Stop" 10 | 11 | # Load IP range config file 12 | $ipRangesToProxy="all" 13 | if (Test-Path -Path "$PSScriptRoot\ips-to-proxy.conf") { 14 | $ipRangesToProxy = [IO.File]::ReadAllText("$PSScriptRoot\ips-to-proxy.conf").replace("`n", " ") 15 | } 16 | 17 | # Delete existing proxy container if it is running 18 | $dockerPsOutput=(docker ps -a -q -f 'name=codespaces-local-proxy-server') -join '' 19 | if ($dockerPsOutput -ne "" ) { 20 | docker rm --force codespaces-local-proxy-server > $null 21 | } 22 | 23 | # Start proxy 24 | Write-Host "Building proxy image..." 25 | Push-Location "$PSScriptRoot\src\proxy" 26 | docker build --build-arg "DANTED_PORT=${localProxyPort}" -t codespaces-local-proxy-server . 27 | Pop-Location 28 | Write-Host "Starting proxy..." 29 | docker run -d --rm --name codespaces-local-proxy-server -p "127.0.0.1:${localProxyPort}:${localProxyPort}" -p "127.0.0.1:${localProxyPort}:${localProxyPort}/udp" codespaces-local-proxy-server 30 | 31 | # Load files to send to remote host via SSH 32 | $redsocksConf=[IO.File]::ReadAllText("$PSScriptRoot\src\redsocks.conf") 33 | $proxyConnectScript=[IO.File]::ReadAllText("$PSScriptRoot\src\proxy-connect") 34 | $proxyResetScript=[IO.File]::ReadAllText("$PSScriptRoot\src\proxy-reset") 35 | 36 | # Script to run on remote host via SSH 37 | $remoteScript = " 38 | cat << 'SCRIPTEOF' | sudo tee /etc/redsocks.conf.orig > /dev/null 39 | $redsocksConf 40 | SCRIPTEOF 41 | cat << 'SCRIPTEOF' | sudo tee /usr/local/bin/proxy-connect > /dev/null 42 | $proxyConnectScript 43 | SCRIPTEOF 44 | cat << 'SCRIPTEOF' | sudo tee /usr/local/bin/proxy-reset > /dev/null 45 | $proxyResetScript 46 | SCRIPTEOF 47 | clear && sudo proxy-connect 127.0.0.1 $remoteProxyPort $ipRangesToProxy && echo -e '\nPress Ctrl+C to disconnect!' && sleep infinity 48 | " 49 | 50 | # Wire up proxy and run script 51 | Write-Host "Wiring up codespace..." 52 | $remoteScript -replace '^r^n', '^n' | ssh -tt -R "127.0.0.1:${remoteProxyPort}:127.0.0.1:${localProxyPort}" -p ${sshPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${remoteUserName}@${sshHost} bash -s 53 | 54 | # Shut down proxy 55 | Write-Host "`nShutting down local proxy..." 56 | docker stop codespaces-local-proxy-server > $null 57 | --------------------------------------------------------------------------------