Merge "Pass valid file descriptor to binder"
diff --git a/api/current.txt b/api/current.txt
index 1a770f0..e419f92 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -27227,10 +27227,13 @@
public final class DnsResolver {
method @NonNull public static android.net.DnsResolver getInstance();
- method public <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>);
- method public <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.AnswerCallback<T>);
- method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.InetAddressAnswerCallback);
+ method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+ method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super java.util.List<java.net.InetAddress>>);
+ method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
+ method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback<? super byte[]>);
field public static final int CLASS_IN = 1; // 0x1
+ field public static final int ERROR_PARSE = 0; // 0x0
+ field public static final int ERROR_SYSTEM = 1; // 0x1
field public static final int FLAG_EMPTY = 0; // 0x0
field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2
@@ -27239,23 +27242,13 @@
field public static final int TYPE_AAAA = 28; // 0x1c
}
- public abstract static class DnsResolver.AnswerCallback<T> {
- ctor public DnsResolver.AnswerCallback(@NonNull android.net.DnsResolver.AnswerParser<T>);
- method public abstract void onAnswer(@NonNull T);
- method public abstract void onParseException(@NonNull android.net.ParseException);
- method public abstract void onQueryException(@NonNull android.system.ErrnoException);
+ public static interface DnsResolver.Callback<T> {
+ method public void onAnswer(@NonNull T, int);
+ method public void onError(@NonNull android.net.DnsResolver.DnsException);
}
- public static interface DnsResolver.AnswerParser<T> {
- method @NonNull public T parse(@NonNull byte[]) throws android.net.ParseException;
- }
-
- public abstract static class DnsResolver.InetAddressAnswerCallback extends android.net.DnsResolver.AnswerCallback<java.util.List<java.net.InetAddress>> {
- ctor public DnsResolver.InetAddressAnswerCallback();
- }
-
- public abstract static class DnsResolver.RawAnswerCallback extends android.net.DnsResolver.AnswerCallback<byte[]> {
- ctor public DnsResolver.RawAnswerCallback();
+ public static class DnsResolver.DnsException extends java.lang.Exception {
+ field public final int code;
}
public class InetAddresses {
@@ -27580,8 +27573,6 @@
}
public class ParseException extends java.lang.RuntimeException {
- ctor public ParseException(@NonNull String);
- ctor public ParseException(@NonNull String, @NonNull Throwable);
field public String response;
}
diff --git a/cmds/uiautomator/api/current.txt b/cmds/uiautomator/api/current.txt
index 634ca4d..489c2ea 100644
--- a/cmds/uiautomator/api/current.txt
+++ b/cmds/uiautomator/api/current.txt
@@ -171,7 +171,7 @@
method public com.android.uiautomator.core.UiSelector checked(boolean);
method public com.android.uiautomator.core.UiSelector childSelector(com.android.uiautomator.core.UiSelector);
method public com.android.uiautomator.core.UiSelector className(java.lang.String);
- method public com.android.uiautomator.core.UiSelector className(java.lang.Class<T>);
+ method public <T> com.android.uiautomator.core.UiSelector className(java.lang.Class<T>);
method public com.android.uiautomator.core.UiSelector classNameMatches(java.lang.String);
method public com.android.uiautomator.core.UiSelector clickable(boolean);
method protected com.android.uiautomator.core.UiSelector cloneSelector();
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 06c32c6..b6c4fe2 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;
}
@@ -222,9 +199,7 @@
queryfd = resNetworkSend((network != null
? network.netId : 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;
}
@@ -265,9 +240,7 @@
queryfd = resNetworkQuery((network != null
? network.netId : 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;
}
@@ -365,9 +348,7 @@
v6fd = resNetworkQuery((network != null
? network.netId : 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,7 +358,9 @@
// 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 {
@@ -385,9 +368,7 @@
? network.netId : 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 +394,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.netId : 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 dd3fff8..d07ff13 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -140,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.
diff --git a/core/java/android/net/ParseException.java b/core/java/android/net/ParseException.java
index 9d4727a..bcfdd7e 100644
--- a/core/java/android/net/ParseException.java
+++ b/core/java/android/net/ParseException.java
@@ -25,12 +25,12 @@
public class ParseException extends RuntimeException {
public String response;
- public ParseException(@NonNull String response) {
+ ParseException(@NonNull String response) {
super(response);
this.response = response;
}
- public ParseException(@NonNull String response, @NonNull Throwable cause) {
+ ParseException(@NonNull String response, @NonNull Throwable cause) {
super(response, cause);
this.response = response;
}
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java
index fa0eeb9e..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 dd754f3..28c59db 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -270,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);
@@ -291,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) {
@@ -354,7 +357,7 @@
{ "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/packages/NetworkStack/res/values-mcc460/config.xml b/packages/NetworkStack/res/values-mcc460/config.xml
new file mode 100644
index 0000000..fd4a848
--- /dev/null
+++ b/packages/NetworkStack/res/values-mcc460/config.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Network validation URL configuration for devices using a Chinese SIM (MCC 460).
+ The below URLs are often whitelisted by captive portals, so they should not be used in the
+ general case as this could degrade the user experience (portals not detected properly).
+ However in China the default URLs are not accessible in general. The below alternatives
+ should allow users to connect to local networks normally. -->
+ <string name="default_captive_portal_https_url" translatable="false">https://connectivitycheck.gstatic.com/generate_204</string>
+ <string-array name="default_captive_portal_fallback_urls" translatable="false">
+ <item>http://www.googleapis.cn/generate_204</item>
+ </string-array>
+</resources>
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 524548f..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;
@@ -276,7 +277,8 @@
private Tethering mTethering;
- private final PermissionMonitor mPermissionMonitor;
+ @VisibleForTesting
+ protected final PermissionMonitor mPermissionMonitor;
private KeyStore mKeyStore;
@@ -829,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();
@@ -875,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);
@@ -961,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.
@@ -2441,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) {
@@ -5465,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()) {
@@ -5630,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;
@@ -5772,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();
@@ -5784,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);
@@ -5794,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);
}
}
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index b6946023..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/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 8005dda..0271d3b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1604,12 +1604,7 @@
if (mNetworkInfo.isConnected()) {
return !appliesToUid(uid);
} else {
- for (UidRange uidRange : mBlockedUsers) {
- if (uidRange.contains(uid)) {
- return true;
- }
- }
- return false;
+ return UidRange.containsUid(mBlockedUsers, uid);
}
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index bf34d8f..bd11d46 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -28,6 +28,7 @@
import android.os.StrictMode;
import android.os.SystemClock;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ProcFileReader;
@@ -65,6 +66,7 @@
private boolean mUseBpfStats;
// A persistent Snapshot since device start for eBPF stats
+ @GuardedBy("mPersistSnapshot")
private final NetworkStats mPersistSnapshot;
// TODO: only do adjustments in NetworkStatsService and remove this.
@@ -289,15 +291,17 @@
stats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
}
if (mUseBpfStats) {
- if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
- null, TAG_ALL, mUseBpfStats) != 0) {
- throw new IOException("Failed to parse network stats");
+ synchronized (mPersistSnapshot) {
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
+ null, TAG_ALL, mUseBpfStats) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
+ mPersistSnapshot.combineAllValues(stats);
+ NetworkStats result = mPersistSnapshot.clone();
+ result.filter(limitUid, limitIfaces, limitTag);
+ return result;
}
- mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
- mPersistSnapshot.combineAllValues(stats);
- NetworkStats result = mPersistSnapshot.clone();
- result.filter(limitUid, limitIfaces, limitTag);
- return result;
} else {
if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid,
limitIfaces, limitTag, mUseBpfStats) != 0) {
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 3728de2..a83d8f0 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -229,4 +229,23 @@
protected void log(String s) {
Rlog.w(mTag, s);
}
+
+ /** @hide */
+ protected static final int inRangeOrUnavailable(int value, int rangeMin, int rangeMax) {
+ if (value < rangeMin || value > rangeMax) return CellInfo.UNAVAILABLE;
+ return value;
+ }
+
+ /** @hide */
+ protected static final long inRangeOrUnavailable(long value, long rangeMin, long rangeMax) {
+ if (value < rangeMin || value > rangeMax) return CellInfo.UNAVAILABLE_LONG;
+ return value;
+ }
+
+ /** @hide */
+ protected static final int inRangeOrUnavailable(
+ int value, int rangeMin, int rangeMax, int special) {
+ if ((value < rangeMin || value > rangeMax) && value != special) return CellInfo.UNAVAILABLE;
+ return value;
+ }
}
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 637f49d..0897e0f 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -28,12 +28,25 @@
private static final String TAG = CellIdentityCdma.class.getSimpleName();
private static final boolean DBG = false;
+ private static final int NETWORK_ID_MAX = 65535;
+ private static final int SYSTEM_ID_MAX = 32767;
+ private static final int BASESTATION_ID_MAX = 65535;
+
+ private static final int LONGITUDE_MIN = -2592000;
+ private static final int LONGITUDE_MAX = 2592000;
+
+ private static final int LATITUDE_MIN = -1296000;
+ private static final int LATITUDE_MAX = 1296000;
+
// Network Id 0..65535
private final int mNetworkId;
+
// CDMA System Id 0..32767
private final int mSystemId;
+
// Base Station Id 0..65535
private final int mBasestationId;
+
/**
* Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
* It is represented in units of 0.25 seconds and ranges from -2592000
@@ -41,6 +54,7 @@
* to +180 degrees).
*/
private final int mLongitude;
+
/**
* Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0.
* It is represented in units of 0.25 seconds and ranges from -1296000
@@ -78,9 +92,12 @@
public CellIdentityCdma(
int nid, int sid, int bid, int lon, int lat, String alphal, String alphas) {
super(TAG, CellInfo.TYPE_CDMA, null, null, alphal, alphas);
- mNetworkId = nid;
- mSystemId = sid;
- mBasestationId = bid;
+ mNetworkId = inRangeOrUnavailable(nid, 0, NETWORK_ID_MAX);
+ mSystemId = inRangeOrUnavailable(sid, 0, SYSTEM_ID_MAX);
+ mBasestationId = inRangeOrUnavailable(bid, 0, BASESTATION_ID_MAX);
+ lat = inRangeOrUnavailable(lat, LATITUDE_MIN, LATITUDE_MAX);
+ lon = inRangeOrUnavailable(lon, LONGITUDE_MIN, LONGITUDE_MAX);
+
if (!isNullIsland(lat, lon)) {
mLongitude = lon;
mLatitude = lat;
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index ae8e13a..4b7c9e4 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -31,6 +31,11 @@
private static final String TAG = CellIdentityGsm.class.getSimpleName();
private static final boolean DBG = false;
+ private static final int MAX_LAC = 65535;
+ private static final int MAX_CID = 65535;
+ private static final int MAX_ARFCN = 65535;
+ private static final int MAX_BSIC = 63;
+
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 16-bit GSM Cell Identity described in TS 27.007, 0..65535
@@ -68,10 +73,10 @@
public CellIdentityGsm(int lac, int cid, int arfcn, int bsic, String mccStr,
String mncStr, String alphal, String alphas) {
super(TAG, CellInfo.TYPE_GSM, mccStr, mncStr, alphal, alphas);
- mLac = lac;
- mCid = cid;
- mArfcn = arfcn;
- mBsic = bsic;
+ mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
+ mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
+ mArfcn = inRangeOrUnavailable(arfcn, 0, MAX_ARFCN);
+ mBsic = inRangeOrUnavailable(bsic, 0, MAX_BSIC);
}
/** @hide */
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index ecd7da7..d81f98f 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -32,6 +32,12 @@
private static final String TAG = CellIdentityLte.class.getSimpleName();
private static final boolean DBG = false;
+ private static final int MAX_CI = 268435455;
+ private static final int MAX_PCI = 503;
+ private static final int MAX_TAC = 65535;
+ private static final int MAX_EARFCN = 262143;
+ private static final int MAX_BANDWIDTH = 20000;
+
// 28-bit cell identity
private final int mCi;
// physical cell id 0..503
@@ -89,11 +95,11 @@
public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr,
String mncStr, String alphal, String alphas) {
super(TAG, CellInfo.TYPE_LTE, mccStr, mncStr, alphal, alphas);
- mCi = ci;
- mPci = pci;
- mTac = tac;
- mEarfcn = earfcn;
- mBandwidth = bandwidth;
+ mCi = inRangeOrUnavailable(ci, 0, MAX_CI);
+ mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
+ mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
+ mEarfcn = inRangeOrUnavailable(earfcn, 0, MAX_EARFCN);
+ mBandwidth = inRangeOrUnavailable(bandwidth, 0, MAX_BANDWIDTH);
}
/** @hide */
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index 44896e2..05e5c75 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -29,6 +29,11 @@
public final class CellIdentityNr extends CellIdentity {
private static final String TAG = "CellIdentityNr";
+ private static final int MAX_PCI = 1007;
+ private static final int MAX_TAC = 65535;
+ private static final int MAX_NRARFCN = 3279165;
+ private static final long MAX_NCI = 68719476735L;
+
private final int mNrArfcn;
private final int mPci;
private final int mTac;
@@ -41,6 +46,7 @@
* @param nrArfcn NR Absolute Radio Frequency Channel Number, in range [0, 3279165].
* @param mccStr 3-digit Mobile Country Code in string format.
* @param mncStr 2 or 3-digit Mobile Network Code in string format.
+ * @param nci The 36-bit NR Cell Identity in range [0, 68719476735].
* @param alphal long alpha Operator Name String or Enhanced Operator Name String.
* @param alphas short alpha Operator Name String or Enhanced Operator Name String.
*
@@ -49,10 +55,10 @@
public CellIdentityNr(int pci, int tac, int nrArfcn, String mccStr, String mncStr,
long nci, String alphal, String alphas) {
super(TAG, CellInfo.TYPE_NR, mccStr, mncStr, alphal, alphas);
- mPci = pci;
- mTac = tac;
- mNrArfcn = nrArfcn;
- mNci = nci;
+ mPci = inRangeOrUnavailable(pci, 0, MAX_PCI);
+ mTac = inRangeOrUnavailable(tac, 0, MAX_TAC);
+ mNrArfcn = inRangeOrUnavailable(nrArfcn, 0, MAX_NRARFCN);
+ mNci = inRangeOrUnavailable(nci, 0, MAX_NCI);
}
/** @hide */
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 937de70..558e346 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -30,12 +30,18 @@
private static final String TAG = CellIdentityTdscdma.class.getSimpleName();
private static final boolean DBG = false;
+ private static final int MAX_LAC = 65535;
+ private static final int MAX_CID = 268435455;
+ private static final int MAX_CPID = 127;
+ private static final int MAX_UARFCN = 65535;
+
// 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown.
private final int mLac;
// 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, CellInfo.UNAVAILABLE
// if unknown.
private final int mCid;
- // 8-bit Cell Parameters ID described in TS 25.331, 0..127, CellInfo.UNAVAILABLE if unknown.
+ // 8-bit Cell Parameters ID described in TS 25.331 sec 10.3.6.9,
+ // 0..127, CellInfo.UNAVAILABLE if unknown.
private final int mCpid;
// 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
private final int mUarfcn;
@@ -68,10 +74,10 @@
public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn,
String alphal, String alphas) {
super(TAG, CellInfo.TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
- mLac = lac;
- mCid = cid;
- mCpid = cpid;
- mUarfcn = uarfcn;
+ mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
+ mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
+ mCpid = inRangeOrUnavailable(cpid, 0, MAX_CPID);
+ mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
}
private CellIdentityTdscdma(CellIdentityTdscdma cid) {
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index aa2085a..e500c92 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -31,6 +31,11 @@
private static final String TAG = CellIdentityWcdma.class.getSimpleName();
private static final boolean DBG = false;
+ private static final int MAX_LAC = 65535;
+ private static final int MAX_CID = 268435455;
+ private static final int MAX_PSC = 511;
+ private static final int MAX_UARFCN = 16383; // a 14 bit number; TS 25.331 ex sec 10.3.8.15
+
// 16-bit Location Area Code, 0..65535
private final int mLac;
// 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455
@@ -68,10 +73,10 @@
public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
String mccStr, String mncStr, String alphal, String alphas) {
super(TAG, CellInfo.TYPE_WCDMA, mccStr, mncStr, alphal, alphas);
- mLac = lac;
- mCid = cid;
- mPsc = psc;
- mUarfcn = uarfcn;
+ mLac = inRangeOrUnavailable(lac, 0, MAX_LAC);
+ mCid = inRangeOrUnavailable(cid, 0, MAX_CID);
+ mPsc = inRangeOrUnavailable(psc, 0, MAX_PSC);
+ mUarfcn = inRangeOrUnavailable(uarfcn, 0, MAX_UARFCN);
}
/** @hide */
diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java
index 8cd633b..baec19a 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThread.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThread.java
@@ -169,8 +169,9 @@
RcsParticipantQueryParams queryParameters =
new RcsParticipantQueryParams.Builder().setThread(this).build();
- RcsParticipantQueryResult queryResult = RcsControllerCall.call(
- iRcs -> iRcs.getParticipants(queryParameters));
+ RcsParticipantQueryResult queryResult = new RcsParticipantQueryResult(
+ RcsControllerCall.call(
+ iRcs -> iRcs.getParticipants(queryParameters)));
List<RcsParticipant> participantList = queryResult.getParticipants();
Set<RcsParticipant> participantSet = new LinkedHashSet<>(participantList);
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
index 3cceddf..6fcb62b 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -72,7 +72,8 @@
public RcsParticipantQueryResult getRcsParticipants(
@Nullable RcsParticipantQueryParams queryParameters)
throws RcsMessageStoreException {
- return RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters));
+ return new RcsParticipantQueryResult(
+ RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters)));
}
/**
@@ -88,7 +89,8 @@
public RcsParticipantQueryResult getRcsParticipants(
@NonNull RcsQueryContinuationToken continuationToken)
throws RcsMessageStoreException {
- return RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken));
+ return new RcsParticipantQueryResult(
+ RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken)));
}
/**
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
index 3e5c231..731c94e 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
@@ -18,11 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
/**
* The result of a {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParams)}
@@ -31,23 +29,12 @@
*
* @hide
*/
-public final class RcsParticipantQueryResult implements Parcelable {
- // A token for the caller to continue their query for the next batch of results
- private RcsQueryContinuationToken mContinuationToken;
- // The list of participant IDs returned with this query
- private List<Integer> mParticipants;
+public final class RcsParticipantQueryResult {
+ private final RcsParticipantQueryResultParcelable mRcsParticipantQueryResultParcelable;
- /**
- * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
- * to create query results
- *
- * @hide
- */
- public RcsParticipantQueryResult(
- RcsQueryContinuationToken continuationToken,
- List<Integer> participants) {
- mContinuationToken = continuationToken;
- mParticipants = participants;
+ RcsParticipantQueryResult(
+ RcsParticipantQueryResultParcelable rcsParticipantQueryResultParcelable) {
+ mRcsParticipantQueryResultParcelable = rcsParticipantQueryResultParcelable;
}
/**
@@ -57,7 +44,7 @@
*/
@Nullable
public RcsQueryContinuationToken getContinuationToken() {
- return mContinuationToken;
+ return mRcsParticipantQueryResultParcelable.mContinuationToken;
}
/**
@@ -67,39 +54,8 @@
*/
@NonNull
public List<RcsParticipant> getParticipants() {
- List<RcsParticipant> participantList = new ArrayList<>();
- for (Integer participantId : mParticipants) {
- participantList.add(new RcsParticipant(participantId));
- }
-
- return participantList;
- }
-
- private RcsParticipantQueryResult(Parcel in) {
- mContinuationToken = in.readParcelable(
- RcsQueryContinuationToken.class.getClassLoader());
- }
-
- public static final @android.annotation.NonNull Creator<RcsParticipantQueryResult> CREATOR =
- new Creator<RcsParticipantQueryResult>() {
- @Override
- public RcsParticipantQueryResult createFromParcel(Parcel in) {
- return new RcsParticipantQueryResult(in);
- }
-
- @Override
- public RcsParticipantQueryResult[] newArray(int size) {
- return new RcsParticipantQueryResult[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mContinuationToken, flags);
+ return mRcsParticipantQueryResultParcelable.mParticipantIds.stream()
+ .map(RcsParticipant::new)
+ .collect(Collectors.toList());
}
}
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.aidl
similarity index 92%
rename from telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl
rename to telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.aidl
index db5c00c..54c72e7 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsParticipantQueryResult;
+parcelable RcsParticipantQueryResultParcelable;
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.java
new file mode 100644
index 0000000..239b0e9
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResultParcelable.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+package android.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class RcsParticipantQueryResultParcelable implements Parcelable {
+ final RcsQueryContinuationToken mContinuationToken;
+ final List<Integer> mParticipantIds;
+
+ public RcsParticipantQueryResultParcelable(
+ RcsQueryContinuationToken continuationToken,
+ List<Integer> participantIds) {
+ mContinuationToken = continuationToken;
+ mParticipantIds = participantIds;
+ }
+
+ private RcsParticipantQueryResultParcelable(Parcel in) {
+ mContinuationToken = in.readParcelable(RcsQueryContinuationToken.class.getClassLoader());
+ mParticipantIds = new ArrayList<>();
+ in.readList(mParticipantIds, Integer.class.getClassLoader());
+ }
+
+ public static final Parcelable.Creator<RcsParticipantQueryResultParcelable> CREATOR =
+ new Parcelable.Creator<RcsParticipantQueryResultParcelable>() {
+ @Override
+ public RcsParticipantQueryResultParcelable createFromParcel(Parcel in) {
+ return new RcsParticipantQueryResultParcelable(in);
+ }
+
+ @Override
+ public RcsParticipantQueryResultParcelable[] newArray(int size) {
+ return new RcsParticipantQueryResultParcelable[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mContinuationToken, flags);
+ dest.writeList(mParticipantIds);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 4aa2367..50dc587 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -26,7 +26,7 @@
import android.telephony.ims.RcsMessageQueryResult;
import android.telephony.ims.RcsOutgoingMessageCreationParams;
import android.telephony.ims.RcsParticipantQueryParams;
-import android.telephony.ims.RcsParticipantQueryResult;
+import android.telephony.ims.RcsParticipantQueryResultParcelable;
import android.telephony.ims.RcsQueryContinuationToken;
import android.telephony.ims.RcsThreadQueryParams;
import android.telephony.ims.RcsThreadQueryResultParcelable;
@@ -44,9 +44,9 @@
RcsThreadQueryResultParcelable getRcsThreadsWithToken(
in RcsQueryContinuationToken continuationToken);
- RcsParticipantQueryResult getParticipants(in RcsParticipantQueryParams queryParams);
+ RcsParticipantQueryResultParcelable getParticipants(in RcsParticipantQueryParams queryParams);
- RcsParticipantQueryResult getParticipantsWithToken(
+ RcsParticipantQueryResultParcelable getParticipantsWithToken(
in RcsQueryContinuationToken continuationToken);
RcsMessageQueryResult getMessages(in RcsMessageQueryParams queryParams);
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 c2fc0b3..ee9d7853 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -60,11 +62,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 +76,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 +103,10 @@
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.PackageManager;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
@@ -151,6 +161,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;
@@ -186,6 +197,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;
@@ -194,6 +206,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;
@@ -261,6 +274,8 @@
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
@Mock NetworkStackClient mNetworkStack;
+ @Mock PackageManager mPackageManager;
+ @Mock UserManager mUserManager;
private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
@@ -331,6 +346,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);
}
@@ -343,6 +359,11 @@
public Resources getResources() {
return mResources;
}
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
}
public void waitForIdle(int timeoutMsAsInt) {
@@ -1057,7 +1078,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;
}
@@ -1196,6 +1217,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();
@@ -1203,6 +1229,17 @@
MockitoAnnotations.initMocks(this);
when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+ when(mUserManager.getUsers(eq(true))).thenReturn(
+ Arrays.asList(new UserInfo[] {
+ new UserInfo(VPN_USER, "", 0),
+ }));
+ when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+ Arrays.asList(new PackageInfo[] {
+ buildPackageInfo(/* SYSTEM */ false, APP1_UID),
+ buildPackageInfo(/* SYSTEM */ false, APP2_UID),
+ buildPackageInfo(/* SYSTEM */ false, VPN_UID)
+ }));
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
@@ -6129,4 +6166,165 @@
assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork()));
assertEquals(testProxyInfo, mService.getProxyForNetwork(null));
}
+
+ @Test
+ 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
+ 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());
+ }
+
+ 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
+ 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
+ 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/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.