Prohibit address families by default unless a VPN explicitly allows them.

Bug: 15972465
Change-Id: I3278d94536fefacc86390c1ba4231680f7be8589
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index a365af0..22da90e 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -106,6 +106,20 @@
      */
     public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
 
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to block all routes for a certain address
+     * family (AF_INET or AF_INET6) on this Network. For VPNs only.
+     * obj = Integer representing the family (AF_INET or AF_INET6)
+     */
+    public static final int EVENT_BLOCK_ADDRESS_FAMILY = BASE + 7;
+
+    /**
+     * Sent by the NetworkAgent to ConnectivityService to unblock routes for a certain address
+     * family (AF_INET or AF_INET6) on this Network. For VPNs only.
+     * obj = Integer representing the family (AF_INET or AF_INET6)
+     */
+    public static final int EVENT_UNBLOCK_ADDRESS_FAMILY = BASE + 8;
+
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
         this(looper, context, logTag, ni, nc, lp, score, null);
@@ -229,6 +243,21 @@
     }
 
     /**
+     * Called by the VPN code when it wants to block an address family from being routed, typically
+     * because the VPN network doesn't support that family.
+     */
+    public void blockAddressFamily(int family) {
+        queueOrSendMessage(EVENT_BLOCK_ADDRESS_FAMILY, family);
+    }
+
+    /**
+     * Called by the VPN code when it wants to unblock an address family from being routed.
+     */
+    public void unblockAddressFamily(int family) {
+        queueOrSendMessage(EVENT_UNBLOCK_ADDRESS_FAMILY, family);
+    }
+
+    /**
      * Called when ConnectivityService has indicated they no longer want this network.
      * The parent factory should (previously) have received indication of the change
      * as well, either canceling NetworkRequests or altering their score such that this
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index 4b07e3f..9b66997 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -373,6 +373,7 @@
                 throw new IllegalArgumentException("Bad address");
             }
             mAddresses.add(new LinkAddress(address, prefixLength));
+            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -413,6 +414,7 @@
                 }
             }
             mRoutes.add(new RouteInfo(new LinkAddress(address, prefixLength), null));
+            mConfig.updateAllowedFamilies(address);
             return this;
         }
 
@@ -497,7 +499,14 @@
          * @return this {@link Builder} object to facilitate chaining of method calls.
          */
         public Builder allowFamily(int family) {
-            // TODO
+            if (family == AF_INET) {
+                mConfig.allowIPv4 = true;
+            } else if (family == AF_INET6) {
+                mConfig.allowIPv6 = true;
+            } else {
+                throw new IllegalArgumentException(family + " is neither " + AF_INET + " nor " +
+                        AF_INET6);
+            }
             return this;
         }
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index c467d4a..d6ee8e0 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -404,4 +404,7 @@
 
     void addInterfaceToLocalNetwork(String iface, in List<RouteInfo> routes);
     void removeInterfaceFromLocalNetwork(String iface);
+
+    void blockAddressFamily(int family, int netId, String iface);
+    void unblockAddressFamily(int family, int netId, String iface);
 }
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index dac59f9..0099269 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -27,6 +27,7 @@
 import android.net.RouteInfo;
 import android.net.LinkAddress;
 
+import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.List;
 import java.util.ArrayList;
@@ -75,6 +76,16 @@
     public boolean legacy;
     public boolean blocking;
     public boolean allowBypass;
+    public boolean allowIPv4;
+    public boolean allowIPv6;
+
+    public void updateAllowedFamilies(InetAddress address) {
+        if (address instanceof Inet4Address) {
+            allowIPv4 = true;
+        } else {
+            allowIPv6 = true;
+        }
+    }
 
     public void addLegacyRoutes(String routesStr) {
         if (routesStr.trim().equals("")) {
@@ -87,6 +98,7 @@
             RouteInfo info = new RouteInfo(new LinkAddress
                     (InetAddress.parseNumericAddress(split[0]), Integer.parseInt(split[1])), null);
             this.routes.add(info);
+            updateAllowedFamilies(info.getDestination().getAddress());
         }
     }
 
@@ -101,6 +113,7 @@
             LinkAddress addr = new LinkAddress(InetAddress.parseNumericAddress(split[0]),
                     Integer.parseInt(split[1]));
             this.addresses.add(addr);
+            updateAllowedFamilies(addr.getAddress());
         }
     }
 
@@ -124,6 +137,8 @@
         out.writeInt(legacy ? 1 : 0);
         out.writeInt(blocking ? 1 : 0);
         out.writeInt(allowBypass ? 1 : 0);
+        out.writeInt(allowIPv4 ? 1 : 0);
+        out.writeInt(allowIPv6 ? 1 : 0);
     }
 
     public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -144,6 +159,8 @@
             config.legacy = in.readInt() != 0;
             config.blocking = in.readInt() != 0;
             config.allowBypass = in.readInt() != 0;
+            config.allowIPv4 = in.readInt() != 0;
+            config.allowIPv6 = in.readInt() != 0;
             return config;
         }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 56265e1..aa7501b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2940,7 +2940,9 @@
                     }
                     try {
                         mNetd.addVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
-                    } catch (RemoteException e) {
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in addVpnUidRanges: " + e);
                     }
                     break;
                 }
