package org.itxtech.daedalus.service;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.v7.app.NotificationCompat;
import android.util.Log;
import de.measite.minidns.util.InetAddressUtil;
import org.itxtech.daedalus.Daedalus;
import org.itxtech.daedalus.R;
import org.itxtech.daedalus.activity.MainActivity;
import org.itxtech.daedalus.provider.DnsProvider;
import org.itxtech.daedalus.provider.TcpDnsProvider;
import org.itxtech.daedalus.provider.UdpDnsProvider;
import org.itxtech.daedalus.receiver.StatusBarBroadcastReceiver;
import org.itxtech.daedalus.util.DnsServerHelper;
import org.itxtech.daedalus.util.RulesResolver;
import java.net.Inet4Address;
import java.util.HashMap;
/**
* Daedalus Project
*
* @author iTX Technologies
* @link https://itxtech.org
* <p>
* 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.
*/
public class DaedalusVpnService extends VpnService implements Runnable {
public static final String ACTION_ACTIVATE = "org.itxtech.daedalus.service.DaedalusVpnService.ACTION_ACTIVATE";
public static final String ACTION_DEACTIVATE = "org.itxtech.daedalus.service.DaedalusVpnService.ACTION_DEACTIVATE";
private static final int NOTIFICATION_ACTIVATED = 0;
private static final String TAG = "DaedalusVpnService";
public static String primaryServer;
public static String secondaryServer;
private static NotificationCompat.Builder notification = null;
private boolean running = false;
private long lastUpdate = 0;
private boolean statisticQuery;
private DnsProvider provider;
private ParcelFileDescriptor descriptor;
private Thread mThread = null;
public HashMap<String, String> dnsServers;
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
switch (intent.getAction()) {
case ACTION_ACTIVATE:
if (Daedalus.getPrefs().getBoolean("settings_notification", true)) {
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
Intent nIntent = new Intent(this, MainActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(this, 0, nIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setWhen(0)
.setContentTitle(getResources().getString(R.string.notification_activated))
.setDefaults(NotificationCompat.DEFAULT_LIGHTS)
.setSmallIcon(R.drawable.ic_security)
.setColor(getResources().getColor(R.color.colorPrimary)) //backward compatibility
.setAutoCancel(false)
.setOngoing(true)
.setTicker(getResources().getString(R.string.notification_activated))
.setContentIntent(pIntent)
.addAction(R.drawable.ic_security, getResources().getString(R.string.button_text_deactivate),
PendingIntent.getBroadcast(this, 0,
new Intent(StatusBarBroadcastReceiver.STATUS_BAR_BTN_DEACTIVATE_CLICK_ACTION), 0))
.addAction(R.drawable.ic_security, getResources().getString(R.string.action_settings),
PendingIntent.getBroadcast(this, 0,
new Intent(StatusBarBroadcastReceiver.STATUS_BAR_BTN_SETTINGS_CLICK_ACTION), 0));
Notification notification = builder.build();
manager.notify(NOTIFICATION_ACTIVATED, notification);
DaedalusVpnService.notification = builder;
}
Daedalus.initHostsResolver();
DnsServerHelper.buildPortCache();
if (this.mThread == null) {
this.mThread = new Thread(this, "DaedalusVpn");
this.running = true;
this.mThread.start();
}
Daedalus.updateShortcut(this.getApplicationContext());
return START_STICKY;
case ACTION_DEACTIVATE:
stopThread();
return START_NOT_STICKY;
}
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
stopThread();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void stopThread() {
Log.d(TAG, "stopThread");
boolean shouldRefresh = false;
try {
if (this.descriptor != null) {
this.descriptor.close();
this.descriptor = null;
}
if (mThread != null) {
running = false;
shouldRefresh = true;
if (provider != null) {
provider.shutdown();
mThread.interrupt();
provider.stop();
} else {
mThread.interrupt();
}
mThread = null;
}
if (notification != null) {
NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ACTIVATED);
notification = null;
}
dnsServers = null;
} catch (Exception e) {
Log.d(TAG, e.toString());
}
stopSelf();
if (shouldRefresh && MainActivity.getInstance() != null && Daedalus.getInstance().isAppOnForeground()) {
MainActivity.getInstance().startActivity(new Intent(getApplicationContext(), MainActivity.class)
.putExtra(MainActivity.LAUNCH_ACTION, MainActivity.LAUNCH_ACTION_AFTER_DEACTIVATE));
} else if (shouldRefresh) {
Daedalus.updateShortcut(getApplicationContext());
}
RulesResolver.clean();
DnsServerHelper.cleanPortCache();
}
@Override
public void onRevoke() {
stopThread();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void run() {
try {
Builder builder = new Builder();
String format = null;
for (String prefix : new String[]{"10.0.0", "192.0.2", "198.51.100", "203.0.113", "192.168.50"}) {
try {
builder.addAddress(prefix + ".1", 24);
} catch (IllegalArgumentException e) {
continue;
}
format = prefix + ".%d";
break;
}
boolean advanced = Daedalus.getPrefs().getBoolean("settings_advanced_switch", false);
statisticQuery = Daedalus.getPrefs().getBoolean("settings_count_query_times", false);
Log.d(TAG, "tun0 add " + format + " pServ " + primaryServer + " sServ " + secondaryServer);
String aliasPrimary;
String aliasSecondary;
if (advanced) {
dnsServers = new HashMap<>();
aliasPrimary = String.format(format, 2);
dnsServers.put(aliasPrimary, primaryServer);
aliasSecondary = String.format(format, 3);
dnsServers.put(aliasSecondary, secondaryServer);
} else {
aliasPrimary = primaryServer;
aliasSecondary = secondaryServer;
}
Inet4Address primaryDNSServer = InetAddressUtil.ipv4From(aliasPrimary);
Inet4Address secondaryDNSServer = InetAddressUtil.ipv4From(aliasSecondary);
Log.d(TAG, "listening on " + format + " pServ " + primaryDNSServer.getHostAddress() + " sServ " + secondaryDNSServer.getHostAddress());
builder.setSession("Daedalus")
.addDnsServer(primaryDNSServer)
.addDnsServer(secondaryDNSServer)
.setConfigureIntent(PendingIntent.getActivity(this, 0,
new Intent(this, MainActivity.class).putExtra(MainActivity.LAUNCH_FRAGMENT, MainActivity.FRAGMENT_SETTINGS),
PendingIntent.FLAG_ONE_SHOT));
if (advanced) {
builder.addRoute(primaryDNSServer, primaryDNSServer.getAddress().length * 8)
.addRoute(secondaryDNSServer, secondaryDNSServer.getAddress().length * 8)
.setBlocking(true);
}
descriptor = builder.establish();
if (advanced) {
if (Daedalus.getPrefs().getBoolean("settings_dns_over_tcp", false)) {
provider = new TcpDnsProvider(descriptor, this);
} else {
provider = new UdpDnsProvider(descriptor, this);
}
provider.start();
provider.process();
} else {
while (running) {
Thread.sleep(1000);
}
}
} catch (Exception e) {
Log.d(TAG, e.toString());
} finally {
Log.d(TAG, "quit");
stopThread();
}
}
public void providerLoopCallback() {
if (statisticQuery) {
updateUserInterface();
}
}
private void updateUserInterface() {
long time = System.currentTimeMillis();
if (time - lastUpdate >= 1000) {
lastUpdate = time;
if (notification != null) {
notification.setContentTitle(getResources().getString(R.string.notification_queries) + " " + String.valueOf(provider.getDnsQueryTimes()));
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ACTIVATED, notification.build());
}
}
}
public static class VpnNetworkException extends Exception {
public VpnNetworkException(String s) {
super(s);
}
public VpnNetworkException(String s, Throwable t) {
super(s, t);
}
}
}