[tor-commits] [orbot/master] implement initial framework for blocking traffic per app

n8fr8 at torproject.org n8fr8 at torproject.org
Tue Apr 28 21:05:00 UTC 2020


commit efb1c0686d0e7a80b7470020d748be4d0814d7e4
Author: n8fr8 <nathan at guardianproject.info>
Date:   Wed Sep 4 17:07:12 2019 -0400

    implement initial framework for blocking traffic per app
    - also add ability to disable tor routing as a per app preference
---
 orbotservice/build.gradle                          |   4 +-
 .../torproject/android/service/OrbotConstants.java |   6 +
 .../android/service/util/TCPSourceApp.java         | 316 +++++++++++++++++++++
 .../android/service/vpn/OrbotVpnManager.java       |  23 +-
 .../torproject/android/service/vpn/Tun2Socks.java  |  64 ++++-
 5 files changed, 401 insertions(+), 12 deletions(-)

diff --git a/orbotservice/build.gradle b/orbotservice/build.gradle
index a62d4590..a5d07d76 100644
--- a/orbotservice/build.gradle
+++ b/orbotservice/build.gradle
@@ -1,7 +1,7 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 28
+    compileSdkVersion 29
     buildToolsVersion '28.0.3'
 
     sourceSets {
@@ -12,7 +12,7 @@ android {
 
     defaultConfig {
         minSdkVersion 16
-        targetSdkVersion 28
+        targetSdkVersion 29
         versionCode 16111000
         versionName '16.1.1-BETA-2-tor-0.4.0.4-rc-orbotservice'
         archivesBaseName = "OrbotService-$versionName"
diff --git a/orbotservice/src/main/java/org/torproject/android/service/OrbotConstants.java b/orbotservice/src/main/java/org/torproject/android/service/OrbotConstants.java
index e452105e..a5dec36e 100644
--- a/orbotservice/src/main/java/org/torproject/android/service/OrbotConstants.java
+++ b/orbotservice/src/main/java/org/torproject/android/service/OrbotConstants.java
@@ -29,4 +29,10 @@ public interface OrbotConstants {
 	String PREF_PREFER_IPV6 = "pref_prefer_ipv6";
 	String PREF_DISABLE_IPV4 = "pref_disable_ipv4";
 
+
+	String APP_TOR_KEY = "_app_tor";
+	String APP_DATA_KEY = "_app_data";
+	String APP_WIFI_KEY = "_app_wifi";
+
+
 }
diff --git a/orbotservice/src/main/java/org/torproject/android/service/util/TCPSourceApp.java b/orbotservice/src/main/java/org/torproject/android/service/util/TCPSourceApp.java
new file mode 100644
index 00000000..610e1ab4
--- /dev/null
+++ b/orbotservice/src/main/java/org/torproject/android/service/util/TCPSourceApp.java
@@ -0,0 +1,316 @@
+package org.torproject.android.service.util;
+
+/***********************************************************************
+ *
+ * Copyright (c) 2013, Sebastiano Gottardo
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the MegaDevs nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL SEBASTIANO GOTTARDO BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+/**
+ * Main class for the TCPSourceApp library.
+ * @author Sebastiano Gottardo
+ *
+ */
+public class TCPSourceApp {
+
+    /*
+     * This class represents an Android application. Each application is
+     * uniquely identified by its package name (e.g. com.megadevs.tcpsourceapp)
+     * and its version (e.g. 1.0).
+     */
+    public static class AppDescriptor {
+
+        private String packageName;
+        private String version;
+        private int uid;
+
+        public AppDescriptor(int uid, String pName, String ver) {
+            this.uid = uid;
+            packageName = pName;
+            version = ver;
+        }
+
+        public int getUid () {
+            return uid;
+        }
+
+        public String getPackageName() {
+            return packageName;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        /*
+         * Override of the 'equals' method, in order to have a proper
+         * comparison between two AppDescriptor objects.
+         *
+         * (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals(Object o) {
+
+            if (o instanceof AppDescriptor) {
+                boolean c1 = ((AppDescriptor) o).packageName.compareTo(this.packageName) == 0;
+                boolean c2 = ((AppDescriptor) o).version.compareTo(this.version) == 0;
+
+                return c1 && c2;
+            }
+
+            return false;
+        }
+
+    }
+
+    /*
+     * In a Linux-based OS, each active TCP socket is mapped in the following
+     * two files. A socket may be mapped in the '/proc/net/tcp' file in case
+     *  of a simple IPv4 address, or in the '/proc/net/tcp6' if an IPv6 address
+     *  is available.
+     */
+    private static final String TCP_4_FILE_PATH 	= "/proc/net/tcp";
+    private static final String TCP_6_FILE_PATH 	= "/proc/net/tcp6";
+
+    /*
+     * Two regular expressions that are able to extract valuable informations
+     * from the two /proc/net/tcp* files. More specifically, there are three
+     * fields that are extracted:
+     * 	- address
+     * 	- port
+     * 	- PID
+     */
+    private static final String TCP_6_PATTERN 	= "\\d+:\\s([0-9A-F]{32}):([0-9A-F]{4})\\s[0-9A-F]{32}:[0-9A-F]{4}\\s[0-9A-F]{2}\\s[0-9]{8}:[0-9]{8}\\s[0-9]{2}:[0-9]{8}\\s[0-9]{8}\\s+([0-9]+)";
+//sargo:/ $ cat /proc/net/tcp6
+//  sl  local_address                         remote_address                        st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+//   0: 00000000000000000000000000000000:C36A 00000000000000000000000000000000:0000 8A 00000000:00000000 00:00000000 00000000  1001        0 35059 1 0000000000000000 99 0 0 10 0
+//   1: 00000000000000000000000000000000:A64B 00000000000000000000000000000000:0000 8A 00000000:00000000 00:00000000 00000000  1001        0 910009 1 0000000000000000 99 0 0 10 0
+
+
+    private static final String TCP_4_PATTERN 	= "\\d+:\\s([0-9A-F]{8}):([0-9A-F]{4})\\s[0-9A-F]{8}:[0-9A-F]{4}\\s[0-9A-F]{2}\\s[0-9A-F]{8}:[0-9A-F]{8}\\s[0-9]{2}:[0-9]{8}\\s[0-9A-F]{8}\\s+([0-9]+)";
+// sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+//   0: 00000000:C368 00000000:0000 8A 00000000:00000000 00:00000000 00000000  1001        0 34999 1 0000000000000000 99 0 0 10 0
+
+    /*
+     * Optimises the socket lookup by checking if the connected network
+     * interface has a 'valid' IPv6 address (a global address, not a link-local
+     * one).
+     */
+    private static boolean checkConnectedIfaces	= true;
+
+
+    /**
+     * The main method of the TCPSourceApp library. This method receives an
+     * Android Context instance, which is used to access the PackageManager.
+     * It parses the /proc/net/tcp* files, looking for a socket entry that
+     * matches the given port. If it finds an entry, this method extracts the
+     * PID value and it uses the PackageManager.getPackagesFromPid() method to
+     * find the originating application.
+     *
+     * @param context a valid Android Context instance
+     * @param daddr the (logical) address of the destination
+     * @param dport the (logical) port of the destination
+     * @return an AppDescriptor object, representing the found application; null
+     * if no application could be found
+     */
+    @SuppressWarnings("unused")
+    public static AppDescriptor getApplicationInfo(Context context, String saddr, int sport, String daddr, int dport) {
+
+        File tcp;
+        BufferedReader reader;
+        String line;
+        StringBuilder builder;
+        String content;
+
+        try {
+            boolean hasIPv6 = true;
+
+            // if true, checks for a connected network interface with a valid
+            // IPv4 / IPv6 address
+            if (checkConnectedIfaces) {
+                String ipv4Address = getIPAddress(true);
+                String ipv6Address = getIPAddress(false);
+
+                hasIPv6 = (ipv6Address.length() > 0);
+            }
+
+            tcp = new File(TCP_6_FILE_PATH);
+            reader = new BufferedReader(new FileReader(tcp));
+            line = "";
+            builder = new StringBuilder();
+
+            while ((line = reader.readLine()) != null) {
+                builder.append(line);
+            }
+
+            content = builder.toString();
+
+            Matcher m6 = Pattern.compile(TCP_6_PATTERN, Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES | Pattern.DOTALL).matcher(content);
+
+            if (hasIPv6)
+                while (m6.find()) {
+                    String addressEntry = m6.group(1);
+                    String portEntry 	= m6.group(2);
+                    int pidEntry 		= Integer.valueOf(m6.group(3));
+
+                    if (Integer.parseInt(portEntry, 16) == dport) {
+                        PackageManager manager = context.getPackageManager();
+                        String[] packagesForUid = manager.getPackagesForUid(pidEntry);
+
+                        if (packagesForUid != null) {
+                            String packageName = packagesForUid[0];
+                            PackageInfo pInfo = manager.getPackageInfo(packageName, 0);
+                            String version = pInfo.versionName;
+
+                            return new AppDescriptor(pidEntry, packageName, version);
+                        }
+                    }
+                }
+
+        } catch (SocketException e) {
+            e.printStackTrace();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (NameNotFoundException e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        // From here, no connection with the given port could be found in the tcp6 file
+        // So let's try the tcp (IPv4) one
+
+        try {
+            tcp = new File(TCP_4_FILE_PATH);
+            reader = new BufferedReader(new FileReader(tcp));
+            line = "";
+            builder = new StringBuilder();
+
+            while ((line = reader.readLine()) != null) {
+                builder.append(line);
+            }
+
+            content = builder.toString();
+
+            Matcher m4 = Pattern.compile(TCP_4_PATTERN, Pattern.CASE_INSENSITIVE | Pattern.UNIX_LINES | Pattern.DOTALL).matcher(content);
+
+            while (m4.find()) {
+                String addressEntry = m4.group(1);
+                String portEntry 	= m4.group(2);
+                int pidEntry 	= Integer.valueOf(m4.group(3));
+
+                if (Integer.parseInt(portEntry, 16) == dport) {
+                    PackageManager manager = context.getPackageManager();
+                    String[] packagesForUid = manager.getPackagesForUid(pidEntry);
+
+                    if (packagesForUid != null) {
+                        String packageName = packagesForUid[0];
+                        PackageInfo pInfo = manager.getPackageInfo(packageName, 0);
+                        String version = pInfo.versionName;
+
+                        return new AppDescriptor(pidEntry, packageName, version);
+                    }
+                }
+            }
+
+        } catch (SocketException e) {
+            e.printStackTrace();
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (NameNotFoundException e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    @SuppressLint("DefaultLocale")
+    public static String getIPAddress(boolean useIPv4) throws SocketException {
+
+        List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+
+        for (NetworkInterface intf : interfaces) {
+            List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
+
+            for (InetAddress addr : addrs) {
+                if (!addr.isLoopbackAddress()) {
+                    String sAddr = addr.getHostAddress().toUpperCase();
+
+                    boolean isIPv4 = addr instanceof Inet4Address;
+
+                    if (useIPv4) {
+                        if (isIPv4)
+                            return sAddr;
+                    } else {
+                        if (!isIPv4) {
+                            if (sAddr.startsWith("fe80") || sAddr.startsWith("FE80")) // skipping link-local addresses
+                                continue;
+
+                            int delim = sAddr.indexOf('%'); // drop ip6 port suffix
+                            return delim < 0 ? sAddr : sAddr.substring(0, delim);
+                        }
+                    }
+                }
+            }
+        }
+
+        return "";
+    }
+
+    /*
+     * Sets the connected interfaces optimisation.
+     */
+    public static void setCheckConnectedIfaces(boolean value) {
+        checkConnectedIfaces = value;
+    }
+
+}
\ No newline at end of file
diff --git a/orbotservice/src/main/java/org/torproject/android/service/vpn/OrbotVpnManager.java b/orbotservice/src/main/java/org/torproject/android/service/vpn/OrbotVpnManager.java
index 9b099a24..03ad79c2 100644
--- a/orbotservice/src/main/java/org/torproject/android/service/vpn/OrbotVpnManager.java
+++ b/orbotservice/src/main/java/org/torproject/android/service/vpn/OrbotVpnManager.java
@@ -38,6 +38,7 @@ import android.widget.Toast;
 import com.runjva.sourceforge.jsocks.protocol.ProxyServer;
 import com.runjva.sourceforge.jsocks.server.ServerAuthenticatorNone;
 
+import org.torproject.android.service.OrbotConstants;
 import org.torproject.android.service.R;
 import org.torproject.android.service.TorService;
 import org.torproject.android.service.TorServiceConstants;
@@ -315,7 +316,6 @@ public class OrbotVpnManager implements Handler.Callback {
 			        builder.addDnsServer(dummyDNS);
 			        builder.addRoute(dummyDNS,32);
 
-
 					//handle ipv6
 			        //builder.addAddress("fdfe:dcba:9876::1", 126);
 					//builder.addRoute("::", 0);
@@ -344,7 +344,7 @@ public class OrbotVpnManager implements Handler.Callback {
 					startDNS(filePdnsd.getCanonicalPath(), localhost,mTorDns, virtualGateway, pdnsdPort);
 					final boolean localDnsTransparentProxy = true;
 
-					Tun2Socks.Start(mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , virtualGateway + ":" + pdnsdPort , localDnsTransparentProxy);
+					Tun2Socks.Start(mService, mInterface, VPN_MTU, virtualIP, virtualNetMask, localSocks , virtualGateway + ":" + pdnsdPort , localDnsTransparentProxy);
 
 
 				}
@@ -366,17 +366,24 @@ public class OrbotVpnManager implements Handler.Callback {
     {    
     	   
         ArrayList<TorifiedApp> apps = TorifiedApp.getApps(mService, getSharedPrefs(mService.getApplicationContext()));
-    
-        boolean perAppEnabled = false;
+
+		SharedPreferences prefs = getSharedPrefs(mService.getApplicationContext());
+
+		boolean perAppEnabled = false;
         
         for (TorifiedApp app : apps)
         {
         	if (app.isTorified() && (!app.getPackageName().equals(mService.getPackageName())))
         	{
-        		builder.addAllowedApplication(app.getPackageName());
-        		perAppEnabled = true;
-        	}
-        	
+				if (prefs.getBoolean(app.getPackageName() + OrbotConstants.APP_TOR_KEY,true)) {
+
+					builder.addAllowedApplication(app.getPackageName());
+
+				}
+
+				perAppEnabled = true;
+
+			}
         }
     
         if (!perAppEnabled)
diff --git a/orbotservice/src/main/java/org/torproject/android/service/vpn/Tun2Socks.java b/orbotservice/src/main/java/org/torproject/android/service/vpn/Tun2Socks.java
index 958fb9ad..e7f0591b 100644
--- a/orbotservice/src/main/java/org/torproject/android/service/vpn/Tun2Socks.java
+++ b/orbotservice/src/main/java/org/torproject/android/service/vpn/Tun2Socks.java
@@ -19,11 +19,22 @@ package org.torproject.android.service.vpn;
  *
  */
 
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.ConnectivityManager;
+import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import org.torproject.android.service.util.TCPSourceApp;
+
 import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.util.HashMap;
+
+import static android.content.Context.CONNECTIVITY_SERVICE;
 
 public class Tun2Socks
 {
@@ -49,7 +60,9 @@ public class Tun2Socks
     private static String mSocksServerAddress;
     private static String mUdpgwServerAddress;
     private static boolean mUdpgwTransparentDNS;
-    
+
+    private static HashMap<Integer,String> mAppUidBlacklist = new HashMap<>();
+    private static Context mContext;
     // Note: this class isn't a singleton, but you can't run more
     // than one instance due to the use of global state (the lwip
     // module, etc.) in the native code.
@@ -57,6 +70,7 @@ public class Tun2Socks
     public static void init () {}
 
     public static void Start(
+            Context context,
             ParcelFileDescriptor vpnInterfaceFileDescriptor,
             int vpnInterfaceMTU,
             String vpnIpAddress,
@@ -65,6 +79,7 @@ public class Tun2Socks
             String udpgwServerAddress,
             boolean udpgwTransparentDNS)
     {
+        mContext = context;
 
         mVpnInterfaceFileDescriptor = vpnInterfaceFileDescriptor;
         mVpnInterfaceMTU = vpnInterfaceMTU;
@@ -118,5 +133,50 @@ public class Tun2Socks
             int udpgwTransparentDNS);
 
     private native static void terminateTun2Socks();
-    
+
+    public static boolean isAllowed (int protocol, String sourceAddr, int sourcePort, String destAddr, int destPort) {
+
+        TCPSourceApp.AppDescriptor aInfo = TCPSourceApp.getApplicationInfo(mContext, sourceAddr, sourcePort, destAddr, destPort);
+
+        if (aInfo != null) {
+            int uid = aInfo.getUid();
+            return mAppUidBlacklist.containsKey(uid);
+        }
+        else
+            return true;
+    }
+
+    @TargetApi(Build.VERSION_CODES.Q)
+    public static boolean isAllowedQ (int protocol, String sourceAddr, int sourcePort, String destAddr, int destPort) {
+        ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(CONNECTIVITY_SERVICE);
+        if (cm == null)
+            return false;
+
+        InetSocketAddress local = new InetSocketAddress(sourceAddr, sourcePort);
+        InetSocketAddress remote = new InetSocketAddress(destAddr, destPort);
+
+        int uid = cm.getConnectionOwnerUid(protocol, local, remote);
+        return mAppUidBlacklist.containsKey(uid);
+    }
+
+    public static void setBlacklist(HashMap<Integer,String> appUidBlacklist)
+    {
+        mAppUidBlacklist = appUidBlacklist;
+    }
+
+    public static void clearBlacklist()
+    {
+        mAppUidBlacklist.clear();
+    }
+
+    public static void addToBlacklist (int uid, String pkgId)
+    {
+        mAppUidBlacklist.put(uid,pkgId);
+    }
+
+    public static void removeFromBlacklist (int uid)
+    {
+        mAppUidBlacklist.remove(uid);
+    }
+
 }
\ No newline at end of file





More information about the tor-commits mailing list