@@ -2952,7 +2954,39 @@
                     }
                     try {
                         mNetd.removeVpnUidRanges(nai.network.netId, (UidRange[])msg.obj);
-                    } catch (RemoteException e) {
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in removeVpnUidRanges: " + e);
+                    }
+                    break;
+                }
+                case NetworkAgent.EVENT_BLOCK_ADDRESS_FAMILY: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_BLOCK_ADDRESS_FAMILY from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.blockAddressFamily((Integer) msg.obj, nai.network.netId,
+                                nai.linkProperties.getInterfaceName());
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in blockAddressFamily: " + e);
+                    }
+                    break;
+                }
+                case NetworkAgent.EVENT_UNBLOCK_ADDRESS_FAMILY: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_UNBLOCK_ADDRESS_FAMILY from unknown NetworkAgent");
+                        break;
+                    }
+                    try {
+                        mNetd.unblockAddressFamily((Integer) msg.obj, nai.network.netId,
+                                nai.linkProperties.getInterfaceName());
+                    } catch (Exception e) {
+                        // Never crash!
+                        loge("Exception in blockAddressFamily: " + e);
                     }
                     break;
                 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 75a7878..362a745 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -25,6 +25,8 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static android.net.TrafficStats.UID_TETHERING;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
 import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
 import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
@@ -85,6 +87,7 @@
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
@@ -2102,4 +2105,36 @@
     public void removeInterfaceFromLocalNetwork(String iface) {
         modifyInterfaceInNetwork("remove", "local", iface);
     }
+
+    @Override
+    public void blockAddressFamily(int family, int netId, String iface) {
+        modifyAddressFamily("add", family, netId, iface);
+    }
+
+    @Override
+    public void unblockAddressFamily(int family, int netId, String iface) {
+        modifyAddressFamily("remove", family, netId, iface);
+    }
+
+    private void modifyAddressFamily(String action, int family, int netId, String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final Command cmd = new Command("network", "route", action, netId, iface);
+
+        if (family == AF_INET) {
+            cmd.appendArg(Inet4Address.ANY.getHostAddress() + "/0");
+        } else if (family == AF_INET6) {
+            cmd.appendArg(Inet6Address.ANY.getHostAddress() + "/0");
+        } else {
+            throw new IllegalStateException(family + " is neither " + AF_INET + " nor " + AF_INET6);
+        }
+
+        cmd.appendArg("unreachable");
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0305424..0a8ca6a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -17,6 +17,8 @@
 package com.android.server.connectivity;
 
 import static android.Manifest.permission.BIND_VPN_SERVICE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
 
 import android.app.AppGlobals;
 import android.app.Notification;
@@ -107,6 +109,8 @@
     private String mPackage;
     private int mOwnerUID;
     private String mInterface;
+    private boolean mAllowIPv4;
+    private boolean mAllowIPv6;
     private Connection mConnection;
     private LegacyVpnRunner mLegacyVpnRunner;
     private PendingIntent mStatusIntent;
@@ -307,6 +311,7 @@
     private void agentConnect() {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName(mInterface);
+
         boolean hasDefaultRoute = false;
         for (RouteInfo route : mConfig.routes) {
             lp.addRoute(route);
@@ -317,11 +322,19 @@
         } else {
             mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         }
+
         if (mConfig.dnsServers != null) {
             for (String dnsServer : mConfig.dnsServers) {
-                lp.addDnsServer(InetAddress.parseNumericAddress(dnsServer));
+                InetAddress address = InetAddress.parseNumericAddress(dnsServer);
+                lp.addDnsServer(address);
+                if (address instanceof Inet4Address) {
+                    mAllowIPv4 = true;
+                } else {
+                    mAllowIPv6 = true;
+                }
             }
         }
+
         // Concatenate search domains into a string.
         StringBuilder buffer = new StringBuilder();
         if (mConfig.searchDomains != null) {
@@ -330,12 +343,13 @@
             }
         }
         lp.setDomains(buffer.toString().trim());
+
         mNetworkInfo.setIsAvailable(true);
         mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+
         NetworkMisc networkMisc = new NetworkMisc();
-        if (mConfig.allowBypass) {
-            networkMisc.allowBypass = true;
-        }
+        networkMisc.allowBypass = mConfig.allowBypass;
+
         long token = Binder.clearCallingIdentity();
         try {
             mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
@@ -347,6 +361,14 @@
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+
+        if (!mAllowIPv4) {
+            mNetworkAgent.blockAddressFamily(AF_INET);
+        }
+        if (!mAllowIPv6) {
+            mNetworkAgent.blockAddressFamily(AF_INET6);
+        }
+
         addVpnUserLocked(mUserId);
         // If we are owner assign all Restricted Users to this VPN
         if (mUserId == UserHandle.USER_OWNER) {
@@ -432,6 +454,8 @@
         NetworkAgent oldNetworkAgent = mNetworkAgent;
         mNetworkAgent = null;
         List<UidRange> oldUsers = mVpnUsers;
+        boolean oldAllowIPv4 = mAllowIPv4;
+        boolean oldAllowIPv6 = mAllowIPv6;
 
         // Configure the interface. Abort if any of these steps fails.
         ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
@@ -464,6 +488,9 @@
 
             // Set up forwarding and DNS rules.
             mVpnUsers = new ArrayList<UidRange>();
+            mAllowIPv4 = mConfig.allowIPv4;
+            mAllowIPv6 = mConfig.allowIPv6;
+
             agentConnect();
 
             if (oldConnection != null) {
@@ -492,6 +519,8 @@
             mVpnUsers = oldUsers;
             mNetworkAgent = oldNetworkAgent;
             mInterface = oldInterface;
+            mAllowIPv4 = oldAllowIPv4;
+            mAllowIPv6 = oldAllowIPv6;
             throw e;
         }
         Log.i(TAG, "Established by " + config.user + " on " + mInterface);
@@ -1175,6 +1204,8 @@
                     // Now INetworkManagementEventObserver is watching our back.
                     mInterface = mConfig.interfaze;
                     mVpnUsers = new ArrayList<UidRange>();
+                    mAllowIPv4 = mConfig.allowIPv4;
+                    mAllowIPv6 = mConfig.allowIPv6;
 
                     agentConnect();