Commit 725fb04f authored by Mygod's avatar Mygod

Implement proxy mode and transproxy mode

* NAT mode is replaced by transproxy mode;
* BaseService classes are moved into bg package and refactored in order to minimize duplicate code;
* Local port is moved out of per-profile settings, along with hardcoded DNS port and transproxy (redsocks) port;
* Reset is removed since the only possible clean-up needed can be performed by using force stop in app info;
* Comparing version code before extracting assets is replaced with a more robust approach: comparing update time.

NB: the only code that involves su now is for toggling TFO.
parent 6678fd95
...@@ -109,13 +109,7 @@ ...@@ -109,13 +109,7 @@
</activity> </activity>
<service <service
android:name=".ShadowsocksNatService" android:name=".bg.VpnService"
android:process=":bg"
android:exported="false">
</service>
<service
android:name=".ShadowsocksVpnService"
android:process=":bg" android:process=":bg"
android:label="@string/app_name" android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
...@@ -125,7 +119,19 @@ ...@@ -125,7 +119,19 @@
</intent-filter> </intent-filter>
</service> </service>
<service android:name=".ShadowsocksTileService" android:label="@string/quick_toggle" <service
android:name=".bg.TransproxyService"
android:process=":bg"
android:exported="false">
</service>
<service
android:name=".bg.ProxyService"
android:process=":bg"
android:exported="false">
</service>
<service android:name=".bg.ServiceTileService" android:label="@string/quick_toggle"
android:process=":bg" android:process=":bg"
android:icon="@drawable/ic_start_connected" android:icon="@drawable/ic_start_connected"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>
...@@ -226,4 +226,15 @@ ...@@ -226,4 +226,15 @@
<item>@string/acl_rule_templates_domain</item> <item>@string/acl_rule_templates_domain</item>
<item>URL</item> <item>URL</item>
</string-array> </string-array>
<string-array name="service_modes">
<item>@string/service_mode_proxy</item>
<item>@string/service_mode_vpn</item>
<item>@string/service_mode_transproxy</item>
</string-array>
<string-array name="service_mode_values">
<item>proxy</item>
<item>vpn</item>
<item>transproxy</item>
</string-array>
</resources> </resources>
...@@ -7,8 +7,15 @@ ...@@ -7,8 +7,15 @@
<!-- misc --> <!-- misc -->
<string name="profile">Profile</string> <string name="profile">Profile</string>
<string name="profile_summary">Switch to another profile or add new profiles</string> <string name="profile_summary">Switch to another profile or add new profiles</string>
<string name="nat">NAT mode (deprecated)</string> <string name="advanced">Advanced</string>
<string name="nat_summary">Use NAT mode instead of VPN mode. Requires ROOT permission.</string> <string name="service_mode">Service mode</string>
<string name="service_mode_proxy">Proxy only</string>
<string name="service_mode_vpn">VPN</string>
<string name="service_mode_transproxy">Transproxy</string>
<string name="port_proxy">SOCKS5 proxy port</string>
<string name="port_local_dns">Local DNS port</string>
<string name="port_transproxy">Transproxy port</string>
<string name="remote_dns">Remote DNS</string> <string name="remote_dns">Remote DNS</string>
<string name="stat_summary">Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s</string> <string name="stat_summary">Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s</string>
<string name="stat_profiles">%1$s↑\t%2$s↓</string> <string name="stat_profiles">%1$s↑\t%2$s↓</string>
...@@ -42,9 +49,9 @@ ...@@ -42,9 +49,9 @@
<string name="route_entry_bypass_lan_chn">Bypass LAN &amp; mainland China</string> <string name="route_entry_bypass_lan_chn">Bypass LAN &amp; mainland China</string>
<string name="route_entry_gfwlist">GFW List</string> <string name="route_entry_gfwlist">GFW List</string>
<string name="route_entry_chinalist">China List</string> <string name="route_entry_chinalist">China List</string>
<string name="proxied_apps">Per-App Proxy</string> <string name="proxied_apps">Apps VPN mode</string>
<string name="proxied_apps_summary">Set proxy for selected apps, requires NAT mode under Android 4.x</string> <string name="proxied_apps_summary">Allow selected apps to bypass VPN, not available on Android 4.x</string>
<string name="proxied_apps_summary_v21">Set proxy for selected apps</string> <string name="proxied_apps_summary_v21">Allow selected apps to bypass VPN</string>
<string name="on">On</string> <string name="on">On</string>
<string name="bypass_apps">Bypass Mode</string> <string name="bypass_apps">Bypass Mode</string>
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string> <string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
...@@ -57,13 +64,11 @@ ...@@ -57,13 +64,11 @@
<!-- notification category --> <!-- notification category -->
<string name="service_vpn">VPN Service</string> <string name="service_vpn">VPN Service</string>
<string name="service_nat">NAT Service</string> <string name="service_proxy">Proxy Service</string>
<string name="service_transproxy">Transproxy Service</string>
<string name="forward_success">Shadowsocks started.</string> <string name="forward_success">Shadowsocks started.</string>
<string name="invalid_server">Invalid server name</string> <string name="invalid_server">Invalid server name</string>
<string name="service_failed">Failed to connect the remote server</string> <string name="service_failed">Failed to connect the remote server</string>
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
<string name="nat_no_root">NAT mode requires ROOT permission</string>
<string name="switch_to_vpn">Switch to VPN mode</string>
<string name="stop">Stop</string> <string name="stop">Stop</string>
<string name="stopping">Shutting down…</string> <string name="stopping">Shutting down…</string>
<string name="vpn_error">%s</string> <string name="vpn_error">%s</string>
...@@ -75,7 +80,6 @@ ...@@ -75,7 +80,6 @@
<string name="profile_empty">Please select a profile</string> <string name="profile_empty">Please select a profile</string>
<string name="proxy_empty">Proxy/Password should not be empty</string> <string name="proxy_empty">Proxy/Password should not be empty</string>
<string name="connect">Connect</string> <string name="connect">Connect</string>
<string name="recovering">Resetting…</string>
<string name="remove_profile">Remove this profile "%s"?</string> <string name="remove_profile">Remove this profile "%s"?</string>
<!-- menu category --> <!-- menu category -->
...@@ -83,7 +87,6 @@ ...@@ -83,7 +87,6 @@
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="faq">FAQ</string> <string name="faq">FAQ</string>
<string name="faq_url">https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md</string> <string name="faq_url">https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md</string>
<string name="recovery">Reset</string>
<string name="about">About</string> <string name="about">About</string>
<string name="about_title">Shadowsocks %s</string> <string name="about_title">Shadowsocks %s</string>
<string name="edit">Edit</string> <string name="edit">Edit</string>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<SwitchPreference android:key="isAutoConnect" <SwitchPreference android:key="isAutoConnect"
android:persistent="false" android:persistent="false"
android:summary="@string/auto_connect_summary" android:summary="@string/auto_connect_summary"
...@@ -7,7 +8,32 @@ ...@@ -7,7 +8,32 @@
<SwitchPreference android:key="tcp_fastopen" <SwitchPreference android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary" android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/> android:title="TCP Fast Open"/>
<SwitchPreference android:key="isNAT" <be.mygod.preference.PreferenceCategory
android:title="@string/nat" android:title="@string/advanced">
android:summary="@string/nat_summary"/> <DropDownPreference
android:key="serviceMode"
android:entries="@array/service_modes"
android:entryValues="@array/service_mode_values"
android:defaultValue="vpn"
android:summary="%s"
android:title="@string/service_mode"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="portProxy"
android:summary="%d"
android:title="@string/port_proxy"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="portLocalDns"
android:summary="%d"
android:title="@string/port_local_dns"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="portTransproxy"
android:summary="%d"
android:title="@string/port_transproxy"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
...@@ -20,12 +20,6 @@ ...@@ -20,12 +20,6 @@
android:key="remotePortNum" android:key="remotePortNum"
android:summary="%d" android:summary="%d"
android:title="@string/remote_port"/> android:title="@string/remote_port"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="localPortNum"
android:summary="%d"
android:title="@string/port"/>
<be.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:inputType="textPassword" android:inputType="textPassword"
android:key="sitekey" android:key="sitekey"
......
...@@ -23,13 +23,13 @@ package com.github.shadowsocks ...@@ -23,13 +23,13 @@ package com.github.shadowsocks
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.Preference
import be.mygod.preference.PreferenceFragment import be.mygod.preference.PreferenceFragment
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.utils.{Key, TcpFastOpen} import com.github.shadowsocks.utils.{Key, TcpFastOpen}
class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStoreChangeListener { class GlobalConfigFragment extends PreferenceFragment {
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore) getPreferenceManager.setPreferenceDataStore(app.dataStore)
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
...@@ -53,16 +53,38 @@ class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStore ...@@ -53,16 +53,38 @@ class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStore
tfo.setEnabled(false) tfo.setEnabled(false)
tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version"))) tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version")))
} }
app.dataStore.registerChangeListener(this)
val serviceMode = findPreference(Key.serviceMode)
val portProxy = findPreference(Key.portProxy)
val portLocalDns = findPreference(Key.portLocalDns)
val portTransproxy = findPreference(Key.portTransproxy)
def onServiceModeChange(p: Preference, v: Any) = {
val (enabledLocalDns, enabledTransproxy) = v match {
case Key.modeProxy => (false, false)
case Key.modeVpn => (true, false)
case Key.modeTransproxy => (true, true)
}
portLocalDns.setEnabled(enabledLocalDns)
portTransproxy.setEnabled(enabledTransproxy)
true
}
MainActivity.stateListener = {
case ServiceState.IDLE | ServiceState.STOPPED =>
serviceMode.setEnabled(true)
portProxy.setEnabled(true)
onServiceModeChange(null, app.dataStore.serviceMode)
case _ =>
serviceMode.setEnabled(false)
portProxy.setEnabled(false)
portLocalDns.setEnabled(false)
portTransproxy.setEnabled(false)
}
MainActivity.stateListener(getActivity.asInstanceOf[MainActivity].state)
serviceMode.setOnPreferenceChangeListener(onServiceModeChange)
} }
override def onDestroy() { override def onDestroy() {
app.dataStore.unregisterChangeListener(this) MainActivity.stateListener = null
super.onDestroy() super.onDestroy()
} }
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(store.getBoolean(key, false))
case _ =>
}
} }
...@@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
findPreference(Key.password).setSummary("\u2022" * 32) findPreference(Key.password).setSummary("\u2022" * 32)
} }
isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference] isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference]
isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled) isProxyApps.setEnabled(Utils.isLollipopOrAbove && app.usingVpnMode)
isProxyApps.setOnPreferenceClickListener(_ => { isProxyApps.setOnPreferenceClickListener(_ => {
startActivity(new Intent(getActivity, classOf[AppManager])) startActivity(new Intent(getActivity, classOf[AppManager]))
isProxyApps.setChecked(true) isProxyApps.setChecked(true)
......
...@@ -31,6 +31,7 @@ import android.view.View.OnLongClickListener ...@@ -31,6 +31,7 @@ import android.view.View.OnLongClickListener
import android.view._ import android.view._
import android.widget.{LinearLayout, PopupMenu, TextView, Toast} import android.widget.{LinearLayout, PopupMenu, TextView, Toast}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.{ServiceState, TrafficMonitor}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
...@@ -56,12 +57,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -56,12 +57,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
* Is ProfilesFragment editable at all. * Is ProfilesFragment editable at all.
*/ */
private def isEnabled = getActivity.asInstanceOf[MainActivity].state match { private def isEnabled = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED | State.STOPPED => true case ServiceState.CONNECTED | ServiceState.STOPPED => true
case _ => false case _ => false
} }
private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match { private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => id != app.dataStore.profileId case ServiceState.CONNECTED => id != app.dataStore.profileId
case State.STOPPED => true case ServiceState.STOPPED => true
case _ => false case _ => false
} }
...@@ -154,7 +155,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -154,7 +155,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
app.switchProfile(item.id) app.switchProfile(item.id)
profilesAdapter.refreshId(old) profilesAdapter.refreshId(old)
itemView.setSelected(true) itemView.setSelected(true)
if (activity.state == State.CONNECTED) Utils.reloadSsService(activity) if (activity.state == ServiceState.CONNECTED) Utils.reloadSsService(activity)
} }
override def onMenuItemClick(menu: MenuItem): Boolean = menu.getItemId match { override def onMenuItemClick(menu: MenuItem): Boolean = menu.getItemId match {
......
...@@ -26,7 +26,8 @@ import android.content.pm.ShortcutManager ...@@ -26,7 +26,8 @@ import android.content.pm.ShortcutManager
import android.os.{Build, Bundle} import android.os.{Build, Bundle}
import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat} import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat}
import android.support.v4.graphics.drawable.IconCompat import android.support.v4.graphics.drawable.IconCompat
import com.github.shadowsocks.utils.{State, Utils} import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.utils.Utils
/** /**
* @author Mygod * @author Mygod
...@@ -56,8 +57,8 @@ class QuickToggleShortcut extends Activity with ServiceBoundContext { ...@@ -56,8 +57,8 @@ class QuickToggleShortcut extends Activity with ServiceBoundContext {
override def onServiceConnected() { override def onServiceConnected() {
bgService.getState match { bgService.getState match {
case State.STOPPED => Utils.startSsService(this) case ServiceState.STOPPED => Utils.startSsService(this)
case State.CONNECTED => Utils.stopSsService(this) case ServiceState.CONNECTED => Utils.stopSsService(this)
case _ => // ignore case _ => // ignore
} }
finish() finish()
......
...@@ -21,10 +21,11 @@ ...@@ -21,10 +21,11 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.content.{ComponentName, Context, Intent, ServiceConnection} import android.content.{ComponentName, Context, Intent, ServiceConnection}
import android.os.{RemoteException, IBinder} import android.os.{IBinder, RemoteException}
import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService} import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.{TransproxyService, VpnService}
/** /**
* @author Mygod * @author Mygod
...@@ -85,9 +86,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { ...@@ -85,9 +86,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient {
protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) { protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) {
this.callback = callback this.callback = callback
if (bgService == null) { if (bgService == null) {
val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] val intent = new Intent(this, app.serviceClass)
val intent = new Intent(this, s)
intent.setAction(Action.SERVICE) intent.setAction(Action.SERVICE)
connection = new ShadowsocksServiceConnection() connection = new ShadowsocksServiceConnection()
......
...@@ -26,12 +26,14 @@ import java.util.Locale ...@@ -26,12 +26,14 @@ import java.util.Locale
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.{Application, NotificationChannel, NotificationManager} import android.app.{Application, NotificationChannel, NotificationManager}
import android.content._ import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.content.res.Configuration import android.content.res.Configuration
import android.os.{Build, LocaleList} import android.os.{Binder, Build, LocaleList}
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.bg.{BaseService, ProxyService, TransproxyService, VpnService}
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager} import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
...@@ -40,10 +42,8 @@ import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardE ...@@ -40,10 +42,8 @@ import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardE
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog import com.j256.ormlite.logger.LocalLog
import eu.chainfire.libsuperuser.Shell
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
object ShadowsocksApplication { object ShadowsocksApplication {
var app: ShadowsocksApplication = _ var app: ShadowsocksApplication = _
...@@ -66,8 +66,12 @@ class ShadowsocksApplication extends Application { ...@@ -66,8 +66,12 @@ class ShadowsocksApplication extends Application {
lazy val profileManager = new ProfileManager(dbHelper) lazy val profileManager = new ProfileManager(dbHelper)
lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper) lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper)
def isNatEnabled: Boolean = dataStore.isNAT def usingVpnMode: Boolean = dataStore.serviceMode == Key.modeVpn
def isVpnEnabled: Boolean = !isNatEnabled def serviceClass: Class[_] = app.dataStore.serviceMode match {
case Key.modeProxy => classOf[ProxyService]
case Key.modeVpn => classOf[VpnService]
case Key.modeTransproxy => classOf[TransproxyService]
}
// send event // send event
def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder() def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder()
...@@ -154,32 +158,32 @@ class ShadowsocksApplication extends Application { ...@@ -154,32 +158,32 @@ class ShadowsocksApplication extends Application {
TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List( if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets()
new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN), // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW) if (!(1025 to 65535 contains dataStore.portProxy))
)) dataStore.putInt(Key.portProxy, 1080 + Binder.getCallingUserHandle.hashCode)
} if (!(1025 to 65535 contains dataStore.portLocalDns))
dataStore.putInt(Key.portLocalDns, 5400 + Binder.getCallingUserHandle.hashCode)
def crashRecovery() { if (!(1025 to 65535 contains dataStore.portTransproxy))
val cmd = new ArrayBuffer[String]() dataStore.putInt(Key.portTransproxy, 8200 + Binder.getCallingUserHandle.hashCode)
for (task <- Executable.EXECUTABLES) { if (Build.VERSION.SDK_INT >= 26) {
cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) val nm = getSystemService(classOf[NotificationManager])
cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" nm.createNotificationChannels(List(
.formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN),
new NotificationChannel("service-proxy", getText(R.string.service_proxy), NotificationManager.IMPORTANCE_LOW),
new NotificationChannel("service-transproxy", getText(R.string.service_transproxy),
NotificationManager.IMPORTANCE_LOW)
))
nm.deleteNotificationChannel("service-nat") // NAT mode is gone for good
} }
if (app.isNatEnabled) {
cmd.append("iptables -t nat -F OUTPUT")
cmd.append("echo done")
val result = Shell.SU.run(cmd.toArray)
if (result != null && !result.isEmpty) return // fallback to SH
}
Shell.SH.run(cmd.toArray)
} }
lazy val info: PackageInfo = getPackageManager.getPackageInfo(getPackageName, PackageManager.GET_SIGNATURES)
def copyAssets() { def copyAssets() {
val assetManager = getAssets val assetManager = getAssets
for (dir <- List("acl", "overture")) { for (dir <- Array("acl", "overture")) {
var files: Array[String] = null var files: Array[String] = null
try files = assetManager.list(dir) catch { try files = assetManager.list(dir) catch {
case e: IOException => case e: IOException =>
...@@ -190,11 +194,9 @@ class ShadowsocksApplication extends Application { ...@@ -190,11 +194,9 @@ class ShadowsocksApplication extends Application {
autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => autoClose(new FileOutputStream(new File(getFilesDir, file)))(out =>
IOUtils.copy(in, out))) IOUtils.copy(in, out)))
} }
dataStore.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE) dataStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
} }
def updateAssets(): Unit = if (dataStore.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
def listenForPackageChanges(callback: => Unit): BroadcastReceiver = { def listenForPackageChanges(callback: => Unit): BroadcastReceiver = {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED) filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
......
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*******************************************************************************/
package com.github.shadowsocks
import java.io.File
import java.net.{Inet6Address, InetAddress}
import java.util.Locale
import android.app.Service
import android.content._
import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
class ShadowsocksNatService extends BaseService {
val TAG = "ShadowsocksNatService"
val CMD_IPTABLES_DNAT_ADD_SOCKS =
"iptables -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:8123"
val myUid: Int = android.os.Process.myUid()
var sslocalProcess: GuardedProcess = _
var sstunnelProcess: GuardedProcess = _
var redsocksProcess: GuardedProcess = _
var overtureProcess: GuardedProcess = _
var su: Shell.Interactive = _
def startShadowsocksDaemon() {
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so",
"-b", "127.0.0.1",
"-l", profile.localPort.toString,
"-t", "600",
"-c", buildShadowsocksConfig("ss-local-nat.conf"))
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
if (profile.route != Acl.ALL) {
cmd += "--acl"
cmd += Acl.getFile(profile.route match {
case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED
case route => route
}).getAbsolutePath
}
sslocalProcess = new GuardedProcess(cmd: _*).start()
}
def startDNSTunnel() {
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so",
"-t", "10",
"-b", "127.0.0.1",
"-l", (profile.localPort + 63).toString,
"-L", profile.remoteDns.split(",").head.trim + ":53",
"-c", buildShadowsocksConfig("ss-tunnel-nat.conf"))
if (profile.udpdns) cmd.append("-u")
sstunnelProcess = new GuardedProcess(cmd: _*).start()
}
def startDnsDaemon() {
overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so",
"-c", buildOvertureConfig("overture-nat.conf"))
.start()
}
def startRedsocksDaemon() {
IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"),
ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort))
redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so",
"-c", "redsocks-nat.conf").start()
}
/** Called when the activity is first created. */
def handleConnection() {
startRedsocksDaemon()
startShadowsocksDaemon()
if (!profile.udpdns)
startDnsDaemon()
else
startDNSTunnel()
setupIptables()
}
def onBind(intent: Intent): IBinder = {
Log.d(TAG, "onBind")
if (Action.SERVICE == intent.getAction) {
binder
} else {
null
}
}
def killProcesses() {
if (sslocalProcess != null) {
sslocalProcess.destroy()
sslocalProcess = null
}
if (sstunnelProcess != null) {
sstunnelProcess.destroy()
sstunnelProcess = null
}
if (redsocksProcess != null) {
redsocksProcess.destroy()
redsocksProcess = null
}
if (overtureProcess != null) {
overtureProcess.destroy()
overtureProcess = null
}
su.addCommand("iptables -t nat -F OUTPUT")
}
def setupIptables() {
val init_sb = new ArrayBuffer[String]
val http_sb = new ArrayBuffer[String]
init_sb.append("ulimit -n 4096")
init_sb.append("iptables -t nat -F OUTPUT")
val cmd_bypass = "iptables -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN"
if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) {
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host))
}
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1"))
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid))
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53"))
init_sb.append("iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:"
+ (profile.localPort + 53))
if (!profile.proxyApps || profile.bypass) {
http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS)
}
if (profile.proxyApps) {
val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap
for (pn <- profile.individual.split('\n')) uidMap.get(pn) match {
case Some(uid) =>
if (!profile.bypass) {
http_sb.append(CMD_IPTABLES_DNAT_ADD_SOCKS
.replace("-t nat", "-t nat -m owner --uid-owner " + uid))
} else {
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid))
}
case _ => // probably removed package, ignore
}
}
su.addCommand((init_sb ++ http_sb).toArray)
}
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (su == null) {
su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) =>
if (exitCode == 0) super.onStartCommand(intent, flags, startId) else {
if (su != null) {
su.close()
su = null
}
super.stopRunner(stopService = true, getString(R.string.nat_no_root))
})
Service.START_NOT_STICKY
} else super.onStartCommand(intent, flags, startId)
override def connect() {
super.connect()
// Clean up
killProcesses()
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
case Some(a) => profile.host = a
case None => throw NameNotResolvedException()
}
handleConnection()
if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES)
AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED)
}
override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-nat", true)
override def stopRunner(stopService: Boolean, msg: String = null) {
// channge the state
changeState(State.STOPPING)
app.track(TAG, "stop")
// reset NAT
killProcesses()
su.close()
su = null
super.stopRunner(stopService, msg)
}
}
...@@ -46,16 +46,12 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { ...@@ -46,16 +46,12 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
} }
def startBackgroundService() { def startBackgroundService() {
if (app.isNatEnabled) { if (app.usingVpnMode) VpnService.prepare(ShadowsocksRunnerActivity.this) match {
case null => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
case intent => startActivityForResult(intent, REQUEST_CONNECT)
} else {
Utils.startSsService(this) Utils.startSsService(this)
finish() finish()
} else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
if (intent != null) {
startActivityForResult(intent, REQUEST_CONNECT)
} else {
onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
}
} }
} }
......
...@@ -15,7 +15,7 @@ import android.view._ ...@@ -15,7 +15,7 @@ import android.view._
import android.widget.{EditText, Spinner, TextView, Toast} import android.widget.{EditText, Spinner, TextView, Toast}
import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider} import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.State import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import com.github.shadowsocks.{MainActivity, R, ToolbarFragment} import com.github.shadowsocks.{MainActivity, R, ToolbarFragment}
...@@ -41,8 +41,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi ...@@ -41,8 +41,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
import CustomRulesFragment._ import CustomRulesFragment._
private def isEnabled = getActivity.asInstanceOf[MainActivity].state match { private def isEnabled = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES case ServiceState.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES
case State.STOPPED => true case ServiceState.STOPPED => true
case _ => false case _ => false
} }
......
package com.github.shadowsocks.bg
import java.util.Locale
import java.util.concurrent.TimeUnit
import android.util.Log
/**
* @author Mygod
*/
object Executable {
val REDSOCKS = "libredsocks.so"
val PDNSD = "libpdnsd.so"
val SS_LOCAL = "libss-local.so"
val SS_TUNNEL = "libss-tunnel.so"
val TUN2SOCKS = "libtun2socks.so"
val OVERTURE = "liboverture.so"
val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE)
def killAll() {
val killer = new ProcessBuilder("killall" +: EXECUTABLES: _*).start()
if (!killer.waitFor(1, TimeUnit.SECONDS))
Log.w("killall", "%s didn't exit within 1s. Post-crash clean-up may have failed."
.formatLocal(Locale.ENGLISH, killer.toString))
}
}
package com.github.shadowsocks.bg
import java.io.File
import java.net.Inet6Address
import com.github.shadowsocks.GuardedProcess
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.utils.{IOUtils, Utils}
import org.json.{JSONArray, JSONObject}
import scala.collection.mutable.ArrayBuffer
/**
* Shadowsocks service with local DNS.
*
* @author Mygod
*/
trait LocalDnsService extends BaseService {
var overtureProcess: GuardedProcess = _
override def startNativeProcesses() {
super.startNativeProcesses()
if (!profile.udpdns) overtureProcess = new GuardedProcess(buildAdditionalArguments(ArrayBuffer[String](
new File(getApplicationInfo.nativeLibraryDir, Executable.OVERTURE).getAbsolutePath,
"-c", buildOvertureConfig("overture.conf")
)): _*).start()
}
private def buildOvertureConfig(file: String): String = {
val config = new JSONObject()
.put("BindAddress", "127.0.0.1:" + app.dataStore.portLocalDns)
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", true)
.put("HostsFile", "hosts")
.put("MinimumTTL", 3600)
.put("CacheSize", 4096)
def makeDns(name: String, address: String, edns: Boolean = true) = {
val dns = new JSONObject()
.put("Name", name)
.put("Address", (Utils.parseNumericAddress(address) match {
case _: Inet6Address => '[' + address + ']'
case _ => address
}) + ":53")
.put("Timeout", 6)
.put("EDNSClientSubnet", new JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
.put("Socks5Address", "127.0.0.1:" + app.dataStore.portProxy)
else dns.put("Protocol", "udp")
dns
}
val remoteDns = new JSONArray(profile.remoteDns.split(",").zipWithIndex.map {
case (dns, i) => makeDns("UserDef-" + i, dns.trim)
})
val localDns = new JSONArray(Array(
makeDns("Primary-1", "119.29.29.29", edns = false),
makeDns("Primary-2", "114.114.114.114", edns = false)
))
try {
val localLinkDns = com.github.shadowsocks.utils.Dns.getDnsResolver(this)
localDns.put(makeDns("Primary-3", localLinkDns, edns = false))
} catch {
case _: Exception => // Ignore
}
profile.route match {
case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", "gfwlist.txt")
case Acl.CHINALIST => config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
case _ => config
.put("PrimaryDNS", remoteDns)
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true)
}
IOUtils.writeString(new File(getFilesDir, file), config.toString)
file
}
override def killProcesses() {
super.killProcesses()
if (overtureProcess != null) {
overtureProcess.destroy()
overtureProcess = null
}
}
}
package com.github.shadowsocks.bg
/**
* Shadowsocks service at its minimum.
*
* @author Mygod
*/
class ProxyService extends BaseService {
val TAG = "ShadowsocksProxyService"
def createNotification() = new ServiceNotification(this, profile.name, "service-proxy", true)
}
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks package com.github.shadowsocks.bg
import java.util.Locale import java.util.Locale
...@@ -28,15 +28,15 @@ import android.os.{Build, PowerManager} ...@@ -28,15 +28,15 @@ import android.os.{Build, PowerManager}
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.BigTextStyle import android.support.v4.app.NotificationCompat.BigTextStyle
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v4.os.BuildCompat
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub
import com.github.shadowsocks.utils.{Action, State, TrafficMonitor, Utils} import com.github.shadowsocks.utils.{Action, Utils}
import com.github.shadowsocks.{MainActivity, R}
/** /**
* @author Mygod * @author Mygod
*/ */
class ShadowsocksNotification(private val service: BaseService, profileName: String, class ServiceNotification(private val service: BaseService, profileName: String,
channel: String, visible: Boolean = false) { channel: String, visible: Boolean = false) {
private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager] private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
private lazy val callback = new Stub { private lazy val callback = new Stub {
...@@ -76,7 +76,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str ...@@ -76,7 +76,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
service.registerReceiver(lockReceiver, screenFilter) service.registerReceiver(lockReceiver, screenFilter)
private def update(action: String, forceShow: Boolean = false) = private def update(action: String, forceShow: Boolean = false) =
if (forceShow || service.getState == State.CONNECTED) action match { if (forceShow || service.getState == ServiceState.CONNECTED) action match {
case Intent.ACTION_SCREEN_OFF => case Intent.ACTION_SCREEN_OFF =>
setVisible(visible && !Utils.isLollipopOrAbove, forceShow) setVisible(visible && !Utils.isLollipopOrAbove, forceShow)
unregisterCallback() // unregister callback to save battery unregisterCallback() // unregister callback to save battery
......
package com.github.shadowsocks.bg
/**
* @author Mygod
*/
object ServiceState {
/**
* This state will never be broadcast by the service. This state is only used to indicate that the current context
* hasn't bound to any context.
*/
val IDLE = 0
val CONNECTING = 1
val CONNECTED = 2
val STOPPING = 3
val STOPPED = 4
}
...@@ -18,20 +18,20 @@ ...@@ -18,20 +18,20 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks package com.github.shadowsocks.bg
import android.annotation.TargetApi import android.annotation.TargetApi
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.service.quicksettings.{Tile, TileService} import android.service.quicksettings.{Tile, TileService}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.utils.{State, Utils} import com.github.shadowsocks.utils.Utils
import com.github.shadowsocks.{R, ServiceBoundContext}
/** /**
* @author Mygod * @author Mygod
*/ */
@TargetApi(24) @TargetApi(24)
final class ShadowsocksTileService extends TileService with ServiceBoundContext { final class ServiceTileService extends TileService with ServiceBoundContext {
private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff) private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff)
private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy) private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy)
private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected) private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected)
...@@ -41,11 +41,11 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext ...@@ -41,11 +41,11 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext
val tile = getQsTile val tile = getQsTile
if (tile != null) { if (tile != null) {
state match { state match {
case State.STOPPED => case ServiceState.STOPPED =>
tile.setIcon(iconIdle) tile.setIcon(iconIdle)
tile.setLabel(getString(R.string.app_name)) tile.setLabel(getString(R.string.app_name))
tile.setState(Tile.STATE_INACTIVE) tile.setState(Tile.STATE_INACTIVE)
case State.CONNECTED => case ServiceState.CONNECTED =>
tile.setIcon(iconConnected) tile.setIcon(iconConnected)
tile.setLabel(if (profileName == null) getString(R.string.app_name) else profileName) tile.setLabel(if (profileName == null) getString(R.string.app_name) else profileName)
tile.setState(Tile.STATE_ACTIVE) tile.setState(Tile.STATE_ACTIVE)
...@@ -74,8 +74,8 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext ...@@ -74,8 +74,8 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext
override def onClick(): Unit = if (isLocked) unlockAndRun(toggle) else toggle() override def onClick(): Unit = if (isLocked) unlockAndRun(toggle) else toggle()
private def toggle() = if (bgService != null) bgService.getState match { private def toggle() = if (bgService != null) bgService.getState match {
case State.STOPPED => Utils.startSsService(this) case ServiceState.STOPPED => Utils.startSsService(this)
case State.CONNECTED => Utils.stopSsService(this) case ServiceState.CONNECTED => Utils.stopSsService(this)
case _ => // ignore case _ => // ignore
} }
} }
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks.utils package com.github.shadowsocks.bg
import java.text.DecimalFormat import java.text.DecimalFormat
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks.utils package com.github.shadowsocks.bg
import java.io.{File, IOException} import java.io.{File, IOException}
import java.nio.{ByteBuffer, ByteOrder} import java.nio.{ByteBuffer, ByteOrder}
......
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*******************************************************************************/
package com.github.shadowsocks.bg
import java.io.File
import java.util.Locale
import com.github.shadowsocks.GuardedProcess
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.IOUtils
import scala.collection.mutable.ArrayBuffer
object TransproxyService {
private val REDSOCKS_CONFIG = "base {\n" +
" log_debug = off;\n" +
" log_info = off;\n" +
" log = stderr;\n" +
" daemon = off;\n" +
" redirector = iptables;\n" +
"}\n" +
"redsocks {\n" +
" local_ip = 127.0.0.1;\n" +
" local_port = %d;\n" +
" ip = 127.0.0.1;\n" +
" port = %d;\n" +
" type = socks5;\n" +
"}\n"
}
class TransproxyService extends LocalDnsService {
import TransproxyService._
val TAG = "ShadowsocksTransproxyService"
var sstunnelProcess: GuardedProcess = _
var redsocksProcess: GuardedProcess = _
def startDNSTunnel() {
val cmd = ArrayBuffer[String](new File(getApplicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).getAbsolutePath,
"-t", "10",
"-b", "127.0.0.1",
"-u",
"-l", app.dataStore.portLocalDns.toString, // ss-tunnel listens on the same port as overture
"-L", profile.remoteDns.split(",").head.trim + ":53",
"-c", "shadowsocks.json") // config is already built by BaseService
sstunnelProcess = new GuardedProcess(cmd: _*).start()
}
def startRedsocksDaemon() {
IOUtils.writeString(new File(getFilesDir, "redsocks.conf"),
REDSOCKS_CONFIG.formatLocal(Locale.ENGLISH, app.dataStore.portTransproxy, app.dataStore.portProxy))
redsocksProcess = new GuardedProcess(
new File(getApplicationInfo.nativeLibraryDir, Executable.REDSOCKS).getAbsolutePath,
"-c", "redsocks.conf"
).start()
}
override def startNativeProcesses() {
startRedsocksDaemon()
super.startNativeProcesses()
if (profile.udpdns) startDNSTunnel()
}
override def killProcesses() {
super.killProcesses()
if (sstunnelProcess != null) {
sstunnelProcess.destroy()
sstunnelProcess = null
}
if (redsocksProcess != null) {
redsocksProcess.destroy()
redsocksProcess = null
}
}
def createNotification() = new ServiceNotification(this, profile.name, "service-transproxy", true)
}
...@@ -18,78 +18,49 @@ ...@@ -18,78 +18,49 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks package com.github.shadowsocks.bg
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import android.app.Service import android.app.Service
import android.content._ import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService import android.net.{VpnService => BaseVpnService}
import android.os._ import android.os.{IBinder, ParcelFileDescriptor}
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet} import com.github.shadowsocks._
import com.github.shadowsocks.utils._ import com.github.shadowsocks.acl.{Acl, Subnet}
import com.github.shadowsocks.utils.Utils
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
class ShadowsocksVpnService extends VpnService with BaseService { class VpnService extends BaseVpnService with LocalDnsService {
val TAG = "ShadowsocksVpnService" val TAG = "ShadowsocksVpnService"
val VPN_MTU = 1500 val VPN_MTU = 1500
val PRIVATE_VLAN = "26.26.26.%s" val PRIVATE_VLAN = "26.26.26.%s"
val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s" val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
var conn: ParcelFileDescriptor = _ var conn: ParcelFileDescriptor = _
var vpnThread: ShadowsocksVpnThread = _ var vpnThread: VpnThread = _
var sslocalProcess: GuardedProcess = _
var overtureProcess: GuardedProcess = _
var tun2socksProcess: GuardedProcess = _ var tun2socksProcess: GuardedProcess = _
override def onBind(intent: Intent): IBinder = { override def onBind(intent: Intent): IBinder = intent.getAction match {
val action = intent.getAction case BaseVpnService.SERVICE_INTERFACE => super[VpnService].onBind(intent)
if (VpnService.SERVICE_INTERFACE == action) { case _ => super[LocalDnsService].onBind(intent)
return super.onBind(intent)
} else if (Action.SERVICE == action) {
return binder
}
null
} }
override def onRevoke() { override def onRevoke() {
stopRunner(stopService = true) stopRunner(stopService = true)
} }
override def stopRunner(stopService: Boolean, msg: String = null) { override def killProcesses() {
if (vpnThread != null) { if (vpnThread != null) {
vpnThread.stopThread() vpnThread.stopThread()
vpnThread = null vpnThread = null
} }
super.killProcesses()
// channge the state
changeState(State.STOPPING)
app.track(TAG, "stop")
// reset VPN
killProcesses()
// close connections
if (conn != null) {
conn.close()
conn = null
}
super.stopRunner(stopService, msg)
}
def killProcesses() {
if (sslocalProcess != null) {
sslocalProcess.destroy()
sslocalProcess = null
}
if (tun2socksProcess != null) { if (tun2socksProcess != null) {
tun2socksProcess.destroy() tun2socksProcess.destroy()
tun2socksProcess = null tun2socksProcess = null
...@@ -98,85 +69,41 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -98,85 +69,41 @@ class ShadowsocksVpnService extends VpnService with BaseService {
overtureProcess.destroy() overtureProcess.destroy()
overtureProcess = null overtureProcess = null
} }
// close connections
if (conn != null) {
conn.close()
conn = null
}
} }
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = { override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = if (app.usingVpnMode)
// ensure the VPNService is prepared // ensure the VPNService is prepared
if (VpnService.prepare(this) != null) { if (BaseVpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity]) val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i) startActivity(i)
stopRunner(stopService = true) stopRunner(stopService = true)
Service.START_NOT_STICKY Service.START_NOT_STICKY
} else super.onStartCommand(intent, flags, startId) } else super.onStartCommand(intent, flags, startId)
else { // system or other apps are trying to start this service but other services could be running
stopSelf()
Service.START_NOT_STICKY
} }
override def createNotification() = new ShadowsocksNotification(this, profile.name, "service-vpn") override def createNotification() = new ServiceNotification(this, profile.name, "service-vpn")
override def connect() {
super.connect()
vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
// reset the context
killProcesses()
// Resolve the server address
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
case Some(addr) => profile.host = addr
case None => throw NameNotResolvedException()
}
handleConnection()
changeState(State.CONNECTED)
if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES)
AclSyncJob.schedule(profile.route)
}
/** Called when the activity is first created. */ /** Called when the activity is first created. */
def handleConnection() { override def startNativeProcesses() {
vpnThread = new VpnThread(this)
startShadowsocksDaemon() vpnThread.start()
if (!profile.udpdns) { super.startNativeProcesses()
startDnsDaemon()
}
val fd = startVpn() val fd = startVpn()
if (!sendFd(fd)) throw new Exception("sendFd failed") if (!sendFd(fd)) throw new Exception("sendFd failed")
} }
override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V" override protected def buildAdditionalArguments(cmd: ArrayBuffer[String]): ArrayBuffer[String] = cmd += "-V"
def startShadowsocksDaemon() {
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so",
"-V",
"-u",
"-b", "127.0.0.1",
"-l", profile.localPort.toString,
"-t", "600",
"-c", buildShadowsocksConfig("ss-local-vpn.conf"))
if (profile.route != Acl.ALL) {
cmd += "--acl"
cmd += Acl.getFile(profile.route match {
case Acl.CUSTOM_RULES => Acl.CUSTOM_RULES_FLATTENED
case route => route
}).getAbsolutePath
}
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
sslocalProcess = new GuardedProcess(cmd: _*).start()
}
def startDnsDaemon() {
overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so",
"-c", buildOvertureConfig("overture-vpn.conf"), "-V")
.start()
}
def startVpn(): Int = { def startVpn(): Int = {
...@@ -229,10 +156,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -229,10 +156,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val fd = conn.getFd val fd = conn.getFd
var cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libtun2socks.so", var cmd = ArrayBuffer[String](new File(getApplicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).getAbsolutePath,
"--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), "--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
"--netif-netmask", "255.255.255.0", "--netif-netmask", "255.255.255.0",
"--socks-server-addr", "127.0.0.1:" + profile.localPort, "--socks-server-addr", "127.0.0.1:" + app.dataStore.portProxy,
"--tunfd", fd.toString, "--tunfd", fd.toString,
"--tunmtu", VPN_MTU.toString, "--tunmtu", VPN_MTU.toString,
"--sock-path", "sock_path", "--sock-path", "sock_path",
...@@ -244,8 +171,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -244,8 +171,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
cmd += "--enable-udprelay" cmd += "--enable-udprelay"
if (!profile.udpdns) if (!profile.udpdns)
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1", cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1", app.dataStore.portLocalDns))
profile.localPort + 53))
tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd)) tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd))
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks package com.github.shadowsocks.bg
import java.io.{File, FileDescriptor, IOException} import java.io.{File, FileDescriptor, IOException}
import java.lang.reflect.Method import java.lang.reflect.Method
...@@ -26,14 +26,15 @@ import java.util.concurrent.Executors ...@@ -26,14 +26,15 @@ import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log import android.util.Log
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
object ShadowsocksVpnThread { object VpnThread {
val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$") val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$")
} }
class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread { class VpnThread(service: VpnService) extends Thread {
import ShadowsocksVpnThread._ import VpnThread._
val TAG = "ShadowsocksVpnService" val TAG = "ShadowsocksVpnService"
val protect = new File(service.getFilesDir, "protect_path") val protect = new File(service.getFilesDir, "protect_path")
......
...@@ -22,11 +22,12 @@ package com.github.shadowsocks.database ...@@ -22,11 +22,12 @@ package com.github.shadowsocks.database
import java.nio.ByteBuffer import java.nio.ByteBuffer
import android.content.{Context, SharedPreferences} import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.preference.PreferenceManager import android.preference.PreferenceManager
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao import com.j256.ormlite.dao.Dao
...@@ -50,7 +51,7 @@ object DBHelper { ...@@ -50,7 +51,7 @@ object DBHelper {
} }
class DBHelper(val context: Context) class DBHelper(val context: Context)
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 23) { extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 24) {
import DBHelper._ import DBHelper._
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
...@@ -148,12 +149,8 @@ class DBHelper(val context: Context) ...@@ -148,12 +149,8 @@ class DBHelper(val context: Context)
val old = PreferenceManager.getDefaultSharedPreferences(app) val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT, kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array())) ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.isNAT, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.isNAT, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN, kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array())) ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.currentVersionCode, TYPE_INT,
ByteBuffer.allocate(4).putInt(-1).array()))
} }
} catch { } catch {
case ex: Exception => case ex: Exception =>
......
...@@ -40,10 +40,6 @@ class Profile { ...@@ -40,10 +40,6 @@ class Profile {
@DatabaseField @DatabaseField
var host: String = "198.199.101.152" var host: String = "198.199.101.152"
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
@DatabaseField
var localPort: Int = 1080 + Binder.getCallingUserHandle.hashCode
@DatabaseField @DatabaseField
var remotePort: Int = 8388 var remotePort: Int = 8388
...@@ -111,7 +107,6 @@ class Profile { ...@@ -111,7 +107,6 @@ class Profile {
def serialize(store: OrmLitePreferenceDataStore) { def serialize(store: OrmLitePreferenceDataStore) {
store.putString(Key.name, name) store.putString(Key.name, name)
store.putString(Key.host, host) store.putString(Key.host, host)
store.putInt(Key.localPort, localPort)
store.putInt(Key.remotePort, remotePort) store.putInt(Key.remotePort, remotePort)
store.putString(Key.password, password) store.putString(Key.password, password)
store.putString(Key.route, route) store.putString(Key.route, route)
...@@ -129,7 +124,6 @@ class Profile { ...@@ -129,7 +124,6 @@ class Profile {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = store.getString(Key.name, null) name = store.getString(Key.name, null)
host = store.getString(Key.host, null) host = store.getString(Key.host, null)
localPort = store.getInt(Key.localPort, 0)
remotePort = store.getInt(Key.remotePort, 0) remotePort = store.getInt(Key.remotePort, 0)
password = store.getString(Key.password, null) password = store.getString(Key.password, null)
method = store.getString(Key.method, null) method = store.getString(Key.method, null)
......
...@@ -28,8 +28,7 @@ object PluginManager { ...@@ -28,8 +28,7 @@ object PluginManager {
* If you don't plan to publish any plugin but is developing/has developed some, it's not necessary to add your * If you don't plan to publish any plugin but is developing/has developed some, it's not necessary to add your
* public key yet since it will also automatically trust packages signed by the same signatures, e.g. debug keys. * public key yet since it will also automatically trust packages signed by the same signatures, e.g. debug keys.
*/ */
lazy val trustedSignatures: Set[Signature] = lazy val trustedSignatures: Set[Signature] = app.info.signatures.toSet +
app.getPackageManager.getPackageInfo(app.getPackageName, PackageManager.GET_SIGNATURES).signatures.toSet +
new Signature(Base64.decode( // @Mygod new Signature(Base64.decode( // @Mygod
""" """
|MIIDWzCCAkOgAwIBAgIEUzfv8DANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD |MIIDWzCCAkOgAwIBAgIEUzfv8DANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD
......
...@@ -19,6 +19,5 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM ...@@ -19,6 +19,5 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM
override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager)
override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG)
override def packageName: String = resolveInfo.resolvePackageName override def packageName: String = resolveInfo.resolvePackageName
override final lazy val trusted: Boolean = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) override final lazy val trusted: Boolean = app.info.signatures.exists(PluginManager.trustedSignatures.contains)
.signatures.exists(PluginManager.trustedSignatures.contains)
} }
...@@ -100,8 +100,10 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat ...@@ -100,8 +100,10 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat
def profileId: Int = getInt(Key.id, 0) def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i) def profileId_=(i: Int): Unit = putInt(Key.id, i)
def isNAT: Boolean = getBoolean(Key.isNAT) def serviceMode: String = getString(Key.serviceMode, Key.modeVpn)
def isNAT_=(value: Boolean): Unit = putBoolean(Key.isNAT, value) def portProxy: Int = getInt(Key.portProxy, 0)
def portLocalDns: Int = getInt(Key.portLocalDns, 0)
def portTransproxy: Int = getInt(Key.portTransproxy, 0)
def proxyApps: Boolean = getBoolean(Key.proxyApps) def proxyApps: Boolean = getBoolean(Key.proxyApps)
def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value) def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
......
...@@ -20,40 +20,20 @@ ...@@ -20,40 +20,20 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
object Executable {
val REDSOCKS = "redsocks"
val PDNSD = "pdnsd"
val SS_LOCAL = "ss-local"
val SS_TUNNEL = "ss-tunnel"
val TUN2SOCKS = "tun2socks"
val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS)
}
object ConfigUtils {
val REDSOCKS = "base {\n" +
" log_debug = off;\n" +
" log_info = off;\n" +
" log = stderr;\n" +
" daemon = off;\n" +
" redirector = iptables;\n" +
"}\n" +
"redsocks {\n" +
" local_ip = 127.0.0.1;\n" +
" local_port = 8123;\n" +
" ip = 127.0.0.1;\n" +
" port = %d;\n" +
" type = socks5;\n" +
"}\n"
}
object Key { object Key {
val id = "profileId" val id = "profileId"
val name = "profileName" val name = "profileName"
val individual = "Proxyed" val individual = "Proxyed"
val isNAT = "isNAT" val serviceMode = "serviceMode"
val modeProxy = "proxy"
val modeVpn = "vpn"
val modeTransproxy = "transproxy"
val portProxy = "portProxy"
val portLocalDns = "portLocalDns"
val portTransproxy = "portTransproxy"
val route = "route" val route = "route"
val isAutoConnect = "isAutoConnect" val isAutoConnect = "isAutoConnect"
...@@ -67,7 +47,6 @@ object Key { ...@@ -67,7 +47,6 @@ object Key {
val password = "sitekey" val password = "sitekey"
val method = "encMethod" val method = "encMethod"
val remotePort = "remotePortNum" val remotePort = "remotePortNum"
val localPort = "localPortNum"
val remoteDns = "remoteDns" val remoteDns = "remoteDns"
val plugin = "plugin" val plugin = "plugin"
...@@ -76,19 +55,7 @@ object Key { ...@@ -76,19 +55,7 @@ object Key {
val dirty = "profileDirty" val dirty = "profileDirty"
val tfo = "tcp_fastopen" val tfo = "tcp_fastopen"
val currentVersionCode = "currentVersionCode" val assetUpdateTime = "assetUpdateTime"
}
object State {
/**
* This state will never be broadcast by the service. This state is only used to indicate that the current context
* hasn't bound to any context.
*/
val IDLE = 0
val CONNECTING = 1
val CONNECTED = 2
val STOPPING = 3
val STOPPED = 4
} }
object Action { object Action {
......
...@@ -33,7 +33,8 @@ import android.view.View.MeasureSpec ...@@ -33,7 +33,8 @@ import android.view.View.MeasureSpec
import android.view.{Gravity, View, Window} import android.view.{Gravity, View, Window}
import android.widget.Toast import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.{BuildConfig, ShadowsocksNatService, ShadowsocksVpnService} import com.github.shadowsocks.bg.{ProxyService, TransproxyService, VpnService}
import com.github.shadowsocks.BuildConfig
import org.xbill.DNS._ import org.xbill.DNS._
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
...@@ -47,11 +48,8 @@ object Utils { ...@@ -47,11 +48,8 @@ object Utils {
def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
def getSignature(context: Context): String = { def getSignature(context: Context): String = {
val info = context
.getPackageManager
.getPackageInfo(context.getPackageName, PackageManager.GET_SIGNATURES)
val mdg = MessageDigest.getInstance("SHA-1") val mdg = MessageDigest.getInstance("SHA-1")
mdg.update(info.signatures(0).toByteArray) mdg.update(app.info.signatures(0).toByteArray)
new String(Base64.encode(mdg.digest, 0)) new String(Base64.encode(mdg.digest, 0))
} }
...@@ -107,7 +105,7 @@ object Utils { ...@@ -107,7 +105,7 @@ object Utils {
} }
def resolve(host: String, enableIPv6: Boolean): Option[String] = def resolve(host: String, enableIPv6: Boolean): Option[String] =
(if (enableIPv6 && Utils.isIPv6Support) resolve(host, Type.AAAA) else None).orElse(resolve(host, Type.A)) (if (enableIPv6 && isIPv6Support) resolve(host, Type.AAAA) else None).orElse(resolve(host, Type.A))
.orElse(resolve(host)) .orElse(resolve(host))
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String]) private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
...@@ -137,8 +135,7 @@ object Utils { ...@@ -137,8 +135,7 @@ object Utils {
} }
def startSsService(context: Context) { def startSsService(context: Context) {
val intent = val intent = new Intent(context, app.serviceClass)
new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService])
if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent) if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent)
} }
def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD)) def reloadSsService(context: Context): Unit = context.sendBroadcast(new Intent(Action.RELOAD))
......
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