Commit 911eb3da authored by Mygod's avatar Mygod

Add documentations to plugin

parent a31befef
......@@ -30,7 +30,7 @@ _Put an `x` inside the [ ] that applies._
* [ ] Per-App Proxy
* [ ] Bypass mode
* [ ] UDP Forwarding
* KCP Parameters: (leave blank if not enabled)
* Plugin configuration (if applicable):
* [ ] Auto Connect
* [ ] TCP Fast Open
* [ ] NAT mode
......
# shadowsocks-android plugin framework
Support library for easier development on [shadowsocks
plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some
useful resources to easily get consistent styling with the main app.
## Official plugins
These are some plugins ready to use on shadowsocks-android.
* [simple-obfs](https://github.com/shadowsocks/simple-obfs-android/releases)
* [kcptun](https://github.com/shadowsocks/kcptun-android/releases)
## Developer's guide
WARNING: This library is still in beta (0.x) and its content is subject to massive changes.
This library is designed with Java interoperability in mind so theoretically you can use this
library with other languages and/or build tools but there isn't documentation for that yet. This
guide is written for Scala + SBT. Contributions are welcome.
The following is just a simple guide to get you started, for more technical details, see
[doc.md](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md).
### Package name
There are no arbitrary restrictions/requirements on package name, component name and content
provider authority, but you're suggested to follow the format in this documentations. For package
name, use `com.github.shadowsocks.plugin.$PLUGIN_ID` if it only contains a single plugin to
prevent duplicated plugins. 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`.
### Add dependency
First you need to add this library to your dependencies. This library is written mostly in Scala
and it's most convenient to use it with SBT:
```scala
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1-SNAPSHOT"
```
### Native binary configuration
First you need to get your native binary compiling on Android platform.
* [Sample project for C](https://github.com/shadowsocks/simple-obfs-android/tree/4f82c4a4e415d666e70a7e2e60955cb0d85c1615);
* [Sample project for Go](https://github.com/shadowsocks/kcptun-android/tree/41f42077e177618553417c16559784a51e9d8c4c).
In addition to functionalities of a normal plugin, it has to support these additional flags that
may get passed through arguments:
* `-V`: VPN mode. In this case, the plugin should pass all file descriptors that needs protecting
from VPN connections (i.e. its traffic will not be forwarded through the VPN) through an
ancillary message to `./protect_path`;
* `--fast-open`: TCP fast open enabled.
### Implement a binary provider
It's super easy. You just need to implement two or three methods. For example for `obfs-local`:
```scala
final class BinaryProvider extends NativePluginProvider {
override protected def populateFiles(provider: PathProvider) {
provider.addPath("obfs-local", "755")
// add additional files here
}
// remove this method to disable fast mode, read more in the documentation
override def getExecutable: String =
getContext.getApplicationInfo.nativeLibraryDir + "/libobfs-local.so"
override def openFile(uri: Uri): ParcelFileDescriptor = uri.getPath match {
case "/obfs-local" =>
ParcelFileDescriptor.open(new File(getExecutable), ParcelFileDescriptor.MODE_READ_ONLY)
// handle additional files here
case _ => throw new FileNotFoundException()
}
}
```
Then add it to your manifest:
```xml
<manifest>
...
<application>
...
<provider android:name=".BinaryProvider"
android:exported="true"
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
</intent-filter>
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:pathPrefix="/$PLUGIN_ID"/>
</intent-filter>
<meta-data android:name="com.github.shadowsocks.plugin.id"
android:value="$PLUGIN_ID"/>
<meta-data android:name="com.github.shadowsocks.plugin.default_config"
android:value="dummy=default;plugin=options"/>
</provider>
...
</application>
</manifest>
```
### Add user interfaces
You should add to your plugin app a configuration activity or a help activity or both if you're
going to use `ConfigurationActivity.fallbackToManualEditor`.
#### Configuration activity
This is used if found instead of a manual input dialog when user clicks "Configure..." in the main
app. This gives you maximum freedom of the user interface. To implement this, you need to extend
`ConfigurationActivity` and you will get current options via
`onInitializePluginOptions(PluginOptions)` and you can invoke `saveChanges(PluginOptions)` or
`discardChanges()` before `finish()` or `fallbackToManualEditor()`. Then add it to your manifest:
```xml
<manifest>
...
<application>
...
<activity android:name=".ConfigActivity">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_CONFIGURE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:path="/$PLUGIN_ID"/>
</intent-filter>
</activity>
...
</application>
</manifest>
```
#### Help activity/callback
This is started when user taps "?" in manual editor. To implement this, you need to extend
`HelpCallback` if you want a simple dialog with help message as `CharSequence` or `HelpActivity`
if you want to provide custom user interface, implement the required methods, then add it to your
manifest:
```xml
<manifest>
...
<application>
...
<activity android:name=".HelpActivity">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_HELP"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:path="/$PLUGIN_ID"/>
</intent-filter>
</activity>
...
</application>
</manifest>
```
Great. Now your plugin is ready to use.
# Overview
Plugin should be bundled as an apk. `$PLUGIN_ID` in this documentation corresponds to the
executable name for the plugin in order to be cross-platform, e.g. `obfs-local`. An apk can have
more than one plugins bundled. We don't care as long as they have different `$PLUGIN_ID`. For
duplicated plugin ID, host should refuse to start.
There are no arbitrary restrictions/requirements on package name, component name and content
provider authority, but you're suggested to follow the format in this documentations. For package
name, use `com.github.shadowsocks.plugin.$PLUGIN_ID` if it only contains a single plugin to prevent
duplicated plugins. 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`.
It's advised to use this library for easier development, but you're free to start from scratch following this
documentation.
# Plugin configuration
Plugins get their args configured via one of the following two options:
* A configuration activity;
([example](https://github.com/shadowsocks/simple-obfs-android/tree/4f82c4a4e415d666e70a7e2e60955cb0d85c1615))
* If no configuration activity is found or the activity requests the fallback mode, the fallback
mode will be used: user manual input and optional help message.
([example](https://github.com/shadowsocks/kcptun-android/tree/41f42077e177618553417c16559784a51e9d8c4c))
Your user interface 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.
## Configuration activity
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.ACTION_CONFIGURE`;
* MUST have category: `android.intent.category.DEFAULT`;
* MUST be able to receive data URI `plugin://com.github.shadowsocks/$PLUGIN_ID`;
* SHOULD parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` (all options as a single
string) and display the current options;
* SHOULD distinguish between server settings and feature settings in some way, e.g. for
`obfs-local`, `obfs` is a server setting and `obfs_host` is a feature setting;
* On finish, it SHOULD return one of the following results:
- `RESULT_OK = 0`: In this case it MUST return the data Intent with the new
`com.github.shadowsocks.plugin.EXTRA_OPTIONS`;
- `RESULT_CANCELED = -1`: Nothing will be changed;
- `RESULT_FALLBACK = 1`: Fallback mode is requested and the host should display the fallback
editor.
This corresponds to `com.github.shadowsocks.plugin.ConfigurationActivity` in the plugin library.
Here's what a proper configuration activity usually should look like in `AndroidManifest.xml`:
```xml
<manifest>
...
<application>
...
<activity android:name=".ConfigActivity">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_CONFIGURE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:path="/$PLUGIN_ID"/>
</intent-filter>
</activity>
...
</application>
</manifest>
```
## 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.ACTION_HELP`;
* MUST have category: `android.intent.category.DEFAULT`;
* MUST be able to receive data URI `plugin://com.github.shadowsocks/$PLUGIN_ID`;
* CAN parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` and display some more
relevant information;
* SHOULD either:
- Be invisible and return help message with CharSequence extra
`com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE` in the data intent with `RESULT_OK`; (in this
case, a simple dialog will be shown containing the message)
- Be visible and return `RESULT_CANCELED`.
* 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.
This corresponds to `com.github.shadowsocks.plugin.HelpActivity` or
`com.github.shadowsocks.plugin.HelpCallback` in the plugin library. Here's what a proper help
activity/callback usually should look like in `AndroidManifest.xml`:
```xml
<manifest>
...
<application>
...
<activity android:name=".HelpActivity">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_HELP"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:path="/$PLUGIN_ID"/>
</intent-filter>
</activity>
...
</application>
</manifest>
```
# 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 its parent `application`)
* MUST have an intent filter with action `com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN`;
(used for discovering plugins)
* MUST have meta-data `com.github.shadowsocks.plugin.id` with string value `$PLUGIN_ID`;
* MUST have an intent filter with action `com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN` and
data `plugin://com.github.shadowsocks/$PLUGIN_ID`; (used for configuring plugin)
* CAN have meta-data `com.github.shadowsocks.plugin.default_config` with string value, default is
empty;
* MUST implement `query` that returns the file list which MUST include `$PLUGIN_ID` when having
these as arguments:
- `uri = "content://$authority_of_your_provider`;
- `projection = ["path", "mode"]`; (relative path, for example `obfs-local`; file mode, for
example `755`)
- `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`.
This corresponds to `com.github.shadowsocks.plugin.NativePluginProvider` in the plugin library.
Here's what a proper native plugin provider usually should look like in `AndroidManifest.xml`:
```xml
<manifest>
...
<application>
...
<provider android:name=".BinaryProvider"
android:exported="true"
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID">
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
</intent-filter>
<intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
<data android:scheme="plugin"
android:host="com.github.shadowsocks"
android:pathPrefix="/$PLUGIN_ID"/>
</intent-filter>
<meta-data android:name="com.github.shadowsocks.plugin.id"
android:value="$PLUGIN_ID"/>
<meta-data android:name="com.github.shadowsocks.plugin.default_config"
android:value="dummy=default;plugin=options"/>
</provider>
...
</application>
</manifest>
```
## Native mode without binary copying
If your plugin binary executable can run in place, you can support native mode without binary
copying. To support this mode, your `ContentProvider` must first support native mode with binary
copying (this will be used if the fast routine fails) and:
* MUST implement `call` that returns absolute path to the entry executable as
`com.github.shadowsocks.plugin.EXTRA_ENTRY` when having `method = "shadowsocks:getExecutable"`;
(`com.github.shadowsocks.plugin.EXTRA_OPTIONS` is provided in extras as well just in case you
need them)
If you don't plan to support this mode, you can just throw `UnsupportedOperationException` when
being invoked. It will fallback to the slow routine automatically.
## JVM mode
This feature hasn't been implemented yet.
# Plugin security
Plugins are certified using package signatures and shadowsocks-android will consider these
signatures as trusted:
* Signatures by [trusted sources](https://github.com/shadowsocks/shadowsocks-android/blob/develop/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22)
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 restrictions will be applied.
# Plugin platform versioning
In order to be able to identify compatible and incompatible plugins, [Semantic
Versioning](http://semver.org/) 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="0.0.1"/>
```
......@@ -26,7 +26,7 @@ public final class PluginContract {
/**
* Activity Action: Used for HelpActivity or HelpCallback.
*
* Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
* Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP"
*/
public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP";
......@@ -39,7 +39,7 @@ public final class PluginContract {
*/
public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY";
/**
* The lookup key for a string that provides the whole command as a string.
* The lookup key for a string that provides the options as a string.
*
* Example: "obfs=http;obfs-host=www.baidu.com"
*
......@@ -69,7 +69,7 @@ public final class PluginContract {
*/
public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config";
static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable";
public static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable";
/** ConfigurationActivity result: fallback to manual edit mode. */
public static final int RESULT_FALLBACK = 1;
......
......@@ -12,9 +12,13 @@ import android.content.Intent
* ...
* &lt;application&gt;
* ...
* &lt;activity android:name="com.github.shadowsocks.$PLUGIN_ID.ConfigureActivity"&gt;
* &lt;activity android:name=".ConfigureActivity"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE" /&gt;
* &lt;action android:name="com.github.shadowsocks.plugin.ACTION_CONFIGURE"/&gt;
* &lt;category android:name="android.intent.category.DEFAULT"/&gt;
* &lt;data android:scheme="plugin"
* android:host="com.github.shadowsocks"
* android:path="/$PLUGIN_ID"/&gt;
* &lt;/intent-filter&gt;
* &lt;/activity&gt;
* ...
......
......@@ -8,9 +8,13 @@ package com.github.shadowsocks.plugin
* ...
* &lt;application&gt;
* ...
* &lt;activity android:name="com.github.shadowsocks.$PLUGIN_ID.HelpActivity"&gt;
* &lt;activity android:name=".HelpActivity"&gt;
* &lt;intent-filter&gt;
* &lt;action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP" /&gt;
* &lt;action android:name="com.github.shadowsocks.plugin.ACTION_HELP"/&gt;
* &lt;category android:name="android.intent.category.DEFAULT"/&gt;
* &lt;data android:scheme="plugin"
* android:host="com.github.shadowsocks"
* android:path="/$PLUGIN_ID"/&gt;
* &lt;/intent-filter&gt;
* &lt;/activity&gt;
* ...
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment