{"action":"created","issue":{"url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/1073","repository_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android","labels_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/1073/labels{/name}","comments_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/1073/comments","events_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/1073/events","html_url":"https://github.com/shadowsocks/shadowsocks-android/issues/1073","id":199576028,"number":1073,"title":"Plugin system [Draft]","user":{"login":"Mygod","id":3511321,"avatar_url":"https://avatars.githubusercontent.com/u/3511321?v=3","gravatar_id":"","url":"https://api.github.com/users/Mygod","html_url":"https://github.com/Mygod","followers_url":"https://api.github.com/users/Mygod/followers","following_url":"https://api.github.com/users/Mygod/following{/other_user}","gists_url":"https://api.github.com/users/Mygod/gists{/gist_id}","starred_url":"https://api.github.com/users/Mygod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mygod/subscriptions","organizations_url":"https://api.github.com/users/Mygod/orgs","repos_url":"https://api.github.com/users/Mygod/repos","events_url":"https://api.github.com/users/Mygod/events{/privacy}","received_events_url":"https://api.github.com/users/Mygod/received_events","type":"User","site_admin":false},"labels":[{"id":22317518,"url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/milestones/2","html_url":"https://github.com/shadowsocks/shadowsocks-android/milestone/2","labels_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/milestones/2/labels","id":2247744,"number":2,"title":"4.0","description":"shadowsocks-android 4.0.","creator":{"login":"Mygod","id":3511321,"avatar_url":"https://avatars.githubusercontent.com/u/3511321?v=3","gravatar_id":"","url":"https://api.github.com/users/Mygod","html_url":"https://github.com/Mygod","followers_url":"https://api.github.com/users/Mygod/followers","following_url":"https://api.github.com/users/Mygod/following{/other_user}","gists_url":"https://api.github.com/users/Mygod/gists{/gist_id}","starred_url":"https://api.github.com/users/Mygod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mygod/subscriptions","organizations_url":"https://api.github.com/users/Mygod/orgs","repos_url":"https://api.github.com/users/Mygod/repos","events_url":"https://api.github.com/users/Mygod/events{/privacy}","received_events_url":"https://api.github.com/users/Mygod/received_events","type":"User","site_admin":false},"open_issues":1,"closed_issues":1,"state":"open","created_at":"2017-01-12T16:03:21Z","updated_at":"2017-01-23T11:28:02Z","due_on":null,"closed_at":null},"comments":47,"created_at":"2017-01-09T15:14:28Z","updated_at":"2017-01-23T11:28:04Z","closed_at":"2017-01-23T11:28:02Z","body":"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.\r\n\r\nThere 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.\r\n\r\nThere will be a library to implement the base framework contrived in this draft and some example plugins for the ease of developers. :smile:\r\n\r\n# 1. Plugin configuration\r\n\r\nPlugins get their args configured via one of the following two options:\r\n\r\n* A configuration activity.\r\n* 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)\r\n\r\n## Configuration activity\r\n\r\n* [x] Should this be implemented at all?\r\n\r\nIf the plugin provides a configuration activity, it will be started when user picks your plugin and taps configure. It:\r\n\r\n* MUST have action: `com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE`;\r\n* SHOULD parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` (all `arg` as a single string) and display the old settings;\r\n* MUST return the data Intent with the new `com.github.shadowsocks.plugin.EXTRA_OPTIONS`;\r\n* 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;\r\n* 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.\r\n\r\n## Help activity/callback\r\n\r\nIf the plugin doesn't provide a configuration activity, it's highly recommended to provide a help message in the form of an Activity. It:\r\n\r\n* MUST have action: `com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP`;\r\n* CAN parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` and display some more relevant information;\r\n* MUST be either:\r\n  - 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)\r\n  - Be visible and return null as data intent.\r\n* 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.\r\n\r\n# 2. Plugin implementation\r\n\r\nEvery plugin can be either in native mode or JVM mode.\r\n\r\n## Native mode\r\n\r\nIn native mode, plugins are provided as native executables and `shadowsocks-libev`'s plugin mode will be used.\r\n\r\nEvery 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:\r\n\r\n* MUST have `android:label` and `android:icon`; (may be configured by `activity` or `application`)\r\n* MUST have action: `com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN`. (used for discovering plugins)\r\n* MUST have `android:authorities` set to `com.github.shadowsocks.plugin.$PLUGIN_ID`;\r\n* MUST implement `query` that returns the file list which MUST include `$PLUGIN_ID` when having these as arguments:\r\n  - `uri = \"content://com.github.shadowsocks.plugin.$PLUGIN_ID\"`;\r\n  - `projection = [\"path\"]`; (relative path, for example `obfs-local`)\r\n  - `selection = null`;\r\n  - `selectionArgs = null`;\r\n  - `sortOrder = null`;\r\n* 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`.\r\n\r\n## Native mode without binary copying\r\n\r\n* [x] Should this be implemented at all?\r\n\r\nBy 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:\r\n\r\n* MUST have action: `com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_START`;\r\n* MUST be invisible;\r\n* MUST return the `com.github.shadowsocks.plugin.EXTRA_EXECUTABLE` with executable path (e.g. `/data/data/com.github.shadowsocks.plugin.kcptun/lib/libkcptun.so`).\r\n\r\nAll plugins in this mode HAVE to support native mode with binary copy.\r\n\r\nP.S. If this is implemented, \"official\" plugins will perform a tiny little bit better than \"unofficial\" ones.\r\n\r\n## JVM mode\r\n\r\nNote: 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.\r\n\r\n* [ ] Should this be implemented at all?\r\n\r\nEvery JVM mode plugin MUST have a service intent filter that:\r\n\r\n* MUST have `android:label` and `android:icon`; (may be configured by `activity` or `application`)\r\n* MUST have action: `com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_START`;\r\n* MUST have action: `com.github.shadowsocks.plugin.ACTION_JVM_PLUGIN`; (used for discovering plugins)\r\n* 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.\r\n* After shutdown or startService timeout, `stopService` will be called on target service. The plugin MUST gracefully handle this and terminate.\r\n\r\n## Default configurations\r\n\r\nIf a plugin need to supply default configuration, it can put it under the ContentProvider/Service's meta-data tag, for example:\r\n\r\n```\r\n<meta-data android:name=\"com.github.shadowsocks.plugin.default_config\"\r\n           android:value=\"obfs=http\"/>\r\n```\r\n\r\nIf 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.\r\n\r\n# 3. Plugin security considerations\r\n\r\nPlugins are certified using package signatures and shadowsocks-android will consider these signatures as trusted:\r\n\r\n* Signatures by trusted sources which includes:\r\n  - @madeye, i.e. the signer of the main repo;\r\n  - 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.\r\n* Current package signature, which means:\r\n  - If you get apk from shadowsocks-android releases or Google Play, this means only apk signed by @madeye will be recognized as trusted.\r\n  - 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.\r\n\r\nA warning will be shown for untrusted plugins. No arbitrary restriction will be applied.\r\n\r\n# 4. Plugin platform versioning\r\n\r\nIn order to be able to identify compatible and incompatible plugins, [Semantic Versioning](http://semver.org/) will be used.\r\n\r\n>Given a version number MAJOR.MINOR.PATCH, increment the:\r\n>\r\n>1. MAJOR version when you make incompatible API changes,\r\n>2. MINOR version when you add functionality in a backwards-compatible manner, and\r\n>3. PATCH version when you make backwards-compatible bug fixes.\r\n\r\nPlugin app must include this in their application tag: (which should be automatically included if you are using our library)\r\n\r\n```\r\n<meta-data android:name=\"com.github.shadowsocks.plugin.version\"\r\n           android:value=\"1.0.0\"/>\r\n```\r\n\r\n# 5. Possible problems\r\n\r\n* 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).\r\n* 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...\r\n\r\n# References\r\n\r\n* SIP003: https://github.com/shadowsocks/shadowsocks-org/issues/28;\r\n* Previous discussion at: https://github.com/shadowsocks/shadowsocks-android/issues/1054#issuecomment-270568281.\r\n\r\nOpinions are welcome!"},"comment":{"url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/comments/274463429","html_url":"https://github.com/shadowsocks/shadowsocks-android/issues/1073#issuecomment-274463429","issue_url":"https://api.github.com/repos/shadowsocks/shadowsocks-android/issues/1073","id":274463429,"user":{"login":"Mygod","id":3511321,"avatar_url":"https://avatars.githubusercontent.com/u/3511321?v=3","gravatar_id":"","url":"https://api.github.com/users/Mygod","html_url":"https://github.com/Mygod","followers_url":"https://api.github.com/users/Mygod/followers","following_url":"https://api.github.com/users/Mygod/following{/other_user}","gists_url":"https://api.github.com/users/Mygod/gists{/gist_id}","starred_url":"https://api.github.com/users/Mygod/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Mygod/subscriptions","organizations_url":"https://api.github.com/users/Mygod/orgs","repos_url":"https://api.github.com/users/Mygod/repos","events_url":"https://api.github.com/users/Mygod/events{/privacy}","received_events_url":"https://api.github.com/users/Mygod/received_events","type":"User","site_admin":false},"created_at":"2017-01-23T11:28:04Z","updated_at":"2017-01-23T11:28:04Z","body":"Nice. Closing this."}}
{"actor":{"display_login":"Mygod"}}