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 @@
</activity>
<service
android:name=".ShadowsocksNatService"
android:process=":bg"
android:exported="false">
</service>
<service
android:name=".ShadowsocksVpnService"
android:name=".bg.VpnService"
android:process=":bg"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
......@@ -125,7 +119,19 @@
</intent-filter>
</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:icon="@drawable/ic_start_connected"
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 @@
<item>@string/acl_rule_templates_domain</item>
<item>URL</item>
</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>
......@@ -7,8 +7,15 @@
<!-- misc -->
<string name="profile">Profile</string>
<string name="profile_summary">Switch to another profile or add new profiles</string>
<string name="nat">NAT mode (deprecated)</string>
<string name="nat_summary">Use NAT mode instead of VPN mode. Requires ROOT permission.</string>
<string name="advanced">Advanced</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="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>
......@@ -42,9 +49,9 @@
<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_chinalist">China List</string>
<string name="proxied_apps">Per-App Proxy</string>
<string name="proxied_apps_summary">Set proxy for selected apps, requires NAT mode under Android 4.x</string>
<string name="proxied_apps_summary_v21">Set proxy for selected apps</string>
<string name="proxied_apps">Apps VPN mode</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">Allow selected apps to bypass VPN</string>
<string name="on">On</string>
<string name="bypass_apps">Bypass Mode</string>
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
......@@ -57,13 +64,11 @@
<!-- notification category -->
<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="invalid_server">Invalid server name</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="stopping">Shutting down…</string>
<string name="vpn_error">%s</string>
......@@ -75,7 +80,6 @@
<string name="profile_empty">Please select a profile</string>
<string name="proxy_empty">Proxy/Password should not be empty</string>
<string name="connect">Connect</string>
<string name="recovering">Resetting…</string>
<string name="remove_profile">Remove this profile "%s"?</string>
<!-- menu category -->
......@@ -83,7 +87,6 @@
<string name="settings">Settings</string>
<string name="faq">FAQ</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_title">Shadowsocks %s</string>
<string name="edit">Edit</string>
......
<?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"
android:persistent="false"
android:summary="@string/auto_connect_summary"
......@@ -7,7 +8,32 @@
<SwitchPreference android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/>
<SwitchPreference android:key="isNAT"
android:title="@string/nat"
android:summary="@string/nat_summary"/>
<be.mygod.preference.PreferenceCategory
android:title="@string/advanced">
<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>
......@@ -20,12 +20,6 @@
android:key="remotePortNum"
android:summary="%d"
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
android:inputType="textPassword"
android:key="sitekey"
......
......@@ -23,13 +23,13 @@ package com.github.shadowsocks
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.PreferenceDataStore
import android.support.v7.preference.Preference
import be.mygod.preference.PreferenceFragment
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}
class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStoreChangeListener {
class GlobalConfigFragment extends PreferenceFragment {
override def onCreatePreferences(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
addPreferencesFromResource(R.xml.pref_global)
......@@ -53,16 +53,38 @@ class GlobalConfigFragment extends PreferenceFragment with OnPreferenceDataStore
tfo.setEnabled(false)
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() {
app.dataStore.unregisterChangeListener(this)
MainActivity.stateListener = null
super.onDestroy()
}
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(store.getBoolean(key, false))
case _ =>
}
}
......@@ -24,12 +24,12 @@ import java.lang.System.currentTimeMillis
import java.net.{HttpURLConnection, URL}
import java.util.Locale
import android.app.Activity
import android.app.backup.BackupManager
import android.app.{Activity, ProgressDialog}
import android.content._
import android.net.{Uri, VpnService}
import android.nfc.{NdefMessage, NfcAdapter}
import android.os.{Build, Bundle, Handler, Message}
import android.os.{Bundle, Handler}
import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat
......@@ -45,6 +45,7 @@ import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, CustomRulesFragment}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.bg.{Executable, ServiceState, TrafficMonitor}
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.CloseUtils.autoDisconnect
import com.github.shadowsocks.utils._
......@@ -61,10 +62,11 @@ object MainActivity {
private final val DRAWER_PROFILES = 0L
private final val DRAWER_GLOBAL_SETTINGS = 1L
private final val DRAWER_RECOVERY = 2L
private final val DRAWER_ABOUT = 3L
private final val DRAWER_FAQ = 4L
private final val DRAWER_CUSTOM_RULES = 5L
var stateListener: Int => Unit = _
}
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
......@@ -109,42 +111,41 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
}
private def changeState(s: Int, profileName: String = null, m: String = null) {
s match {
case State.CONNECTING =>
case ServiceState.CONNECTING =>
fab.setImageResource(R.drawable.ic_start_busy)
fabProgressCircle.show()
statusText.setText(R.string.connecting)
case State.CONNECTED =>
if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation()
case ServiceState.CONNECTED =>
if (state == ServiceState.CONNECTING) fabProgressCircle.beginFinalAnimation()
else fabProgressCircle.postDelayed(hideCircle, 1000)
fab.setImageResource(R.drawable.ic_start_connected)
statusText.setText(if (app.isNatEnabled) R.string.nat_connected else R.string.vpn_connected)
case State.STOPPING =>
statusText.setText(if (app.usingVpnMode) R.string.vpn_connected else R.string.nat_connected)
case ServiceState.STOPPING =>
fab.setImageResource(R.drawable.ic_start_busy)
if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped
if (state == ServiceState.CONNECTED) fabProgressCircle.show() // ignore for stopped
statusText.setText(R.string.stopping)
case _ =>
fab.setImageResource(R.drawable.ic_start_idle)
fabProgressCircle.postDelayed(hideCircle, 1000)
if (m != null) {
val snackbar = Snackbar.make(findViewById(R.id.snackbar),
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG)
if (m == getString(R.string.nat_no_root)) addDisableNatToSnackbar(snackbar)
snackbar.show()
Snackbar.make(findViewById(R.id.snackbar),
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show()
Log.e(TAG, "Error to start VPN service: " + m)
}
statusText.setText(R.string.not_connected)
}
state = s
if (state == State.CONNECTED) fab.setBackgroundTintList(greenTint) else {
if (state == ServiceState.CONNECTED) fab.setBackgroundTintList(greenTint) else {
fab.setBackgroundTintList(greyTint)
updateTraffic(-1, 0, 0, 0, 0)
testCount += 1 // suppress previous test messages
}
if (ProfilesFragment.instance != null)
ProfilesFragment.instance.profilesAdapter.notifyDataSetChanged() // refresh button enabled state
if (stateListener != null) stateListener(s)
fab.setEnabled(false)
if (state == State.CONNECTED || state == State.STOPPED)
handler.postDelayed(() => fab.setEnabled(state == State.CONNECTED || state == State.STOPPED), 1000)
if (state == ServiceState.CONNECTED || state == ServiceState.STOPPED)
handler.postDelayed(() => fab.setEnabled(state == ServiceState.CONNECTED || state == ServiceState.STOPPED), 1000)
}
def updateTraffic(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
txText.setText(TrafficMonitor.formatTraffic(txTotal))
......@@ -152,25 +153,15 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
txRateText.setText(TrafficMonitor.formatTraffic(txRate) + "/s")
rxRateText.setText(TrafficMonitor.formatTraffic(rxRate) + "/s")
val child = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (state != State.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
if (state != ServiceState.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
}
override def onServiceConnected() {
changeState(bgService.getState)
if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) {
val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG)
addDisableNatToSnackbar(snackbar)
snackbar.show()
}
}
override def onServiceDisconnected(): Unit = changeState(State.IDLE)
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
if (state == State.STOPPED) app.dataStore.isNAT = false): View.OnClickListener)
override def onServiceConnected(): Unit = changeState(bgService.getState)
override def onServiceDisconnected(): Unit = changeState(ServiceState.IDLE)
override def binderDied(): Unit = handler.post(() => {
detachService()
app.crashRecovery()
Executable.killAll()
attachService(callback)
})
......@@ -210,12 +201,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
.withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_action_help_outline))
.withIconTintingEnabled(true)
.withSelectable(false),
new PrimaryDrawerItem()
.withIdentifier(DRAWER_RECOVERY)
.withName(R.string.recovery)
.withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_navigation_refresh))
.withIconTintingEnabled(true)
.withSelectable(false),
new PrimaryDrawerItem()
.withIdentifier(DRAWER_ABOUT)
.withName(R.string.about)
......@@ -257,7 +242,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
rxText = findViewById(R.id.rx).asInstanceOf[TextView]
rxRateText = findViewById(R.id.rxRate).asInstanceOf[TextView]
findViewById[View](R.id.stat).setOnClickListener(_ => if (state == State.CONNECTED && app.isVpnEnabled) {
findViewById[View](R.id.stat).setOnClickListener(_ => if (state == ServiceState.CONNECTED && app.usingVpnMode) {
testCount += 1
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
......@@ -299,20 +284,20 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
fab.setOnClickListener(_ => if (state == State.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture {
if (app.isNatEnabled) Utils.startSsService(this) else {
fab.setOnClickListener(_ => if (state == ServiceState.CONNECTED) Utils.stopSsService(this) else Utils.ThrowableFuture {
if (app.usingVpnMode) {
val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null))
}
} else Utils.startSsService(this)
})
fab.setOnLongClickListener(_ => {
Utils.positionToast(Toast.makeText(this, if (state == State.CONNECTED) R.string.stop else R.string.connect,
Utils.positionToast(Toast.makeText(this, if (state == ServiceState.CONNECTED) R.string.stop else R.string.connect,
Toast.LENGTH_SHORT), fab, getWindow, 0, getResources.getDimensionPixelOffset(R.dimen.margin_small)).show()
true
})
changeState(State.IDLE) // reset everything to init state
changeState(ServiceState.IDLE) // reset everything to init state
handler.post(() => attachService(callback))
app.dataStore.registerChangeListener(this)
......@@ -352,7 +337,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
}
def onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String): Unit = key match {
case Key.isNAT => handler.post(() => {
case Key.serviceMode => handler.post(() => {
detachService()
attachService(callback)
})
......@@ -367,18 +352,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = {
drawerItem.getIdentifier match {
case DRAWER_PROFILES => displayFragment(new ProfilesFragment)
case DRAWER_RECOVERY =>
app.track("GlobalConfigFragment", "reset")
Utils.stopSsService(this)
val dialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, false)
val handler = new Handler {
override def handleMessage(msg: Message): Unit = if (dialog.isShowing && !isDestroyed) dialog.dismiss()
}
Utils.ThrowableFuture {
app.crashRecovery()
app.copyAssets()
handler.sendEmptyMessage(0)
}
case DRAWER_GLOBAL_SETTINGS => displayFragment(new GlobalSettingsFragment)
case DRAWER_ABOUT =>
app.track(TAG, "about")
......@@ -394,7 +367,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
super.onResume()
app.remoteConfig.fetch()
state match {
case State.STOPPING | State.CONNECTING =>
case ServiceState.STOPPING | ServiceState.CONNECTING =>
case _ => hideCircle()
}
}
......
......@@ -66,7 +66,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
findPreference(Key.password).setSummary("\u2022" * 32)
}
isProxyApps = findPreference(Key.proxyApps).asInstanceOf[SwitchPreference]
isProxyApps.setEnabled(Utils.isLollipopOrAbove || app.isNatEnabled)
isProxyApps.setEnabled(Utils.isLollipopOrAbove && app.usingVpnMode)
isProxyApps.setOnPreferenceClickListener(_ => {
startActivity(new Intent(getActivity, classOf[AppManager]))
isProxyApps.setChecked(true)
......
......@@ -31,6 +31,7 @@ import android.view.View.OnLongClickListener
import android.view._
import android.widget.{LinearLayout, PopupMenu, TextView, Toast}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.{ServiceState, TrafficMonitor}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.utils._
......@@ -56,12 +57,12 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
* Is ProfilesFragment editable at all.
*/
private def isEnabled = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED | State.STOPPED => true
case ServiceState.CONNECTED | ServiceState.STOPPED => true
case _ => false
}
private def isProfileEditable(id: => Int) = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => id != app.dataStore.profileId
case State.STOPPED => true
case ServiceState.CONNECTED => id != app.dataStore.profileId
case ServiceState.STOPPED => true
case _ => false
}
......@@ -154,7 +155,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
app.switchProfile(item.id)
profilesAdapter.refreshId(old)
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 {
......
......@@ -26,7 +26,8 @@ import android.content.pm.ShortcutManager
import android.os.{Build, Bundle}
import android.support.v4.content.pm.{ShortcutInfoCompat, ShortcutManagerCompat}
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
......@@ -56,8 +57,8 @@ class QuickToggleShortcut extends Activity with ServiceBoundContext {
override def onServiceConnected() {
bgService.getState match {
case State.STOPPED => Utils.startSsService(this)
case State.CONNECTED => Utils.stopSsService(this)
case ServiceState.STOPPED => Utils.startSsService(this)
case ServiceState.CONNECTED => Utils.stopSsService(this)
case _ => // ignore
}
finish()
......
......@@ -21,10 +21,11 @@
package com.github.shadowsocks
import android.content.{ComponentName, Context, Intent, ServiceConnection}
import android.os.{RemoteException, IBinder}
import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
import android.os.{IBinder, RemoteException}
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.{TransproxyService, VpnService}
/**
* @author Mygod
......@@ -85,9 +86,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient {
protected def attachService(callback: IShadowsocksServiceCallback.Stub = null) {
this.callback = callback
if (bgService == null) {
val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
val intent = new Intent(this, s)
val intent = new Intent(this, app.serviceClass)
intent.setAction(Action.SERVICE)
connection = new ShadowsocksServiceConnection()
......
......@@ -26,12 +26,14 @@ import java.util.Locale
import android.annotation.SuppressLint
import android.app.{Application, NotificationChannel, NotificationManager}
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.content.res.Configuration
import android.os.{Build, LocaleList}
import android.os.{Binder, Build, LocaleList}
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import com.evernote.android.job.JobManager
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.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.CloseUtils._
......@@ -40,10 +42,8 @@ import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardE
import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog
import eu.chainfire.libsuperuser.Shell
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
object ShadowsocksApplication {
var app: ShadowsocksApplication = _
......@@ -66,8 +66,12 @@ class ShadowsocksApplication extends Application {
lazy val profileManager = new ProfileManager(dbHelper)
lazy val dataStore = new OrmLitePreferenceDataStore(dbHelper)
def isNatEnabled: Boolean = dataStore.isNAT
def isVpnEnabled: Boolean = !isNatEnabled
def usingVpnMode: Boolean = dataStore.serviceMode == Key.modeVpn
def serviceClass: Class[_] = app.dataStore.serviceMode match {
case Key.modeProxy => classOf[ProxyService]
case Key.modeVpn => classOf[VpnService]
case Key.modeTransproxy => classOf[TransproxyService]
}
// send event
def track(category: String, action: String): Unit = tracker.send(new HitBuilders.EventBuilder()
......@@ -154,32 +158,32 @@ class ShadowsocksApplication extends Application {
TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (Build.VERSION.SDK_INT >= 26) getSystemService(classOf[NotificationManager]).createNotificationChannels(List(
new NotificationChannel("service-vpn", getText(R.string.service_vpn), NotificationManager.IMPORTANCE_MIN),
new NotificationChannel("service-nat", getText(R.string.service_nat), NotificationManager.IMPORTANCE_LOW)
))
}
def crashRecovery() {
val cmd = new ArrayBuffer[String]()
for (task <- Executable.EXECUTABLES) {
cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task))
cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf"
.formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task))
if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets()
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
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)
if (!(1025 to 65535 contains dataStore.portTransproxy))
dataStore.putInt(Key.portTransproxy, 8200 + Binder.getCallingUserHandle.hashCode)
if (Build.VERSION.SDK_INT >= 26) {
val nm = getSystemService(classOf[NotificationManager])
nm.createNotificationChannels(List(
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() {
val assetManager = getAssets
for (dir <- List("acl", "overture")) {
for (dir <- Array("acl", "overture")) {
var files: Array[String] = null
try files = assetManager.list(dir) catch {
case e: IOException =>
......@@ -190,11 +194,9 @@ class ShadowsocksApplication extends Application {
autoClose(new FileOutputStream(new File(getFilesDir, file)))(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 = {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
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 {
}
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)
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._
import android.widget.{EditText, Spinner, TextView, Toast}
import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider}
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.{MainActivity, R, ToolbarFragment}
......@@ -41,8 +41,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
import CustomRulesFragment._
private def isEnabled = getActivity.asInstanceOf[MainActivity].state match {
case State.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES
case State.STOPPED => true
case ServiceState.CONNECTED => app.currentProfile.get.route != Acl.CUSTOM_RULES
case ServiceState.STOPPED => true
case _ => false
}
......
......@@ -18,10 +18,10 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
import java.io.{File, IOException}
import java.net.{Inet6Address, InetAddress}
import java.net.InetAddress
import java.util
import java.util.concurrent.TimeUnit
import java.util.{Timer, TimerTask}
......@@ -33,25 +33,27 @@ import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.{Acl, AclSyncJob}
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions}
import com.github.shadowsocks.utils._
import com.github.shadowsocks.{GuardedProcess, R}
import okhttp3.{Dns, FormBody, OkHttpClient, Request}
import org.json.{JSONArray, JSONObject}
import org.json.JSONObject
import scala.collection.JavaConversions._
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
trait BaseService extends Service {
@volatile private var state = State.STOPPED
protected val TAG: String
@volatile private var state = ServiceState.STOPPED
@volatile protected var profile: Profile = _
@volatile private var plugin: PluginOptions = _
@volatile protected var pluginPath: String = _
var sslocalProcess: GuardedProcess = _
case class NameNotResolvedException() extends IOException
case class NullConnectionException() extends NullPointerException
......@@ -64,7 +66,7 @@ trait BaseService extends Service {
lazy val handler = new Handler(getMainLooper)
lazy val restartHanlder = new Handler(getMainLooper)
private var notification: ShadowsocksNotification = _
private var notification: ServiceNotification = _
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => intent.getAction match {
case Action.RELOAD => forceLoad()
case _ =>
......@@ -87,11 +89,11 @@ trait BaseService extends Service {
if (timer == null) {
timer = new Timer(true)
timer.schedule(new TimerTask {
def run(): Unit = if (state == State.CONNECTED && TrafficMonitor.updateRate()) updateTrafficRate()
def run(): Unit = if (state == ServiceState.CONNECTED && TrafficMonitor.updateRate()) updateTrafficRate()
}, 1000, 1000)
}
TrafficMonitor.updateRate()
if (state == State.CONNECTED) cb.trafficUpdated(profile.id,
if (state == ServiceState.CONNECTED) cb.trafficUpdated(profile.id,
TrafficMonitor.txRate, TrafficMonitor.rxRate, TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
}
......@@ -107,6 +109,11 @@ trait BaseService extends Service {
}
}
def onBind(intent: Intent): IBinder = intent.getAction match {
case Action.SERVICE => binder
case _ => null
}
def checkProfile(profile: Profile): Boolean = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
stopRunner(stopService = true, getString(R.string.proxy_empty))
false
......@@ -115,52 +122,61 @@ trait BaseService extends Service {
def forceLoad(): Unit = app.currentProfile.orNull match {
case null => stopRunner(stopService = true, getString(R.string.profile_empty))
case p => if (checkProfile(p)) state match {
case State.STOPPED => startRunner()
case State.CONNECTED =>
case ServiceState.STOPPED => startRunner()
case ServiceState.CONNECTED =>
stopRunner(stopService = false)
startRunner()
case s => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + s)
}
}
def connect() {
if (profile.host == "198.199.101.152") {
val client = new OkHttpClient.Builder()
.dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match {
case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip))
case _ => Dns.SYSTEM.lookup(hostname)
})
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val requestBody = new FormBody.Builder()
.add("sig", Utils.getSignature(this))
.build()
val request = new Request.Builder()
.url(app.remoteConfig.getString("proxy_url"))
.post(requestBody)
.build()
val proxies = Random.shuffle(client.newCall(request).execute().body.string.split('|').toSeq)
val proxy = proxies.head.split(':')
profile.host = proxy(0).trim
profile.remotePort = proxy(1).trim.toInt
profile.password = proxy(2).trim
profile.method = proxy(3).trim
protected def buildAdditionalArguments(cmd: ArrayBuffer[String]): ArrayBuffer[String] = cmd
/**
* BaseService will only start ss-local. Child class override this class to start other native processes.
*/
def startNativeProcesses() {
buildShadowsocksConfig()
val cmd = buildAdditionalArguments(ArrayBuffer[String](
new File(getApplicationInfo.nativeLibraryDir, Executable.SS_LOCAL).getAbsolutePath,
"-u",
"-b", "127.0.0.1",
"-l", app.dataStore.portProxy.toString,
"-t", "600",
"-c", "shadowsocks.json"))
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 (profile.route == Acl.CUSTOM_RULES) Acl.save(Acl.CUSTOM_RULES_FLATTENED, Acl.customRules.flatten(10))
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.init(plugin)
sslocalProcess = new GuardedProcess(cmd: _*).start()
}
def createNotification(): ShadowsocksNotification
def createNotification(): ServiceNotification
def startRunner(): Unit = if (Build.VERSION.SDK_INT >= 26) startForegroundService(new Intent(this, getClass))
else startService(new Intent(this, getClass))
def killProcesses() {
if (sslocalProcess != null) {
sslocalProcess.destroy()
sslocalProcess = null
}
}
def stopRunner(stopService: Boolean, msg: String = null) {
// channge the state
changeState(ServiceState.STOPPING)
app.track(TAG, "stop")
killProcesses()
// clean up recevier
if (closeReceiverRegistered) {
unregisterReceiver(closeReceiver)
......@@ -179,7 +195,7 @@ trait BaseService extends Service {
}
// change the state
changeState(State.STOPPED, msg)
changeState(ServiceState.STOPPED, msg)
// stop the service if nothing has bound to it
if (stopService) stopSelf()
......@@ -240,16 +256,10 @@ trait BaseService extends Service {
})
}
override def onCreate() {
super.onCreate()
app.updateAssets()
}
// Service of shadowsocks should always be started explicitly
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
state match {
case State.STOPPED | State.IDLE =>
case ServiceState.STOPPED | ServiceState.IDLE =>
case _ => return Service.START_NOT_STICKY // ignore request
}
......@@ -278,9 +288,55 @@ trait BaseService extends Service {
notification = createNotification()
app.track(getClass.getSimpleName, "start")
changeState(State.CONNECTING)
changeState(ServiceState.CONNECTING)
Utils.ThrowableFuture(try {
if (profile.host == "198.199.101.152") {
val client = new OkHttpClient.Builder()
.dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match {
case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip))
case _ => Dns.SYSTEM.lookup(hostname)
})
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val requestBody = new FormBody.Builder()
.add("sig", Utils.getSignature(this))
.build()
val request = new Request.Builder()
.url(app.remoteConfig.getString("proxy_url"))
.post(requestBody)
.build()
val proxies = Random.shuffle(client.newCall(request).execute().body.string.split('|').toSeq)
val proxy = proxies.head.split(':')
profile.host = proxy(0).trim
profile.remotePort = proxy(1).trim.toInt
profile.password = proxy(2).trim
profile.method = proxy(3).trim
}
if (profile.route == Acl.CUSTOM_RULES) Acl.save(Acl.CUSTOM_RULES_FLATTENED, Acl.customRules.flatten(10))
plugin = new PluginConfiguration(profile.plugin).selectedOptions
pluginPath = PluginManager.init(plugin)
Utils.ThrowableFuture(try connect() catch {
// 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()
}
startNativeProcesses()
if (profile.route != Acl.ALL && profile.route != Acl.CUSTOM_RULES)
AclSyncJob.schedule(profile.route)
changeState(ServiceState.CONNECTED)
} catch {
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
case exc: Throwable =>
......@@ -309,78 +365,19 @@ trait BaseService extends Service {
}
}
protected def buildPluginCommandLine(): ArrayBuffer[String] = {
val result = ArrayBuffer(pluginPath)
if (TcpFastOpen.sendEnabled) result += "--fast-open"
result
}
protected final def buildShadowsocksConfig(file: String): String = {
protected final def buildShadowsocksConfig() {
val config = new JSONObject()
.put("server", profile.host)
.put("server_port", profile.remotePort)
.put("password", profile.password)
.put("method", profile.method)
if (pluginPath != null) config
.put("plugin", Commandline.toString(buildPluginCommandLine()))
.put("plugin_opts", plugin.toString)
IOUtils.writeString(new File(getFilesDir, file), config.toString)
file
}
protected final def buildOvertureConfig(file: String): String = {
val config = new JSONObject()
.put("BindAddress", "127.0.0.1:" + (profile.localPort + 53))
.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:" + profile.localPort)
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)
if (pluginPath != null) {
val pluginCmd = ArrayBuffer(pluginPath)
if (TcpFastOpen.sendEnabled) pluginCmd += "--fast-open"
config
.put("plugin", Commandline.toString(buildAdditionalArguments(pluginCmd).toArray))
.put("plugin_opts", plugin.toString)
}
IOUtils.writeString(new File(getFilesDir, file), config.toString)
file
IOUtils.writeString(new File(getFilesDir, "shadowsocks.json"), config.toString)
}
}
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 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
import java.util.Locale
......@@ -28,15 +28,15 @@ import android.os.{Build, PowerManager}
import android.support.v4.app.NotificationCompat
import android.support.v4.app.NotificationCompat.BigTextStyle
import android.support.v4.content.ContextCompat
import android.support.v4.os.BuildCompat
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
*/
class ShadowsocksNotification(private val service: BaseService, profileName: String,
channel: String, visible: Boolean = false) {
class ServiceNotification(private val service: BaseService, profileName: String,
channel: String, visible: Boolean = false) {
private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
private lazy val callback = new Stub {
......@@ -76,7 +76,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
service.registerReceiver(lockReceiver, screenFilter)
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 =>
setVisible(visible && !Utils.isLollipopOrAbove, forceShow)
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 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.graphics.drawable.Icon
import android.service.quicksettings.{Tile, TileService}
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
*/
@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 iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy)
private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected)
......@@ -41,11 +41,11 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext
val tile = getQsTile
if (tile != null) {
state match {
case State.STOPPED =>
case ServiceState.STOPPED =>
tile.setIcon(iconIdle)
tile.setLabel(getString(R.string.app_name))
tile.setState(Tile.STATE_INACTIVE)
case State.CONNECTED =>
case ServiceState.CONNECTED =>
tile.setIcon(iconConnected)
tile.setLabel(if (profileName == null) getString(R.string.app_name) else profileName)
tile.setState(Tile.STATE_ACTIVE)
......@@ -74,8 +74,8 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext
override def onClick(): Unit = if (isLocked) unlockAndRun(toggle) else toggle()
private def toggle() = if (bgService != null) bgService.getState match {
case State.STOPPED => Utils.startSsService(this)
case State.CONNECTED => Utils.stopSsService(this)
case ServiceState.STOPPED => Utils.startSsService(this)
case ServiceState.CONNECTED => Utils.stopSsService(this)
case _ => // ignore
}
}
......@@ -18,7 +18,7 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks.utils
package com.github.shadowsocks.bg
import java.text.DecimalFormat
......
......@@ -18,7 +18,7 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks.utils
package com.github.shadowsocks.bg
import java.io.{File, IOException}
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 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
import java.io.File
import java.util.Locale
import android.app.Service
import android.content._
import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService
import android.os._
import android.net.{VpnService => BaseVpnService}
import android.os.{IBinder, ParcelFileDescriptor}
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet}
import com.github.shadowsocks.utils._
import com.github.shadowsocks._
import com.github.shadowsocks.acl.{Acl, Subnet}
import com.github.shadowsocks.utils.Utils
import scala.collection.mutable.ArrayBuffer
class ShadowsocksVpnService extends VpnService with BaseService {
class VpnService extends BaseVpnService with LocalDnsService {
val TAG = "ShadowsocksVpnService"
val VPN_MTU = 1500
val PRIVATE_VLAN = "26.26.26.%s"
val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
var conn: ParcelFileDescriptor = _
var vpnThread: ShadowsocksVpnThread = _
var vpnThread: VpnThread = _
var sslocalProcess: GuardedProcess = _
var overtureProcess: GuardedProcess = _
var tun2socksProcess: GuardedProcess = _
override def onBind(intent: Intent): IBinder = {
val action = intent.getAction
if (VpnService.SERVICE_INTERFACE == action) {
return super.onBind(intent)
} else if (Action.SERVICE == action) {
return binder
}
null
override def onBind(intent: Intent): IBinder = intent.getAction match {
case BaseVpnService.SERVICE_INTERFACE => super[VpnService].onBind(intent)
case _ => super[LocalDnsService].onBind(intent)
}
override def onRevoke() {
stopRunner(stopService = true)
}
override def stopRunner(stopService: Boolean, msg: String = null) {
override def killProcesses() {
if (vpnThread != null) {
vpnThread.stopThread()
vpnThread = null
}
// 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
}
super.killProcesses()
if (tun2socksProcess != null) {
tun2socksProcess.destroy()
tun2socksProcess = null
......@@ -98,85 +69,41 @@ class ShadowsocksVpnService extends VpnService with BaseService {
overtureProcess.destroy()
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
if (VpnService.prepare(this) != null) {
if (BaseVpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i)
stopRunner(stopService = true)
Service.START_NOT_STICKY
} 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 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)
}
override def createNotification() = new ServiceNotification(this, profile.name, "service-vpn")
/** Called when the activity is first created. */
def handleConnection() {
startShadowsocksDaemon()
override def startNativeProcesses() {
vpnThread = new VpnThread(this)
vpnThread.start()
if (!profile.udpdns) {
startDnsDaemon()
}
super.startNativeProcesses()
val fd = startVpn()
if (!sendFd(fd)) throw new Exception("sendFd failed")
}
override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-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()
}
override protected def buildAdditionalArguments(cmd: ArrayBuffer[String]): ArrayBuffer[String] = cmd += "-V"
def startVpn(): Int = {
......@@ -229,10 +156,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
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-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,
"--tunmtu", VPN_MTU.toString,
"--sock-path", "sock_path",
......@@ -244,8 +171,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
cmd += "--enable-udprelay"
if (!profile.udpdns)
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1",
profile.localPort + 53))
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, "127.0.0.1", app.dataStore.portLocalDns))
tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd))
......
......@@ -18,7 +18,7 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
import java.io.{File, FileDescriptor, IOException}
import java.lang.reflect.Method
......@@ -26,14 +26,15 @@ import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.ShadowsocksApplication.app
object ShadowsocksVpnThread {
object VpnThread {
val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$")
}
class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread {
import ShadowsocksVpnThread._
class VpnThread(service: VpnService) extends Thread {
import VpnThread._
val TAG = "ShadowsocksVpnService"
val protect = new File(service.getFilesDir, "protect_path")
......
......@@ -22,11 +22,12 @@ package com.github.shadowsocks.database
import java.nio.ByteBuffer
import android.content.{Context, SharedPreferences}
import android.content.Context
import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteDatabase
import android.preference.PreferenceManager
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
......@@ -50,7 +51,7 @@ object DBHelper {
}
class DBHelper(val context: Context)
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 23) {
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 24) {
import DBHelper._
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
......@@ -148,12 +149,8 @@ class DBHelper(val context: Context)
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
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,
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 {
case ex: Exception =>
......
......@@ -40,10 +40,6 @@ class Profile {
@DatabaseField
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
var remotePort: Int = 8388
......@@ -111,7 +107,6 @@ class Profile {
def serialize(store: OrmLitePreferenceDataStore) {
store.putString(Key.name, name)
store.putString(Key.host, host)
store.putInt(Key.localPort, localPort)
store.putInt(Key.remotePort, remotePort)
store.putString(Key.password, password)
store.putString(Key.route, route)
......@@ -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
name = store.getString(Key.name, null)
host = store.getString(Key.host, null)
localPort = store.getInt(Key.localPort, 0)
remotePort = store.getInt(Key.remotePort, 0)
password = store.getString(Key.password, null)
method = store.getString(Key.method, null)
......
......@@ -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
* public key yet since it will also automatically trust packages signed by the same signatures, e.g. debug keys.
*/
lazy val trustedSignatures: Set[Signature] =
app.getPackageManager.getPackageInfo(app.getPackageName, PackageManager.GET_SIGNATURES).signatures.toSet +
lazy val trustedSignatures: Set[Signature] = app.info.signatures.toSet +
new Signature(Base64.decode( // @Mygod
"""
|MIIDWzCCAkOgAwIBAgIEUzfv8DANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD
......
......@@ -19,6 +19,5 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM
override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager)
override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG)
override def packageName: String = resolveInfo.resolvePackageName
override final lazy val trusted: Boolean = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
.signatures.exists(PluginManager.trustedSignatures.contains)
override final lazy val trusted: Boolean = app.info.signatures.exists(PluginManager.trustedSignatures.contains)
}
......@@ -100,8 +100,10 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat
def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i)
def isNAT: Boolean = getBoolean(Key.isNAT)
def isNAT_=(value: Boolean): Unit = putBoolean(Key.isNAT, value)
def serviceMode: String = getString(Key.serviceMode, Key.modeVpn)
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_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
......
......@@ -20,40 +20,20 @@
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 {
val id = "profileId"
val name = "profileName"
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 isAutoConnect = "isAutoConnect"
......@@ -67,7 +47,6 @@ object Key {
val password = "sitekey"
val method = "encMethod"
val remotePort = "remotePortNum"
val localPort = "localPortNum"
val remoteDns = "remoteDns"
val plugin = "plugin"
......@@ -76,19 +55,7 @@ object Key {
val dirty = "profileDirty"
val tfo = "tcp_fastopen"
val currentVersionCode = "currentVersionCode"
}
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
val assetUpdateTime = "assetUpdateTime"
}
object Action {
......
......@@ -33,7 +33,8 @@ import android.view.View.MeasureSpec
import android.view.{Gravity, View, Window}
import android.widget.Toast
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 scala.collection.JavaConversions._
......@@ -47,11 +48,8 @@ object Utils {
def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
def getSignature(context: Context): String = {
val info = context
.getPackageManager
.getPackageInfo(context.getPackageName, PackageManager.GET_SIGNATURES)
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))
}
......@@ -107,7 +105,7 @@ object Utils {
}
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))
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
......@@ -137,8 +135,7 @@ object Utils {
}
def startSsService(context: Context) {
val intent =
new Intent(context, if (app.dataStore.isNAT) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService])
val intent = new Intent(context, app.serviceClass)
if (Build.VERSION.SDK_INT >= 26) context.startForegroundService(intent) else context.startService(intent)
}
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