NetGuard/app/src/main/java/eu/faircode/netguard/SinkholeService.java

443 lines
16 KiB
Java
Raw Normal View History

2015-10-24 18:01:55 +00:00
package eu.faircode.netguard;
2015-11-03 17:57:29 +00:00
/*
This file is part of NetGuard.
NetGuard 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.
NetGuard 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 NetGuard. If not, see <http://www.gnu.org/licenses/>.
Copyright 2015 by Marcel Bokhorst (M66B)
*/
import android.app.Notification;
2015-10-24 18:01:55 +00:00
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
2015-10-24 18:01:55 +00:00
import android.net.ConnectivityManager;
import android.net.VpnService;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
2015-10-24 18:01:55 +00:00
import android.os.ParcelFileDescriptor;
2015-10-24 19:50:29 +00:00
import android.preference.PreferenceManager;
2015-10-30 09:51:44 +00:00
import android.support.v4.app.NotificationCompat;
2015-11-04 20:15:57 +00:00
import android.support.v4.app.NotificationManagerCompat;
2015-11-04 20:13:17 +00:00
import android.support.v4.content.ContextCompat;
2015-10-24 18:01:55 +00:00
import android.util.Log;
2015-10-25 18:02:33 +00:00
import android.widget.Toast;
2015-10-24 18:01:55 +00:00
2015-10-29 22:29:01 +00:00
import java.io.FileInputStream;
import java.io.FileOutputStream;
2015-10-24 18:01:55 +00:00
import java.io.IOException;
2015-10-29 22:29:01 +00:00
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
2015-10-24 18:01:55 +00:00
2015-10-29 06:47:12 +00:00
public class SinkholeService extends VpnService {
private static final String TAG = "NetGuard.Service";
2015-10-24 18:01:55 +00:00
private boolean foreground;
2015-11-01 13:16:57 +00:00
private boolean last_roaming;
private ParcelFileDescriptor vpn = null;
2015-10-29 22:29:01 +00:00
private boolean debug = false;
private Thread thread = null;
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private static final int NOTIFY_FOREGROUND = 1;
private static final int NOTIFY_DISABLED = 2;
2015-10-26 16:23:41 +00:00
private static final String EXTRA_COMMAND = "Command";
2015-10-24 18:01:55 +00:00
2015-10-26 16:23:41 +00:00
private enum Command {start, reload, stop}
2015-10-24 18:01:55 +00:00
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Intent intent = (Intent) msg.obj;
Command cmd = (intent == null ? Command.start : (Command) intent.getSerializableExtra(EXTRA_COMMAND));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SinkholeService.this);
Log.i(TAG, "Executing command=" + cmd + " vpn=" + (vpn != null));
switch (cmd) {
case start:
if (prefs.getBoolean("foreground", false)) {
foreground = true;
startForeground(NOTIFY_FOREGROUND, getForegroundNotification());
}
if (vpn == null) {
last_roaming = Util.isRoaming(SinkholeService.this);
vpn = startVPN();
startDebug(vpn);
}
removeDisabledNotification();
Widget.updateWidgets(SinkholeService.this);
break;
case reload:
// Seamless handover
ParcelFileDescriptor prev = vpn;
vpn = startVPN();
stopDebug();
startDebug(vpn);
if (prev != null)
stopVPN(prev);
break;
case stop:
if (vpn != null) {
stopDebug();
stopVPN(vpn);
vpn = null;
}
if (foreground) {
foreground = false;
stopForeground(true);
}
Widget.updateWidgets(SinkholeService.this);
stopSelf();
break;
}
}
2015-10-24 18:01:55 +00:00
}
2015-10-30 07:57:36 +00:00
private ParcelFileDescriptor startVPN() {
Log.i(TAG, "Starting");
// Check state
boolean metered = Util.isMetered(this);
2015-10-25 16:12:25 +00:00
boolean wifi = Util.isWifiActive(this);
2015-11-01 13:16:57 +00:00
boolean roaming = Util.isRoaming(this);
boolean interactive = Util.isInteractive(this);
Log.i(TAG, "metered=" + metered + " wifi=" + wifi + " roaming=" + roaming + " interactive=" + interactive);
// Build VPN service
final Builder builder = new Builder();
2015-10-25 15:28:41 +00:00
builder.setSession(getString(R.string.app_name));
builder.addAddress("10.1.10.1", 32);
2015-10-26 13:08:13 +00:00
builder.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 64);
builder.addRoute("0.0.0.0", 0);
2015-10-26 13:08:13 +00:00
builder.addRoute("0:0:0:0:0:0:0:0", 0);
// Add list of allowed applications
2015-11-01 13:16:57 +00:00
for (Rule rule : Rule.getRules(true, TAG, this)) {
boolean blocked = (metered ? rule.other_blocked : rule.wifi_blocked);
if ((!blocked || (rule.unused && interactive)) && (!metered || !(rule.roaming && roaming))) {
Log.i(TAG, "Allowing " + rule.info.packageName);
try {
builder.addDisallowedApplication(rule.info.packageName);
2015-10-25 18:02:33 +00:00
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
2015-11-01 13:16:57 +00:00
}
2015-10-25 15:28:41 +00:00
// Build configure intent
Intent configure = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, configure, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setConfigureIntent(pi);
2015-10-29 22:29:01 +00:00
if (debug)
builder.setBlocking(true);
// Start VPN service
2015-10-25 18:02:33 +00:00
try {
2015-10-26 13:32:14 +00:00
return builder.establish();
2015-10-26 13:13:23 +00:00
2015-10-25 18:02:33 +00:00
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
// Disable firewall
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", false).apply();
// Feedback
Util.toast(ex.toString(), Toast.LENGTH_LONG, this);
2015-11-07 10:08:30 +00:00
Widget.updateWidgets(this);
2015-10-26 13:32:14 +00:00
return null;
2015-10-25 18:02:33 +00:00
}
}
2015-10-30 07:57:36 +00:00
private void stopVPN(ParcelFileDescriptor pfd) {
Log.i(TAG, "Stopping");
try {
2015-10-26 13:32:14 +00:00
pfd.close();
2015-10-25 18:02:33 +00:00
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
2015-10-30 07:57:36 +00:00
private void startDebug(final ParcelFileDescriptor pfd) {
2015-10-29 22:29:01 +00:00
if (!debug)
return;
thread = new Thread(new Runnable() {
@Override
public void run() {
try {
FileInputStream in = new FileInputStream(pfd.getFileDescriptor());
FileOutputStream out = new FileOutputStream(pfd.getFileDescriptor());
ByteBuffer buffer = ByteBuffer.allocate(32767);
buffer.order(ByteOrder.BIG_ENDIAN);
Log.i(TAG, "Start receiving");
while (!Thread.currentThread().isInterrupted() &&
pfd.getFileDescriptor() != null &&
pfd.getFileDescriptor().valid())
try {
buffer.clear();
int length = in.read(buffer.array());
if (length > 0) {
buffer.limit(length);
Packet pkt = new Packet(buffer);
if (pkt.IPv4.protocol == Packet.IPv4Header.TCP && pkt.TCP.SYN) {
int uid = pkt.getUid4();
if (uid < 0)
Log.w(TAG, "uid not found");
String[] pkg = getPackageManager().getPackagesForUid(uid);
if (pkg == null)
pkg = new String[]{uid == 0 ? "root" : "unknown"};
Log.i(TAG, "Connect " + pkt.IPv4.destinationAddress + ":" + pkt.TCP.destinationPort + " uid=" + uid + " pkg=" + pkg[0]);
// Send RST
pkt.swapAddresses();
pkt.TCP.clearFlags();
pkt.TCP.RST = true;
long ack = pkt.TCP.acknowledgementNumber;
pkt.TCP.acknowledgementNumber = (pkt.TCP.sequenceNumber + 1) & 0xFFFFFFFFL;
pkt.TCP.sequenceNumber = (ack + 1) & 0xFFFFFFFFL;
pkt.send(out);
}
}
} catch (Throwable ex) {
Log.e(TAG, ex.toString());
}
Log.i(TAG, "End receiving");
} catch (Throwable ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
});
thread.start();
}
2015-10-30 07:57:36 +00:00
private void stopDebug() {
2015-10-29 22:29:01 +00:00
if (thread != null)
thread.interrupt();
}
private BroadcastReceiver interactiveStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(TAG, intent);
// Yield system
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
reload(null, SinkholeService.this);
}
};
2015-10-24 18:01:55 +00:00
private BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
Util.logExtras(TAG, intent);
2015-11-01 13:16:57 +00:00
if (last_roaming != Util.isRoaming(SinkholeService.this)) {
2015-11-04 08:36:15 +00:00
// Roaming state changed
2015-11-01 13:16:57 +00:00
last_roaming = !last_roaming;
Log.i(TAG, "New state roaming=" + last_roaming);
reload(null, SinkholeService.this);
} else if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_TYPE) &&
intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_DUMMY) ==
2015-11-04 08:36:15 +00:00
ConnectivityManager.TYPE_WIFI) {
// Wifi connected/disconnected
2015-10-29 06:47:12 +00:00
reload(null, SinkholeService.this);
2015-11-04 08:36:15 +00:00
}
2015-10-25 22:31:00 +00:00
}
};
private BroadcastReceiver packageAddedReceiver = new BroadcastReceiver() {
2015-10-25 22:31:00 +00:00
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
2015-10-26 12:19:52 +00:00
Util.logExtras(TAG, intent);
2015-10-29 06:47:12 +00:00
reload(null, SinkholeService.this);
2015-10-24 18:01:55 +00:00
}
};
@Override
public void onCreate() {
super.onCreate();
2015-10-24 19:50:29 +00:00
Log.i(TAG, "Create");
2015-10-25 15:28:41 +00:00
HandlerThread thread = new HandlerThread(getString(R.string.app_name));
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// Listen for interactive state changes
IntentFilter ifInteractive = new IntentFilter();
ifInteractive.addAction(Intent.ACTION_SCREEN_ON);
ifInteractive.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(interactiveStateReceiver, ifInteractive);
// Listen for connectivity updates
IntentFilter ifConnectivity = new IntentFilter();
ifConnectivity.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(connectivityChangedReceiver, ifConnectivity);
// Listen for added applications
IntentFilter ifPackage = new IntentFilter();
ifPackage.addAction(Intent.ACTION_PACKAGE_ADDED);
ifPackage.addDataScheme("package");
registerReceiver(packageAddedReceiver, ifPackage);
2015-10-24 18:01:55 +00:00
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Get command
final Command cmd = (intent == null ? Command.start : (Command) intent.getSerializableExtra(EXTRA_COMMAND));
Log.i(TAG, "Start intent=" + intent + " command=" + cmd + " vpn=" + (vpn != null));
// Queue command
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
return START_STICKY;
}
2015-10-24 18:01:55 +00:00
@Override
public void onDestroy() {
Log.i(TAG, "Destroy");
2015-10-25 15:28:41 +00:00
2015-10-26 13:32:14 +00:00
if (vpn != null) {
2015-10-30 07:57:36 +00:00
stopDebug();
stopVPN(vpn);
2015-10-26 13:32:14 +00:00
vpn = null;
}
2015-10-25 15:28:41 +00:00
unregisterReceiver(interactiveStateReceiver);
unregisterReceiver(connectivityChangedReceiver);
unregisterReceiver(packageAddedReceiver);
2015-10-25 15:28:41 +00:00
mServiceLooper.quit();
2015-10-24 18:01:55 +00:00
super.onDestroy();
}
@Override
public void onRevoke() {
Log.i(TAG, "Revoke");
2015-10-25 15:28:41 +00:00
2015-11-08 07:41:21 +00:00
// Disable firewall
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("enabled", false).apply();
2015-10-26 13:32:14 +00:00
if (vpn != null) {
2015-10-30 07:57:36 +00:00
stopDebug();
stopVPN(vpn);
2015-10-26 13:32:14 +00:00
vpn = null;
}
2015-10-25 15:28:41 +00:00
2015-11-01 06:44:48 +00:00
// Display warning
showDisabledNotification();
2015-11-07 10:08:30 +00:00
Widget.updateWidgets(this);
2015-11-01 06:44:48 +00:00
super.onRevoke();
}
private Notification getForegroundNotification() {
Intent main = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_security_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_started))
.setContentIntent(pi)
.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.setAutoCancel(true);
return notification.build();
}
2015-11-01 06:44:48 +00:00
private void showDisabledNotification() {
Intent main = new Intent(this, ActivityMain.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, main, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_security_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.msg_revoked))
.setContentIntent(pi)
2015-11-04 20:13:17 +00:00
.setCategory(Notification.CATEGORY_STATUS)
.setVisibility(Notification.VISIBILITY_SECRET)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
.setAutoCancel(true);
2015-11-04 20:15:57 +00:00
NotificationManagerCompat.from(this).notify(NOTIFY_DISABLED, notification.build());
2015-11-01 06:44:48 +00:00
}
2015-11-01 06:44:48 +00:00
private void removeDisabledNotification() {
2015-11-04 20:15:57 +00:00
NotificationManagerCompat.from(this).cancel(NOTIFY_DISABLED);
2015-10-24 18:01:55 +00:00
}
2015-10-25 22:04:10 +00:00
2015-10-26 16:23:41 +00:00
public static void start(Context context) {
2015-10-29 06:47:12 +00:00
Intent intent = new Intent(context, SinkholeService.class);
2015-10-26 16:23:41 +00:00
intent.putExtra(EXTRA_COMMAND, Command.start);
context.startService(intent);
}
2015-10-26 16:32:03 +00:00
public static void reload(String network, Context context) {
2015-11-04 22:45:47 +00:00
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("enabled", false))
if (network == null || ("wifi".equals(network) ? !Util.isMetered(context) : Util.isMetered(context))) {
Intent intent = new Intent(context, SinkholeService.class);
intent.putExtra(EXTRA_COMMAND, Command.reload);
context.startService(intent);
}
2015-10-25 22:04:10 +00:00
}
2015-10-26 16:23:41 +00:00
public static void stop(Context context) {
2015-10-29 06:47:12 +00:00
Intent intent = new Intent(context, SinkholeService.class);
2015-10-26 16:23:41 +00:00
intent.putExtra(EXTRA_COMMAND, Command.stop);
context.startService(intent);
}
2015-10-24 18:01:55 +00:00
}