package de.fun2code.android.piratebox.util; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import android.content.Context; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.preference.PreferenceManager; import android.util.Log; import de.fun2code.android.pawserver.PawServerActivity; import de.fun2code.android.pawserver.PawServerService; import de.fun2code.android.pawserver.util.Utils; import de.fun2code.android.piratebox.Constants; import de.fun2code.android.piratebox.R; public class NetworkUtil { private Context context; private WifiManager wifiMgr; private ConnectivityManager conMgr; public static int PORT_HTTP = 80; public static int PORT_HTTPS = 443; public static int PORT_DROOPY = 8080; public static final String WIFI_INTERFACE = "wl0.1"; public static String DNSMASQ_BIN = "/system/bin/dnsmasq"; public static String DNSMASQ_BIN_BACKUP = DNSMASQ_BIN + ".pb.backup"; public static String IPTABLES_BIN = "iptables"; public static String IPTABLES_CHAIN_FORWARD = "FORWARD"; public static enum WrapResult { NO_BACKUP, OK } // IP tables actions public enum IpTablesAction { IP_TABLES_ADD, IP_TABLES_DELETE } /** * Constucts an NetWorkUtil object * * @param context Context to use */ public NetworkUtil(Context context) { this.context = context; wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); conMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); } /** * Returns the WiFi state * * @return {@code true} if WiFi is enabled, otherwise @{code false} */ public boolean isWifiEnabled() { return wifiMgr.isWifiEnabled(); } /** * Enables/disables WiFi * * @param enable {@code true} if WiFi should be enabled, otherwise @{code false} * @return {@code true} if the operation succeeded */ public boolean setWifiEnabled(boolean enable) { return wifiMgr.setWifiEnabled(enable); } /** * Returns the current WiFi configuration * * @return current WiFi configuration */ public WifiConfiguration getWifiApConfiguration() { try { Method getWifiApConfiguration = WifiManager.class.getMethod("getWifiApConfiguration", new Class[] {}); Object obj = getWifiApConfiguration.invoke(wifiMgr, new Object[] {}); return obj != null ? (WifiConfiguration) obj : null; } catch (Exception e) { e.printStackTrace(); return null; } } /** * Enables/disables WiFi access point with the given WiFi configuration * * @param config WiFi configuratin to use * @param enable {@code true} to enable the access point, otherwise {@code false} * @return {@code true} if the operation succeeded */ public boolean setWifiApEnabled(WifiConfiguration config, boolean enable) { try { Method setWifiApEnabled = WifiManager.class.getMethod("setWifiApEnabled", new Class[] { WifiConfiguration.class, boolean.class }); Object obj = setWifiApEnabled.invoke(wifiMgr, new Object[] { config, enable }); return obj != null ? (Boolean) obj : false; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Sets the WiFi access point configuration * * @param config configuration to use * @return {@code true} if the operation succeeded */ public boolean setWifiApConfiguration(WifiConfiguration config) { try { Method setWifiApConfiguration = WifiManager.class.getMethod("setWifiApConfiguration", new Class[] { WifiConfiguration.class }); Object obj = setWifiApConfiguration.invoke(wifiMgr, new Object[] { config }); return obj != null ? (Boolean) obj : false; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Creates the configuration for an open access point * * @param ssid SSID name to use * @return the created WiFi configuration */ public WifiConfiguration createOpenAp(String ssid) { WifiConfiguration config = new WifiConfiguration(); config.SSID = ssid; config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); return config; } /** * Retrieves the IP address of the access point * * @param timeout timeout to use in millis * @return the access point's IP number of {@code null} if it could not * be fetches withinn the specified timeout */ public String getApIp(long timeout) { long until = System.currentTimeMillis() + timeout; String ip; while((ip = Utils.getLocalIpAddress()) == null && System.currentTimeMillis() < until) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } return ip; } /** * Checks if mobile date is enabled * * @return {@code true} if mobile data is enabled, otherwise {@code false} */ public boolean getMobileDataEnabled() { try { Method getMobileDataEnabled = ConnectivityManager.class.getMethod("getMobileDataEnabled", new Class[] {}); Object obj = getMobileDataEnabled.invoke(conMgr, new Object[] {}); return obj != null ? (Boolean) obj : false; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Turns mobile data on or off * * @param enable {@code true} to enable mobile data, otherwise {@code false} * @return {@code true} if the operation succeeded */ public boolean setMobileDataEnabled(boolean enable) { try { Method setMobileDataEnabled = ConnectivityManager.class.getMethod("setMobileDataEnabled", new Class[] { boolean.class }); setMobileDataEnabled.invoke(conMgr, new Object[] { enable }); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * Redirects a port by using iptables command * * @param actionType action type to use, can be {@code IP_TABLES_ADD} * or {@code IP_TABLES_DELETE} * @param apIp access point IP number * @param redirectFrom redirect from port * @param redirectTo redirect to port * @return {@code true} if the operation succeeded */ public boolean redirectPort(IpTablesAction actionType, String apIp, int redirectFrom, int redirectTo) { if(redirectFrom != redirectTo) { String action = "-" + (actionType == IpTablesAction.IP_TABLES_ADD ? "A" : "D"); String[] iptablesCmds = new String[] { IPTABLES_BIN + " -t nat " + action + " OUTPUT -d 127.0.0.1 -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo, IPTABLES_BIN + " -t nat " + action + " OUTPUT -d " + apIp + " -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo, IPTABLES_BIN + " -t nat " + action + " PREROUTING -d " + apIp + " -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo }; ShellUtil shellUtil = new ShellUtil(); return shellUtil.execRootShell(iptablesCmds); } return true; } /** * Flushes the iptable with the given table name * * @param table */ public void flushIpTable(String table) { String[] cmds = new String[] { IPTABLES_BIN + " -F -t " + table }; ShellUtil shellUtil = new ShellUtil(); shellUtil.execRootShell(cmds); } /** * Wraps the /system/bin/dnsmasq command with a shell script that calls * the backup dnsmasq file with --address parameter * * @param apIp Acess Point IP address to use * @return On success {@code WrapResult.OK} is returned, otherwise there was an error */ public WrapResult wrapDnsmasq(String apIp) { // Check if backup exists if(!new File(NetworkUtil.DNSMASQ_BIN_BACKUP).exists()) { return WrapResult.NO_BACKUP; } ShellUtil shellUtil = new ShellUtil(); shellUtil.remountSystem("rw"); String[] cmd = new String[] { "echo '#!/system/bin/sh' > " + NetworkUtil.DNSMASQ_BIN, "echo 'exec " + NetworkUtil.DNSMASQ_BIN_BACKUP + " --address=/#/" + apIp + " $*' >> " + NetworkUtil.DNSMASQ_BIN}; shellUtil.execRootShell(cmd); shellUtil.remountSystem("ro"); return WrapResult.OK; } /** * Restores the original /system/bin/dnsmasq from backup */ public boolean unwrapDnsmasq() { if(!new File(NetworkUtil.DNSMASQ_BIN_BACKUP).exists()) { return false; } ShellUtil shellUtil = new ShellUtil(); shellUtil.remountSystem("rw"); String[] cmd = new String[] {"cp -pr " + NetworkUtil.DNSMASQ_BIN_BACKUP + " " + NetworkUtil.DNSMASQ_BIN}; shellUtil.execRootShell(cmd); shellUtil.remountSystem("ro"); return true; } /** * Creates a backup of /system/bin/dnsmasq if necessary */ public boolean createDnsMasqBackup() { if(!new File(NetworkUtil.DNSMASQ_BIN_BACKUP).exists()) { ShellUtil shellUtil = new ShellUtil(); shellUtil.remountSystem("rw"); String[] cmd = new String[] {"cp -pr " + NetworkUtil.DNSMASQ_BIN + " " + NetworkUtil.DNSMASQ_BIN_BACKUP}; shellUtil.execRootShell(cmd); shellUtil.remountSystem("ro"); } return new File(NetworkUtil.DNSMASQ_BIN_BACKUP).exists(); } /** * Checks if dnsmasq is running ok * If dnsmasq is running as Zobie process this also returns {@code false}. * * @return {@code true} if dnsmasq is running, otherwise {@code false} */ public boolean isDnsMasqRunning() { ShellUtil shellUtil = new ShellUtil(); int pid; pid = shellUtil.getProcessPid(NetworkUtil.DNSMASQ_BIN_BACKUP); // Check for short name if(pid == -1) { pid = shellUtil.getProcessPid(NetworkUtil.DNSMASQ_BIN_BACKUP.replaceAll("^.*/", "")); } // If pid not -1 but cmdLine is zero, this is likely a Zombie process return pid != -1; } /** * Restart dnsmasq manually if automatic startup did not work. * * @param apIp AP IP Address * @return {@code true} on success, otherwise {@code false} */ public boolean restartDnsMasq(String apIp) { String baseApIp = apIp.replaceAll("^(.*)\\.(.*)$", "$1"); String cmd = DNSMASQ_BIN + " --address=/#/192.168.43.1 --keep-in-foreground " + "--no-resolv --no-poll --dhcp-authoritative --dhcp-option-force=43,ANDROID_METERED " + "--pid-file --dhcp-range=" + baseApIp + ".2," + baseApIp + ".254,1h &"; try { ShellUtil shellUtil = new ShellUtil(); shellUtil.execRootShell(new String[] { cmd }); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * Returns the IP address of the access point * * @return IP address of access point */ public static String getApIp(Context context) { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); boolean autoApStartup = preferences.getBoolean(Constants.PREF_AUTO_AP_STARTUP, true); String apIp = preferences.getString(Constants.PREF_AP_IP, Constants.AP_IP_DEFAULT); // If AP auto start is enabled return AP IP, otherwise return current WiFi IP return autoApStartup ? apIp : getLocalIpAddress(); } /** * Returns the current IP Address * * @return IP address */ public static String getLocalIpAddress() { String ipv4 = null; String ipv6 = null; try { for (Enumeration<NetworkInterface> en = NetworkInterface .getNetworkInterfaces(); en.hasMoreElements();) { NetworkInterface intf = en.nextElement(); /* * Skip inactive interfaces. */ try { Method upMethod = intf.getClass().getMethod("isUp", new Class[] {}); if(upMethod != null) { Boolean isUp = (Boolean) upMethod.invoke(intf, new Object[] {}); if(!isUp) { continue; } } } catch(Exception e) { /* * A NoSuchMethodException may occur. * isUp() is only available since API level 9. */ } boolean ipFound = false; for (Enumeration<InetAddress> enumIpAddr = intf .getInetAddresses(); enumIpAddr.hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { ipv4 = inetAddress.getHostAddress().toString(); ipFound = true; } else if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet6Address) { ipv6 = inetAddress.getHostAddress().toString(); ipFound = true; } } // Wlan interface has priority. Break if found and IP available. if(intf.getName().toLowerCase().startsWith("wlan") && ipFound) { break; } } } catch (SocketException ex) { Log.e(PawServerActivity.TAG, ex.toString()); } // IPv4 has priority return ipv4 != null ? ipv4 : ipv6; } /** * Sets the specified {@literal iptables} chain to {@literal DROP} * * @param chain iptables chain to use */ public static void dropChain(String chain) { if(chain != null) { ShellUtil shellUtil = new ShellUtil(); String[] cmd = new String[] {IPTABLES_BIN + " -P " + chain + " DROP"}; shellUtil.execRootShell(cmd); } } /** * Sets the specified {@literal iptables} chain to {@literal ACCEPT} * * @param chain iptables chain to use */ public static void acceptChain(String chain) { if(chain != null) { ShellUtil shellUtil = new ShellUtil(); String[] cmd = new String[] {IPTABLES_BIN + " -P " + chain + " ACCEPT"}; shellUtil.execRootShell(cmd); } } /** * Returns the port number of the server * <br/> * This can be either the port number of the internal PAW Server * or an external server. * * @param context Context to use * * @return port number */ public static int getServerPortNumber(Context context) { int port; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); if(preferences.getBoolean(Constants.PREF_USE_EXTERNAL_SERVER, false)) { String prefPort = preferences.getString(Constants.PREF_EXTERNAL_SERVER_PORT, context.getResources().getString(R.string.pref_external_server_port_default)); try { port = Integer.valueOf(prefPort); } catch(NumberFormatException e) { port = Integer.valueOf(context.getResources().getString(R.string.pref_external_server_port_default)); } } else { port = Integer.valueOf(PawServerService.getServerPort()); } return port; } }