shadowsocks / shadowsocks-org

www.shadowsocks.org
MIT License
882 stars 541 forks source link

SIP002 - Optional extension configurations as query strings in ss URLs #27

Closed Mygod closed 7 years ago

Mygod commented 7 years ago

Shadowsocks Improvement Proposal 002 (actually why don't we just use issue number to refer to them instead)

Optional configurations as query strings in ss URLs

Since #26, there are at least two optional extension for shadowsocks as far as I can tell:

There may be more extensions in the future. So what about adding them to ss URLs? For example, we can have ss://...?kcpport=8839&kcpcli=--crypt+none......#a+name for easier configuration. Clients that don't support the extensions can safely ignore them.

What should be included:

What shouldn't:

Problems:

Final version:

SIP002 purposed a new URL schema, following RFC3986:

SS-URI = "ss://" userinfo "@" hostname ":" port [ "/" ] [ "?" query ] [ "#" fragment ]
userinfo = websafe-base64-encode-utf8(method  ":" password)

The last / should be appended if query or fragment is present. Example: ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/?plugin=url-encoded-plugin-argument-value&unsupported-arguments=should-be-ignored#Dummy+profile+name. This kind of URIs can be parsed by standard libraries provided by most languages.

For plugin argument, we use the similar format as TOR_PT_SERVER_TRANSPORT_OPTIONS, which have the format like simple-obfs;obfs=http;obfs-host=www.baidu.com where colons, semicolons, equal signs and backslashes MUST be escaped with a backslash.

madeye commented 7 years ago

Should client enable the extension that's been configured when importing?

I prefer disabled by default, even the config is available, especially for the experimental obfuscating.

Should client export the extension configuration even if it's not enabled?

I think if the extension is configured, we may also export the settings.

lqhuang commented 7 years ago

Hi, guys. Firstly, thanks for your discussions @Mygod @madeye .

I’m not a expert on TCP/IP protocol. I'm interested in computer science, but I major in physics and write codes for science researches. Maybe, I could provide some views from a pure user rather than a developer.

When I heard @madeye want to deprecate obfuscation in the next release of shadowsocks-libev, I forked a 2.6.0 version branch into into my repositories immediately. I definitely understand why you think it's a dirty hack for given ISPs and breaks KISS principle. But as a user, I don't like the design of seperating obfuscation into simple-obfs. I'm a zealot of minimalist lifestyle and a practitioner for KISS principle, which means I will choose to enable TCP-BBR algorithm to speed up connections instead of choosing kcptun. Using kcptun will involve another thing, so it's not elegant for me.

The same problem is happen to obfuscation. But obfuscation is a little different with kcptun protocol,

On the server side, when the obfuscation is enabled, it still can handle normal protocol without obfuscating. So,

| Client-Obfs |   Server-Obfs  |  Working |
| No          |   Yes          |  Yes     |

If there is no necessary, clients don't need to make too many changes. For now, I'm glad to try this new feature just like enable UDP reply, Fast-open and One-time authentication. Yeah, I like obfuscation as a option --obfs <http|tls> much more than a new project called simple-obfs.

Imaging someone wants to use both kcptun and simple-obfs for multi-user configuration now, I think it's a terrible, trivial and dirty work to configure during different tools. It is not KISS at all.

Hence, please could you reconsider carefully whether to remove this new feature in the next release of shadowsocks-libev? Or just keep a fundanmental and recommended implementation of obfuscation in shadowsocks-libev, but also provide plugin supports to another obfuscations (like obfs4proxy). A much better solution also is welcome.

Anyway, I think your discussions are meaningful! It's a good try for further developments of shadowsocks protocol. Just keep it and make community of shadowsocks better.

These are my humble opinions. And I appreciate your time to read these. Thanks for your efforts.


PS: the following is not a technical content, just a little personal opinion. I'm sorry for that I'm too lazy to open a new email and send to @madeye.

I have some foreign friends who live and work in China. I volunteer to provide shadowsocks service to them for free. Some of them wanted to donate to me and I rufused all the time. Of course, I always got lots of thanks or became respected. "For the freedom of internet" someone said. They don't know who is the hero in the background. But, I know. I think all these honors should belong to you(@madeye) and @clowwindy. What's more, to everyone(shadowsocks.org members and others) who contributes to shadowsocks protocol and makes it better. That's how open source works!

Finally, Happy New Year! 🎉

Cheers!

madeye commented 7 years ago

@lqhuang Wow! It's really the longest feedback we have received in this issue tracker.

Thanks for your feedback and suggestion! I think one misunderstanding here is that we would remove simple-obfs from shadowsocks. Instead, we will keep providing simple-obfs as a plugin service of shadowsocks.

In other words, for the end users, nothing will change in the next release, although the command line may change to --plugin simple-obfs --plugin-args "--obfs http --obfs-host www.example.com".

The biggest advantage from this design is that we can have more and more plugin services designed for shadowsocks without modifying the upstream, e.g. shadowsocks-libev.

In the multi-user scenario, if you're using shadowsocks-manager, everything will work as usual. The server will automatically start the plugin service defined in the config or the command line. However, if you're with any third-party panel, changes may be required.

At last, KCPTUN, simple-obfs and all the future plugins are all optional. If you haven't experienced serious QoS, please don't enable them. Especially, as simple-obfs is actually a "dirty hack for given ISPs", it may introduce additional characteristics to shadowsocks protocol.

Thanks again for your feedback. Please keep watching our projects and send us more feedbacks!

lqhuang commented 7 years ago

@madeye Thanks for your illustration! It's easier to understand now! Pherhaps I was misleaded by the manual page of simple-obfs. I admit that --obfs <http|tls> as a option is a simplest and most lazy way to play obfuscating tricks. I tried obfuscation and it seems to improve connections under some tests.

Thanks again for your replay. You're doing a great work:)

madeye commented 7 years ago

What about encoding a plugin's settings into one BASE64 string? Then the URL looks like ss://passwd:method-auth@ip:port/?plugin=BASE64_STRING. It will help to simplify the URL parsing.

To follow #28, we should remove kcp_port or plugin_port in the url.

Mygod commented 7 years ago

The problem is that this approach may not be cross platform. For example, executable name can change.

madeye commented 7 years ago

Hmm. The executable name should be the same even in a cross platform scenario? The developer of plugins should take care of this, make sure no conflict between different plugins.

For platform specific settings, users may have to modify them after importing, e.g. interface name to bind. I think shadowsocks clients are only required to pass the arguments to the plugin, never parse them.

Mygod commented 7 years ago

Should plugin provide configuration user interface? In that case plugin would need to do the argument parsing.

For example, I may want to use /data/data/com.github.shadowsocks.plugin.kcptun/lib/libkcptun.so as executable path. What about this?

madeye commented 7 years ago

I prefer no specific UI for different plugins.

IMO, a shadowsocks-android plugin package should provide an interface to copy binaries from its data directory to shadowsocks-android's. In addition, the name of the executable should keep consistent on all the platforms. This name can also be passed from plugin package to shadowsocks-android.

em0minate commented 7 years ago

Want to pitch about the Matrix URIs scheme for the configuration, e.g.:

                                plugin  key=val   key=val
                                  |        |         |
                                ------ --------- ---------
   ss://passwd:method@host:port/kcptun;mode=fast;crypt=aes/obfs;host=foobar.io
   \_/  \___________/ \_______/ \________________________/ \_________________/
    |        |            |                 |                       |
 protocol  authority    remote            kcptun                simple-obfs

Which makes grouping more easily.

Mygod commented 7 years ago

@em0minate We don't support multiple plugins though. Also:

...as matric URI parsing is not a feature of the web. This is just something which could have been.

em0minate commented 7 years ago
em0minate commented 7 years ago

Quick demo to illustrate the parsing:

const { parse } = require('url');

const uri = 'ss://passwd:method@hostname:7070/kcptun;mode=fast;crypt=aes/obfs;host=foobar.io';

const {      protocol, auth, hostname, port, pathname } = parse(uri);
console.info(protocol, auth, hostname, port);

const plugins = pathname
    .split('/')
    .filter(Boolean)
    .map(s => s.replace(';', '?').replace(/;/g, '&'))
    .map(s => parse(s, true))
    .map(({pathname, query}) => ({[pathname]: query}))
;

const options = Object.assign({}, ...plugins);
console.info(options);

play this around on runkit

qiuyuzhou commented 7 years ago

Hello,

I have added kcptun to ShadowsocksX-ng recently.Add kcptun configures to the qrcode like

ss://aes-256-cfb:Password@139.165.131.73:4000?Remark=kcptun%20test&OTA=false&Kcptun=true&mode=normal&key=secrit&crypt=aes-128&datashard=10&parityshard=3&nocomp=false&mtu=1350

The codes has been push to develop branch on github, but has not released a new version.

Any suggestions?

madeye commented 7 years ago

The porposal of @em0minate looks quite interesting. What do you think? @Mygod

@qiuyuzhou Thanks for the implementation, please wait for @Mygod's final decision.

Mygod commented 7 years ago

What if plugin uses non-standard CLI though? This would imply a change to SIP003.

madeye commented 7 years ago

@Mygod Could you elaborate more on "non-standard CLI"? Is there any example?

Mygod commented 7 years ago

@madeye Maybe like: some-plugin [file1] [file2] [file3]...?

Actually there's no constraint that one must use getopt-style command line options. One could even use inline bash scripts.

madeye commented 7 years ago

@Mygod OK, we should add a restriction in SIP003.

Mygod commented 7 years ago

And even if you do that, every client from now on will be forced to parse CLI to generate QR code. And what about illegal characters? (URL encode/decode?)

madeye commented 7 years ago

@Mygod Hmm.. You're right.

What about this https://github.com/shadowsocks/shadowsocks-org/issues/27#issuecomment-271260043? We encode the CLI in BASE64?

Mygod commented 7 years ago

Although I have to say this is actually a more cross-platform solution. For example, options in Windows are by convention prepended by '/' while in getopt they are prepended by one or two -s. I suggest to pass PT-like configuration to client but that would require us to bomb SIP003.

Mygod commented 7 years ago

Personally I prefer to URL/base64 encoding CLI if we're not bombing SIP003.

madeye commented 7 years ago

Unless a plugin is implemented as native Win32 application, I don't think we would have issues in CLI.

In addition, to support cross-platform, a SIP003 plugin should support POSIX getopt() instead of Win32 getopt().

madeye commented 7 years ago

@Mygod Agreed.

Mygod commented 7 years ago

Then we need to put those constraints in SIP003 as well.

Also do you think URL encode or base64 encode is better? I prefer to URL encode since there usually doesn't seem to be sensitive information going there (given the examples of kcptun and simple-http).

madeye commented 7 years ago

@Mygod URL encode should be better, although we made a mistake last year to use BASE64 encode the whole URL. 😳

Mygod commented 7 years ago

@madeye It's okay since it's harder to extract password from the url. :stuck_out_tongue:

Another thing we need to consider about compatibility is how command line in Windows and bash in *nix handles special characters, e.g. --arg="argument 1"\ and' 2' is equivalent to --arg "argument 1 and 2". But I suspect the first one would work under Windows.

Also the client will still need to parse getopt themselves if they are going to provide an user interface. For example, see the platform for shadowsocks-android plugin: https://github.com/shadowsocks/shadowsocks-android/commit/03a78f6841479b32db5eeacd85e85a915dd41699

Mygod commented 7 years ago

@madeye What do you think?

madeye commented 7 years ago

@Mygod I think Windows can also handle --arg "argument 1 and 2"? And what about letting shadowsocks-windows do the tricks instead?

Mygod commented 7 years ago

@wongsyrone Okay. What do you think?

Mygod commented 7 years ago

So do you agree with this design?

madeye commented 7 years ago

@Mygod If the argument follows --arg1 "arg1 value" --arg2 "arv2 value" pattern, I think there shouldn't be any problem on Windows

Mygod commented 7 years ago

No I meant the special characters handling.

madeye commented 7 years ago

@Mygod I think we can handle this in C#. If not, we can even put a bash from msys into shadowsocks-windows.

Mygod commented 7 years ago

We can certainly do that but don't you think there are going to be too many hacks/workarounds if we keep going down this path?

madeye commented 7 years ago

Hmm... So maybe https://github.com/shadowsocks/shadowsocks-org/issues/27#issuecomment-271973164 is better?

Mygod commented 7 years ago

We could use it if it were better documented. For example, how do we handle special characters in matrix URIs?

madeye commented 7 years ago

What about encoding each plugin in the URL matrix?

Mygod commented 7 years ago

Also matrix URL brings another issue for compatibility since we are currently using non web safe base64 encoding, which means [A-Za-z0-9+/] is the alphabet for base64.

I'm looking for better alternatives for now.

Mygod commented 7 years ago

The problem with matrix URIs is that it's not RFC3986-compatible (in plain English: matrix URIs are not URIs).

If the ss URI can be reconstructed, we should make it RFC3986-compatible:

SS-URI = "ss://" userinfo "@" hostname ":" port [ "/" ] [ "?" query ] [ "#" fragment ]
userinfo = websafe-base64-encode-utf8(method [ "-auth" ] ":" password)

The last / should be appended if query or fragment is present. Example: ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/?plugin=url-encoded-plugin-argument-value&unsupported-arguments=should-be-ignored#Dummy+profile+name. This kind of URIs can be parsed by standard libraries provided by most languages.

For plugin argument, we can use the similar format as TOR_PT_SERVER_TRANSPORT_OPTIONS, which have the format like simple-obfs;obfs=http;obfs-host=www.baidu.com where

Colons, semicolons, equal signs and backslashes MUST be
escaped with a backslash.

What do you think?

madeye commented 7 years ago

@Mygod I'm OK with this. Let's add TOR_PT_SERVER_TRANSPORT_OPTIONS as SS_PLUGIN_OPTIONS in SIP003.

Mygod commented 7 years ago

And should we use simple-obfs to refer to the plugin instead?

madeye commented 7 years ago

I prefer obfs-local as the plugin name, as obfs-local and obfs-server would accept different options.

Mygod commented 7 years ago

Okay.

fortuna commented 7 years ago

Here is a pass-by suggestion: when you start thinking about configs, you should take into account multi-level nesting. The Matrix scheme allows for one level of nesting, but you may need other levels.

In the past I realized that a better solution is a nested structure such as JSON instead. However, simply URL-encoding JSON leads to long and terrible URLs. But you can come up with alternative encodings, such as JSURL. That library is javascript, but the code is small and could be reproduced.

Anyway, just my 2 cents. Sorry for parachuting into this conversation where I have no involvement. I've been interested in contributing to Shadowsocks and am taking a look around.

Mygod commented 7 years ago

Plugins should avoid nesting and use format like a.b=1;a.c=2;d.e=3 for now. I don't think it will be encoded into ridiculously big URIs for now.

em0minate commented 7 years ago

I don't see Matrix URI is incompatible to URI, as code snippet showed on above, is exactly utilized the standard URL parsing functionality, in addition with extra parsing of pathname from:

┌─────────────────────────────────────────────────────────────────────────────┐
│                                    href                                     │
├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤
│ protocol ││   auth    │      host       │           path            │ hash  │
│          ││           ├──────────┬──────┼──────────┬────────────────┤       │
│          ││           │ hostname │ port │ pathname │     search     │       │
│          ││           │          │      │          ├─┬──────────────┤       │
│          ││           │          │      │          │ │    query     │       │
"  http:   // user:pass @ host.com : 8080   /p/a/t/h  ?  query=string   #hash "
│          ││           │          │      │          │ │              │       │
└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘
(all spaces in the "" line should be ignored -- they are purely for formatting)

On encoding issue, you could have two protocols as:

ss://passwd:method@host:port/kcptun;mode=fast;crypt=aes/obfs;host=foobar.io
ss-base64://cGFzc3dkOm1ldGhvZEBob3N0OnBvcnQva2NwdHVuO21vZGU9ZmFzdDtjcnlwdD1hZXMvb2Jmcztob3N0PWZvb2Jhci5pbw==
Mygod commented 7 years ago

@em0minate Our current approach is based on Matrix URI. I just checked again and we could use that syntax but that one doesn't make as much sense as putting it into query. Keep in mind SIP003 doesn't support multiple plugins.

riobard commented 7 years ago

@Mygod What's the plan to support both password and key in the websafe-base64 encoded userinfo field? Also, do we need a new scheme prefix because now we have three variants of SS URL?