Unverified Commit 265ecc04 authored by Max Lv's avatar Max Lv Committed by GitHub

Merge pull request #1446 from shadowsocks/transproxy

Implement Proxy-Only Mode and Transproxy Mode
parents 967f1981 fb2fdd5d
......@@ -65,3 +65,36 @@ To scan the QR code through the integrated QR scanner.
By the way, upgrade your Android system already.
### How to use Transproxy mode?
1. Install [AFWall+](https://github.com/ukanth/afwall);
2. Set custom script:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
ULIMIT=/system/bin/ulimit
SHADOWSOCKS_UID=`dumpsys package com.github.shadowsocks | grep userId | cut -d= -f2 - | cut -d' ' -f1 -`
PORT_DNS=5450
PORT_TRANSPROXY=8200
$ULIMIT -n 4096
$IP6TABLES -F
$IP6TABLES -A INPUT -j DROP
$IP6TABLES -A OUTPUT -j DROP
$IPTABLES -t nat -F OUTPUT
$IPTABLES -t nat -A OUTPUT -o lo -j RETURN
$IPTABLES -t nat -A OUTPUT -d 127.0.0.1 -j RETURN
$IPTABLES -t nat -A OUTPUT -m owner --uid-owner $SHADOWSOCKS_UID -j RETURN
$IPTABLES -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
$IPTABLES -t nat -A OUTPUT -p udp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
```
3. Set custom shutdown script:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
$IPTABLES -t nat -F OUTPUT
$IP6TABLES -F
```
4. Make sure to allow traffic for Shadowsocks;
5. Start Shadowsocks transproxy service and enable firewall.
......@@ -15,9 +15,7 @@ _Put an `x` inside the [ ] that applies._
* [ ] IPv6 server address
* [ ] Client IPv4 availability
* [ ] Client IPv6 availability
* Local port: 1080
* Encrypt method:
* [ ] One-time authentication
* Route
* [ ] All
* [ ] Bypass LAN
......@@ -27,14 +25,14 @@ _Put an `x` inside the [ ] that applies._
* [ ] China List
* [ ] Custom rules
* [ ] IPv6 route
* [ ] Per-App Proxy
* [ ] Apps VPN mode
* [ ] Bypass mode
* Remote DNS: 8.8.8.8
* [ ] DNS Forwarding
* Plugin configuration (if applicable):
* [ ] Auto Connect
* [ ] TCP Fast Open
* [ ] NAT mode
* If you're not using VPN mode, please supply more details here:
### What did you do?
......
......@@ -9,6 +9,14 @@
path = mobile/src/main/jni/libancillary
url = https://github.com/shadowsocks/libancillary.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/libevent"]
path = mobile/src/main/jni/libevent
url = https://github.com/shadowsocks/libevent.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/redsocks"]
path = mobile/src/main/jni/redsocks
url = https://github.com/shadowsocks/redsocks.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/mbedtls"]
path = mobile/src/main/jni/mbedtls
url = https://github.com/ARMmbed/mbedtls
......
......@@ -109,13 +109,7 @@
</activity>
<service
android:name=".ShadowsocksLocalService"
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">
......
......@@ -78,6 +78,32 @@ LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE))
include $(BUILD_STATIC_LIBRARY)
########################################################
## libevent
########################################################
include $(CLEAR_VARS)
LIBEVENT_SOURCES := \
buffer.c \
bufferevent.c bufferevent_filter.c \
bufferevent_pair.c bufferevent_ratelim.c \
bufferevent_sock.c epoll.c \
epoll_sub.c evdns.c event.c \
event_tagging.c evmap.c \
evrpc.c evthread.c \
evthread_pthread.c evutil.c \
evutil_rand.c http.c \
listener.c log.c poll.c \
select.c signal.c strlcpy.c
LOCAL_MODULE := event
LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES))
LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \
-I$(LOCAL_PATH)/libevent/include \
include $(BUILD_STATIC_LIBRARY)
########################################################
## libancillary
########################################################
......@@ -174,6 +200,28 @@ LOCAL_SRC_FILES := \
include $(BUILD_STATIC_LIBRARY)
########################################################
## redsocks
########################################################
include $(CLEAR_VARS)
REDSOCKS_SOURCES := base.c http-connect.c \
log.c md5.c socks5.c \
base64.c http-auth.c http-relay.c main.c \
parser.c redsocks.c socks4.c utils.c
LOCAL_STATIC_LIBRARIES := libevent
LOCAL_MODULE := redsocks
LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES))
LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \
-I$(LOCAL_PATH)/redsocks \
-I$(LOCAL_PATH)/libevent/include \
-I$(LOCAL_PATH)/libevent
include $(BUILD_SHARED_EXECUTABLE)
########################################################
## shadowsocks-libev local
########################################################
......
Subproject commit 359ca847a649b9c318f9217c0755484d98ecb779
Subproject commit 274334f14839431ae003774d99c3d1de337afff4
<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="local">SOCKS5 mode</string>
<string name="local_summary">Enable SOCKS5 proxy mode instead of VPN mode to work with AFWall+ or Orbot</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</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,11 +64,11 @@
<!-- notification category -->
<string name="service_vpn">VPN Service</string>
<string name="service_local">SOCKS5 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="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>
......@@ -73,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 -->
......@@ -81,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>
......@@ -122,7 +127,6 @@
<string name="received">Received:</string>
<string name="connecting">Connecting…</string>
<string name="vpn_connected">Connected, tap to check connection</string>
<string name="local_connected">Connected</string>
<string name="not_connected">Not connected</string>
<!-- acl -->
......
<?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/local"
android:summary="@string/local_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 _ =>
}
}
......@@ -21,15 +21,15 @@
package com.github.shadowsocks
import java.lang.System.currentTimeMillis
import java.net.{HttpURLConnection, URL}
import java.net.{HttpURLConnection, InetSocketAddress, URL, Proxy => JavaProxy}
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,41 +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.isLocalEnabled) R.string.local_connected else R.string.vpn_connected)
case State.STOPPING =>
statusText.setText(R.string.vpn_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)
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))
......@@ -151,18 +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)
}
override def onServiceConnected() {
changeState(bgService.getState)
if (state != ServiceState.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
}
override def onServiceDisconnected(): Unit = changeState(State.IDLE)
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)
})
......@@ -202,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)
......@@ -250,17 +243,21 @@ 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) {
testCount += 1
statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code
Utils.ThrowableFuture {
// Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640
autoDisconnect(new URL("https", app.currentProfile.get.route match {
case Acl.CHINALIST => "www.qualcomm.cn"
case _ => "www.google.com"
}, "/generate_204").openConnection()
.asInstanceOf[HttpURLConnection]) { conn =>
autoDisconnect {
val url = new URL("https", app.currentProfile.get.route match {
case Acl.CHINALIST => "www.qualcomm.cn"
case _ => "www.google.com"
}, "/generate_204")
(if (app.usingVpnMode) url.openConnection() else url.openConnection(
new JavaProxy(JavaProxy.Type.SOCKS, new InetSocketAddress("127.0.0.1", app.dataStore.portProxy))))
.asInstanceOf[HttpURLConnection]
} { conn =>
conn.setConnectTimeout(5 * 1000)
conn.setReadTimeout(5 * 1000)
conn.setInstanceFollowRedirects(false)
......@@ -292,20 +289,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.isLocalEnabled) 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)
......@@ -345,7 +342,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)
})
......@@ -361,18 +358,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = {
if (position == previousPosition) drawer.closeDrawer() else 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")
......@@ -389,7 +374,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.isLocalEnabled)
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.isLocalEnabled) classOf[ShadowsocksLocalService] 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 isLocalEnabled: Boolean = dataStore.isNAT
def isVpnEnabled: Boolean = !isLocalEnabled
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,26 +158,30 @@ 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-local", getText(R.string.service_local), 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-local.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
lazy val userIndex = Binder.getCallingUserHandle.hashCode
if (!(1025 to 65535 contains dataStore.portProxy)) dataStore.putInt(Key.portProxy, 1080 + userIndex)
if (!(1025 to 65535 contains dataStore.portLocalDns)) dataStore.putInt(Key.portLocalDns, 5450 + userIndex)
if (!(1025 to 65535 contains dataStore.portTransproxy)) dataStore.putInt(Key.portTransproxy, 8200 + userIndex)
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
}
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 =>
......@@ -184,11 +192,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)
......
......@@ -46,16 +46,12 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
}
def startBackgroundService() {
if (app.isLocalEnabled) {
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}
......
......@@ -18,104 +18,80 @@
/* */
/*******************************************************************************/
package com.github.shadowsocks
package com.github.shadowsocks.bg
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.GuardedProcess
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 com.github.shadowsocks.utils.IOUtils
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
class ShadowsocksLocalService extends BaseService {
val TAG = "ShadowsocksLocalService"
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"
}
var sslocalProcess: GuardedProcess = _
class TransproxyService extends LocalDnsService {
import TransproxyService._
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-local.conf"))
val TAG = "ShadowsocksTransproxyService"
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
var sstunnelProcess: GuardedProcess = _
var redsocksProcess: GuardedProcess = _
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
}
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
sslocalProcess = new GuardedProcess(cmd: _*).start()
sstunnelProcess = new GuardedProcess(cmd: _*).start()
}
/** Called when the activity is first created. */
def handleConnection() {
startShadowsocksDaemon()
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()
}
def onBind(intent: Intent): IBinder = {
Log.d(TAG, "onBind")
if (Action.SERVICE == intent.getAction) {
binder
} else {
null
}
override def startNativeProcesses() {
startRedsocksDaemon()
super.startNativeProcesses()
if (profile.udpdns) startDNSTunnel()
}
def killProcesses() {
if (sslocalProcess != null) {
sslocalProcess.destroy()
sslocalProcess = null
override def killProcesses() {
super.killProcesses()
if (sstunnelProcess != null) {
sstunnelProcess.destroy()
sstunnelProcess = null
}
}
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()
if (redsocksProcess != null) {
redsocksProcess.destroy()
redsocksProcess = null
}
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-local", true)
override def stopRunner(stopService: Boolean, msg: String = null) {
// channge the state
changeState(State.STOPPING)
app.track(TAG, "stop")
// reset NAT
killProcesses()
super.stopRunner(stopService, msg)
}
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 =>
......
......@@ -23,7 +23,6 @@ package com.github.shadowsocks.database
import java.util.Locale
import android.net.Uri
import android.os.Binder
import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
......@@ -40,10 +39,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 +106,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 +123,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, ShadowsocksLocalService, 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[ShadowsocksLocalService] 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