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:
- A configuration activity.
- If no configuration activity is found, a fallback mode will be used: user manual input and help message; (example: https://github.com/shadowsocks/shadowsocks-android/blob/67fab03/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala#L38)
Configuration activity
- [x] Should this be implemented at all?
If the plugin provides a configuration activity, it will be started when user picks your plugin and taps configure. It:
- MUST have action:
com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE
;
- SHOULD parse string extra
com.github.shadowsocks.plugin.EXTRA_OPTIONS
(all arg
as a single string) and display the old settings;
- MUST return the data Intent with the new
com.github.shadowsocks.plugin.EXTRA_OPTIONS
;
- SHOULD distinguish between server settings and feature settings in some way, e.g. for
simple_obfs
, obfs
is a server setting and obfs_host
is a feature setting;
- NEED NOT be consistent with shadowsocks-android styling - you don't need to use preferences UI at all if you don't feel like it - however it's recommended to use Material Design at minimum.
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:
- MUST have action:
com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP
;
- CAN parse string extra
com.github.shadowsocks.plugin.EXTRA_OPTIONS
and display some more relevant information;
- MUST be either:
- Be invisible and return help message with CharSequence extra
com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE
in the data intent; (in this case, default dialog will be shown containing the message)
- Be visible and return null as data intent.
- SHOULD distinguish between server settings and feature settings in some way, e.g. for
simple_obfs
, obfs
is a server setting and obfs_host
is a feature setting.
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:
- MUST have
android:label
and android:icon
; (may be configured by activity
or application
)
- MUST have action:
com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN
. (used for discovering plugins)
- MUST have
android:authorities
set to com.github.shadowsocks.plugin.$PLUGIN_ID
;
- MUST implement
query
that returns the file list which MUST include $PLUGIN_ID
when having these as arguments:
uri = "content://com.github.shadowsocks.plugin.$PLUGIN_ID"
;
projection = ["path"]
; (relative path, for example obfs-local
)
selection = null
;
selectionArgs = null
;
sortOrder = null
;
- MUST implement
openFile
that for files returned in query
, openFile
with mode = "r"
returns a valid ParcelFileDescriptor
for reading. For example, uri
can be content://com.github.shadowsocks.plugin.kcptun/kcptun
.
Native mode without binary copying
- [x] Should this be implemented at all?
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:
- MUST have action:
com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_START
;
- MUST be invisible;
- MUST return the
com.github.shadowsocks.plugin.EXTRA_EXECUTABLE
with executable path (e.g. /data/data/com.github.shadowsocks.plugin.kcptun/lib/libkcptun.so
).
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.
- [ ] Should this be implemented at all?
Every JVM mode plugin MUST have a service intent filter that:
- MUST have
android:label
and android:icon
; (may be configured by activity
or application
)
- MUST have action:
com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_START
;
- MUST have action:
com.github.shadowsocks.plugin.ACTION_JVM_PLUGIN
; (used for discovering plugins)
- After
onStartCommand(intent, flags, startId)
, where caller package name, server_ip
, server_port
and com.github.shadowsocks.plugin.EXTRA_OPTIONS
will be passed via intent extra. Within 5000ms, it MUST broadcast com.github.shadowsocks.plugin.ACTION_JVM_PLUGIN_READY
with local_port
(should be the result of new ServerSocket(0).getLocalPort()
) as extra or com.github.shadowsocks.plugin.ACTION_JVM_PLUGIN_FAIL
with optional error message as string extra to caller package.
- After shutdown or startService timeout,
stopService
will be called on target service. The plugin MUST gracefully handle this and terminate.
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:
- Signatures by trusted sources which includes:
- @madeye, i.e. the signer of the main repo;
- The main repo doesn't contain any other trusted signatures. Third-party forks should add their signatures to this trusted sources if they have plugins signed by them before publishing their source code.
- Current package signature, which means:
- If you get apk from shadowsocks-android releases or Google Play, this means only apk signed by @madeye will be recognized as trusted.
- If you get apk from a third-party fork, all plugins from that developer will get recognized as trusted automatically even if its source code isn't available anywhere online.
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:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards-compatible manner, and
- 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
- In some places hyphens are not accepted, for example package name. In that case, hyphens
-
should be changed into underscores _
. For example, the package name for obfs-local
would probably be com.github.shadowsocks.plugin.obfs_local
(if it turns out that we really can't use hyphens in package name).
- In the current implementation, kcptun needs to be restarted whenever a network change is detected. We may need to put this into this platform somehow, e.g. a restart receiver for shadowsocks and a start/stop receiver for plugins...
References
- SIP003: https://github.com/shadowsocks/shadowsocks-org/issues/28;
- Previous discussion at: https://github.com/shadowsocks/shadowsocks-android/issues/1054#issuecomment-270568281.
Opinions are welcome!
enhancement