qdm12 / gluetun

VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.
https://hub.docker.com/r/qmcgaw/gluetun
MIT License
7.45k stars 350 forks source link

Feature request: SOCKS5 plaintext proxy in Go #234

Open qdm12 opened 4 years ago

qdm12 commented 4 years ago
  1. What's the feature?

SOCKS5 plaintext proxy in Go

  1. Why do you need this feature?

When you don't want to use a Shadowsocks client and don't care about encryption (i.e. in your LAN) or because your browser is limited to SOCKS5 and doesn't have a Shadowsocks extension. Also for low power devices where encryption can have a performance penalty.

  1. Extra information?

This should be added in Go and should be relatively straight forward to implement.

alpe12 commented 2 years ago

Ouch, 2020. Please. Maybe add Dante support instead?

qdm12 commented 2 years ago

@alpe12 you can plug in any socks server container for now (dante or other server implementing the socks protocol).

Right now a SOCKS5 proxy built-in is not a priority since there is already an http proxy and a shadowsocks server (encrypted socks5)

alpe12 commented 2 years ago

To anyone who might be interested. This is what I'm using now to get socks5:

version: "3"
services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    ports:
      - 8888:8888/tcp # HTTP proxy
      - 1080:1080 #socks5 proxy
    volumes:
      - ./config:/gluetun
    environment:
      - VPNSP=nordvpn
      - VPN_TYPE=openvpn
      - OPENVPN_USER=>USER<
      - OPENVPN_PASSWORD=>PASSWORD<
      - REGION=Brazil
      - OPENVPN_PROTOCOL=udp
      - OPENVPN_FLAGS=--auth-nocache
      - TZ=America/Sao_Paulo
      - HTTPPROXY=on
  socks5:
    image: serjs/go-socks5-proxy
    depends_on:
      - gluetun
    network_mode: "service:gluetun"
x0st commented 6 months ago

If anybody still needs this, I forked gluetun and added support for socks5.

https://github.com/x0st/gluetun

docker run -it --rm --cap-add=NET_ADMIN -e VPN_SERVICE_PROVIDER=... -e OPENVPN_USER=... -e OPENVPN_PASSWORD=... -e SOCKS5=on -e SOCKS5_PASSWORD=123 -p 1080:1080 gluetun

You can pass any username, only password will be checked.

curl -X GET https://github.com -x socks5://user:123@127.0.0.1:1080
irishj commented 6 months ago

What's the image name for this new image with socks5 support ?

I'm using docker compose and the image "qmcgaw/gluetun". I tried with "x0st/gluetun" and docker returns an error.

CEbbinghaus commented 6 months ago

@x0st Well done, Those changes look pretty good. Have you considered opening a PR to merge those changes back to Gluetun?

x0st commented 6 months ago

What's the image name for this new image with socks5 support ?

I'm using docker compose and the image "qmcgaw/gluetun". I tried with "x0st/gluetun" and docker returns an error.

I didn't publish the image. Just clone the repo and build it yourself :)

docker build -t gluetun-socks5 .
docker run ... gluetun-socks5
x0st commented 6 months ago

@x0st Well done, Those changes look pretty good. Have you considered opening a PR to merge those changes back to Gluetun?

The socks5 lib is not mine, I just found it on github. I use it in all my projects, but I am not sure if it's any good. I don't want to merge into open source something I can't guarantee will work well :)

qdm12 commented 5 months ago

