├── .gitignore ├── openvpn-up.sh ├── .editorconfig ├── COPYING ├── Dockerfile ├── entrypoint.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /openvpn-up.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | /etc/openvpn/up.sh 3 | echo done > /openvpn-fifo 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | max_line_length = 80 8 | insert_final_newline = true 9 | charset = utf_8 10 | 11 | [*.bat] 12 | end_of_line = crlf 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | COPY openvpn-up.sh /usr/local/bin/ 4 | COPY entrypoint.sh /usr/local/bin/ 5 | 6 | RUN echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ 7 | && apk add --no-cache bash curl wget openvpn openresolv openrc openssl 3proxy \ 8 | && mkdir -p /etc/openvpn \ 9 | && curl -sL -o /etc/openvpn/update-resolv-conf \ 10 | https://github.com/masterkorp/openvpn-update-resolv-conf/raw/master/update-resolv-conf.sh \ 11 | && chmod +x \ 12 | /usr/local/bin/openvpn-up.sh \ 13 | /usr/local/bin/entrypoint.sh \ 14 | /etc/openvpn/update-resolv-conf 15 | 16 | # OpenVPN Options 17 | ENV OPENVPN_CONFIG "" 18 | ENV OPENVPN_UP "" 19 | 20 | # aria2 Options 21 | ENV ARIA2_PORT "" 22 | ENV ARIA2_PASS "" 23 | ENV ARIA2_PATH "." 24 | ENV ARIA2_ARGS "" 25 | ENV ARIA2_UP "" 26 | 27 | # Proxy Options 28 | ENV PROXY_USER "" 29 | ENV PROXY_PASS "" 30 | ENV PROXY_UP "" 31 | 32 | # Proxy Ports Options 33 | ENV SOCKS5_PROXY_PORT "1080" 34 | ENV HTTP_PROXY_PORT "3128" 35 | 36 | ENV DAEMON_MODE "false" 37 | 38 | ENTRYPOINT [ "entrypoint.sh" ] 39 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | function spawn { 4 | if [[ -z ${PIDS+x} ]]; then PIDS=(); fi 5 | "$@" & 6 | PIDS+=($!) 7 | } 8 | 9 | function join { 10 | if [[ ! -z ${PIDS+x} ]]; then 11 | for pid in "${PIDS[@]}"; do 12 | wait "${pid}" 13 | done 14 | fi 15 | } 16 | 17 | function on_kill { 18 | if [[ ! -z ${PIDS+x} ]]; then 19 | for pid in "${PIDS[@]}"; do 20 | kill "${pid}" 2> /dev/null 21 | done 22 | fi 23 | kill "${ENTRYPOINT_PID}" 2> /dev/null 24 | } 25 | 26 | function log { 27 | local LEVEL="$1" 28 | local MSG="$(date '+%D %T') [${LEVEL}] $2" 29 | case "${LEVEL}" in 30 | INFO*) MSG="\x1B[94m${MSG}";; 31 | WARNING*) MSG="\x1B[93m${MSG}";; 32 | ERROR*) MSG="\x1B[91m${MSG}";; 33 | *) 34 | esac 35 | echo -e "${MSG}" 36 | } 37 | 38 | export ENTRYPOINT_PID="${BASHPID}" 39 | 40 | trap "on_kill" EXIT 41 | trap "on_kill" SIGINT 42 | 43 | if [ -z "${OPENVPN_CONFIG}" ]; then 44 | log "ERROR" "\$OPENVPN_CONFIG not set!" 45 | exit 1 46 | fi 47 | 48 | if [ ! -f "${OPENVPN_CONFIG}" ]; then 49 | log "ERROR" "${OPENVPN_CONFIG} is not a valid file!" 50 | exit 1 51 | fi 52 | 53 | export OPENVPN_CONFIG=$(readlink -f "${OPENVPN_CONFIG}") 54 | PROXY_CONFIG="/etc/3proxy.cfg" 55 | PROXY_LOG="/var/log/3proxy.log" 56 | 57 | SUBNET=$(ip -o -f inet addr show dev eth0 | awk '{print $4}') 58 | IPADDR=$(echo "${SUBNET}" | cut -f1 -d'/') 59 | GATEWAY=$(route -n | grep 'UG[ \t]' | awk '{print $2}') 60 | eval $(ipcalc -np "${SUBNET}") 61 | 62 | ip rule add from "${IPADDR}" table 128 63 | ip route add table 128 to "${NETWORK}/${PREFIX}" dev eth0 64 | ip route add table 128 default via "${GATEWAY}" 65 | 66 | SAVED_DIR="${PWD}" 67 | cd $(dirname "${OPENVPN_CONFIG}") 68 | mkfifo /openvpn-fifo 69 | spawn openvpn \ 70 | --script-security 2 \ 71 | --config "${OPENVPN_CONFIG}" \ 72 | --up /usr/local/bin/openvpn-up.sh 73 | cd "${SAVED_DIR}" 74 | log "INFO" "Spawn OpenVPN" 75 | 76 | declare -A PROXY_USERS 77 | 78 | function check_and_add_proxy_user { 79 | local user="${!1}" 80 | local pass="${!2}" 81 | if [[ -z "${user}" ]]; then 82 | return 1 83 | fi 84 | if [[ -z "${pass}" ]]; then 85 | log "ERROR" "empty password for user ${user} is not allowed!" 86 | exit 1 87 | fi 88 | if [[ -n "${PROXY_USERS["${user}"]}" ]]; then 89 | log "WARNING" "duplicated user ${user}, overwriting previous password." 90 | fi 91 | PROXY_USERS["${user}"]="${pass}" 92 | log "INFO" "Add proxy user ${user}" 93 | } 94 | 95 | if [[ -n "${SOCKS5_PROXY_PORT}" || -n "${HTTP_PROXY_PORT}" ]]; then 96 | 97 | # single user short-hand 98 | check_and_add_proxy_user PROXY_USER PROXY_PASS 99 | 100 | # backward compatibility 101 | check_and_add_proxy_user SOCKS5_USER SOCKS5_PASS 102 | 103 | # multi-user support 104 | USER_SEQ="1" 105 | USER_SEQ_END="false" 106 | while [[ "${USER_SEQ_END}" != "true" ]]; do 107 | check_and_add_proxy_user "PROXY_USER_${USER_SEQ}" "PROXY_PASS_${USER_SEQ}" 108 | STATUS=$? 109 | if [[ "${STATUS}" != 0 ]]; then 110 | USER_SEQ_END="true" 111 | fi 112 | USER_SEQ=$(( "${USER_SEQ}" + 1 )) 113 | done 114 | 115 | echo "nscache 65536" > "${PROXY_CONFIG}" 116 | for PROXY_USER in "${!PROXY_USERS[@]}"; do 117 | echo "users \"${PROXY_USER}:$(mycrypt "$(openssl rand -hex 16)" "${PROXY_USERS["${PROXY_USER}"]}")\"" >> "${PROXY_CONFIG}" 118 | done 119 | echo "log \"${PROXY_LOG}\" D" >> "${PROXY_CONFIG}" 120 | echo "logformat \"- +_L%t.%. %N.%p %E %U %C:%c %R:%r %O %I %h %T\"" >> "${PROXY_CONFIG}" 121 | echo "rotate 30" >> "${PROXY_CONFIG}" 122 | echo "external 0.0.0.0" >> "${PROXY_CONFIG}" 123 | echo "internal 0.0.0.0" >> "${PROXY_CONFIG}" 124 | if [[ "${#PROXY_USERS[@]}" -gt 0 ]]; then 125 | echo "auth strong" >> "${PROXY_CONFIG}" 126 | fi 127 | echo "flush" >> "${PROXY_CONFIG}" 128 | for PROXY_USER in "${!PROXY_USERS[@]}"; do 129 | echo "allow \"${PROXY_USER}\"" >> "${PROXY_CONFIG}" 130 | done 131 | echo "maxconn 384" >> "${PROXY_CONFIG}" 132 | if [[ -n "${SOCKS5_PROXY_PORT}" ]]; then 133 | echo "socks -p${SOCKS5_PROXY_PORT}" >> "${PROXY_CONFIG}" 134 | fi 135 | if [[ -n "${HTTP_PROXY_PORT}" ]]; then 136 | echo "proxy -p${HTTP_PROXY_PORT}" >> "${PROXY_CONFIG}" 137 | fi 138 | 139 | log "INFO" "Write 3proxy config" 140 | 141 | spawn 3proxy "${PROXY_CONFIG}" 142 | log "INFO" "Spawn 3proxy" 143 | 144 | PROXY_ENABLED="true" 145 | fi 146 | 147 | if [[ -n "${ARIA2_PORT}" ]]; then 148 | cmd=(aria2c --enable-rpc --disable-ipv6 --rpc-listen-all --rpc-listen-port="${ARIA2_PORT}") 149 | if [[ -n "${ARIA2_PASS}" ]]; then 150 | cmd+=(--rpc-secret "${ARIA2_PASS}") 151 | fi 152 | if [[ -n "${ARIA2_PATH}" ]]; then 153 | cmd+=(--dir "${ARIA2_PATH}") 154 | fi 155 | if [[ -n "${ARIA2_ARGS}" ]]; then 156 | eval cmd\+\=\( ${ARIA2_ARGS} \) 157 | fi 158 | spawn "${cmd[@]}" 159 | log "INFO" "Spawn aria2c" 160 | ARIA2_ENABLED="true" 161 | fi 162 | 163 | cat /openvpn-fifo > /dev/null 164 | rm -f /openvpn-fifo 165 | log "INFO" "OpenVPN become stable" 166 | 167 | if [[ "${ARIA2_ENABLED}" == "true" && -n "${ARIA2_UP}" ]]; then 168 | spawn "${ARIA2_UP}" 169 | log "INFO" "Spawn aria2 up script: ${ARIA2_UP}" 170 | fi 171 | 172 | if [[ "${PROXY_ENABLED}" == "true" && -n "${PROXY_UP}" ]]; then 173 | spawn "${PROXY_UP}" 174 | log "INFO" "Spawn proxy up script: ${PROXY_UP}" 175 | fi 176 | 177 | if [[ -n "${OPENVPN_UP}" ]]; then 178 | spawn "${OPENVPN_UP}" 179 | log "INFO" "Spawn OpenVPN up script: ${SS_UP}" 180 | fi 181 | 182 | if [[ $# -gt 0 ]]; then 183 | log "INFO" "Execute command line: $@" 184 | "$@" 185 | fi 186 | 187 | if [[ $# -eq 0 || "${DAEMON_MODE}" == true ]]; then 188 | join 189 | fi 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenVPN to SOCKS5/HTTP Proxy Docker Image 2 | 3 | Convers OpenVPN connection to SOCKS5/HTTP proxy in Docker. This allows you to have multiple proxies on different ports connecting to different OpenVPN upstreams. 4 | 5 | Supports latest Docker for both Windows, Linux, and MacOS. 6 | 7 | ## Related Projects 8 | 9 | - [OpenVPN](https://hub.docker.com/r/curve25519xsalsa20poly1305/openvpn/) ([GitHub](https://github.com/curve25519xsalsa20poly1305/docker-openvpn)) 10 | - [WireGuard](https://hub.docker.com/r/curve25519xsalsa20poly1305/wireguard/) ([GitHub](https://github.com/curve25519xsalsa20poly1305/docker-wireguard)) 11 | - [Shadowsocks/ShadowsocksR](https://hub.docker.com/r/curve25519xsalsa20poly1305/shadowsocks/) ([GitHub](https://github.com/curve25519xsalsa20poly1305/docker-shadowsocks)) 12 | 13 | ## What it does? 14 | 15 | 1. It reads in an OpenVPN configuration file (`.ovpn`) from a mounted file, specified through `OPENVPN_CONFIG` environment variable. 16 | 2. It starts the OpenVPN client program to establish the VPN connection. 17 | 3. It optionally runs the executable defined by `OPENVPN_UP` when the VPN connection is stable. 18 | 4. It starts [3proxy](https://3proxy.ru/) server and listen on container-scoped port 1080 for SOCKS5 and 3128 for HTTP proxy on default. Proxy authentication can be enabled with `PROXY_USER` and `PROXY_PASS` environment variables. `SOCKS5_PROXY_PORT` and `HTTP_PROXY_PORT` can be used to change the default ports. For multi-user support, use sequence of `PROXY_USER_1`, `PROXY_PASS_1`, `PROXY_USER_2`, `PROXY_PASS_2`, etc. 19 | 5. It optionally runs the executable defined by `PROXY_UP` when the proxy server is ready. 20 | 6. If `ARIA2_PORT` is defined, it starts an aria2 RPC server on the port, and optionally runs the executable defined by `ARIA2_UP`. 21 | 7. It optionally runs the user specified CMD line from `docker run` positional arguments ([see Docker doc](https://docs.docker.com/engine/reference/run/#cmd-default-command-or-options)). The program will use the VPN connection inside the container. 22 | 8. If user has provided CMD line, and `DAEMON_MODE` environment variable is not set to `true`, then after running the CMD line, it will shutdown the OpenVPN client and terminate the container. 23 | 24 | ## How to use? 25 | 26 | Prepare your OpenVPN configuration file with `.ovpn` extension, which you can usually get from your VPN provider's website. 27 | 28 | If you want to specify OpenVPN username and password, you can change the line in your `.ovpn` configuration with `auth-user-pass` to `auth-user-pass secret`, then create a file named `secret` at the same directory as your `.ovpn` configuration file. `secret` file should contain two lines, where first line is your username, and second line is your password. 29 | 30 | Proxy server options are specified through these container environment variables: 31 | 32 | - `SOCKS5_PROXY_PORT` (Default: `"1080"`) - SOCKS5 server listening port 33 | - `HTTP_PROXY_PORT` (Default: `"3128"`) - HTTP proxy server listening port 34 | - `PROXY_USER` (Default: `""`) - Proxy server authentication username 35 | - `PROXY_PASS` (Default: `""`) - Proxy server authentication password 36 | - `PROXY_USER_` (Default: `""`) - The `N`-th username for multi-user proxy authentication. `N` starts from 1. 37 | - `PROXY_PASS_` (Default: `""`) - The `N`-th password for multi-user proxy authentication. `N` starts from 1. 38 | - `PROXY_UP` (Default: `""`) - optional command to be executed when proxy server becomes stable 39 | 40 | Arai2 options are specified through these container environment variables: 41 | 42 | - `ARIA2_PORT` (Default: `""`) - JSON-RPC server listening port 43 | - `ARIA2_PASS` (Default: `""`) - `--rpc-secret` password 44 | - `ARIA2_PATH` (Default: `"."`) - The directory to store the downloaded file 45 | - `ARIA2_ARGS` (Default: `""`) - BASH-style escaped command line to append to the `aria2c` command 46 | - `ARIA2_UP` (Default: `""`) - optional command to be executed when aria2 JSON-RPC server becomes stable 47 | 48 | Other container environment variables: 49 | 50 | - `DAEMON_MODE` (Default: `"false"`) - force enter daemon mode when CMD line is specified 51 | 52 | ### Simple Example 53 | 54 | The following example will run `curl ifconfig.co/json` through VPN configured in `./vpn.ovpn` on host machine. 55 | 56 | ```bash 57 | # Unix 58 | docker run -it --rm --device=/dev/net/tun --cap-add=NET_ADMIN \ 59 | -v "${PWD}":/vpn:ro -e OPENVPN_CONFIG=/vpn/vpn.ovpn \ 60 | curve25519xsalsa20poly1305/openvpn \ 61 | curl ifconfig.co/json 62 | 63 | # Windows 64 | docker run -it --rm --device=/dev/net/tun --cap-add=NET_ADMIN ^ 65 | -v "%CD%":/vpn:ro -e OPENVPN_CONFIG=/vpn/vpn.ovpn ^ 66 | curve25519xsalsa20poly1305/openvpn ^ 67 | curl ifconfig.co/json 68 | ``` 69 | 70 | ### Daemon Mode 71 | 72 | You can leave the VPN connection running in background, and later use `docker exec` to run your program inside the running container without ever closing and repoening your VPN connection multiple times. Just leave out the CMD line when you start the container with `docker run`, it will automatically enter daemon mode. 73 | 74 | ```bash 75 | # Unix 76 | NAME="myvpn" 77 | PORT="7777" 78 | docker run --name "${NAME}" -dit --rm --device=/dev/net/tun --cap-add=NET_ADMIN \ 79 | -v "${PWD}":/vpn:ro -e OPENVPN_CONFIG=/vpn/vpn.ovpn \ 80 | -p "${PORT}":1080 \ 81 | curve25519xsalsa20poly1305/openvpn 82 | 83 | # Windows 84 | SET NAME="myvpn" 85 | SET PORT="7777" 86 | docker run --name "%NAME%" -dit --rm --device=/dev/net/tun --cap-add=NET_ADMIN ^ 87 | -v "%PWD%":/vpn:ro -e OPENVPN_CONFIG=/vpn/vpn.ovpn ^ 88 | -p "%PORT%":1080 ^ 89 | curve25519xsalsa20poly1305/openvpn 90 | ``` 91 | 92 | Then you run commads using `docker exec`: 93 | 94 | ```bash 95 | # Unix 96 | NAME="myvpn" 97 | docker exec -it "${NAME}" curl ifconfig.co/json 98 | 99 | # Windows 100 | SET NAME="myvpn" 101 | docker exec -it "%NAME%" curl ifconfig.co/json 102 | ``` 103 | 104 | Or use the SOCKS5 server available on host machine: 105 | 106 | ```bash 107 | curl ifconfig.co/json -x socks5h://127.0.0.1:7777 108 | ``` 109 | 110 | To stop the daemon, run this: 111 | 112 | ```bash 113 | # Unix 114 | NAME="myvpn" 115 | docker stop "${NAME}" 116 | 117 | # Windows 118 | SET NAME="myvpn" 119 | docker stop "%NAME%" 120 | ``` 121 | 122 | ## Contributing 123 | 124 | Please feel free to contribute to this project. But before you do so, just make 125 | sure you understand the following: 126 | 127 | 1\. Make sure you have access to the official repository of this project where 128 | the maintainer is actively pushing changes. So that all effective changes can go 129 | into the official release pipeline. 130 | 131 | 2\. Make sure your editor has [EditorConfig](https://editorconfig.org/) plugin 132 | installed and enabled. It's used to unify code formatting style. 133 | 134 | 3\. Use [Conventional Commits 1.0.0-beta.2](https://conventionalcommits.org/) to 135 | format Git commit messages. 136 | 137 | 4\. Use [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) 138 | as Git workflow guideline. 139 | 140 | 5\. Use [Semantic Versioning 2.0.0](https://semver.org/) to tag release 141 | versions. 142 | 143 | ## License 144 | 145 | Copyright © 2019 curve25519xsalsa20poly1305 <> 146 | 147 | This work is free. You can redistribute it and/or modify it under the 148 | terms of the Do What The Fuck You Want To Public License, Version 2, 149 | as published by Sam Hocevar. See the COPYING file for more details. 150 | --------------------------------------------------------------------------------