From d3bf2d09f740221771806789b00bf915c9f5f2e3 Mon Sep 17 00:00:00 2001 From: Daniel Weipert Date: Fri, 26 Jan 2024 14:51:36 +0100 Subject: initial commit --- mobile-kt/app/src/DNSProxyConnection.java | 43 +++++ mobile-kt/app/src/DNSProxyRunner.java | 43 +++++ mobile-kt/app/src/DNSProxyService.java | 171 ++++++++++++++++++++ mobile-kt/app/src/MainActivity.kt | 35 +++++ mobile-kt/app/src/SettingsActivity.java | 132 ++++++++++++++++ mobile-kt/app/src/WifiListenerReceiver.java | 50 ++++++ mobile-kt/app/src/WifiListenerService.java | 236 ++++++++++++++++++++++++++++ 7 files changed, 710 insertions(+) create mode 100644 mobile-kt/app/src/DNSProxyConnection.java create mode 100644 mobile-kt/app/src/DNSProxyRunner.java create mode 100644 mobile-kt/app/src/DNSProxyService.java create mode 100644 mobile-kt/app/src/MainActivity.kt create mode 100644 mobile-kt/app/src/SettingsActivity.java create mode 100644 mobile-kt/app/src/WifiListenerReceiver.java create mode 100644 mobile-kt/app/src/WifiListenerService.java (limited to 'mobile-kt/app/src') diff --git a/mobile-kt/app/src/DNSProxyConnection.java b/mobile-kt/app/src/DNSProxyConnection.java new file mode 100644 index 0000000..420b5fc --- /dev/null +++ b/mobile-kt/app/src/DNSProxyConnection.java @@ -0,0 +1,43 @@ +package org.pihole.dnsproxy; + +import android.os.ParcelFileDescriptor; + +import android.util.Log; + +public class DNSProxyConnection { + + public static String THREAD_NAME = "org.pihole.dnsproxy.service.dnsproxy.thread"; + + private DNSProxyService service; + private Thread thread; + private ParcelFileDescriptor networkInterface; + + public DNSProxyConnection(DNSProxyService service) { + this.service = service; + } + + /** + * Setup and start the connection + */ + public void start() { + DNSProxyRunner runner = new DNSProxyRunner(this.service); + runner.setOnEstablishListener(tunInterface -> { + this.networkInterface = tunInterface; + }); + + this.thread = new Thread(runner, DNSProxyConnection.THREAD_NAME); + this.thread.start(); + } + + /** + * Stop and close the connection + */ + public void stop() { + try { + this.thread.interrupt(); + this.networkInterface.close(); + } catch (Exception exception) { + Log.e(DNSProxyService.LOG_TAG, "Closing VPN interface", exception); + } + } +} diff --git a/mobile-kt/app/src/DNSProxyRunner.java b/mobile-kt/app/src/DNSProxyRunner.java new file mode 100644 index 0000000..dd0e3e5 --- /dev/null +++ b/mobile-kt/app/src/DNSProxyRunner.java @@ -0,0 +1,43 @@ +package org.pihole.dnsproxy; + +import android.net.VpnService; + +import android.os.ParcelFileDescriptor; + +import android.util.Log; + +public class DNSProxyRunner implements Runnable { + + public interface OnEstablishListener { + void onEstablish(ParcelFileDescriptor networkInterface); + } + private OnEstablishListener onEstablishListener; + + private DNSProxyService service; + + DNSProxyRunner(DNSProxyService service) { + this.service = service; + } + + @Override + public void run() { + try { + VpnService.Builder builder = this.service.newBuilder() + .setSession("Pihole DNS Proxy") + .addAddress("10.111.222.1", 24) + .addDnsServer(DNSProxyService.PIHOLE_ADDRESS) + .setBlocking(true); + + this.onEstablishListener.onEstablish(builder.establish()); + } catch (Exception exception) { + Log.e(DNSProxyService.LOG_TAG, "Failed to establish VPN connection", exception); + } + } + + /** + * Callback when proxy connection is established + */ + public void setOnEstablishListener(OnEstablishListener listener) { + this.onEstablishListener = listener; + } +} diff --git a/mobile-kt/app/src/DNSProxyService.java b/mobile-kt/app/src/DNSProxyService.java new file mode 100644 index 0000000..10e127b --- /dev/null +++ b/mobile-kt/app/src/DNSProxyService.java @@ -0,0 +1,171 @@ +package org.pihole.dnsproxy; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.SharedPreferences; + +import android.content.Context; +import android.content.Intent; + +import android.net.DhcpInfo; +import android.net.VpnService; +import android.net.wifi.WifiManager; + +import android.preference.PreferenceManager; + +import android.util.Log; + +import java.net.InetAddress; + +public class DNSProxyService extends VpnService { + + public static String LOG_TAG = "org.pihole.dnsproxy.log"; + public static String NOTIFICATION_NOTIFY = "org.pihole.dnsproxy.service.dnsproxy.notification.NOTIFY"; + public static String NOTIFICATION_CHANNEL_ID = "org.pihole.dnsproxy.service.dnsproxy.NOTIFICATION"; + public static String ACTION_START = "org.pihole.dnsproxy.service.dnsproxy.START"; + public static String ACTION_STOP = "org.pihole.dnsproxy.service.dnsproxy.STOP"; + + public static String PIHOLE_ADDRESS = ""; + + private static DNSProxyConnection connection; + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent.getAction().equals(DNSProxyService.ACTION_START)) { + this.connect(); + + return Service.START_STICKY; + } else { + this.disconnect(); + + return Service.START_NOT_STICKY; + } + } + + @Override + public void onDestroy() { + this.disconnect(); + + super.onDestroy(); + } + + /** + * Builder has to be created by service + */ + public VpnService.Builder newBuilder() { + return new VpnService.Builder(); + } + + /** + * Whether the proxy is setup running + */ + public static boolean isRunning() + { + return DNSProxyService.connection != null; + } + + /** + * Start this service + */ + public static void start(Context context) { + context.startService((new Intent(context, DNSProxyService.class)).setAction(DNSProxyService.ACTION_START)); + } + + /** + * Stop this service + */ + public static void stop(Context context) { + context.startService((new Intent(context, DNSProxyService.class)).setAction(DNSProxyService.ACTION_STOP)); + } + + /** + * Setup connection + */ + private void connect() { + this.setPiholeAddress(); + + DNSProxyService.connection = new DNSProxyConnection(this); + DNSProxyService.connection.start(); + + this.startForeground(); + + // send notification when started + Intent notification = new Intent(DNSProxyService.NOTIFICATION_NOTIFY); + sendBroadcast(notification); + } + + /** + * Disconnect from connection + */ + private void disconnect() { + DNSProxyService.connection.stop(); + DNSProxyService.connection = null; + + stopForeground(true); + + // send notification when stopped + Intent notification = new Intent(DNSProxyService.NOTIFICATION_NOTIFY); + sendBroadcast(notification); + } + + /** + * Get and set IP address of Pihole DNS server + */ + private void setPiholeAddress() { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + if (!sharedPreferences.getBoolean("use_automatic_dns_server_discovery", true)) { + DNSProxyService.PIHOLE_ADDRESS = sharedPreferences.getString("dns_server_address", ""); + return; + } + + WifiManager manager = (WifiManager) getSystemService(WIFI_SERVICE); + DhcpInfo info = manager.getDhcpInfo(); + + byte[] addressBytes = { + (byte) (0xff & info.dns1), + (byte) (0xff & (info.dns1 >> 8)), + (byte) (0xff & (info.dns1 >> 16)), + (byte) (0xff & (info.dns1 >> 24)), + }; + + try { + DNSProxyService.PIHOLE_ADDRESS = InetAddress.getByAddress(addressBytes).toString().replaceAll("/", ""); + } catch (Exception e) { + DNSProxyService.PIHOLE_ADDRESS = ""; + } + } + + /** + * Start the Foreground notification process + */ + private void startForeground() { + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + NotificationChannel channel = new NotificationChannel( + DNSProxyService.NOTIFICATION_CHANNEL_ID, + DNSProxyService.NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT + ); + manager.createNotificationChannel(channel); + + Notification notification = new Notification.Builder(this, DNSProxyService.NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.logo) + .setContentTitle(getString(R.string.app_label)) + .setContentText(String.format(getString(R.string.dns_proxy_service__notification__text), DNSProxyService.PIHOLE_ADDRESS)) + .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), Intent.FLAG_ACTIVITY_NEW_TASK)) + .addAction( + R.drawable.logo, + getString(R.string.dns_proxy_service__notification__action__stop), + PendingIntent.getService(this, 0, + (new Intent(this, DNSProxyService.class)).setAction(DNSProxyService.ACTION_STOP), + PendingIntent.FLAG_IMMUTABLE + ) + ) + .build(); + + startForeground(1, notification); + } +} diff --git a/mobile-kt/app/src/MainActivity.kt b/mobile-kt/app/src/MainActivity.kt new file mode 100644 index 0000000..8d42f6a --- /dev/null +++ b/mobile-kt/app/src/MainActivity.kt @@ -0,0 +1,35 @@ +package org.pihole.dnsproxy; + +import android.os.Bundle +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import kotlinx.coroutines.* + +class MainActivity : AppCompatActivity() { + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + findViewById(R.id.tv_label).text = "Hello Bazel, from Kotlin!" + + `kotlin_13_test`("test") + + launchCoroutine() + } + + private fun kotlin_13_test(x: String?) { + if (!x.isNullOrEmpty()) { + println("length of '$x' is ${x.length}") // Yay, smartcasted to not-null! + } + } + + private fun launchCoroutine() { + GlobalScope.launch(context = Dispatchers.Default) { + delay(1000) + withContext(context = Dispatchers.Main) { + findViewById(R.id.tv_label).text = "Hello Bazel, from Kotlin And Coroutine!" + } + } + } +} diff --git a/mobile-kt/app/src/SettingsActivity.java b/mobile-kt/app/src/SettingsActivity.java new file mode 100644 index 0000000..0b3c7f2 --- /dev/null +++ b/mobile-kt/app/src/SettingsActivity.java @@ -0,0 +1,132 @@ +package org.pihole.dnsproxy; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; + +import android.os.Bundle; + +import android.preference.PreferenceActivity; +import android.preference.PreferenceFragment; + +import android.util.Log; + +public class SettingsActivity extends PreferenceActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit(); + } + + public static class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.settings); + } + + @Override + public void onResume() { + super.onResume(); + + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + + DNSServerAddress.toggleEnabled(this); + DNSServerAddress.displayValue(this); + WifiListener.SSID.displayValue(this); + } + + @Override + public void onPause() { + super.onPause(); + + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals("use_automatic_dns_server_discovery")) { + DNSServerAddress.toggleEnabled(this); + } + + else if (key.equals("dns_server_address")) { + DNSServerAddress.displayValue(this); + } + + else if (key.equals("use_wifi_listener")) { + WifiListener.toggle(this); + } + + else if (key.equals("use_wifi_listener_for_activation")) { + if (sharedPreferences.getBoolean("use_wifi_listener_for_activation", false)) { + WifiListener.Activation.enable(this); + } + } + + else if (key.equals("wifi_listener_ssid")) { + WifiListener.SSID.displayValue(this); + } + } + + static class DNSServerAddress { + public static void toggleEnabled(PreferenceFragment context) { + context.findPreference("dns_server_address").setEnabled( + !context.getPreferenceScreen().getSharedPreferences().getBoolean("use_automatic_dns_server_discovery", true) + ); + } + + public static void displayValue(PreferenceFragment context) { + context.findPreference("dns_server_address").setSummary( + context.getPreferenceScreen().getSharedPreferences().getString("dns_server_address", "") + ); + } + } + + static class WifiListener { + public static void toggle(PreferenceFragment context) { + SharedPreferences sharedPreferences = context.getPreferenceScreen().getSharedPreferences(); + + if (sharedPreferences.getBoolean("use_wifi_listener", false)) { + WifiListenerService.start(context.getActivity()); + } else { + disable(context); + } + } + + public static void disable(PreferenceFragment context) { + SharedPreferences sharedPreferences = context.getPreferenceScreen().getSharedPreferences(); + + WifiListenerService.disable(context.getActivity()); + } + + static class Activation { + public static void enable(PreferenceFragment context) { + SharedPreferences sharedPreferences = context.getPreferenceScreen().getSharedPreferences(); + + WifiListenerService.OnActivationListener.askLocationIfNeeded(context.getActivity()); + + if (sharedPreferences.getString("wifi_listener_ssid", "").equals("")) { + String ssid = WifiListenerService.getWifiSSID(context.getActivity()); + + if (!ssid.equals("unknown ssid")) { + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + sharedPreferencesEditor.putString("wifi_listener_ssid", ssid); + sharedPreferencesEditor.commit(); + } + } + } + } + + static class SSID { + public static void displayValue(PreferenceFragment context) { + context.findPreference("wifi_listener_ssid").setSummary( + context.getPreferenceScreen().getSharedPreferences().getString("wifi_listener_ssid", "") + ); + } + } + } + } +} diff --git a/mobile-kt/app/src/WifiListenerReceiver.java b/mobile-kt/app/src/WifiListenerReceiver.java new file mode 100644 index 0000000..5e05654 --- /dev/null +++ b/mobile-kt/app/src/WifiListenerReceiver.java @@ -0,0 +1,50 @@ +package org.pihole.dnsproxy; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; + +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; + +import android.os.Handler; + +import android.preference.PreferenceManager; + +import android.util.Log; + +public class WifiListenerReceiver extends BroadcastReceiver +{ + @Override + public void onReceive(Context context, Intent intent) { + NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + Intent dnsProxyService = new Intent(context, DNSProxyService.class); + + // start + if (networkInfo.getState().equals(NetworkInfo.State.CONNECTED)) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + if (!sharedPreferences.getBoolean("use_wifi_listener_for_activation", false)) { + return; + } + + // wait a few moments for wifi to be fully there + (new Handler()).postDelayed(new Runnable() { + @Override + public void run() { + String ssid = WifiListenerService.getWifiSSID(context); + + if (ssid.equals(sharedPreferences.getString("wifi_listener_ssid", ""))) { + DNSProxyService.start(context); + } + } + }, 1000); + } + + // stop + else if (networkInfo.getState().equals(NetworkInfo.State.DISCONNECTED) && DNSProxyService.isRunning()) { + DNSProxyService.stop(context); + } + } +} diff --git a/mobile-kt/app/src/WifiListenerService.java b/mobile-kt/app/src/WifiListenerService.java new file mode 100644 index 0000000..0920677 --- /dev/null +++ b/mobile-kt/app/src/WifiListenerService.java @@ -0,0 +1,236 @@ +package org.pihole.dnsproxy; + +import android.app.Activity; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; + +import android.location.LocationManager; + +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; + +import android.os.IBinder; + +import android.preference.PreferenceManager; + +import android.util.Log; + +public class WifiListenerService extends Service +{ + + public static String NOTIFICATION_CHANNEL_ID = "org.pihole.dnsproxy.service.wifiListener.NOTIFICATION"; + public static String ACTION_START = "org.pihole.dnsproxy.service.wifiListener.START"; + public static String ACTION_STOP = "org.pihole.dnsproxy.service.wifiListener.STOP"; + public static String ACTION_STOP_SET_PREFERENCE = "org.pihole.dnsproxy.service.wifiListener.STOP_SET_PREFERENCE"; + + private BroadcastReceiver receiver = new WifiListenerReceiver(); + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // start the service + if (intent.getAction().equals(WifiListenerService.ACTION_START)) { + this.listen(); + + return Service.START_STICKY; + } + + // stop the service + else if (intent.getAction().equals(WifiListenerService.ACTION_STOP)) { + try { + this.deafen(); + } catch (Exception exception) {} + + return Service.START_NOT_STICKY; + } + + // stop and disable + else if (intent.getAction().equals(WifiListenerService.ACTION_STOP_SET_PREFERENCE)) { + WifiListenerService.disable(this); + + return Service.START_NOT_STICKY; + } + + return Service.START_NOT_STICKY; + } + + @Override + public void onDestroy() { + this.deafen(); + + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { return null; } + + /** + * Start the service + */ + public static void start(Context context) { + context.startService((new Intent(context, WifiListenerService.class)).setAction(WifiListenerService.ACTION_START)); + } + + /** + * Stop the service + */ + public static void stop(Context context) { + context.startService((new Intent(context, WifiListenerService.class)).setAction(WifiListenerService.ACTION_STOP)); + } + + /** + * Start/Stop based on setting + */ + public static void toggle(Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + if (sharedPreferences.getBoolean("use_wifi_listener", false)) { + WifiListenerService.start(context); + } else { + WifiListenerService.stop(context); + } + } + + /** + * Stop Wifi Listener and disable completely + */ + public static void disable(Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + + WifiListenerService.stop(context); + sharedPreferencesEditor.putBoolean("use_wifi_listener", false); + sharedPreferencesEditor.commit(); + + WifiListenerService.OnActivationListener.disable(context); + } + + /** + * Get current Wifi SSID + */ + public static String getWifiSSID(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + String ssid = wifiInfo.getSSID(); + + // remove quotes around ssid + ssid = ssid.substring(1, ssid.length() - 1); + + return ssid; + } + + /** + * Setup listener + */ + private void listen() { + registerReceiver(this.receiver, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + + this.startForeground(); + } + + /** + * Stop listening + */ + private void deafen() { + unregisterReceiver(this.receiver); + + stopForeground(true); + } + + /** + * Start the Foreground notification process + */ + private void startForeground() { + NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + NotificationChannel channel = new NotificationChannel( + WifiListenerService.NOTIFICATION_CHANNEL_ID, + WifiListenerService.NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_DEFAULT + ); + manager.createNotificationChannel(channel); + + Notification notification = new Notification.Builder(this, WifiListenerService.NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.logo) + .setContentTitle(getString(R.string.app_label) + " - WiFi Listener") + .setContentText(getString(R.string.wifi_listener_service__notification__text)) + .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, SettingsActivity.class), Intent.FLAG_ACTIVITY_NEW_TASK)) + .addAction( + R.drawable.logo, + getString(R.string.wifi_listener_service__notification__action__stop_listener), + PendingIntent.getService(this, 0, + (new Intent(this, WifiListenerService.class)).setAction(WifiListenerService.ACTION_STOP_SET_PREFERENCE), + PendingIntent.FLAG_IMMUTABLE + ) + ) + .addAction( + R.drawable.logo, + getString(R.string.wifi_listener_service__notification__action__start_proxy), + PendingIntent.getService(this, 0, + (new Intent(this, DNSProxyService.class)).setAction(DNSProxyService.ACTION_START), + PendingIntent.FLAG_IMMUTABLE + ) + ) + .build(); + + startForeground(2, notification); + } + + /** + * SubClass to enable auto-activation + */ + public static class OnActivationListener { + + /** + * Disable auto-activation + */ + public static void disable(Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor sharedPreferencesEditor = sharedPreferences.edit(); + LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + sharedPreferencesEditor.putBoolean("use_wifi_listener_for_activation", false); + sharedPreferencesEditor.commit(); + + if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + context.startActivity((new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } + + /** + * Check if location services are needed + */ + public static boolean checkLocationNeeded(Context context) { + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + if (!sharedPreferences.getBoolean("use_wifi_listener_for_activation", false)) { + return false; + } + + LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + return !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + } + + /** + * Ask User to enable location services if they are needed + */ + public static void askLocationIfNeeded(Activity context) { + if (checkLocationNeeded(context)) { + context.requestPermissions(new String[]{ + android.Manifest.permission.ACCESS_FINE_LOCATION, + }, 3765); + + context.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } + } + } +} -- cgit v1.2.3