I went a bit crazy a few days ago and I have a working socks5 server (no gssapi for now) locally. But it's blocked by various maintenance bits I'm working on (#1742 first, then a 'loops rework'), so probably a few more weeks, but it'll come ultimately. Although really, just plugin another socks5 container through Gluetun and it should do the job just fine I think.

marshalleq commented 2 months ago

The problem is that sabnzbd won't support http proxy according to their dev. They do support socks5. So this means only option is to use the network container bridge which isn't working for me and would be a lot easier just to use socks5. Thought I should just raise that as sabnzbd is a common component.

gitterspec commented 2 months ago

@x0st Thanks for your effort, but getting build error:

internal/socks5/loop.go:6: File is not `gci`-ed with --skip-generated -s standard -s default (gci)
        "github.com/qdm12/gluetun/internal/socks5/lib"
internal/socks5/loop.go:13: File is not `gci`-ed with --skip-generated -s standard -s default (gci)
        "github.com/qdm12/gluetun/internal/models"
cmd/gluetun/main.go:7: File is not `gci`-ed with --skip-generated -s standard -s default (gci)
        "github.com/qdm12/gluetun/internal/socks5"
internal/socks5/lib/request.go:180:1: cognitive complexity 53 of func `(*Request).transformUDP` is high (> 30) (gocognit)
func (r *Request) transformUDP() {
^
internal/socks5/lib/request.go:264:13: appendAssign: append result not assigned to the same slice (gocritic)
                                body := append(exchange.headerData, buf[:ln]...)
                                        ^
internal/socks5/lib/request.go:56:25: commentFormatting: put a space between `//` and comment text (gocritic)
        if !r.tcpGram.viaUDP { //tcp
                               ^
internal/socks5/lib/request.go:69:4: commentFormatting: put a space between `//` and comment text (gocritic)
                        //IPv4, len is 4
                        ^
internal/socks5/lib/request.go:73:4: commentFormatting: put a space between `//` and comment text (gocritic)
                        //IPv6, len is 16
                        ^
internal/socks5/loop.go:9: File is not `goimports`-ed (goimports)
        "time"
cmd/gluetun/main.go:14: File is not `goimports`-ed (goimports)
        _ "time/tzdata"
internal/socks5/lib/request.go:57: line is 137 characters (lll)
                if conn, err := net.DialTimeout("tcp", r.tcpGram.networkString(), time.Second*time.Duration(r.server.writeTimeoutSecond)); err != nil {
cmd/gluetun/main.go:452: line is 136 characters (lll)
        socks5Handler, socks5Ctx, socks5Done := goshutdown.NewGoRoutineHandler("socks5 proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
cmd/gluetun/main.go:457: line is 156 characters (lll)
        shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler("shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
internal/socks5/lib/request.go:154:11: superfluous-else: if block ends with a break statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
                        } else {
                                _ = r.ClientConn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(r.server.writeTimeoutSecond)))
                                if _, err := r.ClientConn.Write(buf[:ln]); err != nil {
                                        break
                                }
                        }
internal/socks5/lib/request.go:168:10: superfluous-else: if block ends with a break statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
                } else {
                        _ = r.RemoteConn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(r.server.writeTimeoutSecond)))
                        if _, err := r.RemoteConn.Write(buf[:ln]); err != nil {
                                break
                        }
                }
internal/socks5/lib/protocol.go:11:2: var-naming: const CmdUdpAssociate should be CmdUDPAssociate (revive)
        CmdUdpAssociate byte = 0x03
        ^
internal/socks5/lib/server.go:10:18: unexported-return: exported func NewServer returns unexported type *lib.server, which can be annoying to use (revive)
func NewServer() *server {
                 ^
internal/socks5/lib/udp_protocol.go:54:10: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
                } else {
                        p.ip = ipAddr.IP
                        p.port = int(binary.BigEndian.Uint16(buf[5+domainLen : 5+domainLen+2]))
                }
internal/socks5/lib/server.go:54:9: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
        } else {
                return nil
        }
internal/socks5/lib/tcp_protocol.go:41:9: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
        } else {
                p.cmd = cmd
                p.atyp = atyp
        }
internal/socks5/lib/tcp_protocol.go:55:10: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)
                } else {
                        p.ip = addr.IP
                }
internal/socks5/lib/tcp_protocol.go:95:9: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
        } else {
                return []byte{Version, MethodNoAuth}, nil
        }
internal/socks5/lib/tcp_protocol.go:142:9: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (revive)
        } else {
                return []byte{0x01, 0x00}, nil
        }
internal/socks5/lib/request.go:214: unnecessary trailing newline (whitespace)

        }()
