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

SIP003 - A simplified plugin design for shadowsocks #28

Closed
madeye opened this issue Jan 4, 2017 · 60 comments
Closed

SIP003 - A simplified plugin design for shadowsocks #28

madeye opened this issue Jan 4, 2017 · 60 comments

Comments

@madeye
Copy link
Contributor

madeye commented Jan 4, 2017

As discussed in #26, it's dirty to hack original shadowsocks protocol for additional transport features.

A proposal from @falseen , @Artoria2e5, and @anonymous-contributor is that we may support Pluggable Transport from Tor. However, I found PT seems too heavy for shadowsocks. As we never try to or plan to support distributed architecture, I propose a simplified design instead.

SIP003: A simplified plugin design for shadowsocks

  1. Architecture Overview

Dislike the socks5 proxy design in PT, every SIP003 plugin works like a tunnel (or called local port forwarding). This design aims to avoid per-connection arguments in PT, leading to much easier implementation.

     +------------+                    +---------------------------+
     |  SS Client +-- Local Loopback --+  Plugin Client (Tunnel)   +--+
     +------------+                    +---------------------------+  |
                                                                      |
                 Public Internet (Obfuscated/Transformed traffic) ==> |
                                                                      |
     +------------+                    +---------------------------+  |
     |  SS Server +-- Local Loopback --+  Plugin Server (Tunnel)   +--+
     +------------+                    +---------------------------+
  1. Life cycle of a plugin

Very similar to PT, the plugin client/server is started as child process of shadowsocks client/server.

If any error happens, the child process of plugin should exit with a error code. Then, the parent process of shadowsocks stops as well (SIGCHLD).

When a shadowsocks client/server is stopped by user, the child process of plugin will also be terminated.

  1. Passing arguments to a plugin

A plugin accepts arguments through environment variables.

a. Four MUST-HAVE environment variables are SS_REMOTE_HOST, SS_REMOTE_PORT, SS_LOCAL_HOST and SS_LOCAL_PORT. SS_REMOTE_HOST and SS_REMOTE_PORT are the hostname and port of the remote plugin service. SS_LOCAL_HOST and SS_LOCAL_PORT are the hostname and port of the local shadowsocks or plugin service.

b. One OPTIONAL environment variable is 'SS_PLUGIN_OPTIONS'. If a plugin requires additional arguments, like path to a config file, these arguments can be passed as extra options in a formatted string. An example is 'obfs=http;obfs-host=www.baidu.com', where semicolons, equal signs and backslashes MUST be escaped with a backslash.

  1. Compatibility with PT

For all the plugins from Tor projects, there are two possible ways to support them. 1) We can fork these plugins and modify them to support SIP003, e.g. obfs4-tunnel. 2) Implement a adapter of PT as SIP003 plugin.

  1. Licenses of plugins

As all plugin services should run in a separate process, they can pick any license they like. There is no GPL restrictions for any plugin providers.

  1. Restrictions

a. Plugin over plugin is NOT supported. Only one plugin can be enabled when a shadowsocks service is started. If you really need this feature, implement a plugin-over-plugin transport as a SIP003 plugin.
b. Only TCP traffic is forwarded. For now, there is no plan to support UDP traffic forwarding.

  1. Example projects
  1. Command line example

On the server:

ss-server --plugin obfs-server --plugin-opts "obfs=http"

On the client:

ss-local -c config.json --plugin obfs-local --plugin-opts "obfs=http;obfs-host=www.baidu.com"
@Mygod
Copy link
Contributor

Mygod commented Jan 4, 2017

What about --plugin 'obfs-server --obfs http', --plugin 'obfs-local --obfs http --obfs-host www.baidu.com'?

@madeye
Copy link
Contributor Author

madeye commented Jan 4, 2017

The plugin here is the name of the executable. Maybe rename obfs-local to obfs-http-local and handle the mode in the plugin would be clearer.

@ghost
Copy link

ghost commented Jan 4, 2017

Since all plugins are processes already, I wonder if just letting systemd do the job is more KISS?

@madeye
Copy link
Contributor Author

madeye commented Jan 4, 2017

@nfjinjing Here we need a cross platform solution. Systemd looks not a good idea.

@Mygod
Copy link
Contributor

Mygod commented Jan 4, 2017

I mean --plugin command_string. So that we don't need --plugin-args. Since the executable is just $0.

@madeye
Copy link
Contributor Author

madeye commented Jan 4, 2017

@Mygod It looks doable. Actually no spec for the command line, let me refine it later.

@anonymous-contributor
Copy link

anonymous-contributor commented Jan 4, 2017

I still prefer the PT one just because we can use it right now, and the saved overhead can be easily eaten by obfuscation itself.

The new draft and PT just differs in the level of integration.

And personally speaking, if user want to use obfuscation, then they are already trading bandwidth and latency for extra security.(Not data security, but channel security)
A really good obfuscation will (and should) add extra bandwidth usage (to obfuscating packet size) and extra latency (to obfuscating packet sequence).

in that case, no matter how hard we try our best to make the integration lightweight, the overhead will increase hugely.
The draft maybe good for certain case, just like the http hack, as a performance hack, but I just
want to clarify that, obfuscation (at least, good one) will introduce obvious overhead to make our effort just meaningless.

So for user who want maxium bandwidth, go plain ss and spend more time finetuning their server.(Encryption of ss won't really be a problem, for the most common use-case behind GFW)

If there is some really strong deep packet filter, such obfuscation may bring some usability, but never expect high performance.
(And in that case, more obvious fix would be load balancing to multiple ss server)

@ghost
Copy link

ghost commented Jan 4, 2017

@anonymous-contributor I agree with you about the "personally speaking" part.

@Mygod
Copy link
Contributor

Mygod commented Jan 4, 2017

I think you are aiming for two different things here.

I believe @madeye introduces obfuscation to boost performance. #26 was meant to boost performance (i.e. bandwidth) (see #26 (comment)). #26 is about HTTP obfuscation because HTTP/HTTPS traffic is the most common (i.e. least suspicious) traffic.

Other people including @anonymous-contributor is wishing to use obfuscation to hide from your ISP that you use Shadowsocks or Tor, i.e. making detection computationally expensive (the so-called channel security).

And currently SIP003 is a balance of both. It wishes to minimize performance overhead while making the platform easily extensible and compatible with Tor PT so that we will have something to move onto if the cheaper approach is no longer beneficial (which is why we call #26 a dirty hack).

@ghost
Copy link

ghost commented Jan 4, 2017

There's an obvious use case for chaining of plugins: ss -> obfs4 -> simple-obfs / fteproxy. This will increase the priority of QoS in some ISPs, confuse the hell out of gfw, and still be modular enough to deploy. This only works on the condition that gfw will not interfere with fake http requests. I know, strange. So we might keep the option of enabling multiple plugins all at the parent process open?

@Mygod
Copy link
Contributor

Mygod commented Jan 4, 2017

And additionally, we are currently adding obfuscation mainly to gain additional performance since Shadowsocks is designed to be indistinguishable with random traffic. We wish to get additional bandwidth and get prioritized by pretending it's something else (like a normal HTTP/TLS connection).

@anonymous-contributor
Copy link

And mentioning lightweight, it's better to have a benchmark to determine if it's worthy.

I'd like to compare the same obfuscation method using standalone mode vs the local interface mode.
Same server, same obfuscation, only different plugin implementation.

If the difference is less than, for example 10%, for average speed, using PT seems more reasonable.

@librehat
Copy link
Contributor

librehat commented Jan 4, 2017

I'm quite in favour of this draft mainly because it doesn't touch the core protocol of shadowsocks!
With this plugin approach, we should expect more contributions to the shadowsocks project since it means everyone can write their green-field plugins (no need to touch existing code bases)

@librehat
Copy link
Contributor

librehat commented Jan 4, 2017

The communication of shadowsocks service and plugin service is not very clear to me. Does it mean the local shadowsocks client process would pass the datagram to plugin and retrieve them back, then finally sends it to the remote shadowsocks service?

@Artoria2e5
Copy link

Artoria2e5 commented Jan 4, 2017

@librehat This proposal traces its roots to my mention of Tor's Pluggable Transport protocol in #26. You are welcome to read Tor's PT spec for more idea on this.

In short, both the server and the client speaks and hears through an instance of the transport program. The transport is responsible for communication. Think KCPTUN.

@madeye
Copy link
Contributor Author

madeye commented Jan 5, 2017

Thanks for all the feedbacks!

@anonymous-contributor Yes, I think reusing PT is also important to us. However, it's quite a lot effort to implement it for all our implementations. My suggestion is to build a adapter to PT based on SIP003. This adapter would be very easy to write as you can reuse all the golang source code from Tor project. After that, all the SIP003 based shadowsocks implementations would also benefit from PT.

@nfjinjing There may be real cases for plugin chains, although ss-obfs4-simpleobfs is not necessary in my opinion. An easy way to do this is also implement SIP003 in your plugin. It means a SIP003 plugin could fork other plugins as a chain. However, shadowsocks itself should only start, maintain and talk to one plugin.

@librehat Every SIP003 plugin works like a tunnel, like KCPTUN, stunnel and ssh -L. It also helps to simplify the development of a plugin that a programmer only needs to focus on his own transport protocol with his familiar language and techniques.

This proposal aims to provide a easy way to integrate third-party transport protocols with shadowsocks infrastructure. With this, I hope we can end all the debates about forking and license issues in the future.

I suggest all the contributors try to add SIP003 support following the example. I believe you will find it's really easy to implement (It takes me four hours to write and debug the full example).

@ghost
Copy link

ghost commented Jan 5, 2017

Looks good. It seems without a tunnel software doing anything, a simple wrapper script that passes environment variables to command line arguments can make it complaint with this spec.

@v2ray
Copy link

v2ray commented Jan 5, 2017

  1. Can you clarify the meaning of four environment variables and how the plugin is supposed to use them? IIUC the plugin should always listen on SS_LOCAL_HOST:SS_LOCAL_PORT and tunnel whatever data it receives to SS_REMOTE_HOST:SS_REMOTE_PORT, no matter whether the plugin runs on client or server.

  2. Is there a side channel for the plugin to talk to ss process, other than exit-code? How is the plugin supposed the handle errors like "port already bind"?

  3. I don't think the handshake procedure in socks protocol (as used in PT) would cause any performance penalty on the whole proxying workflow. The RTT is single-digit millisecond on local loopback, while over 100 ms on the internet. It is true that the flexibility in socks handshake is not necessary in this case, but on the other hand it looks not wise to 'reinvent a wheel`.

@madeye
Copy link
Contributor Author

madeye commented Jan 5, 2017

@v2ray

  1. On the server, SS_LOCAL_HOST:SS_LOCAL_PORT is the address of the ss-server listening on the loopback, and SS_REMOTE_HOST:SS_REMOTE_PORT is the address of the plugin-server listening on the outbound interface. On the client, SS_LOCAL_HOST:SS_LOCAL_PORT is the address of the plugin-local listening on the loopback, and SS_REMOTE_HOST:SS_REMOTE_PORT is the address that plugin-local will connect to.

  2. No side channel here. The error log of the plugin will be output to the parent process' stdout/stderr or a log file of the plugin. Actually here I assume shadowsocks isn't aware of any error code of the plugins.

  3. Yes, I agree that there is no performance issue for per-connection arguments. That's why I encourage to implement an adapter for PT instead of directly adding it to shadowsocks: the adapter wouldn't hurt the performance at all. At the same time, this approach would simplify the development of both shadowsocks and plugins.

@v2ray
Copy link

v2ray commented Jan 5, 2017

No side channel here. The error log of the plugin will be output to the parent process' stdout/stderr or a log file of the plugin. Actually here I assume shadowsocks isn't aware of any error code of the plugins.

Then what is the benefit from launching the plugin process by shadowsocks? How about just 1) bringing up the plugin in advance, and 2) asking shadowsocks to talk to SS_LOCAL_HOST:SS_LOCAL_PORT? It could be another program for doing these 2 steps automatically and smoothly, and handling errors from both parties.

@madeye
Copy link
Contributor Author

madeye commented Jan 5, 2017

@v2ray I think it's possible. Actually, in that case, shadowsocks itself works like a plugin. It'd be a better way to integrate all protocols in one framework (maybe it's just what v2ray is doing? 😄 )

However, as a SIP, I still prefer the design proposed here.

@v2ray
Copy link

v2ray commented Jan 5, 2017

Yes. That is exactly what V2Ray is trying to achieve.

@anonymous-contributor
Copy link

@madeye The extra work is almost to nothing if using standalone mode Tor PT.
Managed mode needs extra work, but still less than the draft, which is just a SS variant of Tor PT.
(Which we need PT style communication infrastructure for both ss and plugins)

No idea why it's needed to re-invent the wheel, just for so-called lightweight communication?
(And I already expressed the doubt if it's really the bottleneck)

Using standalone mode parent process just exec the obfsproxy, redirecting its output as log, passing random port as SS<->obfsproxy communication, passing real destination port for obfsproxy <-> server.

In that case, the only difference between your draft is, we are using the random local port to run ss.
Instead of lo interface.

From this respect of view, it's even easier to implement than the draft.
No wrapper, just extra configuration parameters.
Even no extra environment variants.

In this case, I can even submit a pull-request in several days for implement cmdline parameters.
(Json parameter will only be added if we settled how the plugin system works.)

@madeye
Copy link
Contributor Author

madeye commented Jan 6, 2017

@anonymous-contributor

  1. I think if standalone mode is available (seems not in obfs4), we should take advantage of it to implement our plugin. That's why I forked obfs4 and made it work in standalone mode: https://github.com/madeye/obfs4-tunnel

  2. Any pull request is welcome. If we can integrate obfproxy very easily with a simple wrapper, that should be great!

@Artoria2e5
Copy link

Hey, have we figured out how to properly pass server-returned args to the client yet? There has to be some stdout capturing and passing.

@madeye
Copy link
Contributor Author

madeye commented Jan 6, 2017

@Artoria2e5 Answer 2 in #28 (comment)

@Mygod
Copy link
Contributor

Mygod commented Jan 8, 2017

If this is going stable (for real), I think we should remove the "[Draft]" in the title.

@madeye
Copy link
Contributor Author

madeye commented Feb 24, 2017

Implemented and published via https://github.com/shadowsocks/shadowsocks-libev/releases/tag/v3.0.3

@madeye madeye closed this as completed Feb 24, 2017
@Mygod
Copy link
Contributor

Mygod commented Feb 24, 2017 via email

madeye added a commit that referenced this issue Feb 24, 2017
@madeye
Copy link
Contributor Author

madeye commented Feb 24, 2017

@Mygod Fixed via 08efb61.

@M66B
Copy link

M66B commented Mar 19, 2017

I am not sure if I (already) understand this all, but I am wondering if this architecture can be used to let Shadowsocks and NetGuard (I am the author) operate together? If this is the wrong place to discuss this and there is interest in this, I am happy to create a new issue to discuss about this.

@madeye
Copy link
Contributor Author

madeye commented Mar 19, 2017

@M66B It looks NetGuard works as a firewall? If so, it'd be not related. This issue is a proposal for plugin architecture of shadowsocks protocol.

@M66B
Copy link

M66B commented Mar 19, 2017

@madeye yes, NetGuard is a VPN based firewall.

I was thinking like this, but maybe I am thinking wrong and/or there is a better way:

     +------------+                    +---------------------------+
     |  SS Client +-- Local Loopback --+  NetGuard firewall        +--+
     +------------+                    +---------------------------+  |
                                                                      |
                                                  Public Internet ==> |
                                                                      |
     +------------+                    +---------------------------+  |
     |  SS Server +-- Local Loopback --+  <empty>                  +--+
     +------------+                    +---------------------------+

@madeye
Copy link
Contributor Author

madeye commented Mar 20, 2017

@M66B If it works as a TCP firewall, I think it'd be doable.

What you need is only to implement a simple TCP tunnel feature for your app and follow the instruction here: https://github.com/shadowsocks/shadowsocks-android/blob/master/plugin/README.md

@Mygod Mygod mentioned this issue Nov 3, 2017
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants