diff options
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/tethering')
6 files changed, 450 insertions, 0 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringBroadcastReceiver.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringBroadcastReceiver.java new file mode 100644 index 00000000..369a6cf6 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringBroadcastReceiver.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package se.leap.bitmaskclient.tethering; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiManager; +import android.util.Log; + +public class TetheringBroadcastReceiver extends BroadcastReceiver { + private static final String TAG = TetheringBroadcastReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + if ("android.net.wifi.WIFI_AP_STATE_CHANGED".equals(intent.getAction())) { + Log.d(TAG, "TETHERING WIFI_AP_STATE_CHANGED"); + TetheringStateManager.updateWifiTetheringState(); + } else if ("android.net.conn.TETHER_STATE_CHANGED".equals(intent.getAction())) { + Log.d(TAG, "TETHERING TETHER_STATE_CHANGED"); + TetheringStateManager.updateUsbTetheringState(); + TetheringStateManager.updateBluetoothTetheringState(); + TetheringStateManager.updateWifiTetheringState(); + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java new file mode 100644 index 00000000..75d29417 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2020 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.tethering; + +import android.support.annotation.NonNull; + +import java.util.Observable; + +public class TetheringObservable extends Observable { + private static TetheringObservable instance; + + private TetheringState tetheringState; + + private TetheringObservable() { + tetheringState = new TetheringState(); + } + + public static TetheringObservable getInstance() { + if (instance == null) { + instance = new TetheringObservable(); + } + return instance; + } + + public static void allowVpnWifiTethering(boolean enabled) { + if (getInstance().tetheringState.isVpnWifiTetheringAllowed != enabled) { + getInstance().tetheringState.isVpnWifiTetheringAllowed = enabled; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + } + + public static void allowVpnUsbTethering(boolean enabled) { + if (getInstance().tetheringState.isVpnUsbTetheringAllowed != enabled) { + getInstance().tetheringState.isVpnUsbTetheringAllowed = enabled; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + } + + public static void allowVpnBluetoothTethering(boolean enabled) { + if (getInstance().tetheringState.isVpnBluetoothTetheringAllowed != enabled) { + getInstance().tetheringState.isVpnBluetoothTetheringAllowed = enabled; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + } + + static void setWifiTethering(boolean enabled, @NonNull String address, @NonNull String interfaceName) { + if (getInstance().tetheringState.isWifiTetheringEnabled != enabled || + !getInstance().tetheringState.wifiInterface.equals(interfaceName) || + !getInstance().tetheringState.wifiAddress.equals(address)) { + TetheringState state = getInstance().tetheringState; + state.isWifiTetheringEnabled = enabled; + state.wifiInterface = interfaceName; + state.wifiAddress = address; + state.lastSeenWifiAddress = address.isEmpty() ? state.lastSeenWifiAddress : address; + state.lastSeenWifiInterface = interfaceName.isEmpty() ? state.lastSeenWifiInterface : interfaceName; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + + } + + static void setUsbTethering(boolean enabled, @NonNull String address, @NonNull String interfaceName) { + if (getInstance().tetheringState.isUsbTetheringEnabled != enabled || + !getInstance().tetheringState.usbAddress.equals(address) || + !getInstance().tetheringState.usbInterface.equals(interfaceName)) { + TetheringState state = getInstance().tetheringState; + state.isUsbTetheringEnabled = enabled; + state.usbAddress = address; + state.usbInterface = interfaceName; + state.lastSeenUsbAddress = address.isEmpty() ? state.lastSeenUsbAddress : address; + state.lastSeenUsbInterface = interfaceName.isEmpty() ? state.lastSeenUsbInterface : interfaceName; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + } + + static void setBluetoothTethering(boolean enabled) { + if (getInstance().tetheringState.isBluetoothTetheringEnabled != enabled) { + getInstance().tetheringState.isBluetoothTetheringEnabled = enabled; + getInstance().setChanged(); + getInstance().notifyObservers(); + } + } + + public boolean isBluetoothTetheringEnabled() { + return tetheringState.isBluetoothTetheringEnabled; + } + + public boolean isUsbTetheringEnabled() { + return tetheringState.isUsbTetheringEnabled; + } + + public boolean isWifiTetheringEnabled() { + return tetheringState.isWifiTetheringEnabled; + } + + public TetheringState getTetheringState() { + return tetheringState; + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java new file mode 100644 index 00000000..8ef237c6 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java @@ -0,0 +1,45 @@ +package se.leap.bitmaskclient.tethering; + +public class TetheringState implements Cloneable { + public boolean isWifiTetheringEnabled; + public boolean isUsbTetheringEnabled; + public boolean isBluetoothTetheringEnabled; + public boolean isVpnWifiTetheringAllowed; + public boolean isVpnUsbTetheringAllowed; + public boolean isVpnBluetoothTetheringAllowed; + public String wifiInterface = ""; + public String lastSeenWifiInterface = ""; + public String wifiAddress = ""; + public String lastSeenWifiAddress = ""; + public String usbInterface = ""; + public String lastSeenUsbInterface = ""; + public String usbAddress = ""; + public String lastSeenUsbAddress = ""; + public String bluetoothInterface = ""; + public String lastSeenBluetoothInterface = ""; + public String bluetoothAddress = ""; + public String lastSeenBluetoothAddress = ""; + + + public boolean tetherWifiVpn() { + return isWifiTetheringEnabled && isVpnWifiTetheringAllowed; + } + + public boolean tetherUsbVpn() { + return isUsbTetheringEnabled && isVpnUsbTetheringAllowed; + } + + public boolean tetherBluetoothVpn() { + return isBluetoothTetheringEnabled && isVpnBluetoothTetheringAllowed; + } + + public boolean hasAnyDeviceTetheringEnabled() { + return isBluetoothTetheringEnabled || isUsbTetheringEnabled || isWifiTetheringEnabled; + } + + public boolean hasAnyVpnTetheringAllowed() { + return isVpnWifiTetheringAllowed || isVpnUsbTetheringAllowed || isVpnBluetoothTetheringAllowed; + } + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java new file mode 100644 index 00000000..d3c934f6 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2020 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.tethering; + +import android.content.Context; +import android.content.IntentFilter; +import android.support.annotation.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; +import java.util.List; + +import se.leap.bitmaskclient.utils.Cmd; + +import static se.leap.bitmaskclient.utils.PreferenceHelper.isBluetoothTetheringAllowed; +import static se.leap.bitmaskclient.utils.PreferenceHelper.isUsbTetheringAllowed; +import static se.leap.bitmaskclient.utils.PreferenceHelper.isWifiTetheringAllowed; + +/** + * This manager tries to figure out the current tethering states for Wifi, USB and Bluetooth + * The default behavior differs for failing attempts to get these states: + * Wifi: keeps old state + * USB: defaults to false + * Bluetooth defaults to false + * For Wifi there's a second method to check the current state (see TetheringBroadcastReceiver). + * Either of both methods can change the state if they succeed, but are ignored if they fail. + * This should avoid any interference between both methods. + */ +public class TetheringStateManager { + private static final String TAG = TetheringStateManager.class.getSimpleName(); + private static TetheringStateManager instance; + + private WifiManagerWrapper wifiManager; + + private TetheringStateManager() { } + + public static TetheringStateManager getInstance() { + if (instance == null) { + instance = new TetheringStateManager(); + } + return instance; + } + + public void init(Context context) { + TetheringBroadcastReceiver broadcastReceiver = new TetheringBroadcastReceiver(); + IntentFilter intentFilter = new IntentFilter("android.net.conn.TETHER_STATE_CHANGED"); + intentFilter.addAction("android.net.wifi.WIFI_AP_STATE_CHANGED"); + context.getApplicationContext().registerReceiver(broadcastReceiver, intentFilter); + instance.wifiManager = new WifiManagerWrapper(context); + TetheringObservable.allowVpnWifiTethering(isWifiTetheringAllowed(context)); + TetheringObservable.allowVpnUsbTethering(isUsbTetheringAllowed(context)); + TetheringObservable.allowVpnBluetoothTethering(isBluetoothTetheringAllowed(context)); + updateWifiTetheringState(); + updateUsbTetheringState(); + updateBluetoothTetheringState(); + } + + static void updateWifiTetheringState() { + WifiManagerWrapper manager = getInstance().wifiManager; + try { + TetheringObservable.setWifiTethering(manager.isWifiAPEnabled(), getWifiAddressRange(), getWlanInterfaceName()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + static void updateUsbTetheringState() { + TetheringObservable.setUsbTethering(isUsbTetheringEnabled(), getUsbAddressRange(), getUsbInterfaceName()); + } + + static void updateBluetoothTetheringState() { + //TetheringObservable.setBluetoothTethering(isBluetoothTetheringEnabled()); + } + + private static String getWifiAddressRange() { + String interfaceAddress = getInterfaceAddress(getWlanInterface()); + return getAddressRange(interfaceAddress); + } + + private static String getUsbAddressRange() { + String interfaceAddress = getInterfaceAddress(getUsbInterface()); + return getAddressRange(interfaceAddress); + } + + private static String getWlanInterfaceName() { + return getInterfaceName(getWlanInterface()); + } + + private static String getUsbInterfaceName() { + return getInterfaceName(getUsbInterface()); + } + + private static NetworkInterface getWlanInterface() { + return getNetworkInterface(new String[]{"wlan", "eth"}); + } + + private static NetworkInterface getUsbInterface() { + return getNetworkInterface(new String[]{"rndis", "usb"}); + } + + private static boolean isBluetoothTetheringEnabled() { + StringBuilder log = new StringBuilder(); + boolean hasBtPan = false; + try { + hasBtPan = Cmd.runBlockingCmd(new String[] {"ifconfig bt-pan"}, log) == 0; + //Log.d(TAG, "ifconfig result: " + log.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + return hasBtPan; + + } + + private static boolean isUsbTetheringEnabled() { + return getUsbInterface() != null; + } + + private static String getAddressRange(String interfaceAddress) { + if (interfaceAddress.split("\\.").length == 4) { + String result = interfaceAddress.substring(0, interfaceAddress.lastIndexOf(".")); + result = result + ".0/24"; + return result; + } + return ""; + } + + private static String getInterfaceAddress(NetworkInterface networkInterface) { + if (networkInterface != null) { + List<InterfaceAddress> ifaceAddresses = networkInterface.getInterfaceAddresses(); + for (InterfaceAddress ifaceAddres : ifaceAddresses) { + if (ifaceAddres.getAddress() instanceof Inet4Address) { + return ifaceAddres.getAddress().getHostAddress(); + } + } + } + return ""; + } + + private static String getInterfaceName(NetworkInterface networkInterface) { + if (networkInterface != null) { + return networkInterface.getName(); + } + return ""; + } + + private static NetworkInterface getNetworkInterface(String[] interfaceNames) { + try { + for(Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { + NetworkInterface networkInterface = en.nextElement(); + if(!networkInterface.isLoopback()){ + for (String interfaceName : interfaceNames) { + if (networkInterface.getName().contains(interfaceName)) { + return networkInterface; + } + } + } + } + } catch(Exception e){ + e.printStackTrace(); + } + + return null; + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/WifiHotspotState.java b/app/src/main/java/se/leap/bitmaskclient/tethering/WifiHotspotState.java new file mode 100644 index 00000000..f29a87f8 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/WifiHotspotState.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2020 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.tethering; + +public enum WifiHotspotState { + WIFI_AP_STATE_DISABLING, + WIFI_AP_STATE_DISABLED, + WIFI_AP_STATE_ENABLING, + WIFI_AP_STATE_ENABLED, + WIFI_AP_STATE_FAILED +} diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/WifiManagerWrapper.java b/app/src/main/java/se/leap/bitmaskclient/tethering/WifiManagerWrapper.java new file mode 100644 index 00000000..ed395d7f --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/WifiManagerWrapper.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package se.leap.bitmaskclient.tethering; + +import android.content.Context; +import android.net.wifi.WifiManager; + +import java.lang.reflect.Method; + +/** + * This Wrapper allows better Unit testing. + */ +class WifiManagerWrapper { + + private WifiManager wifiManager; + + WifiManagerWrapper(Context context) { + this.wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + } + + boolean isWifiAPEnabled() throws Exception { + Method method = wifiManager.getClass().getMethod("getWifiApState"); + int tmp = ((Integer) method.invoke(wifiManager)); + return WifiHotspotState.WIFI_AP_STATE_ENABLED.ordinal() == tmp % 10; + } + +} |