internal/socks5/lib/request.go:251: unnecessary trailing newline (whitespace)

                        } else {
internal/socks5/lib/request.go:63:4: type assertion must be checked (forcetypeassert)
                        r.RemoteConn = conn.(*net.TCPConn)
                        ^
internal/socks5/lib/request.go:66:3: type assertion must be checked (forcetypeassert)
                bindIP := r.ClientConn.LocalAddr().(*net.TCPAddr).IP
                ^
internal/socks5/lib/request.go:79:50: type assertion must be checked (forcetypeassert)
                binary.BigEndian.PutUint16(portByte[:], uint16(r.ClientConn.LocalAddr().(*net.TCPAddr).Port))
                                                               ^
internal/socks5/lib/server.go:50:10: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"server is not running\")" (goerr113)
                return errors.New("server is not running")
                       ^
internal/socks5/lib/tcp_protocol.go:66:11: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"remote address error\")" (goerr113)
                        return errors.New("remote address error")
                               ^
internal/socks5/lib/tcp_protocol.go:86:39: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported socks version\")" (goerr113)
                return []byte{Version, MethodNone}, errors.New("unsupported socks version")
                                                    ^
internal/socks5/lib/tcp_protocol.go:115:30: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported auth version or username is empty\")" (goerr113)
                return []byte{0x01, 0x01}, errors.New("unsupported auth version or username is empty")
                                           ^
internal/socks5/lib/tcp_protocol.go:131:30: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"password is empty\")" (goerr113)
                return []byte{0x01, 0x01}, errors.New("password is empty")
                                           ^
internal/socks5/lib/tcp_protocol.go:141:30: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"username or password invalid\")" (goerr113)
                return []byte{0x01, 0x01}, errors.New("username or password invalid")
                                           ^
internal/socks5/lib/tcp_protocol.go:159:9: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported socks version\")" (goerr113)
                err = errors.New("unsupported socks version")
                      ^
internal/socks5/lib/tcp_protocol.go:164:9: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported CMD\")" (goerr113)
                err = errors.New("unsupported CMD")
                      ^
internal/socks5/lib/tcp_protocol.go:186:10: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"length of domain is zero\")" (goerr113)
                        err = errors.New("length of domain is zero")
                              ^
internal/socks5/lib/tcp_protocol.go:202:9: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported ATYP\")" (goerr113)
                err = errors.New("unsupported ATYP")
                      ^
internal/socks5/lib/udp_exchange.go:24:15: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"UDPExchange is expired\")" (goerr113)
                return nil, errors.New("UDPExchange is expired")
                            ^
internal/socks5/lib/udp_protocol.go:28:20: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"fail\")" (goerr113)
                return nil, nil, errors.New("fail")
                                 ^
internal/socks5/lib/udp_protocol.go:37:21: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"header is too short for IPv4\")" (goerr113)
                        return nil, nil, errors.New("header is too short for IPv4")
                                         ^
internal/socks5/lib/udp_protocol.go:45:21: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"header is too short for domain\")" (goerr113)
                        return nil, nil, errors.New("header is too short for domain")
                                         ^
internal/socks5/lib/udp_protocol.go:49:21: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"header is too short for domain\")" (goerr113)
                        return nil, nil, errors.New("header is too short for domain")
                                         ^
internal/socks5/lib/udp_protocol.go:53:21: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"can't resolve domain:\" + p.domain)" (goerr113)
                        return nil, nil, errors.New("can't resolve domain:" + p.domain)
                                         ^
internal/socks5/lib/udp_protocol.go:62:21: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"header is too short for IPv6\")" (goerr113)
                        return nil, nil, errors.New("header is too short for IPv6")
                                         ^
internal/socks5/lib/udp_protocol.go:69:20: err113: do not define dynamic errors, use wrapped static errors instead: "errors.New(\"unsupported atyp\")" (goerr113)
                return nil, nil, errors.New("unsupported atyp")
                                 ^
internal/socks5/lib/request.go:67:26: mnd: Magic number: 22, in <argument> detected (gomnd)
                res := make([]byte, 0, 22)
                                       ^
internal/socks5/lib/request.go:106:26: mnd: Magic number: 22, in <argument> detected (gomnd)
                res := make([]byte, 0, 22)
                                       ^
internal/socks5/lib/request.go:149:23: mnd: Magic number: 1024, in <argument> detected (gomnd)
                buf := make([]byte, 1024*8)
                                    ^
internal/socks5/lib/request.go:163:22: mnd: Magic number: 1024, in <argument> detected (gomnd)
        buf := make([]byte, 1024*8)
                            ^
internal/socks5/lib/request.go:202:36: mnd: Magic number: 2, in <argument> detected (gomnd)
                        case <-time.After(time.Second * 2):
                                                        ^
internal/socks5/lib/request.go:217:22: mnd: Magic number: 65535, in <argument> detected (gomnd)
        buf := make([]byte, 65535)
                            ^
internal/socks5/lib/request.go:237:88: mnd: Magic number: 60, in <argument> detected (gomnd)
                                r.udpGram.UDPExchangeMap[r.udpGram.remoteAddr().String()] = NewUDPExchange(header, 60)
                                                                                                                   ^
internal/socks5/lib/request.go:253:20: mnd: Magic number: 60, in <argument> detected (gomnd)
                                exchange.Delay(60)
                                               ^
internal/socks5/lib/request.go:265:20: mnd: Magic number: 60, in <argument> detected (gomnd)
                                exchange.Delay(60)
                                               ^
internal/socks5/lib/tcp_protocol.go:78:33: mnd: Magic number: 2, in <argument> detected (gomnd)
        if buf, err := p.readBuf(conn, 2); err != nil {
                                       ^
internal/socks5/lib/tcp_protocol.go:107:33: mnd: Magic number: 2, in <argument> detected (gomnd)
        if buf, err := p.readBuf(conn, 2); err != nil {
                                       ^
internal/socks5/lib/tcp_protocol.go:148:32: mnd: Magic number: 4, in <argument> detected (gomnd)
        if buf, er := p.readBuf(conn, 4); er != nil {
                                      ^
internal/socks5/lib/tcp_protocol.go:171:36: mnd: Magic number: 4, in <argument> detected (gomnd)
                addrBytes, err = p.readBuf(conn, 4)
                                                 ^
internal/socks5/lib/tcp_protocol.go:196:36: mnd: Magic number: 16, in <argument> detected (gomnd)
                addrBytes, err = p.readBuf(conn, 16)
                                                 ^
internal/socks5/lib/tcp_protocol.go:226:57: mnd: Magic number: 10, in <argument> detected (gomnd)
                _ = conn.SetReadDeadline(time.Now().Add(time.Second * 10))
                                                                      ^
internal/socks5/lib/tcp_protocol.go:237:58: mnd: Magic number: 10, in <argument> detected (gomnd)
                _ = conn.SetWriteDeadline(time.Now().Add(time.Second * 10))
                                                                       ^
internal/socks5/lib/udp_protocol.go:36:17: mnd: Magic number: 10, in <condition> detected (gomnd)
                if len(buf) < 10 {
                              ^
internal/socks5/lib/udp_protocol.go:44:17: mnd: Magic number: 5, in <condition> detected (gomnd)
                if len(buf) < 5 {
                              ^
internal/socks5/lib/udp_protocol.go:61:17: mnd: Magic number: 22, in <condition> detected (gomnd)
                if len(buf) < 22 {
                              ^
internal/socks5/lib/server.go:12:23: mnd: Magic number: 600, in <assign> detected (gomnd)
                readTimeoutSecond:  600,
                                    ^
internal/socks5/lib/server.go:13:23: mnd: Magic number: 30, in <assign> detected (gomnd)
                writeTimeoutSecond: 30,
                                    ^
internal/socks5/lib/tcp_protocol.go:236:5: S1009: should omit nil check; len() for []byte is defined as zero (gosimple)
        if data != nil && len(data) > 0 {
           ^
internal/socks5/lib/request.go:58:4: S1038: should use log.Printf(...) instead of log.Println(fmt.Sprintf(...)) (gosimple)
                        log.Println(fmt.Sprintf("dial target dest: %v", err))
                        ^
internal/socks5/lib/tcp_protocol.go:151:3: naked return in func `getAddr` with 70 lines of code (nakedret)
                return
                ^
internal/socks5/lib/tcp_protocol.go:161:3: naked return in func `getAddr` with 70 lines of code (nakedret)
                return
                ^
internal/socks5/lib/tcp_protocol.go:166:3: naked return in func `getAddr` with 70 lines of code (nakedret)
                return
                ^
internal/socks5/lib/server.go:84:2: error is not nil (line 75) but it returns nil (nilerr)
        return nil
        ^
ERROR: Service 'gluetun' failed to build : The command '/bin/sh -c golangci-lint run --timeout=10m' returned a non-zero code: 1