Merge "Backwards-incompatible resolv module API change for making setResolverConfiguration take a parcelable."
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2906710..0e10de8 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -510,7 +510,7 @@
* The absence of a connection type.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_NONE = -1;
/**
@@ -627,7 +627,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_FOTA = 10;
/**
@@ -645,7 +645,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_CBS = 12;
/**
@@ -655,7 +655,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_WIFI_P2P = 13;
/**
@@ -674,7 +674,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static final int TYPE_MOBILE_EMERGENCY = 15;
/**
@@ -775,7 +775,7 @@
*/
public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
@@ -867,7 +867,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public static boolean isNetworkTypeMobile(int networkType) {
switch (networkType) {
case TYPE_MOBILE:
@@ -1304,7 +1304,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkPropertiesForType(networkType);
@@ -3042,7 +3042,7 @@
*/
@Deprecated
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
public boolean isNetworkSupported(int networkType) {
try {
return mService.isNetworkSupported(networkType);
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 06c32c6..68826cb 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -93,6 +93,23 @@
public static final int FLAG_NO_CACHE_STORE = 1 << 1;
public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_PARSE,
+ ERROR_SYSTEM
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DnsError {}
+ /**
+ * Indicates that there was an error parsing the response the query.
+ * The cause of this error is available via getCause() and is a ParseException.
+ */
+ public static final int ERROR_PARSE = 0;
+ /**
+ * Indicates that there was an error sending the query.
+ * The cause of this error is available via getCause() and is an ErrnoException.
+ */
+ public static final int ERROR_SYSTEM = 1;
+
private static final int NETID_UNSET = 0;
private static final DnsResolver sInstance = new DnsResolver();
@@ -107,97 +124,57 @@
private DnsResolver() {}
/**
- * Answer parser for parsing raw answers
+ * Base interface for answer callbacks
*
- * @param <T> The type of the parsed answer
+ * @param <T> The type of the answer
*/
- public interface AnswerParser<T> {
- /**
- * Creates a <T> answer by parsing the given raw answer.
- *
- * @param rawAnswer the raw answer to be parsed
- * @return a parsed <T> answer
- * @throws ParseException if parsing failed
- */
- @NonNull T parse(@NonNull byte[] rawAnswer) throws ParseException;
- }
-
- /**
- * Base class for answer callbacks
- *
- * @param <T> The type of the parsed answer
- */
- public abstract static class AnswerCallback<T> {
- /** @hide */
- public final AnswerParser<T> parser;
-
- public AnswerCallback(@NonNull AnswerParser<T> parser) {
- this.parser = parser;
- };
-
+ public interface Callback<T> {
/**
* Success response to
- * {@link android.net.DnsResolver#query query()}.
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
*
* Invoked when the answer to a query was successfully parsed.
*
- * @param answer parsed answer to the query.
+ * @param answer <T> answer to the query.
+ * @param rcode The response code in the DNS response.
*
* {@see android.net.DnsResolver#query query()}
*/
- public abstract void onAnswer(@NonNull T answer);
-
+ void onAnswer(@NonNull T answer, int rcode);
/**
* Error response to
- * {@link android.net.DnsResolver#query query()}.
+ * {@link android.net.DnsResolver#query query()} or
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
*
* Invoked when there is no valid answer to
* {@link android.net.DnsResolver#query query()}
+ * {@link android.net.DnsResolver#rawQuery rawQuery()}.
*
- * @param exception a {@link ParseException} object with additional
+ * @param error a {@link DnsException} object with additional
* detail regarding the failure
*/
- public abstract void onParseException(@NonNull ParseException exception);
-
- /**
- * Error response to
- * {@link android.net.DnsResolver#query query()}.
- *
- * Invoked if an error happens when
- * issuing the DNS query or receiving the result.
- * {@link android.net.DnsResolver#query query()}
- *
- * @param exception an {@link ErrnoException} object with additional detail
- * regarding the failure
- */
- public abstract void onQueryException(@NonNull ErrnoException exception);
+ void onError(@NonNull DnsException error);
}
/**
- * Callback for receiving raw answers
+ * Class to represent DNS error
*/
- public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
- public RawAnswerCallback() {
- super(rawAnswer -> rawAnswer);
- }
- }
+ public static class DnsException extends Exception {
+ /**
+ * DNS error code as one of the ERROR_* constants
+ */
+ @DnsError public final int code;
- /**
- * Callback for receiving parsed {@link InetAddress} answers
- *
- * Note that if the answer does not contain any IP addresses,
- * onAnswer will be called with an empty list.
- */
- public abstract static class InetAddressAnswerCallback
- extends AnswerCallback<List<InetAddress>> {
- public InetAddressAnswerCallback() {
- super(rawAnswer -> new DnsAddressAnswer(rawAnswer).getAddresses());
+ DnsException(@DnsError int code, @Nullable Throwable cause) {
+ super(cause);
+ this.code = code;
}
}
/**
* Send a raw DNS query.
- * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
*
* @param network {@link Network} specifying which network to query on.
* {@code null} for query on default network.
@@ -206,13 +183,13 @@
* @param executor The {@link Executor} that the callback should be executed on.
* @param cancellationSignal used by the caller to signal if the query should be
* cancelled. May be {@code null}.
- * @param callback an {@link AnswerCallback} which will be called to notify the caller
+ * @param callback a {@link Callback} which will be called to notify the caller
* of the result of dns query.
*/
- public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
+ public void rawQuery(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
@NonNull @CallbackExecutor Executor executor,
@Nullable CancellationSignal cancellationSignal,
- @NonNull AnswerCallback<T> callback) {
+ @NonNull Callback<? super byte[]> callback) {
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
return;
}
@@ -220,11 +197,9 @@
final FileDescriptor queryfd;
try {
queryfd = resNetworkSend((network != null
- ? network.netId : NETID_UNSET), query, query.length, flags);
+ ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
} catch (ErrnoException e) {
- executor.execute(() -> {
- callback.onQueryException(e);
- });
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
}
@@ -237,7 +212,7 @@
/**
* Send a DNS query with the specified name, class and query type.
- * The answer will be provided asynchronously through the provided {@link AnswerCallback}.
+ * The answer will be provided asynchronously through the provided {@link Callback}.
*
* @param network {@link Network} specifying which network to query on.
* {@code null} for query on default network.
@@ -248,14 +223,14 @@
* @param executor The {@link Executor} that the callback should be executed on.
* @param cancellationSignal used by the caller to signal if the query should be
* cancelled. May be {@code null}.
- * @param callback an {@link AnswerCallback} which will be called to notify the caller
+ * @param callback a {@link Callback} which will be called to notify the caller
* of the result of dns query.
*/
- public <T> void query(@Nullable Network network, @NonNull String domain,
+ public void rawQuery(@Nullable Network network, @NonNull String domain,
@QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
@NonNull @CallbackExecutor Executor executor,
@Nullable CancellationSignal cancellationSignal,
- @NonNull AnswerCallback<T> callback) {
+ @NonNull Callback<? super byte[]> callback) {
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
return;
}
@@ -263,11 +238,9 @@
final FileDescriptor queryfd;
try {
queryfd = resNetworkQuery((network != null
- ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
+ ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
} catch (ErrnoException e) {
- executor.execute(() -> {
- callback.onQueryException(e);
- });
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
}
synchronized (lock) {
@@ -277,27 +250,28 @@
}
}
- private class InetAddressAnswerAccumulator extends InetAddressAnswerCallback {
+ private class InetAddressAnswerAccumulator implements Callback<byte[]> {
private final List<InetAddress> mAllAnswers;
- private ParseException mParseException;
- private ErrnoException mErrnoException;
- private final InetAddressAnswerCallback mUserCallback;
+ private int mRcode;
+ private DnsException mDnsException;
+ private final Callback<? super List<InetAddress>> mUserCallback;
private final int mTargetAnswerCount;
private int mReceivedAnswerCount = 0;
- InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerCallback callback) {
+ InetAddressAnswerAccumulator(int size,
+ @NonNull Callback<? super List<InetAddress>> callback) {
mTargetAnswerCount = size;
mAllAnswers = new ArrayList<>();
mUserCallback = callback;
}
- private boolean maybeReportException() {
- if (mErrnoException != null) {
- mUserCallback.onQueryException(mErrnoException);
+ private boolean maybeReportError() {
+ if (mRcode != 0) {
+ mUserCallback.onAnswer(mAllAnswers, mRcode);
return true;
}
- if (mParseException != null) {
- mUserCallback.onParseException(mParseException);
+ if (mDnsException != null) {
+ mUserCallback.onError(mDnsException);
return true;
}
return false;
@@ -305,34 +279,43 @@
private void maybeReportAnswer() {
if (++mReceivedAnswerCount != mTargetAnswerCount) return;
- if (mAllAnswers.isEmpty() && maybeReportException()) return;
+ if (mAllAnswers.isEmpty() && maybeReportError()) return;
// TODO: Do RFC6724 sort.
- mUserCallback.onAnswer(mAllAnswers);
+ mUserCallback.onAnswer(mAllAnswers, mRcode);
}
@Override
- public void onAnswer(@NonNull List<InetAddress> answer) {
- mAllAnswers.addAll(answer);
+ public void onAnswer(@NonNull byte[] answer, int rcode) {
+ // If at least one query succeeded, return an rcode of 0.
+ // Otherwise, arbitrarily return the first rcode received.
+ if (mReceivedAnswerCount == 0 || rcode == 0) {
+ mRcode = rcode;
+ }
+ try {
+ mAllAnswers.addAll(new DnsAddressAnswer(answer).getAddresses());
+ } catch (ParseException e) {
+ mDnsException = new DnsException(ERROR_PARSE, e);
+ }
maybeReportAnswer();
}
@Override
- public void onParseException(@NonNull ParseException e) {
- mParseException = e;
- maybeReportAnswer();
- }
-
- @Override
- public void onQueryException(@NonNull ErrnoException e) {
- mErrnoException = e;
+ public void onError(@NonNull DnsException error) {
+ mDnsException = error;
maybeReportAnswer();
}
}
/**
- * Send a DNS query with the specified name, get back a set of InetAddresses asynchronously.
- * The answer will be provided asynchronously through the provided
- * {@link InetAddressAnswerCallback}.
+ * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
+ * get back a set of InetAddresses asynchronously.
+ *
+ * This method will examine the connection ability on given network, and query IPv4
+ * and IPv6 if connection is available.
+ *
+ * If at least one query succeeded with valid answer, rcode will be 0
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
*
* @param network {@link Network} specifying which network to query on.
* {@code null} for query on default network.
@@ -341,13 +324,13 @@
* @param executor The {@link Executor} that the callback should be executed on.
* @param cancellationSignal used by the caller to signal if the query should be
* cancelled. May be {@code null}.
- * @param callback an {@link InetAddressAnswerCallback} which will be called to notify the
+ * @param callback a {@link Callback} which will be called to notify the
* caller of the result of dns query.
*/
public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
@NonNull @CallbackExecutor Executor executor,
@Nullable CancellationSignal cancellationSignal,
- @NonNull InetAddressAnswerCallback callback) {
+ @NonNull Callback<? super List<InetAddress>> callback) {
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
return;
}
@@ -363,11 +346,10 @@
if (queryIpv6) {
try {
v6fd = resNetworkQuery((network != null
- ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
+ ? network.getNetIdForResolv() : NETID_UNSET),
+ domain, CLASS_IN, TYPE_AAAA, flags);
} catch (ErrnoException e) {
- executor.execute(() -> {
- callback.onQueryException(e);
- });
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
}
queryCount++;
@@ -377,17 +359,18 @@
// Avoiding gateways drop packets if queries are sent too close together
try {
Thread.sleep(SLEEP_TIME_MS);
- } catch (InterruptedException ex) { }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
if (queryIpv4) {
try {
v4fd = resNetworkQuery((network != null
- ? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
+ ? network.getNetIdForResolv() : NETID_UNSET),
+ domain, CLASS_IN, TYPE_A, flags);
} catch (ErrnoException e) {
if (queryIpv6) resNetworkCancel(v6fd); // Closes fd, marks it invalid.
- executor.execute(() -> {
- callback.onQueryException(e);
- });
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
return;
}
queryCount++;
@@ -413,34 +396,89 @@
}
}
- private <T> void registerFDListener(@NonNull Executor executor,
- @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback,
+ /**
+ * Send a DNS query with the specified name and query type, get back a set of
+ * InetAddresses asynchronously.
+ *
+ * The answer will be provided asynchronously through the provided {@link Callback}.
+ *
+ * @param network {@link Network} specifying which network to query on.
+ * {@code null} for query on default network.
+ * @param domain domain name to query
+ * @param nsType dns resource record (RR) type as one of the TYPE_* constants
+ * @param flags flags as a combination of the FLAGS_* constants
+ * @param executor The {@link Executor} that the callback should be executed on.
+ * @param cancellationSignal used by the caller to signal if the query should be
+ * cancelled. May be {@code null}.
+ * @param callback a {@link Callback} which will be called to notify the caller
+ * of the result of dns query.
+ */
+ public void query(@Nullable Network network, @NonNull String domain,
+ @QueryType int nsType, @QueryFlag int flags,
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull Callback<? super List<InetAddress>> callback) {
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ return;
+ }
+ final Object lock = new Object();
+ final FileDescriptor queryfd;
+ try {
+ queryfd = resNetworkQuery((network != null
+ ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
+ } catch (ErrnoException e) {
+ executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+ return;
+ }
+ final InetAddressAnswerAccumulator accumulator =
+ new InetAddressAnswerAccumulator(1, callback);
+ synchronized (lock) {
+ registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
+ if (cancellationSignal == null) return;
+ addCancellationSignal(cancellationSignal, queryfd, lock);
+ }
+ }
+
+ /**
+ * Class to retrieve DNS response
+ *
+ * @hide
+ */
+ public static final class DnsResponse {
+ public final @NonNull byte[] answerbuf;
+ public final int rcode;
+ public DnsResponse(@NonNull byte[] answerbuf, int rcode) {
+ this.answerbuf = answerbuf;
+ this.rcode = rcode;
+ }
+ }
+
+ private void registerFDListener(@NonNull Executor executor,
+ @NonNull FileDescriptor queryfd, @NonNull Callback<? super byte[]> answerCallback,
@Nullable CancellationSignal cancellationSignal, @NonNull Object lock) {
Looper.getMainLooper().getQueue().addOnFileDescriptorEventListener(
queryfd,
FD_EVENTS,
(fd, events) -> {
executor.execute(() -> {
+ DnsResponse resp = null;
+ ErrnoException exception = null;
synchronized (lock) {
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
return;
}
- byte[] answerbuf = null;
try {
- answerbuf = resNetworkResult(fd); // Closes fd, marks it invalid.
+ resp = resNetworkResult(fd); // Closes fd, marks it invalid.
} catch (ErrnoException e) {
Log.e(TAG, "resNetworkResult:" + e.toString());
- answerCallback.onQueryException(e);
- return;
- }
-
- try {
- answerCallback.onAnswer(
- answerCallback.parser.parse(answerbuf));
- } catch (ParseException e) {
- answerCallback.onParseException(e);
+ exception = e;
}
}
+ if (exception != null) {
+ answerCallback.onError(new DnsException(ERROR_SYSTEM, exception));
+ return;
+ }
+ answerCallback.onAnswer(resp.answerbuf, resp.rcode);
});
// Unregister this fd listener
return 0;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 99375f8..dfd7089 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -822,6 +822,11 @@
mEstablishingVpnAppUid = uid;
}
+ /** @hide */
+ public int getEstablishingVpnAppUid() {
+ return mEstablishingVpnAppUid;
+ }
+
/**
* Value indicating that link bandwidth is unspecified.
* @hide
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d2d886b..d07ff13 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -24,7 +24,6 @@
import android.util.Pair;
import java.io.FileDescriptor;
-import java.io.IOException;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -45,32 +44,6 @@
private static final String TAG = "NetworkUtils";
/**
- * Attaches a socket filter that accepts DHCP packets to the given socket.
- */
- @UnsupportedAppUsage
- public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
-
- /**
- * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
- * @param fd the socket's {@link FileDescriptor}.
- * @param packetType the hardware address type, one of ARPHRD_*.
- */
- @UnsupportedAppUsage
- public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
-
- /**
- * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
- *
- * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
- *
- * @param fd the socket's {@link FileDescriptor}.
- * @param packetType the hardware address type, one of ARPHRD_*.
- */
- @UnsupportedAppUsage
- public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
- throws SocketException;
-
- /**
* Attaches a socket filter that drops all of incoming packets.
* @param fd the socket's {@link FileDescriptor}.
*/
@@ -167,9 +140,10 @@
/**
* DNS resolver series jni method.
* Read a result for the query associated with the {@code fd}.
- * @return a byte array containing blob answer
+ * @return DnsResponse containing blob answer and rcode
*/
- public static native byte[] resNetworkResult(FileDescriptor fd) throws ErrnoException;
+ public static native DnsResolver.DnsResponse resNetworkResult(FileDescriptor fd)
+ throws ErrnoException;
/**
* DNS resolver series jni method.
@@ -178,18 +152,6 @@
public static native void resNetworkCancel(FileDescriptor fd);
/**
- * Add an entry into the ARP cache.
- */
- public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
- FileDescriptor fd) throws IOException {
- addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
- }
-
- private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
- FileDescriptor fd) throws IOException;
-
-
- /**
* Get the tcp repair window associated with the {@code fd}.
*
* @param fd the tcp socket's {@link FileDescriptor}.
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index fb5acfa..f01e213 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -114,8 +114,8 @@
}
/**
- * Get a {@link String} listing in priority order of the comma separated domains to search when
- * resolving host names on the link.
+ * Get a {@link String} containing the comma separated domains to search when resolving host
+ * names on this link, in priority order.
*/
public @Nullable String getDomains() {
return domains;
diff --git a/core/java/android/net/TestNetworkInterface.java b/core/java/android/net/TestNetworkInterface.java
index 30e68f5..8455083 100644
--- a/core/java/android/net/TestNetworkInterface.java
+++ b/core/java/android/net/TestNetworkInterface.java
@@ -27,8 +27,6 @@
*/
@TestApi
public final class TestNetworkInterface implements Parcelable {
- private static final String TAG = "TestNetworkInterface";
-
private final ParcelFileDescriptor mFileDescriptor;
private final String mInterfaceName;
diff --git a/core/java/android/net/TestNetworkManager.java b/core/java/android/net/TestNetworkManager.java
index cd58e66..e274005 100644
--- a/core/java/android/net/TestNetworkManager.java
+++ b/core/java/android/net/TestNetworkManager.java
@@ -17,7 +17,6 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
-import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
@@ -33,11 +32,9 @@
@NonNull private static final String TAG = TestNetworkManager.class.getSimpleName();
@NonNull private final ITestNetworkManager mService;
- @NonNull private final Context mContext;
/** @hide */
- public TestNetworkManager(@NonNull Context context, @NonNull ITestNetworkManager service) {
- mContext = Preconditions.checkNotNull(context, "missing Context");
+ public TestNetworkManager(@NonNull ITestNetworkManager service) {
mService = Preconditions.checkNotNull(service, "missing ITestNetworkManager");
}
@@ -88,4 +85,21 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Create a tap interface for testing purposes
+ *
+ * @return A ParcelFileDescriptor of the underlying TAP interface. Close this to tear down the
+ * TAP interface.
+ * @hide
+ */
+ @TestApi
+ public TestNetworkInterface createTapInterface() {
+ try {
+ return mService.createTapInterface();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
}
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index fa0eeb9..a1ac960 100644
--- a/core/java/android/net/UidRange.java
+++ b/core/java/android/net/UidRange.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Collection;
+
/**
* An inclusive range of UIDs.
*
@@ -42,10 +44,16 @@
return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
+ /** Returns the smallest user Id which is contained in this UidRange */
public int getStartUser() {
return start / PER_USER_RANGE;
}
+ /** Returns the largest user Id which is contained in this UidRange */
+ public int getEndUser() {
+ return stop / PER_USER_RANGE;
+ }
+
public boolean contains(int uid) {
return start <= uid && uid <= stop;
}
@@ -117,4 +125,23 @@
return new UidRange[size];
}
};
+
+ /**
+ * Returns whether any of the UidRange in the collection contains the specified uid
+ *
+ * @param ranges The collection of UidRange to check
+ * @param uid the uid in question
+ * @return {@code true} if the uid is contained within the ranges, {@code false} otherwise
+ *
+ * @see UidRange#contains(int)
+ */
+ public static boolean containsUid(Collection<UidRange> ranges, int uid) {
+ if (ranges == null) return false;
+ for (UidRange range : ranges) {
+ if (range.contains(uid)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 82acf6f..28c59db 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -48,17 +48,6 @@
namespace android {
-static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
-static const uint32_t kEtherHeaderLen = sizeof(ether_header);
-static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
-static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
-static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
-static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
-static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
-static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
-static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
-static const uint16_t kDhcpClientPort = 68;
-
constexpr int MAXPACKETSIZE = 8 * 1024;
// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
constexpr int MAXCMDSIZE = 1024;
@@ -84,149 +73,6 @@
env->Throw(reinterpret_cast<jthrowable>(exception));
}
-static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
-{
- struct sock_filter filter_code[] = {
- // Check the protocol is UDP.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
-
- // Check this is not a fragment.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
- BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0),
-
- // Get the IP header length.
- BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
-
- // Check the destination port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
- };
- struct sock_fprog filter = {
- sizeof(filter_code) / sizeof(filter_code[0]),
- filter_code,
- };
-
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
- }
-}
-
-static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
- jint hardwareAddressType)
-{
- if (hardwareAddressType != ARPHRD_ETHER) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "attachRaFilter only supports ARPHRD_ETHER");
- return;
- }
-
- struct sock_filter filter_code[] = {
- // Check IPv6 Next Header is ICMPv6.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
-
- // Check ICMPv6 type is Router Advertisement.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
- };
- struct sock_fprog filter = {
- sizeof(filter_code) / sizeof(filter_code[0]),
- filter_code,
- };
-
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
- }
-}
-
-// TODO: Move all this filter code into libnetutils.
-static void android_net_utils_attachControlPacketFilter(
- JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
- if (hardwareAddressType != ARPHRD_ETHER) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "attachControlPacketFilter only supports ARPHRD_ETHER");
- return;
- }
-
- // Capture all:
- // - ARPs
- // - DHCPv4 packets
- // - Router Advertisements & Solicitations
- // - Neighbor Advertisements & Solicitations
- //
- // tcpdump:
- // arp or
- // '(ip and udp port 68)' or
- // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
- struct sock_filter filter_code[] = {
- // Load the link layer next payload field.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset),
-
- // Accept all ARP.
- // TODO: Figure out how to better filter ARPs on noisy networks.
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),
-
- // If IPv4:
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),
-
- // Check the protocol is UDP.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14),
-
- // Check this is not a fragment.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
- BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0),
-
- // Get the IP header length.
- BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
-
- // Check the source port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0),
-
- // Check the destination port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7),
-
- // IPv6 ...
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
- // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
- BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4),
- // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
- BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2),
- BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
-
- // Accept or reject.
- BPF_STMT(BPF_RET | BPF_K, 0xffff),
- BPF_STMT(BPF_RET | BPF_K, 0)
- };
- struct sock_fprog filter = {
- sizeof(filter_code) / sizeof(filter_code[0]),
- filter_code,
- };
-
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
- }
-}
-
static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
struct sock_filter filter_code[] = {
@@ -389,46 +235,6 @@
return true;
}
-static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
- jbyteArray ipv4Addr, jstring ifname, jobject javaFd)
-{
- struct arpreq req = {};
- struct sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
- struct sockaddr& ethAddrStruct = req.arp_ha;
-
- ethAddrStruct.sa_family = ARPHRD_ETHER;
- if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
- jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
- return;
- }
-
- netAddrStruct.sin_family = AF_INET;
- if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
- jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
- return;
- }
-
- int ifLen = env->GetStringLength(ifname);
- // IFNAMSIZ includes the terminating NULL character
- if (ifLen >= IFNAMSIZ) {
- jniThrowException(env, "java/io/IOException", "ifname too long");
- return;
- }
- env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
-
- req.arp_flags = ATF_COM; // Completed entry (ha valid)
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
- if (fd < 0) {
- jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
- return;
- }
- // See also: man 7 arp
- if (ioctl(fd, SIOCSARP, &req)) {
- jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
- return;
- }
-}
-
static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
jstring dname, jint ns_class, jint ns_type, jint flags) {
const jsize javaCharsCount = env->GetStringLength(dname);
@@ -464,7 +270,7 @@
return jniCreateFileDescriptor(env, fd);
}
-static jbyteArray android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
+static jobject android_net_utils_resNetworkResult(JNIEnv *env, jobject thiz, jobject javaFd) {
int fd = jniGetFDFromFileDescriptor(env, javaFd);
int rcode;
std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
@@ -485,7 +291,10 @@
reinterpret_cast<jbyte*>(buf.data()));
}
- return answer;
+ jclass class_DnsResponse = env->FindClass("android/net/DnsResolver$DnsResponse");
+ jmethodID ctor = env->GetMethodID(class_DnsResponse, "<init>", "([BI)V");
+
+ return env->NewObject(class_DnsResponse, ctor, answer, rcode);
}
static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -542,17 +351,13 @@
{ "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
- { "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) android_net_utils_addArpEntry },
- { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
- { "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
- { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
- { "resNetworkResult", "(Ljava/io/FileDescriptor;)[B", (void*) android_net_utils_resNetworkResult },
+ { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
{ "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
};
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fc9d6f8..c1aff75 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -47,6 +47,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.BroadcastOptions;
import android.app.NotificationManager;
@@ -108,7 +109,6 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
-import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
@@ -133,6 +133,7 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -237,6 +238,16 @@
private static final boolean LOGD_BLOCKED_NETWORKINFO = true;
+ /**
+ * Default URL to use for {@link #getCaptivePortalServerUrl()}. This should not be changed
+ * by OEMs for configuration purposes, as this value is overridden by
+ * Settings.Global.CAPTIVE_PORTAL_HTTP_URL.
+ * R.string.config_networkCaptivePortalServerUrl should be overridden instead for this purpose
+ * (preferably via runtime resource overlays).
+ */
+ private static final String DEFAULT_CAPTIVE_PORTAL_HTTP_URL =
+ "http://connectivitycheck.gstatic.com/generate_204";
+
// TODO: create better separation between radio types and network types
// how long to wait before switching back to a radio's default network
@@ -266,7 +277,8 @@
private Tethering mTethering;
- private final PermissionMonitor mPermissionMonitor;
+ @VisibleForTesting
+ protected final PermissionMonitor mPermissionMonitor;
private KeyStore mKeyStore;
@@ -819,13 +831,13 @@
public ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
this(context, netManager, statsService, policyManager,
- getDnsResolver(), new IpConnectivityLog());
+ getDnsResolver(), new IpConnectivityLog(), NetdService.getInstance());
}
@VisibleForTesting
protected ConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
- IDnsResolver dnsresolver, IpConnectivityLog logger) {
+ IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd) {
if (DBG) log("ConnectivityService starting up");
mSystemProperties = getSystemProperties();
@@ -865,7 +877,7 @@
mDnsResolver = checkNotNull(dnsresolver, "missing IDnsResolver");
mProxyTracker = makeProxyTracker();
- mNetd = NetdService.getInstance();
+ mNetd = netd;
mKeyStore = KeyStore.getInstance();
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -951,7 +963,7 @@
mTethering = makeTethering();
- mPermissionMonitor = new PermissionMonitor(mContext, mNMS, mNetd);
+ mPermissionMonitor = new PermissionMonitor(mContext, mNetd);
// Set up the listener for user state for creating user VPNs.
// Should run on mHandler to avoid any races.
@@ -1628,8 +1640,11 @@
*/
private boolean disallowedBecauseSystemCaller() {
// TODO: start throwing a SecurityException when GnssLocationProvider stops calling
- // requestRouteToHost.
- if (isSystem(Binder.getCallingUid())) {
+ // requestRouteToHost. In Q, GnssLocationProvider is changed to not call requestRouteToHost
+ // for devices launched with Q and above. However, existing devices upgrading to Q and
+ // above must continued to be supported for few more releases.
+ if (isSystem(Binder.getCallingUid()) && SystemProperties.getInt(
+ "ro.product.first_api_level", 0) > Build.VERSION_CODES.P) {
log("This method exists only for app backwards compatibility"
+ " and must not be called by system services.");
return true;
@@ -1761,17 +1776,12 @@
// the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
// event callback for certain nai. e.g. cellular. Register here to pass to
// NetworkMonitor instead.
- // TODO: Move the Dns Event to NetworkMonitor. Use Binder.clearCallingIdentity() in
- // registerNetworkAgent to have NetworkMonitor created with system process as design
- // expectation. Also, NetdEventListenerService only allow one callback from each
- // caller type. Need to re-factor NetdEventListenerService to allow multiple
- // NetworkMonitor registrants.
+ // TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one
+ // callback from each caller type. Need to re-factor NetdEventListenerService to allow
+ // multiple NetworkMonitor registrants.
if (nai != null && nai.satisfies(mDefaultRequest)) {
- try {
- nai.networkMonitor().notifyDnsResponse(returnCode);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
+ Binder.withCleanCallingIdentity(() ->
+ nai.networkMonitor().notifyDnsResponse(returnCode));
}
}
@@ -2433,6 +2443,13 @@
pw.println("NetworkStackClient logs:");
pw.increaseIndent();
NetworkStackClient.getInstance().dump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Permission Monitor:");
+ pw.increaseIndent();
+ mPermissionMonitor.dump(pw);
+ pw.decreaseIndent();
}
private void dumpNetworks(IndentingPrintWriter pw) {
@@ -5457,6 +5474,11 @@
networkAgent.clatd.fixupLinkProperties(oldLp, newLp);
updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities);
+
+ // update filtering rules, need to happen after the interface update so netd knows about the
+ // new interface (the interface name -> index map becomes initialized)
+ updateVpnFiltering(newLp, oldLp, networkAgent);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -5622,6 +5644,37 @@
}
}
+ private void updateVpnFiltering(LinkProperties newLp, LinkProperties oldLp,
+ NetworkAgentInfo nai) {
+ final String oldIface = oldLp != null ? oldLp.getInterfaceName() : null;
+ final String newIface = newLp != null ? newLp.getInterfaceName() : null;
+ final boolean wasFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, oldLp);
+ final boolean needsFiltering = requiresVpnIsolation(nai, nai.networkCapabilities, newLp);
+
+ if (!wasFiltering && !needsFiltering) {
+ // Nothing to do.
+ return;
+ }
+
+ if (Objects.equals(oldIface, newIface) && (wasFiltering == needsFiltering)) {
+ // Nothing changed.
+ return;
+ }
+
+ final Set<UidRange> ranges = nai.networkCapabilities.getUids();
+ final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid();
+ // TODO: this create a window of opportunity for apps to receive traffic between the time
+ // when the old rules are removed and the time when new rules are added. To fix this,
+ // make eBPF support two whitelisted interfaces so here new rules can be added before the
+ // old rules are being removed.
+ if (wasFiltering) {
+ mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
+ }
+ if (needsFiltering) {
+ mPermissionMonitor.onVpnUidRangesAdded(newIface, ranges, vpnAppUid);
+ }
+ }
+
private int getNetworkPermission(NetworkCapabilities nc) {
if (!nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
return INetd.PERMISSION_SYSTEM;
@@ -5764,6 +5817,34 @@
}
}
+ /**
+ * Returns whether VPN isolation (ingress interface filtering) should be applied on the given
+ * network.
+ *
+ * Ingress interface filtering enforces that all apps under the given network can only receive
+ * packets from the network's interface (and loopback). This is important for VPNs because
+ * apps that cannot bypass a fully-routed VPN shouldn't be able to receive packets from any
+ * non-VPN interfaces.
+ *
+ * As a result, this method should return true iff
+ * 1. the network is an app VPN (not legacy VPN)
+ * 2. the VPN does not allow bypass
+ * 3. the VPN is fully-routed
+ * 4. the VPN interface is non-null
+ *
+ * @See INetd#firewallAddUidInterfaceRules
+ * @See INetd#firewallRemoveUidInterfaceRules
+ */
+ private boolean requiresVpnIsolation(@NonNull NetworkAgentInfo nai, NetworkCapabilities nc,
+ LinkProperties lp) {
+ if (nc == null || lp == null) return false;
+ return nai.isVPN()
+ && !nai.networkMisc.allowBypass
+ && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
+ && lp.getInterfaceName() != null
+ && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
+ }
+
private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
NetworkCapabilities newNc) {
Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids();
@@ -5776,6 +5857,12 @@
newRanges.removeAll(prevRangesCopy);
try {
+ // When updating the VPN uid routing rules, add the new range first then remove the old
+ // range. If old range were removed first, there would be a window between the old
+ // range being removed and the new range being added, during which UIDs contained
+ // in both ranges are not subject to any VPN routing rules. Adding new range before
+ // removing old range works because, unlike the filtering rules below, it's possible to
+ // add duplicate UID routing rules.
if (!newRanges.isEmpty()) {
final UidRange[] addedRangesArray = new UidRange[newRanges.size()];
newRanges.toArray(addedRangesArray);
@@ -5786,9 +5873,31 @@
prevRanges.toArray(removedRangesArray);
mNMS.removeVpnUidRanges(nai.network.netId, removedRangesArray);
}
+ final boolean wasFiltering = requiresVpnIsolation(nai, prevNc, nai.linkProperties);
+ final boolean shouldFilter = requiresVpnIsolation(nai, newNc, nai.linkProperties);
+ final String iface = nai.linkProperties.getInterfaceName();
+ // For VPN uid interface filtering, old ranges need to be removed before new ranges can
+ // be added, due to the range being expanded and stored as invidiual UIDs. For example
+ // the UIDs might be updated from [0, 99999] to ([0, 10012], [10014, 99999]) which means
+ // prevRanges = [0, 99999] while newRanges = [0, 10012], [10014, 99999]. If prevRanges
+ // were added first and then newRanges got removed later, there would be only one uid
+ // 10013 left. A consequence of removing old ranges before adding new ranges is that
+ // there is now a window of opportunity when the UIDs are not subject to any filtering.
+ // Note that this is in contrast with the (more robust) update of VPN routing rules
+ // above, where the addition of new ranges happens before the removal of old ranges.
+ // TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
+ // to be removed will never overlap with the new range to be added.
+ if (wasFiltering && !prevRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges,
+ prevNc.getEstablishingVpnAppUid());
+ }
+ if (shouldFilter && !newRanges.isEmpty()) {
+ mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges,
+ newNc.getEstablishingVpnAppUid());
+ }
} catch (Exception e) {
// Never crash!
- loge("Exception in updateUids: " + e);
+ loge("Exception in updateUids: ", e);
}
}
@@ -6549,7 +6658,7 @@
uid, newRules, metered, mRestrictBackground);
}
if (oldBlocked == newBlocked) {
- return;
+ continue;
}
final int arg = encodeBool(newBlocked);
for (int i = 0; i < nai.numNetworkRequests(); i++) {
@@ -6707,9 +6816,20 @@
@Override
public String getCaptivePortalServerUrl() {
enforceConnectivityInternalPermission();
- final String defaultUrl = mContext.getResources().getString(
- R.string.config_networkDefaultCaptivePortalServerUrl);
- return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext, defaultUrl);
+ String settingUrl = mContext.getResources().getString(
+ R.string.config_networkCaptivePortalServerUrl);
+
+ if (!TextUtils.isEmpty(settingUrl)) {
+ return settingUrl;
+ }
+
+ settingUrl = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+ if (!TextUtils.isEmpty(settingUrl)) {
+ return settingUrl;
+ }
+
+ return DEFAULT_CAPTIVE_PORTAL_HTTP_URL;
}
@Override
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index e64ab78..40bf7bc 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -60,6 +60,7 @@
@NonNull private static final String TAG = TestNetworkService.class.getSimpleName();
@NonNull private static final String TEST_NETWORK_TYPE = "TEST_NETWORK";
@NonNull private static final String TEST_TUN_PREFIX = "testtun";
+ @NonNull private static final String TEST_TAP_PREFIX = "testtap";
@NonNull private static final AtomicInteger sTestTunIndex = new AtomicInteger();
@NonNull private final Context mContext;
@@ -70,7 +71,7 @@
@NonNull private final Handler mHandler;
// Native method stubs
- private static native int jniCreateTun(@NonNull String iface);
+ private static native int jniCreateTunTap(boolean isTun, @NonNull String iface);
@VisibleForTesting
protected TestNetworkService(
@@ -85,23 +86,23 @@
}
/**
- * Create a TUN interface with the given interface name and link addresses
+ * Create a TUN or TAP interface with the given interface name and link addresses
*
- * <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the
- * TUN interface.
+ * <p>This method will return the FileDescriptor to the interface. Close it to tear down the
+ * interface.
*/
- @Override
- public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
+ private TestNetworkInterface createInterface(boolean isTun, LinkAddress[] linkAddrs) {
enforceTestNetworkPermissions(mContext);
checkNotNull(linkAddrs, "missing linkAddrs");
- String iface = TEST_TUN_PREFIX + sTestTunIndex.getAndIncrement();
+ String ifacePrefix = isTun ? TEST_TUN_PREFIX : TEST_TAP_PREFIX;
+ String iface = ifacePrefix + sTestTunIndex.getAndIncrement();
return Binder.withCleanCallingIdentity(
() -> {
try {
ParcelFileDescriptor tunIntf =
- ParcelFileDescriptor.adoptFd(jniCreateTun(iface));
+ ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, iface));
for (LinkAddress addr : linkAddrs) {
mNetd.interfaceAddAddress(
iface,
@@ -116,6 +117,28 @@
});
}
+ /**
+ * Create a TUN interface with the given interface name and link addresses
+ *
+ * <p>This method will return the FileDescriptor to the TUN interface. Close it to tear down the
+ * TUN interface.
+ */
+ @Override
+ public TestNetworkInterface createTunInterface(@NonNull LinkAddress[] linkAddrs) {
+ return createInterface(true, linkAddrs);
+ }
+
+ /**
+ * Create a TAP interface with the given interface name
+ *
+ * <p>This method will return the FileDescriptor to the TAP interface. Close it to tear down the
+ * TAP interface.
+ */
+ @Override
+ public TestNetworkInterface createTapInterface() {
+ return createInterface(false, new LinkAddress[0]);
+ }
+
// Tracker for TestNetworkAgents
@GuardedBy("mTestNetworkTracker")
@NonNull
@@ -310,7 +333,7 @@
public void teardownTestNetwork(int netId) {
enforceTestNetworkPermissions(mContext);
- TestNetworkAgent agent;
+ final TestNetworkAgent agent;
synchronized (mTestNetworkTracker) {
agent = mTestNetworkTracker.get(netId);
}
@@ -325,14 +348,10 @@
agent.teardown();
}
- // STOPSHIP: Change this back to android.Manifest.permission.MANAGE_TEST_NETWORKS
- private static final String PERMISSION_NAME = "dummy";
+ private static final String PERMISSION_NAME =
+ android.Manifest.permission.MANAGE_TEST_NETWORKS;
public static void enforceTestNetworkPermissions(@NonNull Context context) {
- // STOPSHIP: Re-enable these checks. Disabled until adoptShellPermissionIdentity() can be
- // called from CTS test code.
- if (false) {
- context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService");
- }
+ context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService");
}
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 35f7ea3..77a18e2 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -191,6 +191,7 @@
case NOT_STARTED : return "NOT_STARTED";
case STARTING : return "STARTING";
case STARTED : return "STARTED";
+ case STOPPING : return "STOPPING";
}
throw new IllegalArgumentException("Unknown state");
}
@@ -314,18 +315,27 @@
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
}
}
- if (NOT_STARTED != mStartedState) {
- mStartedState = STOPPING;
- Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
- if (mType == TYPE_NATT) {
- mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
- } else if (mType == TYPE_TCP) {
- mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
- mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot);
- mTcpController.stopSocketMonitor(mSlot);
- } else {
- Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
- }
+ Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name() + ": " + reason);
+ switch (mStartedState) {
+ case NOT_STARTED:
+ // Remove the reference of the keepalive that meet error before starting,
+ // e.g. invalid parameter.
+ cleanupStoppedKeepalive(mNai, mSlot);
+ break;
+ case STOPPING:
+ // Keepalive is already in stopping state, ignore.
+ return;
+ default:
+ mStartedState = STOPPING;
+ if (mType == TYPE_NATT) {
+ mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ } else if (mType == TYPE_TCP) {
+ mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot);
+ mTcpController.stopSocketMonitor(mSlot);
+ } else {
+ Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
+ }
}
// Close the duplicated fd that maintains the lifecycle of socket whenever
@@ -405,9 +415,9 @@
for (KeepaliveInfo ki : networkKeepalives.values()) {
ki.stop(reason);
}
- networkKeepalives.clear();
- mKeepalives.remove(nai);
}
+ // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
+ // freed.
}
public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) {
@@ -423,8 +433,24 @@
return;
}
ki.stop(reason);
+ // Clean up keepalives will be done as a result of calling ki.stop() after the slots are
+ // freed.
+ }
+
+ private void cleanupStoppedKeepalive(NetworkAgentInfo nai, int slot) {
+ String networkName = (nai == null) ? "(null)" : nai.name();
+ HashMap<Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai);
+ if (networkKeepalives == null) {
+ Log.e(TAG, "Attempt to remove keepalive on nonexistent network " + networkName);
+ return;
+ }
+ KeepaliveInfo ki = networkKeepalives.get(slot);
+ if (ki == null) {
+ Log.e(TAG, "Attempt to remove nonexistent keepalive " + slot + " on " + networkName);
+ return;
+ }
networkKeepalives.remove(slot);
- Log.d(TAG, "Stopped keepalive " + slot + " on " + networkName + ", "
+ Log.d(TAG, "Remove keepalive " + slot + " on " + networkName + ", "
+ networkKeepalives.size() + " remains.");
if (networkKeepalives.isEmpty()) {
mKeepalives.remove(nai);
@@ -495,10 +521,11 @@
handleStopKeepalive(nai, slot, reason);
}
} else if (KeepaliveInfo.STOPPING == ki.mStartedState) {
- // The message indicated result of stopping : don't call handleStopKeepalive.
+ // The message indicated result of stopping : clean up keepalive slots.
Log.d(TAG, "Stopped keepalive " + slot + " on " + nai.name()
+ " stopped: " + reason);
ki.mStartedState = KeepaliveInfo.NOT_STARTED;
+ cleanupStoppedKeepalive(nai, slot);
} else {
Log.wtf(TAG, "Event " + message.what + "," + slot + "," + reason
+ " for keepalive in wrong state: " + ki.toString());
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
index 948c690..a1a8e35 100644
--- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -33,6 +33,7 @@
import android.util.Pair;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.TrafficStatsConstants;
import libcore.io.IoUtils;
@@ -381,7 +382,8 @@
protected void setupSocket(
int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
throws ErrnoException, IOException {
- final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE);
+ final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+ TrafficStatsConstants.TAG_SYSTEM_PROBE);
try {
mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
} finally {
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index b694602..f8582cd 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -37,22 +37,27 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.net.INetd;
+import android.net.UidRange;
import android.os.Build;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.system.OsConstants;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -60,6 +65,7 @@
import java.util.Map.Entry;
import java.util.Set;
+
/**
* A utility class to inform Netd of UID permisisons.
* Does a mass update at boot and then monitors for app install/remove.
@@ -73,18 +79,29 @@
protected static final Boolean NETWORK = Boolean.FALSE;
private static final int VERSION_Q = Build.VERSION_CODES.Q;
- private final Context mContext;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
- private final INetworkManagementService mNMS;
private final INetd mNetd;
// Values are User IDs.
+ @GuardedBy("this")
private final Set<Integer> mUsers = new HashSet<>();
- // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
+ // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission.
+ @GuardedBy("this")
private final Map<Integer, Boolean> mApps = new HashMap<>();
+ // Keys are active non-bypassable and fully-routed VPN's interface name, Values are uid ranges
+ // for apps under the VPN
+ @GuardedBy("this")
+ private final Map<String, Set<UidRange>> mVpnUidRanges = new HashMap<>();
+
+ // A set of appIds for apps across all users on the device. We track appIds instead of uids
+ // directly to reduce its size and also eliminate the need to update this set when user is
+ // added/removed.
+ @GuardedBy("this")
+ private final Set<Integer> mAllApps = new HashSet<>();
+
private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
private int getPermissionForUid(int uid) {
@@ -118,12 +135,10 @@
}
}
- public PermissionMonitor(Context context, INetworkManagementService nms, INetd netdService) {
- mContext = context;
+ public PermissionMonitor(Context context, INetd netd) {
mPackageManager = context.getPackageManager();
- mUserManager = UserManager.get(context);
- mNMS = nms;
- mNetd = netdService;
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mNetd = netd;
}
// Intended to be called only once at startup, after the system is ready. Installs a broadcast
@@ -151,6 +166,7 @@
if (uid < 0) {
continue;
}
+ mAllApps.add(UserHandle.getAppId(uid));
boolean isNetwork = hasNetworkPermission(app);
boolean hasRestrictedPermission = hasRestrictedNetworkPermission(app);
@@ -270,10 +286,11 @@
}
}
- private int[] toIntArray(List<Integer> list) {
+ private int[] toIntArray(Collection<Integer> list) {
int[] array = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- array[i] = list.get(i);
+ int i = 0;
+ for (Integer item : list) {
+ array[i++] = item;
}
return array;
}
@@ -289,11 +306,11 @@
}
try {
if (add) {
- mNMS.setPermission("NETWORK", toIntArray(network));
- mNMS.setPermission("SYSTEM", toIntArray(system));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_NETWORK, toIntArray(network));
+ mNetd.networkSetPermissionForUser(INetd.PERMISSION_SYSTEM, toIntArray(system));
} else {
- mNMS.clearPermission(toIntArray(network));
- mNMS.clearPermission(toIntArray(system));
+ mNetd.networkClearPermissionForUser(toIntArray(network));
+ mNetd.networkClearPermissionForUser(toIntArray(system));
}
} catch (RemoteException e) {
loge("Exception when updating permissions: " + e);
@@ -376,6 +393,19 @@
apps.put(uid, permission);
update(mUsers, apps, true);
}
+
+ // If the newly-installed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen after the mApps update above, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, true);
+ }
+ }
+ mAllApps.add(UserHandle.getAppId(uid));
}
/**
@@ -386,8 +416,23 @@
* @hide
*/
public synchronized void onPackageRemoved(int uid) {
- Map<Integer, Boolean> apps = new HashMap<>();
+ // If the newly-removed package falls within some VPN's uid range, update Netd with it.
+ // This needs to happen before the mApps update below, since removeBypassingUids() depends
+ // on mApps to check if the package can bypass VPN.
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ if (UidRange.containsUid(vpn.getValue(), uid)) {
+ final Set<Integer> changedUids = new HashSet<>();
+ changedUids.add(uid);
+ removeBypassingUids(changedUids, /* vpnAppUid */ -1);
+ updateVpnUids(vpn.getKey(), changedUids, false);
+ }
+ }
+ // If the package has been removed from all users on the device, clear it form mAllApps.
+ if (mPackageManager.getNameForUid(uid) == null) {
+ mAllApps.remove(UserHandle.getAppId(uid));
+ }
+ Map<Integer, Boolean> apps = new HashMap<>();
Boolean permission = null;
String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages != null && packages.length > 0) {
@@ -443,6 +488,121 @@
}
/**
+ * Called when a new set of UID ranges are added to an active VPN network
+ *
+ * @param iface The active VPN network's interface name
+ * @param rangesToAdd The new UID ranges to be added to the network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesAdded(@NonNull String iface, Set<UidRange> rangesToAdd,
+ int vpnAppUid) {
+ // Calculate the list of new app uids under the VPN due to the new UID ranges and update
+ // Netd about them. Because mAllApps only contains appIds instead of uids, the result might
+ // be an overestimation if an app is not installed on the user on which the VPN is running,
+ // but that's safe.
+ final Set<Integer> changedUids = intersectUids(rangesToAdd, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, true);
+ if (mVpnUidRanges.containsKey(iface)) {
+ mVpnUidRanges.get(iface).addAll(rangesToAdd);
+ } else {
+ mVpnUidRanges.put(iface, new HashSet<UidRange>(rangesToAdd));
+ }
+ }
+
+ /**
+ * Called when a set of UID ranges are removed from an active VPN network
+ *
+ * @param iface The VPN network's interface name
+ * @param rangesToRemove Existing UID ranges to be removed from the VPN network
+ * @param vpnAppUid The uid of the VPN app
+ */
+ public synchronized void onVpnUidRangesRemoved(@NonNull String iface,
+ Set<UidRange> rangesToRemove, int vpnAppUid) {
+ // Calculate the list of app uids that are no longer under the VPN due to the removed UID
+ // ranges and update Netd about them.
+ final Set<Integer> changedUids = intersectUids(rangesToRemove, mAllApps);
+ removeBypassingUids(changedUids, vpnAppUid);
+ updateVpnUids(iface, changedUids, false);
+ Set<UidRange> existingRanges = mVpnUidRanges.getOrDefault(iface, null);
+ if (existingRanges == null) {
+ loge("Attempt to remove unknown vpn uid Range iface = " + iface);
+ return;
+ }
+ existingRanges.removeAll(rangesToRemove);
+ if (existingRanges.size() == 0) {
+ mVpnUidRanges.remove(iface);
+ }
+ }
+
+ /**
+ * Compute the intersection of a set of UidRanges and appIds. Returns a set of uids
+ * that satisfies:
+ * 1. falls into one of the UidRange
+ * 2. matches one of the appIds
+ */
+ private Set<Integer> intersectUids(Set<UidRange> ranges, Set<Integer> appIds) {
+ Set<Integer> result = new HashSet<>();
+ for (UidRange range : ranges) {
+ for (int userId = range.getStartUser(); userId <= range.getEndUser(); userId++) {
+ for (int appId : appIds) {
+ final int uid = UserHandle.getUid(userId, appId);
+ if (range.contains(uid)) {
+ result.add(uid);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Remove all apps which can elect to bypass the VPN from the list of uids
+ *
+ * An app can elect to bypass the VPN if it hold SYSTEM permission, or if its the active VPN
+ * app itself.
+ *
+ * @param uids The list of uids to operate on
+ * @param vpnAppUid The uid of the VPN app
+ */
+ private void removeBypassingUids(Set<Integer> uids, int vpnAppUid) {
+ uids.remove(vpnAppUid);
+ uids.removeIf(uid -> mApps.getOrDefault(uid, NETWORK) == SYSTEM);
+ }
+
+ /**
+ * Update netd about the list of uids that are under an active VPN connection which they cannot
+ * bypass.
+ *
+ * This is to instruct netd to set up appropriate filtering rules for these uids, such that they
+ * can only receive ingress packets from the VPN's tunnel interface (and loopback).
+ *
+ * @param iface the interface name of the active VPN connection
+ * @param add {@code true} if the uids are to be added to the interface, {@code false} if they
+ * are to be removed from the interface.
+ */
+ private void updateVpnUids(String iface, Set<Integer> uids, boolean add) {
+ if (uids.size() == 0) {
+ return;
+ }
+ try {
+ if (add) {
+ mNetd.firewallAddUidInterfaceRules(iface, toIntArray(uids));
+ } else {
+ mNetd.firewallRemoveUidInterfaceRules(toIntArray(uids));
+ }
+ } catch (ServiceSpecificException e) {
+ // Silently ignore exception when device does not support eBPF, otherwise just log
+ // the exception and do not crash
+ if (e.errorCode != OsConstants.EOPNOTSUPP) {
+ loge("Exception when updating permissions: ", e);
+ }
+ } catch (RemoteException e) {
+ loge("Exception when updating permissions: ", e);
+ }
+ }
+
+ /**
* Called by PackageListObserver when a package is installed/uninstalled. Send the updated
* permission information to netd.
*
@@ -528,6 +688,24 @@
}
}
+ /** Should only be used by unit tests */
+ @VisibleForTesting
+ public Set<UidRange> getVpnUidRanges(String iface) {
+ return mVpnUidRanges.get(iface);
+ }
+
+ /** Dump info to dumpsys */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("Interface filtering rules:");
+ pw.increaseIndent();
+ for (Map.Entry<String, Set<UidRange>> vpn : mVpnUidRanges.entrySet()) {
+ pw.println("Interface: " + vpn.getKey());
+ pw.println("UIDs: " + vpn.getValue().toString());
+ pw.println();
+ }
+ pw.decreaseIndent();
+ }
+
private static void log(String s) {
if (DBG) {
Log.d(TAG, s);
diff --git a/services/core/jni/com_android_server_TestNetworkService.cpp b/services/core/jni/com_android_server_TestNetworkService.cpp
index b90ff23..36a6fde 100644
--- a/services/core/jni/com_android_server_TestNetworkService.cpp
+++ b/services/core/jni/com_android_server_TestNetworkService.cpp
@@ -54,12 +54,12 @@
jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
-static int createTunInterface(JNIEnv* env, const char* iface) {
+static int createTunTapInterface(JNIEnv* env, bool isTun, const char* iface) {
base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
ifreq ifr{};
// Allocate interface.
- ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+ ifr.ifr_flags = (isTun ? IFF_TUN : IFF_TAP) | IFF_NO_PI;
strlcpy(ifr.ifr_name, iface, IFNAMSIZ);
if (ioctl(tun.get(), TUNSETIFF, &ifr)) {
throwException(env, errno, "allocating", ifr.ifr_name);
@@ -80,23 +80,23 @@
//------------------------------------------------------------------------------
-static jint create(JNIEnv* env, jobject /* thiz */, jstring jIface) {
+static jint create(JNIEnv* env, jobject /* thiz */, jboolean isTun, jstring jIface) {
ScopedUtfChars iface(env, jIface);
if (!iface.c_str()) {
jniThrowNullPointerException(env, "iface");
return -1;
}
- int tun = createTunInterface(env, iface.c_str());
+ int tun = createTunTapInterface(env, isTun, iface.c_str());
- // Any exceptions will be thrown from the createTunInterface call
+ // Any exceptions will be thrown from the createTunTapInterface call
return tun;
}
//------------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
- {"jniCreateTun", "(Ljava/lang/String;)I", (void*)create},
+ {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
};
int register_android_server_TestNetworkService(JNIEnv* env) {
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index c62d85e..9098f90 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -1,12 +1,10 @@
//########################################################################
// Build FrameworksNetTests package
//########################################################################
-
-android_test {
- name: "FrameworksNetTests",
- // Include all test java files.
- srcs: ["java/**/*.java"],
+java_defaults {
+ name: "FrameworksNetTests-jni-defaults",
static_libs: [
+ "FrameworksNetCommonTests",
"frameworks-base-testutils",
"framework-protos",
"androidx.test.rules",
@@ -20,6 +18,52 @@
"android.test.base",
"android.test.mock",
],
+ jni_libs: [
+ "ld-android",
+ "libartbase",
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libbinderthreadstate",
+ "libbpf",
+ "libbpf_android",
+ "libc++",
+ "libcgrouprc",
+ "libcrypto",
+ "libcutils",
+ "libdexfile",
+ "libdl_android",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "libjsoncpp",
+ "liblog",
+ "liblzma",
+ "libnativehelper",
+ "libnetdbpf",
+ "libnetdutils",
+ "libpackagelistparser",
+ "libpcre2",
+ "libprocessgroup",
+ "libselinux",
+ "libui",
+ "libutils",
+ "libvndksupport",
+ "libtinyxml2",
+ "libunwindstack",
+ "libutilscallstack",
+ "libziparchive",
+ "libz",
+ "netd_aidl_interface-cpp",
+ "libnetworkstatsfactorytestjni",
+ ],
+}
+
+android_test {
+ name: "FrameworksNetTests",
+ defaults: ["FrameworksNetTests-jni-defaults"],
+ srcs: ["java/**/*.java"],
platform_apis: true,
test_suites: ["device-tests"],
certificate: "platform",
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
new file mode 100644
index 0000000..0a1ac75
--- /dev/null
+++ b/tests/net/common/Android.bp
@@ -0,0 +1,29 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Tests in this folder are included both in unit tests and CTS.
+// They must be fast and stable, and exercise public or test APIs.
+java_library {
+ name: "FrameworksNetCommonTests",
+ srcs: ["java/**/*.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "junit",
+ ],
+ libs: [
+ "android.test.base.stubs",
+ ],
+}
\ No newline at end of file
diff --git a/tests/net/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
similarity index 84%
rename from tests/net/java/android/net/IpPrefixTest.java
rename to tests/net/common/java/android/net/IpPrefixTest.java
index abf019a..719960d 100644
--- a/tests/net/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -39,7 +39,7 @@
@SmallTest
public class IpPrefixTest {
- private static InetAddress Address(String addr) {
+ private static InetAddress address(String addr) {
return InetAddress.parseNumericAddress(addr);
}
@@ -58,59 +58,59 @@
try {
p = new IpPrefix((byte[]) null, 9);
fail("Expected NullPointerException: null byte array");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix((InetAddress) null, 10);
fail("Expected NullPointerException: null InetAddress");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix((String) null);
fail("Expected NullPointerException: null String");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
byte[] b2 = {1, 2, 3, 4, 5};
p = new IpPrefix(b2, 29);
fail("Expected IllegalArgumentException: invalid array length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.4");
fail("Expected IllegalArgumentException: no prefix length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.4/");
fail("Expected IllegalArgumentException: empty prefix length");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("foo/32");
fail("Expected IllegalArgumentException: invalid address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1/32");
fail("Expected IllegalArgumentException: deprecated IPv4 format");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("1.2.3.256/32");
fail("Expected IllegalArgumentException: invalid IPv4 address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("foo/32");
fail("Expected IllegalArgumentException: non-address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
try {
p = new IpPrefix("f00:::/32");
fail("Expected IllegalArgumentException: invalid IPv6 address");
- } catch(IllegalArgumentException expected) {}
+ } catch (IllegalArgumentException expected) { }
}
@Test
@@ -132,17 +132,17 @@
try {
p = new IpPrefix(IPV4_BYTES, 33);
fail("Expected IllegalArgumentException: invalid prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV4_BYTES, 128);
fail("Expected IllegalArgumentException: invalid prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV4_BYTES, -1);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
p = new IpPrefix(IPV6_BYTES, 128);
assertEquals("2001:db8:dead:beef:f00::a0/128", p.toString());
@@ -162,12 +162,12 @@
try {
p = new IpPrefix(IPV6_BYTES, -1);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
try {
p = new IpPrefix(IPV6_BYTES, 129);
fail("Expected IllegalArgumentException: negative prefix length");
- } catch(RuntimeException expected) {}
+ } catch (RuntimeException expected) { }
}
@@ -226,28 +226,28 @@
@Test
public void testContainsInetAddress() {
IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127");
- assertTrue(p.contains(Address("2001:db8:f00::ace:d00c")));
- assertTrue(p.contains(Address("2001:db8:f00::ace:d00d")));
- assertFalse(p.contains(Address("2001:db8:f00::ace:d00e")));
- assertFalse(p.contains(Address("2001:db8:f00::bad:d00d")));
- assertFalse(p.contains(Address("2001:4868:4860::8888")));
- assertFalse(p.contains(Address("8.8.8.8")));
+ assertTrue(p.contains(address("2001:db8:f00::ace:d00c")));
+ assertTrue(p.contains(address("2001:db8:f00::ace:d00d")));
+ assertFalse(p.contains(address("2001:db8:f00::ace:d00e")));
+ assertFalse(p.contains(address("2001:db8:f00::bad:d00d")));
+ assertFalse(p.contains(address("2001:4868:4860::8888")));
+ assertFalse(p.contains(address("8.8.8.8")));
p = new IpPrefix("192.0.2.0/23");
- assertTrue(p.contains(Address("192.0.2.43")));
- assertTrue(p.contains(Address("192.0.3.21")));
- assertFalse(p.contains(Address("192.0.0.21")));
- assertFalse(p.contains(Address("8.8.8.8")));
- assertFalse(p.contains(Address("2001:4868:4860::8888")));
+ assertTrue(p.contains(address("192.0.2.43")));
+ assertTrue(p.contains(address("192.0.3.21")));
+ assertFalse(p.contains(address("192.0.0.21")));
+ assertFalse(p.contains(address("8.8.8.8")));
+ assertFalse(p.contains(address("2001:4868:4860::8888")));
IpPrefix ipv6Default = new IpPrefix("::/0");
- assertTrue(ipv6Default.contains(Address("2001:db8::f00")));
- assertFalse(ipv6Default.contains(Address("192.0.2.1")));
+ assertTrue(ipv6Default.contains(address("2001:db8::f00")));
+ assertFalse(ipv6Default.contains(address("192.0.2.1")));
IpPrefix ipv4Default = new IpPrefix("0.0.0.0/0");
- assertTrue(ipv4Default.contains(Address("255.255.255.255")));
- assertTrue(ipv4Default.contains(Address("192.0.2.1")));
- assertFalse(ipv4Default.contains(Address("2001:db8::f00")));
+ assertTrue(ipv4Default.contains(address("255.255.255.255")));
+ assertTrue(ipv4Default.contains(address("192.0.2.1")));
+ assertFalse(ipv4Default.contains(address("2001:db8::f00")));
}
@Test
@@ -315,10 +315,10 @@
p = new IpPrefix(b, random.nextInt(129));
}
if (p.equals(oldP)) {
- assertEquals(p.hashCode(), oldP.hashCode());
+ assertEquals(p.hashCode(), oldP.hashCode());
}
if (p.hashCode() != oldP.hashCode()) {
- assertNotEquals(p, oldP);
+ assertNotEquals(p, oldP);
}
}
}
@@ -332,9 +332,9 @@
new IpPrefix("0.0.0.0/0"),
};
for (int i = 0; i < prefixes.length; i++) {
- for (int j = i + 1; j < prefixes.length; j++) {
- assertNotEquals(prefixes[i].hashCode(), prefixes[j].hashCode());
- }
+ for (int j = i + 1; j < prefixes.length; j++) {
+ assertNotEquals(prefixes[i].hashCode(), prefixes[j].hashCode());
+ }
}
}
@@ -371,8 +371,8 @@
}
public void assertParcelingIsLossless(IpPrefix p) {
- IpPrefix p2 = passThroughParcel(p);
- assertEquals(p, p2);
+ IpPrefix p2 = passThroughParcel(p);
+ assertEquals(p, p2);
}
@Test
diff --git a/tests/net/java/android/net/RouteInfoTest.java b/tests/net/java/android/net/RouteInfoTest.java
index 831fefd..2edbd40 100644
--- a/tests/net/java/android/net/RouteInfoTest.java
+++ b/tests/net/java/android/net/RouteInfoTest.java
@@ -16,15 +16,16 @@
package android.net;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
-import android.net.IpPrefix;
-import android.net.RouteInfo;
import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
public class RouteInfoTest extends TestCase {
@@ -152,67 +153,85 @@
}
public void testHostAndDefaultRoutes() {
- RouteInfo r;
+ RouteInfo r;
- r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertTrue(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/0"), Address("0.0.0.0"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertTrue(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
- assertFalse(r.isHostRoute());
- assertTrue(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertTrue(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/0"), Address("::"), "wlan0");
+ assertFalse(r.isHostRoute());
+ assertTrue(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertTrue(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
- assertFalse(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/48"), null, "wlan0");
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), Address("0.0.0.0"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), Address("::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("192.0.2.0/32"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("2001:db8::/128"), null, "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("::/128"), Address("fe80::"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
- r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
- assertTrue(r.isHostRoute());
- assertFalse(r.isDefaultRoute());
- assertFalse(r.isIPv4Default());
- assertFalse(r.isIPv6Default());
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(Prefix("0.0.0.0/32"), Address("192.0.2.1"), "wlan0");
+ assertTrue(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
+
+ r = new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE);
+ assertFalse(r.isHostRoute());
+ assertFalse(r.isDefaultRoute());
+ assertFalse(r.isIPv4Default());
+ assertFalse(r.isIPv6Default());
}
public void testTruncation() {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a4016ef..e3c6c41 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -60,11 +60,13 @@
import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
import static com.android.internal.util.TestUtils.waitForIdleHandler;
import static com.android.internal.util.TestUtils.waitForIdleLooper;
import static com.android.internal.util.TestUtils.waitForIdleSerialExecutor;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -72,12 +74,14 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -97,6 +101,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -152,6 +159,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.system.Os;
import android.test.mock.MockContentResolver;
@@ -187,6 +195,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
@@ -195,6 +204,7 @@
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -262,6 +272,7 @@
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@Mock NetworkStackClient mNetworkStack;
+ @Mock UserManager mUserManager;
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -333,6 +344,7 @@
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
+ if (Context.USER_SERVICE.equals(name)) return mUserManager;
return super.getSystemService(name);
}
@@ -1059,7 +1071,7 @@
public WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager,
IpConnectivityLog log, INetd netd, IDnsResolver dnsResolver) {
- super(context, netManager, statsService, policyManager, dnsResolver, log);
+ super(context, netManager, statsService, policyManager, dnsResolver, log, netd);
mNetd = netd;
mLingerDelayMs = TEST_LINGER_DELAY_MS;
}
@@ -1198,6 +1210,11 @@
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
}
+ private static final int VPN_USER = 0;
+ private static final int APP1_UID = UserHandle.getUid(VPN_USER, 10100);
+ private static final int APP2_UID = UserHandle.getUid(VPN_USER, 10101);
+ private static final int VPN_UID = UserHandle.getUid(VPN_USER, 10043);
+
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
@@ -1205,6 +1222,11 @@
MockitoAnnotations.initMocks(this);
when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(VPN_USER, "", 0),
+ }));
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
@@ -4272,8 +4294,9 @@
}
// Check that there is no port leaked after all keepalives and sockets are closed.
- assertFalse(isUdpPortInUse(srcPort));
- assertFalse(isUdpPortInUse(srcPort2));
+ // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+ // assertFalse(isUdpPortInUse(srcPort));
+ // assertFalse(isUdpPortInUse(srcPort2));
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
@@ -4401,7 +4424,8 @@
assertEquals(anyIPv4, sa.getAddress());
testPfd.close();
- assertFalse(isUdpPortInUse(srcPort));
+ // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7.
+ // assertFalse(isUdpPortInUse(srcPort));
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
@@ -6120,4 +6144,171 @@
assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
}
+
+ @Test
+ @Ignore
+ public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+ assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange));
+
+ vpnNetworkAgent.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ @Ignore
+ public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // Legacy VPN should not have interface rules set up
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ @Ignore
+ public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
+ throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, Process.SYSTEM_UID, vpnRange);
+
+ // IPv6 unreachable route should not be misinterpreted as a default route
+ verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any());
+ }
+
+ @Test
+ @Ignore
+ public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID, vpnRange);
+
+ // Connected VPN should have interface rules set up. There are two expected invocations,
+ // one during VPN uid update, one during VPN LinkProperties update
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+ lp.setInterfaceName("tun1");
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN handover (switch to a new interface) should result in rules being updated (old rules
+ // removed first, then new rules added)
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1"));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // VPN not routing everything should no longer have interface filtering rules
+ verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+
+ reset(mMockNetd);
+ lp = new LinkProperties();
+ lp.setInterfaceName("tun1");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ vpnNetworkAgent.sendLinkProperties(lp);
+ waitForIdle();
+ // Back to routing all IPv6 traffic should have filtering rules
+ verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ }
+
+ @Test
+ @Ignore
+ public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("tun0");
+ lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
+ // The uid range needs to cover the test app so the network is visible to it.
+ final UidRange vpnRange = UidRange.createForUser(VPN_USER);
+ final MockNetworkAgent vpnNetworkAgent = establishVpn(lp, VPN_UID,
+ Collections.singleton(vpnRange));
+
+ reset(mMockNetd);
+ InOrder inOrder = inOrder(mMockNetd);
+
+ // Update to new range which is old range minus APP1, i.e. only APP2
+ final Set<UidRange> newRanges = new HashSet<>(Arrays.asList(
+ new UidRange(vpnRange.start, APP1_UID - 1),
+ new UidRange(APP1_UID + 1, vpnRange.stop)));
+ vpnNetworkAgent.setUids(newRanges);
+ waitForIdle();
+
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ // Verify old rules are removed before new rules are added
+ inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture());
+ assertContainsExactly(uidCaptor.getValue(), APP2_UID);
+ }
+
+
+ private MockNetworkAgent establishVpn(LinkProperties lp, int establishingUid,
+ Set<UidRange> vpnRange) {
+ final MockNetworkAgent vpnNetworkAgent = new MockNetworkAgent(TRANSPORT_VPN, lp);
+ vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid);
+ mMockVpn.setNetworkAgent(vpnNetworkAgent);
+ mMockVpn.connect();
+ mMockVpn.setUids(vpnRange);
+ vpnNetworkAgent.connect(true);
+ waitForIdle();
+ return vpnNetworkAgent;
+ }
+
+ private void assertContainsExactly(int[] actual, int... expected) {
+ int[] sortedActual = Arrays.copyOf(actual, actual.length);
+ int[] sortedExpected = Arrays.copyOf(expected, expected.length);
+ Arrays.sort(sortedActual);
+ Arrays.sort(sortedExpected);
+ assertArrayEquals(sortedExpected, sortedActual);
+ }
+
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) {
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.requestedPermissions = new String[0];
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.privateFlags = 0;
+ packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM,
+ UserHandle.getAppId(uid));
+ return packageInfo;
+ }
}
diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 68ff777..22a2c94 100644
--- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
@@ -134,11 +135,11 @@
IBinder binderMock = mock(IBinder.class);
doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
- RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
-
- // Verify that cleanup is performed (Spy limitations prevent verification of method calls
- // for binder death scenario; check refcount to determine if cleanup was performed.)
- assertEquals(-1, refcountedResource.mRefCount);
+ try {
+ getTestRefcountedResource(binderMock);
+ fail("Expected exception to propogate when binder fails to link to death");
+ } catch (RuntimeException expected) {
+ }
}
@Test
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index b5c3e92..4a35015 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -156,10 +156,21 @@
@Test
public void testOpenAndCloseUdpEncapsulationSocket() throws Exception {
- int localport = findUnusedPort();
+ int localport = -1;
+ IpSecUdpEncapResponse udpEncapResp = null;
- IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ for (int i = 0; i < IpSecService.MAX_PORT_BIND_ATTEMPTS; i++) {
+ localport = findUnusedPort();
+
+ udpEncapResp = mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ assertNotNull(udpEncapResp);
+ if (udpEncapResp.status == IpSecManager.Status.OK) {
+ break;
+ }
+
+ // Else retry to reduce possibility for port-bind failures.
+ }
+
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
assertEquals(localport, udpEncapResp.port);
@@ -204,12 +215,11 @@
@Test
public void testOpenUdpEncapsulationSocketAfterClose() throws Exception {
- int localport = findUnusedPort();
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
+ int localport = udpEncapResp.port;
mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
udpEncapResp.fileDescriptor.close();
@@ -226,12 +236,11 @@
*/
@Test
public void testUdpEncapPortNotReleased() throws Exception {
- int localport = findUnusedPort();
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
+ int localport = udpEncapResp.port;
udpEncapResp.fileDescriptor.close();
@@ -273,14 +282,11 @@
@Test
public void testOpenUdpEncapsulationSocketTwice() throws Exception {
- int localport = findUnusedPort();
-
IpSecUdpEncapResponse udpEncapResp =
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ mIpSecService.openUdpEncapsulationSocket(0, new Binder());
assertNotNull(udpEncapResp);
assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
- assertEquals(localport, udpEncapResp.port);
- mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
+ int localport = udpEncapResp.port;
IpSecUdpEncapResponse testUdpEncapResp =
mIpSecService.openUdpEncapsulationSocket(localport, new Binder());
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 106cd1f..62a4718 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -28,6 +28,7 @@
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.os.Process.SYSTEM_UID;
import static com.android.server.connectivity.PermissionMonitor.NETWORK;
@@ -36,13 +37,16 @@
import static junit.framework.Assert.fail;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.anyInt;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,10 +57,12 @@
import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
import android.net.INetd;
+import android.net.UidRange;
import android.os.Build;
-import android.os.INetworkManagementService;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.SparseIntArray;
import androidx.test.filters.SmallTest;
@@ -73,7 +79,12 @@
import org.mockito.invocation.InvocationOnMock;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -84,10 +95,12 @@
private static final int MOCK_UID2 = 10086;
private static final int SYSTEM_UID1 = 1000;
private static final int SYSTEM_UID2 = 1008;
+ private static final int VPN_UID = 10002;
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
+ private static final String VPN_PACKAGE = "vpnApp";
private static final String PARTITION_SYSTEM = "system";
private static final String PARTITION_OEM = "oem";
private static final String PARTITION_PRODUCT = "product";
@@ -97,9 +110,9 @@
@Mock private Context mContext;
@Mock private PackageManager mPackageManager;
- @Mock private INetworkManagementService mNMS;
@Mock private INetd mNetdService;
@Mock private PackageManagerInternal mMockPmi;
+ @Mock private UserManager mUserManager;
private PackageManagerInternal.PackageListObserver mObserver;
private PermissionMonitor mPermissionMonitor;
@@ -108,7 +121,14 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS, mNetdService));
+ when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(MOCK_USER1, "", 0),
+ new UserInfo(MOCK_USER2, "", 0),
+ }));
+
+ mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService));
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mMockPmi);
@@ -134,7 +154,7 @@
return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
}
- private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
int[] requestedPermissionsFlags = new int[permissions.length];
for (int i = 0; i < permissions.length; i++) {
requestedPermissionsFlags[i] = REQUESTED_PERMISSION_GRANTED;
@@ -143,7 +163,7 @@
requestedPermissionsFlags);
}
- private PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
+ private static PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
int[] requestedPermissionsFlags) {
final PackageInfo packageInfo = new PackageInfo();
packageInfo.requestedPermissions = permissions;
@@ -165,6 +185,18 @@
return packageInfo;
}
+ private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) {
+ final PackageInfo pkgInfo;
+ if (hasSystemPermission) {
+ pkgInfo = packageInfoWithPermissions(new String[] {CHANGE_NETWORK_STATE, NETWORK_STACK},
+ PARTITION_SYSTEM);
+ } else {
+ pkgInfo = packageInfoWithPermissions(new String[] {}, "");
+ }
+ pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
+ return pkgInfo;
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = packageInfoWithPermissions(new String[] {}, PARTITION_SYSTEM);
@@ -245,14 +277,14 @@
assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE));
}
- private class NMSMonitor {
+ private class NetdMonitor {
private final HashMap<Integer, Boolean> mApps = new HashMap<>();
- NMSMonitor(INetworkManagementService mockNMS) throws Exception {
+ NetdMonitor(INetd mockNetd) throws Exception {
// Add hook to verify and track result of setPermission.
doAnswer((InvocationOnMock invocation) -> {
final Object[] args = invocation.getArguments();
- final Boolean isSystem = args[0].equals("SYSTEM");
+ final Boolean isSystem = args[0].equals(INetd.PERMISSION_SYSTEM);
for (final int uid : (int[]) args[1]) {
// TODO: Currently, permission monitor will send duplicate commands for each uid
// corresponding to each user. Need to fix that and uncomment below test.
@@ -262,7 +294,7 @@
mApps.put(uid, isSystem);
}
return null;
- }).when(mockNMS).setPermission(anyString(), any(int[].class));
+ }).when(mockNetd).networkSetPermissionForUser(anyInt(), any(int[].class));
// Add hook to verify and track result of clearPermission.
doAnswer((InvocationOnMock invocation) -> {
@@ -276,7 +308,7 @@
mApps.remove(uid);
}
return null;
- }).when(mockNMS).clearPermission(any(int[].class));
+ }).when(mockNetd).networkClearPermissionForUser(any(int[].class));
}
public void expectPermission(Boolean permission, int[] users, int[] apps) {
@@ -307,7 +339,7 @@
@Test
public void testUserAndPackageAddRemove() throws Exception {
- final NMSMonitor mNMSMonitor = new NMSMonitor(mNMS);
+ final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
// MOCK_UID1: MOCK_PACKAGE1 only has network permission.
// SYSTEM_UID: SYSTEM_PACKAGE1 has system permission.
@@ -323,48 +355,123 @@
// Add SYSTEM_PACKAGE2, expect only have network permission.
mPermissionMonitor.onUserAdded(MOCK_USER1);
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
// Add SYSTEM_PACKAGE1, expect permission escalate.
addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID});
mPermissionMonitor.onUserAdded(MOCK_USER2);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1);
- mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{MOCK_UID1});
// Remove MOCK_UID1, expect no permission left for all user.
mPermissionMonitor.onPackageRemoved(MOCK_UID1);
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_UID1);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1});
// Remove SYSTEM_PACKAGE1, expect permission downgrade.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2});
removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_UID);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID});
mPermissionMonitor.onUserRemoved(MOCK_USER1);
- mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
+ mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID});
// Remove all packages, expect no permission left.
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{});
removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_UID);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
// Remove last user, expect no redundant clearPermission is invoked.
mPermissionMonitor.onUserRemoved(MOCK_USER2);
- mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
+ mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2},
new int[]{SYSTEM_UID, MOCK_UID1});
}
+ @Test
+ public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID1, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, MOCK_UID2, MOCK_USER1),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+ mPermissionMonitor.startMonitoring();
+ // Every app on user 0 except MOCK_UID2 are under VPN.
+ final Set<UidRange> vpnRange1 = new HashSet<>(Arrays.asList(new UidRange[] {
+ new UidRange(0, MOCK_UID2 - 1),
+ new UidRange(MOCK_UID2 + 1, UserHandle.PER_USER_RANGE - 1)}));
+ final Set<UidRange> vpnRange2 = Collections.singleton(new UidRange(MOCK_UID2, MOCK_UID2));
+
+ // When VPN is connected, expect a rule to be set up for user app MOCK_UID1
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // When MOCK_UID1 package is uninstalled and reinstalled, expect Netd to be updated
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ reset(mNetdService);
+
+ // During VPN uid update (vpnRange1 -> vpnRange2), ConnectivityService first deletes the
+ // old UID rules then adds the new ones. Expect netd to be updated
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange1, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID2}));
+
+ reset(mNetdService);
+
+ // When VPN is disconnected, expect rules to be torn down
+ mPermissionMonitor.onVpnUidRangesRemoved("tun0", vpnRange2, VPN_UID);
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID2}));
+ assertNull(mPermissionMonitor.getVpnUidRanges("tun0"));
+ }
+
+ @Test
+ public void testUidFilteringDuringPackageInstallAndUninstall() throws Exception {
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(true, SYSTEM_UID1, MOCK_USER1),
+ buildPackageInfo(false, VPN_UID, MOCK_USER1)
+ }));
+ when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS))).thenReturn(
+ buildPackageInfo(false, MOCK_UID1, MOCK_USER1));
+
+ mPermissionMonitor.startMonitoring();
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(MOCK_USER1));
+ mPermissionMonitor.onVpnUidRangesAdded("tun0", vpnRange, VPN_UID);
+
+ // Newly-installed package should have uid rules added
+ mPermissionMonitor.onPackageAdded(MOCK_PACKAGE1, UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallAddUidInterfaceRules(eq("tun0"),
+ aryEq(new int[] {MOCK_UID1}));
+
+ // Removed package should have its uid rules removed
+ mPermissionMonitor.onPackageRemoved(UserHandle.getUid(MOCK_USER1, MOCK_UID1));
+ verify(mNetdService).firewallRemoveUidInterfaceRules(aryEq(new int[] {MOCK_UID1}));
+ }
+
+
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
// each user. To simulate generic package operations, the onPackageAdded/Removed will need to be
// called multiple times with the uid corresponding to each user.
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
similarity index 96%
rename from tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
index 4ec4fdd..95bc7d9 100644
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.net;
+package com.android.server.net;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.METERED_NO;
@@ -70,6 +70,10 @@
IoUtils.deleteContents(mTestProc);
}
+ // The libandroid_servers which have the native method is not available to
+ // applications. So in order to have a test support native library, the native code
+ // related to networkStatsFactory is compiled to a minimal native library and loaded here.
+ System.loadLibrary("networkstatsfactorytestjni");
mFactory = new NetworkStatsFactory(mTestProc, false);
}
diff --git a/tests/net/smoketest/Android.bp b/tests/net/smoketest/Android.bp
new file mode 100644
index 0000000..ef1ad2c
--- /dev/null
+++ b/tests/net/smoketest/Android.bp
@@ -0,0 +1,17 @@
+// This test exists only because the jni_libs list for these tests is difficult to
+// maintain: the test itself only depends on libnetworkstatsfactorytestjni, but the test
+// fails to load that library unless *all* the dependencies of that library are explicitly
+// listed in jni_libs. This means that whenever any of the dependencies changes the test
+// starts failing and breaking presubmits in frameworks/base. We cannot easily put
+// FrameworksNetTests into global presubmit because they are at times flaky, but this
+// test is effectively empty beyond validating that the libraries load correctly, and
+// thus should be stable enough to put in global presubmit.
+//
+// TODO: remove this hack when there is a better solution for jni_libs that includes
+// dependent libraries.
+android_test {
+ name: "FrameworksNetSmokeTests",
+ defaults: ["FrameworksNetTests-jni-defaults"],
+ srcs: ["java/SmokeTest.java"],
+ test_suites: ["device-tests"],
+}