Add documentations to plugin
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
# 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.
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
# 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
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <activity android:name="com.github.shadowsocks.$PLUGIN_ID.ConfigureActivity">
|
||||
* <activity android:name=".ConfigureActivity">
|
||||
* <intent-filter>
|
||||
* <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE" />
|
||||
* <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>
|
||||
* ...
|
||||
|
||||
@@ -8,9 +8,13 @@ package com.github.shadowsocks.plugin
|
||||
* ...
|
||||
* <application>
|
||||
* ...
|
||||
* <activity android:name="com.github.shadowsocks.$PLUGIN_ID.HelpActivity">
|
||||
* <activity android:name=".HelpActivity">
|
||||
* <intent-filter>
|
||||
* <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP" />
|
||||
* <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>
|
||||
* ...
|
||||
|
||||
Reference in New Issue
Block a user