Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: SOCKS5 plaintext proxy in Go #234

Open
qdm12 opened this issue Sep 7, 2020 · 15 comments
Open

Feature request: SOCKS5 plaintext proxy in Go #234

qdm12 opened this issue Sep 7, 2020 · 15 comments
Assignees

Comments

@qdm12
Copy link
Owner

qdm12 commented Sep 7, 2020

  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
Copy link

alpe12 commented Jan 3, 2022

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

@qdm12
Copy link
Owner Author

qdm12 commented Jan 3, 2022

@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
Copy link

alpe12 commented Jan 3, 2022

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
Copy link

x0st commented Feb 27, 2024

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
Copy link

irishj commented Feb 29, 2024

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
Copy link

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

@x0st
Copy link

x0st commented Mar 9, 2024

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
Copy link

x0st commented Mar 9, 2024

@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
Copy link
Owner Author

qdm12 commented Mar 26, 2024

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
Copy link

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
Copy link

@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

@blixten85
Copy link

How is it going? @qdm12 Your weeks are longer than mine. 🧐😋

@Bloopps
Copy link

Bloopps commented Nov 5, 2024

Is there any news about socks5 in Gluetun?

@marshalleq
Copy link

There is a socks proxy in binhex/arch-delugevpn though I haven't exactly gotten it to work yet. But it's socks5. So maybe try that since it isn't a priority here.

@matthewgrossman
Copy link

As another workaround option, the official tailscale docker image has a socks5 server too; https://tailscale.com/kb/1282/docker#ts_socks5_server. If you don't use tailscale, the rest of this answer might not be very useful for you.


I was already running a tailscale container, so it was pretty easy to just put tailscale behind network_mode: service:gluetun:

  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - 1080:1080 #socks5 proxy
  tailscale:
    image: tailscale/tailscale
    network_mode: "service:gluetun"
    environment:
      - TS_SOCKS5_SERVER=:1080

(I've removed much of the config for both gluetun/tailscale in this example)

Now I can just point my socks5-client (e.g firefox) at my_gluetun_proxy_ip:1080 and all traffic gets funneled through my gluetun container/vpn.

In my full setup, I also have the tailscale-container act as an exit-node in my tailnet, which means by simply changing my phone's exit-node in the TS app, it'll funnel all traffic through gluetun

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants