Skip to content

[Feature request] SOCKS Proxy Support for sslocal #1962

Open

Description

@Pyvonix

Summary

Currently, sslocal does not natively support forwarding traffic through another SOCKS proxy.
I am requesting a new feature and config option that allows sslocal to route its traffic through a specified SOCKS5 proxy.

Use Case

The use case involves chaining sslocal traffic through another SOCKS proxy. For example, if a local SOCKS proxy is exposed on 127.0.0.1:1234 and only this socks proxy allowed to connect to the internet, I would like to configure sslocal to route its traffic through this proxy natively (without relying on other package/binary).

This feature is especially useful in scenarios like SSH tunneling (ssh -D) to a VPS, where sslocal could utilize the local SSH SOCKS proxy for further forwarding.

Workarounds Tried

  1. local-tunnel
openbsd# sslocal -v --protocol tunnel  -b "127.0.0.1:1080" -f "127.0.0.1:1234" ...
2025-05-27T04:59:22.080355343-04:00 DEBUG main ThreadId(01) shadowsocks_rust::sys: rlimit NOFILE adjusted rlimit { rlim_cur: 1024, rlim_max: 1024 }    
2025-05-27T04:59:22.080621565-04:00  INFO main ThreadId(01) shadowsocks_rust::service::local: shadowsocks local 1.23.4 build 2025-05-27T08:01:34.889851441+00:00    
2025-05-27T04:59:22.081944065-04:00  INFO tokio-runtime-worker ThreadId(02) shadowsocks_service::local::tunnel::tcprelay: shadowsocks TCP tunnel listening on 127.0.0.1:1080

But the service that try to get through don't succeed:

socks_handshake: TCP port read failed on recv(): Operation now in progress (errno=36)
  1. External binary/package

I attempted to use socksify (similar to proxychains on BSD) to achieve this, but it results in the following error:

openbsd# socksify sslocal -v ....  
2025-05-27T04:44:45.61702666-04:00 DEBUG main ThreadId(01) shadowsocks_rust::sys: rlimit NOFILE adjusted rlimit { rlim_cur: 1024, rlim_max: 1024 }    
2025-05-27T04:44:45.617247157-04:00  INFO main ThreadId(01) shadowsocks_rust::service::local: shadowsocks local 1.23.4 build 2025-05-27T08:01:34.889851441+00:00    

thread 'main' panicked at /home/shadowsocks-rust/src/service/local.rs:1002:50:  
create local: Os { code: 61, kind: ConnectionRefused, message: "Connection refused" }  
stack backtrace:  
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.  
Abort trap

Note: I also got sslocal to start but probably due to socksify hooking, sslocal start listening on 0.0.0.0:XXXX where “XXXX” is a random port. It doesn't seem to apply the config file.

Proposed Solution

Add native support for specifying a SOCKS proxy in sslocal's configuration or command-line arguments.

For example:

sslocal --socks-proxy 127.0.0.1:1234  ...

Or allow forward to support socks5?

This would ensure sslocal encapsulates its traffic within the specified SOCKS5 proxy without requiring external tools like socksify, proxychains, etc...

Benefits

  • Simplifies integration in environments where a SOCKS proxy is the only allowed outbound connection.
  • Reduces dependency on third-party tools for SOCKS chaining.
  • Improves usability and flexibility for users requiring advanced routing scenarios.

Environment

  • Version: shadowsocks local 1.23.4 (commit c28c40c)
  • Platform: OpenBSD 7.7
  • Additional Context: SSH-based SOCKS proxy (ssh -D).

Thank you for considering this feature request!

Activity

  1. zonyitoo commented on May 27, 2025

    @zonyitoo
    Collaborator

    Similar request is #1397 .

    I personally totally Ok with this feature. But it may require quite a lot of work. Adding a proxy option inside ConnectOpts and makes TcpStream as an enum type.

  2. database64128 commented on May 27, 2025

    @database64128
    Contributor

    All you need is a tool that listens on a port and forwards traffic via a SOCKS server to an arbitrary destination, which in this case is the Shadowsocks server. Easy. With shadowsocks-go, you can have:

    {
        "servers": [
            {
                "name": "ss-via-socks5",
                "protocol": "direct",
                "tcpListeners": [
                    {
                        "network": "tcp",
                        "address": ":1234"
                    }
                ],
                "udpListeners": [
                    {
                        "network": "udp",
                        "address": ":1234"
                    }
                ],
                "mtu": 1500,
                "tunnelRemoteAddress": "ssserverhost:port"
            }
        ],
        "clients": [
            {
                "name": "socks5",
                "protocol": "socks5",
                "endpoint": "socks5host:port",
                "enableTCP": true,
                "enableUDP": true,
                "mtu": 1500,
                "socks5": {
                    "username": "username",
                    "password": "password",
                    "enableUserPassAuth": true
                }
            }
        ]
    }

    Then you ask sslocal to connect to [::1]:1234. Of course, you can just add your Shadowsocks configuration to the above example and run the whole thing in a single instance.

  3. database64128 commented on May 27, 2025

    @database64128
    Contributor

    I personally totally Ok with this feature. But it may require quite a lot of work. Adding a proxy option inside ConnectOpts and makes TcpStream as an enum type.

    Sounds like a lot of engineering effort for seemingly little to no benefit other than supporting very niche use cases like this.

    In fact, there are so many different ways of combining protocols, that you can keep making changes to add support, and users will keep finding new unsupported combinations.

    I recently made some changes in shadowsocks-go that had incidentally made it possible to write code to "chain" clients indefinitely. However, I have decided against exposing this capability through configuration. Doing so would just add extra complexity and failure modes with little to no practical value to users.

  4. Pyvonix commented on Jun 1, 2025

    @Pyvonix
    Author

    Thank you both for your responses.

    @zonyitoo, I agree to close this issue in favor of #1397 but I will answer here to keep the conversation follow.

    But it may require quite a lot of work.

    I have no idea how much work this would require, but I'm just in favor of it. 😄

    @database64128, regarding your first comment: if I understand correctly, the shadowsocks's Rust implementation separates ssslocal and ssserver, which means running two binaries to have the excepted feature. Is that accurate? I want to ensure I'm understanding this correctly what you suggest.

    As for your second comment: unfortunately, I can't give an opinion on "lot of engineering effort". I find it interesting that two issues already exist for this same feature. That suggests it may have more merit than "seemingly little to no benefit".

    From a practical perspective, the most widely used proxy protocols—SOCKS and HTTP/HTTPS—are standard enough to be directly exportable as environment variables on modern Linux and macOS systems. It's a bit ironic for a proxy service like shadowsocks to lack support for other proxies, considering their ubiquity. 😅

    While I want to avoid introducing "risky features" into shadowsocks, I do see value in enabling SOCKS proxy support for outgoing traffic. It will provide a flexible way to root the client traffic without requiring further changes in shadowsocks in near future 😃

  5. database64128 commented on Jun 1, 2025

    @database64128
    Contributor

    if I understand correctly, the shadowsocks's Rust implementation separates ssslocal and ssserver, which means running two binaries to have the excepted feature. Is that accurate? I want to ensure I'm understanding this correctly what you suggest.

    I'm not sure I understand what you mean. You are already running an ssserver instance on your server, and you want your local sslocal instance to forward encapsulated Shadowsocks traffic via your SOCKS server to ssserver. Did I get that right? My first comment suggests that you use another tool like shadowsocks-go to do the forwarding via SOCKS part. You could also just use shadowsocks-go for the whole thing, since it speaks the encrypted proxy protocols as well.

  6. zonyitoo commented on Jun 2, 2025

    @zonyitoo
    Collaborator

    I just remember that I made a "proxy plugin":

    https://github.com/zonyitoo/shadowsocks-proxy-plugin

    It should fix this issue perfectly.

  7. Pyvonix commented on Jun 2, 2025

    @Pyvonix
    Author

    @zonyitoo this is exactly what I expected!

    This should be mentioned somewhere in the README.md and/or included in the project as feature (in my opinion).
    Thank you for sharing it

  8. Pyvonix commented on Aug 24, 2025

    @Pyvonix
    Author

    After having had time to seriously consider the suggested solution, I am forced to reopen this issue because shadowsocks-proxy-plugin opens a random port that I cannot know in advance in order to authorize it in my firewall.

    With the following configuration file:

    {
      "server": "1.1.1.1",
      "server_port": 443,
      "password": "password",
      "method": "aes-256-gcm",
      "local_address": "127.0.0.1",
      "local_port": 1234,
      "plugin": "./proxy-plugin-local",
      "plugin_opts": "proxy_protocol=socks5&proxy_addr=192.168.0.1:1080"
    }
    

    I get the following debug logs:

    2025-08-24T10:40:29.772858249-04:00 INFO main ThreadId(01) shadowsocks_rust::service::local: shadowsocks local 1.23.5 build 2025-08-24T14:05:49.774144491+00:00 
    2025-08-24T10:40:29.775513798-04:00 DEBUG main ThreadId(01) shadowsocks::plugin: started plugin "proxy-plugin-local" on 127.0.0.1:44027 <-> 1.1.1.1:443 (79271) tcp_only 
    2025-08-24T10:40:32.775975089-04:00 DEBUG main ThreadId(01) shadowsocks_rust::service::local: server-loader task is now listening USR1    
    2025-08-24T10:40:32.776233764-04:00 INFO tokio-runtime-worker ThreadId(02) shadowsocks_service::local::socks::server::server: shadowsocks socks TCP listening on 127.0.0.1:1234 
    

    Where started plugin "proxy-plugin-local" on <IP> changes each time it is run.

    So I ask again, is it not possible to consider chaining socks5 directly with shadowsocks? 😅

  9. zonyitoo commented on Aug 25, 2025

    @zonyitoo
    Collaborator

    The port 44027 is only for communicating with sslocal in local network. You could bypass all traffics in the local network (IP: ::1 and 127.0.0.1).

  10. Pyvonix commented on Aug 25, 2025

    @Pyvonix
    Author

    I understood what the random port (here 44027) was for, but I preferred “native chaining” with sslocal rather than binding/opening a new port that would change at each execution.

    I admit that I am a bit biased due to the “constraints” I set 😄
    However, I support the value of such a native feature to avoid the use of an external plugin, the opening of an additional port and to facilitate the usage of sslocal through an HTTP or SOCKS5 proxy.

  11. zonyitoo commented on Aug 26, 2025

    @zonyitoo
    Collaborator

    Wrapping shadowsocks' data traffic with other protocols are supported by "plugin", which allow other "plugin" implementations to provide services as a separated project instead of commiting all of them into shadowsocks' repository.

  12. yjd commented on Feb 2, 2026

    @yjd

    @zonyitoo

    Question: Is this plugin suitable for the ssserver side?
    I need the outbound traffic from ssserver to be converted to socks5 and sent to local sing-box, then sing-box handles rule-based routing and forwards to different IPs according to the rules.
    I know sing-box can be configured as a Shadowsocks server, but I prefer using the native/original program.
    I don't want to use ss-local on the client side and sing-box on the server side. Thanks!

  13. zonyitoo commented on Feb 3, 2026

    @zonyitoo
    Collaborator

    No. The SIP003 plugin framework is for the connection between sslocal and ssserver.

  14. yjd commented on Feb 3, 2026

    @yjd

    No. The SIP003 plugin framework is for the connection between sslocal and ssserver.

    Got it, thanks! In the end, I settled for using sing-box directly as the server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions