shadowsocks / shadowsocks-android

A shadowsocks client for Android
Other
35.07k stars 11.58k forks source link

Plugin system [Draft] #1073

Closed Mygod closed 7 years ago

Mygod commented 7 years ago

Plugin should be bundled as an apk. $PLUGIN_ID in this draft corresponds to the executable name for the plugin in order to be cross-platform, e.g. simple-obfs. An apk can have more than one plugins bundled. We don't care as long as they have different $PLUGIN_ID. Duplicated plugins will be disabled so user has to uninstall them until there's only not more than one left.

There is no restrictions/requirements on package name, but you're suggested to use com.github.shadowsocks.plugin.$PLUGIN_ID if it only contains a single plugin to prevent duplicate plugins.

There will be a library to implement the base framework contrived in this draft and some example plugins for the ease of developers. :smile:

1. Plugin configuration

Plugins get their args configured via one of the following two options:

Configuration activity

If the plugin provides a configuration activity, it will be started when user picks your plugin and taps configure. It:

Help activity/callback

If the plugin doesn't provide a configuration activity, it's highly recommended to provide a help message in the form of an Activity. It:

2. Plugin implementation

Every plugin can be either in native mode or JVM mode.

Native mode

In native mode, plugins are provided as native executables and shadowsocks-libev's plugin mode will be used.

Every native mode plugin MUST have a content provider to provide the native executables (since they can exceed 1M which is the limit of Intent size) that:

Native mode without binary copying

By taking advantage of android:protectionLevel="signature", plugins that have the same signature as the base package (shadowsocks-android in this case) should be able to be accessed directly by ss-local. If any native mode plugin wishes to support this mode, it must have an Activity which:

All plugins in this mode HAVE to support native mode with binary copy.

P.S. If this is implemented, "official" plugins will perform a tiny little bit better than "unofficial" ones.

JVM mode

Note: This can actually be hybrid mode since I'm not caring what you do under the hood. But this should probably be a good idea if your plugin was written in a JVM language.

Every JVM mode plugin MUST have a service intent filter that:

Default configurations

If a plugin need to supply default configuration, it can put it under the ContentProvider/Service's meta-data tag, for example:

<meta-data android:name="com.github.shadowsocks.plugin.default_config"
           android:value="obfs=http"/>

If you need to provide different default configurations for different plugins, you have to use multiple ContentProvider/Service. It should be easy to implement with sub-classing.

3. Plugin security considerations

Plugins are certified using package signatures and shadowsocks-android will consider these signatures as trusted:

A warning will be shown for untrusted plugins. No arbitrary restriction will be applied.

4. Plugin platform versioning

In order to be able to identify compatible and incompatible plugins, Semantic Versioning will be used.

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner, and
  3. PATCH version when you make backwards-compatible bug fixes.

Plugin app must include this in their application tag: (which should be automatically included if you are using our library)

<meta-data android:name="com.github.shadowsocks.plugin.version"
           android:value="1.0.0"/>

5. Possible problems

References

Opinions are welcome!

madeye commented 7 years ago

The configuration activity looks a great idea.

I'm not sure if JVM plugin could work on Android 4.4, as it requires per-app proxy feature of VPN service.

Let me dig into the whole proposal tomorrow. 😪

Mygod commented 7 years ago

Hmm yeah native runnable plugins and JVM plugins might not work. We could put those off for now.

UPDATE: Added "Plugin platform versioning" section.

UPDATE: Changed "Possible problems" section.

Btw I'm planning to implement this at least after kcptun is migrated to SIP003, probably even later.

Mygod commented 7 years ago

@madeye What do you think? If you need more time, meanwhile I can help you migrate kcptun to SIP003.

madeye commented 7 years ago

Just not sure JVM mode could work.No more questions about other part.

What about implement a SIP003 example for simple-obfs first?

Mygod commented 7 years ago

Okay.

Mygod commented 7 years ago

Some questions towards compatibility:

My answer:

madeye commented 7 years ago

Surprisingly, your answer == my answer. 😃

Mygod commented 7 years ago

Updated this draft to use the new SS_PLUGIN_OPTIONS in https://github.com/shadowsocks/shadowsocks-org/issues/28.

Mygod commented 7 years ago

Interface for plugins in shadowsocks-android

Interface for plugins in shadowsocks-android. See develop branch for more info.

Further development is blocked by https://github.com/shadowsocks/shadowsocks-libev/issues/1070, etc.

Mygod commented 7 years ago

@madeye Please check out 70e0b3991d49e137a6b29cf783ab371f5e1fc5e0 and simple-obfs-android. There are >2k of code changes and at this point all the basic features are implemented and everything is just barely building. Nothing is guaranteed working. I wish to take a break here and please do some bugfixes for me. Thanks.

madeye commented 7 years ago

Thanks for the big changes... I will help to test and fix bugs.

Mygod commented 7 years ago

Yeah I think it's safe to say that I have nuked almost every part of this repo at least once now.

madeye commented 7 years ago

I tried this change locally, but failed to load plugins. I saw fetchPlugins() is correctly called but no plugin can be found in that function.

Am I missing anything here? (It's tested on Android 6.0 emulator and Google Pixel Android 7.1.1)

Mygod commented 7 years ago

It's possible. I haven't even tested this code once. 🤣

madeye commented 7 years ago

Oh My God... 😓

madeye commented 7 years ago

Maybe querying a provider through category doesn't work? Let me try the old ways...

Mygod commented 7 years ago

@madeye Okay let me get back to work now. 😅 Feel free to help me fix JNI related stuff since I'm not familiar with the native code base yet...

Mygod commented 7 years ago

@madeye The problem is querying through category doesn't work. It seems only querying with action is supported.

EDIT: Updated doc.

Mygod commented 7 years ago

@madeye Okay check it out now. Everything looks fine now except connection isn't still working. There are no traffic until E/shadowsocks: accept: Too many open files after which huge outbound traffic is seen in the monitor. Full log. And I'm going to take another break now. :stuck_out_tongue:

I have also changed a lot of the original interface. Updating doc later.

P.S. Run sbt plugin/publish to use it in simple-obfs-android. We'll deal with publishing it to public repository later.

madeye commented 7 years ago

@Mygod Great! I think now it's working. Please try d734e9f.

Mygod commented 7 years ago

@madeye It's working now. We can make a new beta release when kcptun is ported as well.

madeye commented 7 years ago

@Mygod OK. BTW, I've added SIP003 to kcptun. It works well now with shadowsocks-libev.

madeye commented 7 years ago

@Mygod What about publishing android plugins in your own Google Play account? i think it would encourage more people developing plugins for this project.

Mygod commented 7 years ago

I don't think that would make a big difference though. :stuck_out_tongue:

I'm working on native mode without binary copying now. After that I'll start creating kcptun-android. The important thing is native mode without binary copying requires plugin app has the same signature as host, so I'd rather not.

Mygod commented 7 years ago

@madeye Hmm it turns out implementing native mode without binary copying is very convenient as long as the binary has execute permission for other (fortunately default mode for binary under lib is 755) and plugin app only need to implement 1 more method which returns executable path (it probably already does anyway). We don't even need shared user id and/or same signature. I guess we are done here for simple-obfs. (I will push the source code shortly after I post this comment)

For kcptun, there's one thing that makes it complicated. In 3.x we workaround a bug by restarting kcptun process whenever a network change is detected. This would be problematic when putting it in the current plugin platform. Here are some possible solutions:

  1. Fix this bug in kcptun itself, after this we can just copy simple-obfs-android and create another kcptun-android;
  2. Implement JVM mode and use it to start/stop/restart kcptun and detect network changes;
  3. Provide a restart plugin callback in main app? (this would be hard since plugin lifecycle is managed by ss-local and ss-tunnel)

Oh speaking of ss-local and ss-tunnel if we are using pdnsd for DNS, there will be two instances of plugin running simultaneously which is kind of a waste of resources. Any thoughts toward that?

madeye commented 7 years ago
  1. I'm still worrying about passing file descriptors between different UIDs. If it really works, should it be a security issue of Android? 😈

  2. A small enough autoexpire could be a workaround. Besides, I'm thinking a "need-to-restart" parameter for plugin that shadowsocks needs to restart ss-local/ss-tunnel when the plugin is enabled and network changes.

  3. The duplicates of one plugin is definitely a problem. I'm thinking of using other dns clients in the future, e.g. dns-over-https. If so, there is no need to use ss-tunnel anymore.

Mygod commented 7 years ago
  1. I think you're answering 2... It should work. Remember ParcelFileDescriptor?
  2. Now you're probably answering 1 and 3... Is this bug so hard to fix?
  3. Maybe DIY? How does DNS over https work though?
madeye commented 7 years ago
  1. OK. It looks reasonable.
  2. It should be a fundamental problem of kcptun on mobile devices...
  3. Yes. It's quite easy to implement and I think we can DIY here. In additional, with edns-client-subnet, we will never worry about CDN related geo IP issue anymore.

Some references for dns-over-https:

  1. https://github.com/wrouesnel/dns-over-https-proxy/blob/master/dns-over-https-proxy.go
  2. https://developers.google.com/speed/public-dns/docs/dns-over-https
Mygod commented 7 years ago

So maybe we should do the JVM mode for now? sigh

I see. But that requires a DNS-over-HTTPS-capable server. Fortunately there is. :raised_hands:

madeye commented 7 years ago
  1. Currently, let's just restart ss-local/ss-tunnel...

  2. I found something looks better: https://github.com/aarond10/https_dns_proxy

Mygod commented 7 years ago

But I don't want to add a meta-data to a plugin named restart_ss_local_and_ss_tunnel_whenever_network_changes nor add kcptun as a special case. Both of them look ugly.

madeye commented 7 years ago

Hmm... Maybe JVM mode is more reasonable here.

Mygod commented 7 years ago

It'd be best if we can fix this in kcptun. I'm going to copy simple-obfs-android for now...

Mygod commented 7 years ago

kcptun-android has been built. Next, TODOs before we can finally close this issue:

Btw do you think we should create a configuration activity for kcptun as well? Currently this plugin looks exactly the same as 3.x, except that it's a plugin now.

madeye commented 7 years ago

It turns out to be a config related issue, fixed via https://github.com/shadowsocks/kcptun/commit/b2d702ff5e51cd62814f9046637baa5178452091

Let's submit to Sonatype now. I think it's ready.

Mygod commented 7 years ago

@madeye Okay. Please follow this guide and create a ticket with your account (or maybe create a public one?). I think you should be able to do the plugin publishing since you're publishing the main app.

madeye commented 7 years ago

@Mygod Hmm.. How to create a public account?

madeye commented 7 years ago

@Mygod What about publishing the library in your account? I found it's quite complicated....

Mygod commented 7 years ago

Let's just use your account to publish for now so that in the future we can push out updates to plugin lib and main app simultaneously... 😌

madeye commented 7 years ago

It seems that we need to merge the develop branch to master first. A pull request or I directly rebase to master?

Mygod commented 7 years ago

Why's that?

madeye commented 7 years ago

It looks that I need to provide the path to git repo for Sonatype. Not sure if I can specify the branch name there.

Mygod commented 7 years ago

Add a comment perhaps?

Mygod commented 7 years ago

@madeye Okay I pushed everything to beta branch. We can make a beta release after publishing our 0.0.1 library...

madeye commented 7 years ago

I'm not sure if I have publish the package correctly. But it seems to work now.

https://oss.sonatype.org/content/repositories/snapshots/com/github/shadowsocks/plugin_2.11/0.0.1-SNAPSHOT/

Mygod commented 7 years ago

LGTM. Please checkout beta branch and publish 0.0.1. :turkey:

Mygod commented 7 years ago

Nice. Closing this.