Merge changes Id533cdb4,I6a9ce3d6,I345bf073 into sc-mainline-prod
* changes: Merge EthernetServiceTests into FrameworksNetTests Prevent building T+ sources on sc-mainline-prod Add framework-t, service-t to sc-mainline prod
This commit is contained in:
committed by
Android (Google) Code Review
commit
c3315f2560
@@ -3,6 +3,10 @@
|
||||
{
|
||||
"name": "ConnectivityCoverageTests"
|
||||
},
|
||||
{
|
||||
// In addition to ConnectivityCoverageTests, runs non-connectivity-module tests
|
||||
"name": "FrameworksNetTests"
|
||||
},
|
||||
// Run in addition to mainline-presubmit as mainline-presubmit is not
|
||||
// supported in every branch.
|
||||
// CtsNetTestCasesLatestSdk uses stable API shims, so does not exercise
|
||||
|
||||
175
framework-t/Sources.bp
Normal file
175
framework-t/Sources.bp
Normal file
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// Copyright (C) 2021 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.
|
||||
//
|
||||
|
||||
// NetworkStats related libraries.
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-netstats-internal-sources",
|
||||
srcs: [
|
||||
"src/android/app/usage/*.java",
|
||||
"src/android/net/DataUsageRequest.*",
|
||||
"src/android/net/INetworkStatsService.aidl",
|
||||
"src/android/net/INetworkStatsSession.aidl",
|
||||
"src/android/net/NetworkIdentity.java",
|
||||
"src/android/net/NetworkIdentitySet.java",
|
||||
"src/android/net/NetworkStateSnapshot.*",
|
||||
"src/android/net/NetworkStats.*",
|
||||
"src/android/net/NetworkStatsAccess.*",
|
||||
"src/android/net/NetworkStatsCollection.*",
|
||||
"src/android/net/NetworkStatsHistory.*",
|
||||
"src/android/net/NetworkTemplate.*",
|
||||
"src/android/net/TrafficStats.java",
|
||||
"src/android/net/UnderlyingNetworkInfo.*",
|
||||
"src/android/net/netstats/**/*.*",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-netstats-sources",
|
||||
srcs: [
|
||||
":framework-connectivity-netstats-internal-sources",
|
||||
],
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// Nsd related libraries.
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-nsd-internal-sources",
|
||||
srcs: [
|
||||
"src/android/net/nsd/*.aidl",
|
||||
"src/android/net/nsd/*.java",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-nsd-sources",
|
||||
srcs: [
|
||||
":framework-connectivity-nsd-internal-sources",
|
||||
],
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// IpSec related libraries.
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-ipsec-sources",
|
||||
srcs: [
|
||||
"src/android/net/IIpSecService.aidl",
|
||||
"src/android/net/IpSec*.*",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// Ethernet related libraries.
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-ethernet-sources",
|
||||
srcs: [
|
||||
"src/android/net/EthernetManager.java",
|
||||
"src/android/net/EthernetNetworkManagementException.java",
|
||||
"src/android/net/EthernetNetworkManagementException.aidl",
|
||||
"src/android/net/EthernetNetworkSpecifier.java",
|
||||
"src/android/net/EthernetNetworkUpdateRequest.java",
|
||||
"src/android/net/EthernetNetworkUpdateRequest.aidl",
|
||||
"src/android/net/IEthernetManager.aidl",
|
||||
"src/android/net/IEthernetServiceListener.aidl",
|
||||
"src/android/net/INetworkInterfaceOutcomeReceiver.aidl",
|
||||
"src/android/net/ITetheredInterfaceCallback.aidl",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// Connectivity-T common libraries.
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-tiramisu-internal-sources",
|
||||
srcs: [
|
||||
"src/android/net/ConnectivityFrameworkInitializerTiramisu.java",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// TODO: remove this empty filegroup.
|
||||
filegroup {
|
||||
name: "framework-connectivity-tiramisu-sources",
|
||||
srcs: [],
|
||||
visibility: ["//frameworks/base"],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "framework-connectivity-tiramisu-updatable-sources",
|
||||
srcs: [
|
||||
":framework-connectivity-ethernet-sources",
|
||||
":framework-connectivity-ipsec-sources",
|
||||
":framework-connectivity-netstats-sources",
|
||||
":framework-connectivity-nsd-sources",
|
||||
":framework-connectivity-tiramisu-internal-sources",
|
||||
],
|
||||
visibility: [
|
||||
"//frameworks/base",
|
||||
"//packages/modules/Connectivity:__subpackages__",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libframework-connectivity-tiramisu-jni",
|
||||
min_sdk_version: "30",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
// Don't warn about S API usage even with
|
||||
// min_sdk 30: the library is only loaded
|
||||
// on S+ devices
|
||||
"-Wno-unguarded-availability",
|
||||
"-Wthread-safety",
|
||||
],
|
||||
srcs: [
|
||||
"jni/android_net_TrafficStats.cpp",
|
||||
"jni/onload.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libandroid",
|
||||
"liblog",
|
||||
"libnativehelper",
|
||||
],
|
||||
stl: "none",
|
||||
apex_available: [
|
||||
"com.android.tethering",
|
||||
],
|
||||
}
|
||||
46
framework-t/jni/android_net_TrafficStats.cpp
Normal file
46
framework-t/jni/android_net_TrafficStats.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
#include <android/file_descriptor_jni.h>
|
||||
#include <android/multinetwork.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) {
|
||||
int fd = AFileDescriptor_getFd(env, fileDescriptor);
|
||||
if (fd == -1) return -EBADF;
|
||||
return android_tag_socket_with_uid(fd, tag, uid);
|
||||
}
|
||||
|
||||
static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) {
|
||||
int fd = AFileDescriptor_getFd(env, fileDescriptor);
|
||||
if (fd == -1) return -EBADF;
|
||||
return android_untag_socket(fd);
|
||||
}
|
||||
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
/* name, signature, funcPtr */
|
||||
{ "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd },
|
||||
{ "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd },
|
||||
};
|
||||
|
||||
int register_android_net_TrafficStats(JNIEnv* env) {
|
||||
return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods));
|
||||
}
|
||||
|
||||
}; // namespace android
|
||||
|
||||
39
framework-t/jni/onload.cpp
Normal file
39
framework-t/jni/onload.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "FrameworkConnectivityJNI"
|
||||
|
||||
#include <log/log.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
int register_android_net_TrafficStats(JNIEnv* env);
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (register_android_net_TrafficStats(env) < 0) return JNI_ERR;
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
}; // namespace android
|
||||
|
||||
742
framework-t/src/android/app/usage/NetworkStats.java
Normal file
742
framework-t/src/android/app/usage/NetworkStats.java
Normal file
@@ -0,0 +1,742 @@
|
||||
/**
|
||||
* Copyright (C) 2015 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.app.usage;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.net.INetworkStatsService;
|
||||
import android.net.INetworkStatsSession;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.TrafficStats;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class providing enumeration over buckets of network usage statistics. {@link NetworkStats} objects
|
||||
* are returned as results to various queries in {@link NetworkStatsManager}.
|
||||
*/
|
||||
public final class NetworkStats implements AutoCloseable {
|
||||
private final static String TAG = "NetworkStats";
|
||||
|
||||
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||||
|
||||
/**
|
||||
* Start timestamp of stats collected
|
||||
*/
|
||||
private final long mStartTimeStamp;
|
||||
|
||||
/**
|
||||
* End timestamp of stats collected
|
||||
*/
|
||||
private final long mEndTimeStamp;
|
||||
|
||||
/**
|
||||
* Non-null array indicates the query enumerates over uids.
|
||||
*/
|
||||
private int[] mUids;
|
||||
|
||||
/**
|
||||
* Index of the current uid in mUids when doing uid enumeration or a single uid value,
|
||||
* depending on query type.
|
||||
*/
|
||||
private int mUidOrUidIndex;
|
||||
|
||||
/**
|
||||
* Tag id in case if was specified in the query.
|
||||
*/
|
||||
private int mTag = android.net.NetworkStats.TAG_NONE;
|
||||
|
||||
/**
|
||||
* State in case it was not specified in the query.
|
||||
*/
|
||||
private int mState = Bucket.STATE_ALL;
|
||||
|
||||
/**
|
||||
* The session while the query requires it, null if all the stats have been collected or close()
|
||||
* has been called.
|
||||
*/
|
||||
private INetworkStatsSession mSession;
|
||||
private NetworkTemplate mTemplate;
|
||||
|
||||
/**
|
||||
* Results of a summary query.
|
||||
*/
|
||||
private android.net.NetworkStats mSummary = null;
|
||||
|
||||
/**
|
||||
* Results of detail queries.
|
||||
*/
|
||||
private NetworkStatsHistory mHistory = null;
|
||||
|
||||
/**
|
||||
* Where we are in enumerating over the current result.
|
||||
*/
|
||||
private int mEnumerationIndex = 0;
|
||||
|
||||
/**
|
||||
* Recycling entry objects to prevent heap fragmentation.
|
||||
*/
|
||||
private android.net.NetworkStats.Entry mRecycledSummaryEntry = null;
|
||||
private NetworkStatsHistory.Entry mRecycledHistoryEntry = null;
|
||||
|
||||
/** @hide */
|
||||
NetworkStats(Context context, NetworkTemplate template, int flags, long startTimestamp,
|
||||
long endTimestamp, INetworkStatsService statsService)
|
||||
throws RemoteException, SecurityException {
|
||||
// Open network stats session
|
||||
mSession = statsService.openSessionForUsageStats(flags, context.getOpPackageName());
|
||||
mCloseGuard.open("close");
|
||||
mTemplate = template;
|
||||
mStartTimeStamp = startTimestamp;
|
||||
mEndTimeStamp = endTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
close();
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------BEGINNING OF PUBLIC API-----------------------------------
|
||||
|
||||
/**
|
||||
* Buckets are the smallest elements of a query result. As some dimensions of a result may be
|
||||
* aggregated (e.g. time or state) some values may be equal across all buckets.
|
||||
*/
|
||||
public static class Bucket {
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "STATE_" }, value = {
|
||||
STATE_ALL,
|
||||
STATE_DEFAULT,
|
||||
STATE_FOREGROUND
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface State {}
|
||||
|
||||
/**
|
||||
* Combined usage across all states.
|
||||
*/
|
||||
public static final int STATE_ALL = -1;
|
||||
|
||||
/**
|
||||
* Usage not accounted for in any other state.
|
||||
*/
|
||||
public static final int STATE_DEFAULT = 0x1;
|
||||
|
||||
/**
|
||||
* Foreground usage.
|
||||
*/
|
||||
public static final int STATE_FOREGROUND = 0x2;
|
||||
|
||||
/**
|
||||
* Special UID value for aggregate/unspecified.
|
||||
*/
|
||||
public static final int UID_ALL = android.net.NetworkStats.UID_ALL;
|
||||
|
||||
/**
|
||||
* Special UID value for removed apps.
|
||||
*/
|
||||
public static final int UID_REMOVED = TrafficStats.UID_REMOVED;
|
||||
|
||||
/**
|
||||
* Special UID value for data usage by tethering.
|
||||
*/
|
||||
public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "METERED_" }, value = {
|
||||
METERED_ALL,
|
||||
METERED_NO,
|
||||
METERED_YES
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Metered {}
|
||||
|
||||
/**
|
||||
* Combined usage across all metered states. Covers metered and unmetered usage.
|
||||
*/
|
||||
public static final int METERED_ALL = -1;
|
||||
|
||||
/**
|
||||
* Usage that occurs on an unmetered network.
|
||||
*/
|
||||
public static final int METERED_NO = 0x1;
|
||||
|
||||
/**
|
||||
* Usage that occurs on a metered network.
|
||||
*
|
||||
* <p>A network is classified as metered when the user is sensitive to heavy data usage on
|
||||
* that connection.
|
||||
*/
|
||||
public static final int METERED_YES = 0x2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "ROAMING_" }, value = {
|
||||
ROAMING_ALL,
|
||||
ROAMING_NO,
|
||||
ROAMING_YES
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Roaming {}
|
||||
|
||||
/**
|
||||
* Combined usage across all roaming states. Covers both roaming and non-roaming usage.
|
||||
*/
|
||||
public static final int ROAMING_ALL = -1;
|
||||
|
||||
/**
|
||||
* Usage that occurs on a home, non-roaming network.
|
||||
*
|
||||
* <p>Any cellular usage in this bucket was incurred while the device was connected to a
|
||||
* tower owned or operated by the user's wireless carrier, or a tower that the user's
|
||||
* wireless carrier has indicated should be treated as a home network regardless.
|
||||
*
|
||||
* <p>This is also the default value for network types that do not support roaming.
|
||||
*/
|
||||
public static final int ROAMING_NO = 0x1;
|
||||
|
||||
/**
|
||||
* Usage that occurs on a roaming network.
|
||||
*
|
||||
* <p>Any cellular usage in this bucket as incurred while the device was roaming on another
|
||||
* carrier's network, for which additional charges may apply.
|
||||
*/
|
||||
public static final int ROAMING_YES = 0x2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "DEFAULT_NETWORK_" }, value = {
|
||||
DEFAULT_NETWORK_ALL,
|
||||
DEFAULT_NETWORK_NO,
|
||||
DEFAULT_NETWORK_YES
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface DefaultNetworkStatus {}
|
||||
|
||||
/**
|
||||
* Combined usage for this network regardless of default network status.
|
||||
*/
|
||||
public static final int DEFAULT_NETWORK_ALL = -1;
|
||||
|
||||
/**
|
||||
* Usage that occurs while this network is not a default network.
|
||||
*
|
||||
* <p>This implies that the app responsible for this usage requested that it occur on a
|
||||
* specific network different from the one(s) the system would have selected for it.
|
||||
*/
|
||||
public static final int DEFAULT_NETWORK_NO = 0x1;
|
||||
|
||||
/**
|
||||
* Usage that occurs while this network is a default network.
|
||||
*
|
||||
* <p>This implies that the app either did not select a specific network for this usage,
|
||||
* or it selected a network that the system could have selected for app traffic.
|
||||
*/
|
||||
public static final int DEFAULT_NETWORK_YES = 0x2;
|
||||
|
||||
/**
|
||||
* Special TAG value for total data across all tags
|
||||
*/
|
||||
public static final int TAG_NONE = android.net.NetworkStats.TAG_NONE;
|
||||
|
||||
private int mUid;
|
||||
private int mTag;
|
||||
private int mState;
|
||||
private int mDefaultNetworkStatus;
|
||||
private int mMetered;
|
||||
private int mRoaming;
|
||||
private long mBeginTimeStamp;
|
||||
private long mEndTimeStamp;
|
||||
private long mRxBytes;
|
||||
private long mRxPackets;
|
||||
private long mTxBytes;
|
||||
private long mTxPackets;
|
||||
|
||||
private static int convertSet(@State int state) {
|
||||
switch (state) {
|
||||
case STATE_ALL: return android.net.NetworkStats.SET_ALL;
|
||||
case STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
|
||||
case STATE_FOREGROUND: return android.net.NetworkStats.SET_FOREGROUND;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static @State int convertState(int networkStatsSet) {
|
||||
switch (networkStatsSet) {
|
||||
case android.net.NetworkStats.SET_ALL : return STATE_ALL;
|
||||
case android.net.NetworkStats.SET_DEFAULT : return STATE_DEFAULT;
|
||||
case android.net.NetworkStats.SET_FOREGROUND : return STATE_FOREGROUND;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int convertUid(int uid) {
|
||||
switch (uid) {
|
||||
case TrafficStats.UID_REMOVED: return UID_REMOVED;
|
||||
case TrafficStats.UID_TETHERING: return UID_TETHERING;
|
||||
}
|
||||
return uid;
|
||||
}
|
||||
|
||||
private static int convertTag(int tag) {
|
||||
switch (tag) {
|
||||
case android.net.NetworkStats.TAG_NONE: return TAG_NONE;
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
private static @Metered int convertMetered(int metered) {
|
||||
switch (metered) {
|
||||
case android.net.NetworkStats.METERED_ALL : return METERED_ALL;
|
||||
case android.net.NetworkStats.METERED_NO: return METERED_NO;
|
||||
case android.net.NetworkStats.METERED_YES: return METERED_YES;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static @Roaming int convertRoaming(int roaming) {
|
||||
switch (roaming) {
|
||||
case android.net.NetworkStats.ROAMING_ALL : return ROAMING_ALL;
|
||||
case android.net.NetworkStats.ROAMING_NO: return ROAMING_NO;
|
||||
case android.net.NetworkStats.ROAMING_YES: return ROAMING_YES;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static @DefaultNetworkStatus int convertDefaultNetworkStatus(
|
||||
int defaultNetworkStatus) {
|
||||
switch (defaultNetworkStatus) {
|
||||
case android.net.NetworkStats.DEFAULT_NETWORK_ALL : return DEFAULT_NETWORK_ALL;
|
||||
case android.net.NetworkStats.DEFAULT_NETWORK_NO: return DEFAULT_NETWORK_NO;
|
||||
case android.net.NetworkStats.DEFAULT_NETWORK_YES: return DEFAULT_NETWORK_YES;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Bucket() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Key of the bucket. Usually an app uid or one of the following special values:<p />
|
||||
* <ul>
|
||||
* <li>{@link #UID_REMOVED}</li>
|
||||
* <li>{@link #UID_TETHERING}</li>
|
||||
* <li>{@link android.os.Process#SYSTEM_UID}</li>
|
||||
* </ul>
|
||||
* @return Bucket key.
|
||||
*/
|
||||
public int getUid() {
|
||||
return mUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag of the bucket.<p />
|
||||
* @return Bucket tag.
|
||||
*/
|
||||
public int getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage state. One of the following values:<p/>
|
||||
* <ul>
|
||||
* <li>{@link #STATE_ALL}</li>
|
||||
* <li>{@link #STATE_DEFAULT}</li>
|
||||
* <li>{@link #STATE_FOREGROUND}</li>
|
||||
* </ul>
|
||||
* @return Usage state.
|
||||
*/
|
||||
public @State int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metered state. One of the following values:<p/>
|
||||
* <ul>
|
||||
* <li>{@link #METERED_ALL}</li>
|
||||
* <li>{@link #METERED_NO}</li>
|
||||
* <li>{@link #METERED_YES}</li>
|
||||
* </ul>
|
||||
* <p>A network is classified as metered when the user is sensitive to heavy data usage on
|
||||
* that connection. Apps may warn before using these networks for large downloads. The
|
||||
* metered state can be set by the user within data usage network restrictions.
|
||||
*/
|
||||
public @Metered int getMetered() {
|
||||
return mMetered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roaming state. One of the following values:<p/>
|
||||
* <ul>
|
||||
* <li>{@link #ROAMING_ALL}</li>
|
||||
* <li>{@link #ROAMING_NO}</li>
|
||||
* <li>{@link #ROAMING_YES}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public @Roaming int getRoaming() {
|
||||
return mRoaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default network status. One of the following values:<p/>
|
||||
* <ul>
|
||||
* <li>{@link #DEFAULT_NETWORK_ALL}</li>
|
||||
* <li>{@link #DEFAULT_NETWORK_NO}</li>
|
||||
* <li>{@link #DEFAULT_NETWORK_YES}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public @DefaultNetworkStatus int getDefaultNetworkStatus() {
|
||||
return mDefaultNetworkStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return Start of interval.
|
||||
*/
|
||||
public long getStartTimeStamp() {
|
||||
return mBeginTimeStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* End timestamp of the bucket's time interval. Defined in terms of "Unix time", see
|
||||
* {@link java.lang.System#currentTimeMillis}.
|
||||
* @return End of interval.
|
||||
*/
|
||||
public long getEndTimeStamp() {
|
||||
return mEndTimeStamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of bytes received during the bucket's time interval. Statistics are measured at
|
||||
* the network layer, so they include both TCP and UDP usage.
|
||||
* @return Number of bytes.
|
||||
*/
|
||||
public long getRxBytes() {
|
||||
return mRxBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of bytes transmitted during the bucket's time interval. Statistics are measured at
|
||||
* the network layer, so they include both TCP and UDP usage.
|
||||
* @return Number of bytes.
|
||||
*/
|
||||
public long getTxBytes() {
|
||||
return mTxBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packets received during the bucket's time interval. Statistics are measured at
|
||||
* the network layer, so they include both TCP and UDP usage.
|
||||
* @return Number of packets.
|
||||
*/
|
||||
public long getRxPackets() {
|
||||
return mRxPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packets transmitted during the bucket's time interval. Statistics are measured
|
||||
* at the network layer, so they include both TCP and UDP usage.
|
||||
* @return Number of packets.
|
||||
*/
|
||||
public long getTxPackets() {
|
||||
return mTxPackets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the recycled bucket with data of the next bin in the enumeration.
|
||||
* @param bucketOut Bucket to be filled with data.
|
||||
* @return true if successfully filled the bucket, false otherwise.
|
||||
*/
|
||||
public boolean getNextBucket(Bucket bucketOut) {
|
||||
if (mSummary != null) {
|
||||
return getNextSummaryBucket(bucketOut);
|
||||
} else {
|
||||
return getNextHistoryBucket(bucketOut);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if it is possible to ask for a next bucket in the enumeration.
|
||||
* @return true if there is at least one more bucket.
|
||||
*/
|
||||
public boolean hasNextBucket() {
|
||||
if (mSummary != null) {
|
||||
return mEnumerationIndex < mSummary.size();
|
||||
} else if (mHistory != null) {
|
||||
return mEnumerationIndex < mHistory.size()
|
||||
|| hasNextUid();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the enumeration. Call this method before this object gets out of scope.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (mSession != null) {
|
||||
try {
|
||||
mSession.close();
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
// Otherwise, meh
|
||||
}
|
||||
}
|
||||
mSession = null;
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.close();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------END OF PUBLIC API-----------------------------------
|
||||
|
||||
/**
|
||||
* Collects device summary results into a Bucket.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
Bucket getDeviceSummaryForNetwork() throws RemoteException {
|
||||
mSummary = mSession.getDeviceSummaryForNetwork(mTemplate, mStartTimeStamp, mEndTimeStamp);
|
||||
|
||||
// Setting enumeration index beyond end to avoid accidental enumeration over data that does
|
||||
// not belong to the calling user.
|
||||
mEnumerationIndex = mSummary.size();
|
||||
|
||||
return getSummaryAggregate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects summary results and sets summary enumeration mode.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
void startSummaryEnumeration() throws RemoteException {
|
||||
mSummary = mSession.getSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp,
|
||||
false /* includeTags */);
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects tagged summary results and sets summary enumeration mode.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
void startTaggedSummaryEnumeration() throws RemoteException {
|
||||
mSummary = mSession.getTaggedSummaryForAllUid(mTemplate, mStartTimeStamp, mEndTimeStamp);
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects history results for uid and resets history enumeration index.
|
||||
*/
|
||||
void startHistoryUidEnumeration(int uid, int tag, int state) {
|
||||
mHistory = null;
|
||||
try {
|
||||
mHistory = mSession.getHistoryIntervalForUid(mTemplate, uid,
|
||||
Bucket.convertSet(state), tag, NetworkStatsHistory.FIELD_ALL,
|
||||
mStartTimeStamp, mEndTimeStamp);
|
||||
setSingleUidTagState(uid, tag, state);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
// Leaving mHistory null
|
||||
}
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects history results for network and resets history enumeration index.
|
||||
*/
|
||||
void startHistoryDeviceEnumeration() {
|
||||
try {
|
||||
mHistory = mSession.getHistoryIntervalForNetwork(
|
||||
mTemplate, NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
mHistory = null;
|
||||
}
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts uid enumeration for current user.
|
||||
* @throws RemoteException
|
||||
*/
|
||||
void startUserUidEnumeration() throws RemoteException {
|
||||
// TODO: getRelevantUids should be sensitive to time interval. When that's done,
|
||||
// the filtering logic below can be removed.
|
||||
int[] uids = mSession.getRelevantUids();
|
||||
// Filtering of uids with empty history.
|
||||
final ArrayList<Integer> filteredUids = new ArrayList<>();
|
||||
for (int uid : uids) {
|
||||
try {
|
||||
NetworkStatsHistory history = mSession.getHistoryIntervalForUid(mTemplate, uid,
|
||||
android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
|
||||
NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
|
||||
if (history != null && history.size() > 0) {
|
||||
filteredUids.add(uid);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Error while getting history of uid " + uid, e);
|
||||
}
|
||||
}
|
||||
mUids = CollectionUtils.toIntArray(filteredUids);
|
||||
mUidOrUidIndex = -1;
|
||||
stepHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps to next uid in enumeration and collects history for that.
|
||||
*/
|
||||
private void stepHistory(){
|
||||
if (hasNextUid()) {
|
||||
stepUid();
|
||||
mHistory = null;
|
||||
try {
|
||||
mHistory = mSession.getHistoryIntervalForUid(mTemplate, getUid(),
|
||||
android.net.NetworkStats.SET_ALL, android.net.NetworkStats.TAG_NONE,
|
||||
NetworkStatsHistory.FIELD_ALL, mStartTimeStamp, mEndTimeStamp);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, e);
|
||||
// Leaving mHistory null
|
||||
}
|
||||
mEnumerationIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillBucketFromSummaryEntry(Bucket bucketOut) {
|
||||
bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
|
||||
bucketOut.mTag = Bucket.convertTag(mRecycledSummaryEntry.tag);
|
||||
bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
|
||||
bucketOut.mDefaultNetworkStatus = Bucket.convertDefaultNetworkStatus(
|
||||
mRecycledSummaryEntry.defaultNetwork);
|
||||
bucketOut.mMetered = Bucket.convertMetered(mRecycledSummaryEntry.metered);
|
||||
bucketOut.mRoaming = Bucket.convertRoaming(mRecycledSummaryEntry.roaming);
|
||||
bucketOut.mBeginTimeStamp = mStartTimeStamp;
|
||||
bucketOut.mEndTimeStamp = mEndTimeStamp;
|
||||
bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes;
|
||||
bucketOut.mRxPackets = mRecycledSummaryEntry.rxPackets;
|
||||
bucketOut.mTxBytes = mRecycledSummaryEntry.txBytes;
|
||||
bucketOut.mTxPackets = mRecycledSummaryEntry.txPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the next item in summary enumeration.
|
||||
* @param bucketOut Next item will be set here.
|
||||
* @return true if a next item could be set.
|
||||
*/
|
||||
private boolean getNextSummaryBucket(Bucket bucketOut) {
|
||||
if (bucketOut != null && mEnumerationIndex < mSummary.size()) {
|
||||
mRecycledSummaryEntry = mSummary.getValues(mEnumerationIndex++, mRecycledSummaryEntry);
|
||||
fillBucketFromSummaryEntry(bucketOut);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bucket getSummaryAggregate() {
|
||||
if (mSummary == null) {
|
||||
return null;
|
||||
}
|
||||
Bucket bucket = new Bucket();
|
||||
if (mRecycledSummaryEntry == null) {
|
||||
mRecycledSummaryEntry = new android.net.NetworkStats.Entry();
|
||||
}
|
||||
mSummary.getTotal(mRecycledSummaryEntry);
|
||||
fillBucketFromSummaryEntry(bucket);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the next item in a history enumeration.
|
||||
* @param bucketOut Next item will be set here.
|
||||
* @return true if a next item could be set.
|
||||
*/
|
||||
private boolean getNextHistoryBucket(Bucket bucketOut) {
|
||||
if (bucketOut != null && mHistory != null) {
|
||||
if (mEnumerationIndex < mHistory.size()) {
|
||||
mRecycledHistoryEntry = mHistory.getValues(mEnumerationIndex++,
|
||||
mRecycledHistoryEntry);
|
||||
bucketOut.mUid = Bucket.convertUid(getUid());
|
||||
bucketOut.mTag = Bucket.convertTag(mTag);
|
||||
bucketOut.mState = mState;
|
||||
bucketOut.mDefaultNetworkStatus = Bucket.DEFAULT_NETWORK_ALL;
|
||||
bucketOut.mMetered = Bucket.METERED_ALL;
|
||||
bucketOut.mRoaming = Bucket.ROAMING_ALL;
|
||||
bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
|
||||
bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
|
||||
mRecycledHistoryEntry.bucketDuration;
|
||||
bucketOut.mRxBytes = mRecycledHistoryEntry.rxBytes;
|
||||
bucketOut.mRxPackets = mRecycledHistoryEntry.rxPackets;
|
||||
bucketOut.mTxBytes = mRecycledHistoryEntry.txBytes;
|
||||
bucketOut.mTxPackets = mRecycledHistoryEntry.txPackets;
|
||||
return true;
|
||||
} else if (hasNextUid()) {
|
||||
stepHistory();
|
||||
return getNextHistoryBucket(bucketOut);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------ UID LOGIC------------------------
|
||||
|
||||
private boolean isUidEnumeration() {
|
||||
return mUids != null;
|
||||
}
|
||||
|
||||
private boolean hasNextUid() {
|
||||
return isUidEnumeration() && (mUidOrUidIndex + 1) < mUids.length;
|
||||
}
|
||||
|
||||
private int getUid() {
|
||||
// Check if uid enumeration.
|
||||
if (isUidEnumeration()) {
|
||||
if (mUidOrUidIndex < 0 || mUidOrUidIndex >= mUids.length) {
|
||||
throw new IndexOutOfBoundsException(
|
||||
"Index=" + mUidOrUidIndex + " mUids.length=" + mUids.length);
|
||||
}
|
||||
return mUids[mUidOrUidIndex];
|
||||
}
|
||||
// Single uid mode.
|
||||
return mUidOrUidIndex;
|
||||
}
|
||||
|
||||
private void setSingleUidTagState(int uid, int tag, int state) {
|
||||
mUidOrUidIndex = uid;
|
||||
mTag = tag;
|
||||
mState = state;
|
||||
}
|
||||
|
||||
private void stepUid() {
|
||||
if (mUids != null) {
|
||||
++mUidOrUidIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
1181
framework-t/src/android/app/usage/NetworkStatsManager.java
Normal file
1181
framework-t/src/android/app/usage/NetworkStatsManager.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.SystemServiceRegistry;
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.content.Context;
|
||||
import android.net.nsd.INsdManager;
|
||||
import android.net.nsd.NsdManager;
|
||||
|
||||
/**
|
||||
* Class for performing registration for Connectivity services which are exposed via updatable APIs
|
||||
* since Android T.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
|
||||
public final class ConnectivityFrameworkInitializerTiramisu {
|
||||
private ConnectivityFrameworkInitializerTiramisu() {}
|
||||
|
||||
/**
|
||||
* Called by {@link SystemServiceRegistry}'s static initializer and registers NetworkStats, nsd,
|
||||
* ipsec and ethernet services to {@link Context}, so that {@link Context#getSystemService} can
|
||||
* return them.
|
||||
*
|
||||
* @throws IllegalStateException if this is called anywhere besides
|
||||
* {@link SystemServiceRegistry}.
|
||||
*/
|
||||
public static void registerServiceWrappers() {
|
||||
SystemServiceRegistry.registerContextAwareService(
|
||||
Context.NSD_SERVICE,
|
||||
NsdManager.class,
|
||||
(context, serviceBinder) -> {
|
||||
INsdManager service = INsdManager.Stub.asInterface(serviceBinder);
|
||||
return new NsdManager(context, service);
|
||||
}
|
||||
);
|
||||
|
||||
SystemServiceRegistry.registerContextAwareService(
|
||||
Context.IPSEC_SERVICE,
|
||||
IpSecManager.class,
|
||||
(context, serviceBinder) -> {
|
||||
IIpSecService service = IIpSecService.Stub.asInterface(serviceBinder);
|
||||
return new IpSecManager(context, service);
|
||||
}
|
||||
);
|
||||
|
||||
SystemServiceRegistry.registerContextAwareService(
|
||||
Context.NETWORK_STATS_SERVICE,
|
||||
NetworkStatsManager.class,
|
||||
(context, serviceBinder) -> {
|
||||
INetworkStatsService service =
|
||||
INetworkStatsService.Stub.asInterface(serviceBinder);
|
||||
return new NetworkStatsManager(context, service);
|
||||
}
|
||||
);
|
||||
|
||||
SystemServiceRegistry.registerContextAwareService(
|
||||
Context.ETHERNET_SERVICE,
|
||||
EthernetManager.class,
|
||||
(context, serviceBinder) -> {
|
||||
IEthernetManager service = IEthernetManager.Stub.asInterface(serviceBinder);
|
||||
return new EthernetManager(context, service);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
19
framework-t/src/android/net/DataUsageRequest.aidl
Normal file
19
framework-t/src/android/net/DataUsageRequest.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2016, 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.net;
|
||||
|
||||
parcelable DataUsageRequest;
|
||||
112
framework-t/src/android/net/DataUsageRequest.java
Normal file
112
framework-t/src/android/net/DataUsageRequest.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Copyright (C) 2016 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.net;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Defines a request to register a callbacks. Used to be notified on data usage via
|
||||
* {@link android.app.usage.NetworkStatsManager#registerDataUsageCallback}.
|
||||
* If no {@code uid}s are set, callbacks are restricted to device-owners,
|
||||
* carrier-privileged apps, or system apps.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class DataUsageRequest implements Parcelable {
|
||||
|
||||
public static final String PARCELABLE_KEY = "DataUsageRequest";
|
||||
public static final int REQUEST_ID_UNSET = 0;
|
||||
|
||||
/**
|
||||
* Identifies the request. {@link DataUsageRequest}s should only be constructed by
|
||||
* the Framework and it is used internally to identify the request.
|
||||
*/
|
||||
public final int requestId;
|
||||
|
||||
/**
|
||||
* {@link NetworkTemplate} describing the network to monitor.
|
||||
*/
|
||||
public final NetworkTemplate template;
|
||||
|
||||
/**
|
||||
* Threshold in bytes to be notified on.
|
||||
*/
|
||||
public final long thresholdInBytes;
|
||||
|
||||
public DataUsageRequest(int requestId, NetworkTemplate template, long thresholdInBytes) {
|
||||
this.requestId = requestId;
|
||||
this.template = template;
|
||||
this.thresholdInBytes = thresholdInBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(requestId);
|
||||
dest.writeParcelable(template, flags);
|
||||
dest.writeLong(thresholdInBytes);
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Creator<DataUsageRequest> CREATOR =
|
||||
new Creator<DataUsageRequest>() {
|
||||
@Override
|
||||
public DataUsageRequest createFromParcel(Parcel in) {
|
||||
int requestId = in.readInt();
|
||||
NetworkTemplate template = in.readParcelable(null);
|
||||
long thresholdInBytes = in.readLong();
|
||||
DataUsageRequest result = new DataUsageRequest(requestId, template,
|
||||
thresholdInBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataUsageRequest[] newArray(int size) {
|
||||
return new DataUsageRequest[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DataUsageRequest [ requestId=" + requestId
|
||||
+ ", networkTemplate=" + template
|
||||
+ ", thresholdInBytes=" + thresholdInBytes + " ]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof DataUsageRequest == false) return false;
|
||||
DataUsageRequest that = (DataUsageRequest) obj;
|
||||
return that.requestId == this.requestId
|
||||
&& Objects.equals(that.template, this.template)
|
||||
&& that.thresholdInBytes == this.thresholdInBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(requestId, template, thresholdInBytes);
|
||||
}
|
||||
|
||||
}
|
||||
729
framework-t/src/android/net/EthernetManager.java
Normal file
729
framework-t/src/android/net/EthernetManager.java
Normal file
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresFeature;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.OutcomeReceiver;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.modules.utils.BackgroundThread;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* A class that manages and configures Ethernet interfaces.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@SystemService(Context.ETHERNET_SERVICE)
|
||||
public class EthernetManager {
|
||||
private static final String TAG = "EthernetManager";
|
||||
|
||||
private final IEthernetManager mService;
|
||||
@GuardedBy("mListenerLock")
|
||||
private final ArrayList<ListenerInfo<InterfaceStateListener>> mIfaceListeners =
|
||||
new ArrayList<>();
|
||||
@GuardedBy("mListenerLock")
|
||||
private final ArrayList<ListenerInfo<IntConsumer>> mEthernetStateListeners =
|
||||
new ArrayList<>();
|
||||
final Object mListenerLock = new Object();
|
||||
private final IEthernetServiceListener.Stub mServiceListener =
|
||||
new IEthernetServiceListener.Stub() {
|
||||
@Override
|
||||
public void onEthernetStateChanged(int state) {
|
||||
synchronized (mListenerLock) {
|
||||
for (ListenerInfo<IntConsumer> li : mEthernetStateListeners) {
|
||||
li.executor.execute(() -> {
|
||||
li.listener.accept(state);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterfaceStateChanged(String iface, int state, int role,
|
||||
IpConfiguration configuration) {
|
||||
synchronized (mListenerLock) {
|
||||
for (ListenerInfo<InterfaceStateListener> li : mIfaceListeners) {
|
||||
li.executor.execute(() ->
|
||||
li.listener.onInterfaceStateChanged(iface, state, role,
|
||||
configuration));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates that Ethernet is disabled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int ETHERNET_STATE_DISABLED = 0;
|
||||
|
||||
/**
|
||||
* Indicates that Ethernet is enabled.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int ETHERNET_STATE_ENABLED = 1;
|
||||
|
||||
private static class ListenerInfo<T> {
|
||||
@NonNull
|
||||
public final Executor executor;
|
||||
@NonNull
|
||||
public final T listener;
|
||||
|
||||
private ListenerInfo(@NonNull Executor executor, @NonNull T listener) {
|
||||
this.executor = executor;
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface is absent.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int STATE_ABSENT = 0;
|
||||
|
||||
/**
|
||||
* The interface is present but link is down.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int STATE_LINK_DOWN = 1;
|
||||
|
||||
/**
|
||||
* The interface is present and link is up.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int STATE_LINK_UP = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "STATE_", value = {STATE_ABSENT, STATE_LINK_DOWN, STATE_LINK_UP})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface InterfaceState {}
|
||||
|
||||
/**
|
||||
* The interface currently does not have any specific role.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int ROLE_NONE = 0;
|
||||
|
||||
/**
|
||||
* The interface is in client mode (e.g., connected to the Internet).
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int ROLE_CLIENT = 1;
|
||||
|
||||
/**
|
||||
* Ethernet interface is in server mode (e.g., providing Internet access to tethered devices).
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public static final int ROLE_SERVER = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "ROLE_", value = {ROLE_NONE, ROLE_CLIENT, ROLE_SERVER})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Role {}
|
||||
|
||||
/**
|
||||
* A listener that receives notifications about the state of Ethernet interfaces on the system.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public interface InterfaceStateListener {
|
||||
/**
|
||||
* Called when an Ethernet interface changes state.
|
||||
*
|
||||
* @param iface the name of the interface.
|
||||
* @param state the current state of the interface, or {@link #STATE_ABSENT} if the
|
||||
* interface was removed.
|
||||
* @param role whether the interface is in client mode or server mode.
|
||||
* @param configuration the current IP configuration of the interface.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
|
||||
@Role int role, @Nullable IpConfiguration configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener interface to receive notification on changes in Ethernet.
|
||||
* This has never been a supported API. Use {@link InterfaceStateListener} instead.
|
||||
* @hide
|
||||
*/
|
||||
public interface Listener extends InterfaceStateListener {
|
||||
/**
|
||||
* Called when Ethernet port's availability is changed.
|
||||
* @param iface Ethernet interface name
|
||||
* @param isAvailable {@code true} if Ethernet port exists.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
void onAvailabilityChanged(String iface, boolean isAvailable);
|
||||
|
||||
/** Default implementation for backwards compatibility. Only calls the legacy listener. */
|
||||
default void onInterfaceStateChanged(@NonNull String iface, @InterfaceState int state,
|
||||
@Role int role, @Nullable IpConfiguration configuration) {
|
||||
onAvailabilityChanged(iface, (state >= STATE_LINK_UP));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new EthernetManager instance.
|
||||
* Applications will almost always want to use
|
||||
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
|
||||
* the standard {@link android.content.Context#ETHERNET_SERVICE Context.ETHERNET_SERVICE}.
|
||||
* @hide
|
||||
*/
|
||||
public EthernetManager(Context context, IEthernetManager service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet configuration.
|
||||
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public IpConfiguration getConfiguration(String iface) {
|
||||
try {
|
||||
return mService.getConfiguration(iface);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Ethernet configuration.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public void setConfiguration(@NonNull String iface, @NonNull IpConfiguration config) {
|
||||
try {
|
||||
mService.setConfiguration(iface, config);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the system currently has one or more Ethernet interfaces.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public boolean isAvailable() {
|
||||
return getAvailableInterfaces().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the system has given interface.
|
||||
*
|
||||
* @param iface Ethernet interface name
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public boolean isAvailable(String iface) {
|
||||
try {
|
||||
return mService.isAvailable(iface);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener.
|
||||
* This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
|
||||
*
|
||||
* @param listener A {@link Listener} to add.
|
||||
* @throws IllegalArgumentException If the listener is null.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public void addListener(@NonNull Listener listener) {
|
||||
addListener(listener, BackgroundThread.getExecutor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener.
|
||||
* This has never been a supported API. Use {@link #addInterfaceStateListener} instead.
|
||||
*
|
||||
* @param listener A {@link Listener} to add.
|
||||
* @param executor Executor to run callbacks on.
|
||||
* @throws IllegalArgumentException If the listener or executor is null.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public void addListener(@NonNull Listener listener, @NonNull Executor executor) {
|
||||
addInterfaceStateListener(executor, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to changes in the state of Ethernet interfaces.
|
||||
*
|
||||
* Adds a listener to receive notification for any state change of all existing Ethernet
|
||||
* interfaces.
|
||||
* <p>{@link Listener#onInterfaceStateChanged} will be triggered immediately for all
|
||||
* existing interfaces upon adding a listener. The same method will be called on the
|
||||
* listener every time any of the interface changes state. In particular, if an
|
||||
* interface is removed, it will be called with state {@link #STATE_ABSENT}.
|
||||
* <p>Use {@link #removeInterfaceStateListener} with the same object to stop listening.
|
||||
*
|
||||
* @param executor Executor to run callbacks on.
|
||||
* @param listener A {@link Listener} to add.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void addInterfaceStateListener(@NonNull Executor executor,
|
||||
@NonNull InterfaceStateListener listener) {
|
||||
if (listener == null || executor == null) {
|
||||
throw new NullPointerException("listener and executor must not be null");
|
||||
}
|
||||
synchronized (mListenerLock) {
|
||||
maybeAddServiceListener();
|
||||
mIfaceListeners.add(new ListenerInfo<InterfaceStateListener>(executor, listener));
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mListenerLock")
|
||||
private void maybeAddServiceListener() {
|
||||
if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
|
||||
|
||||
try {
|
||||
mService.addListener(mServiceListener);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of available Ethernet interface names.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public String[] getAvailableInterfaces() {
|
||||
try {
|
||||
return mService.getAvailableInterfaces();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener.
|
||||
*
|
||||
* @param listener A {@link Listener} to remove.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
synchronized (mListenerLock) {
|
||||
mIfaceListeners.removeIf(l -> l.listener == listener);
|
||||
maybeRemoveServiceListener();
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mListenerLock")
|
||||
private void maybeRemoveServiceListener() {
|
||||
if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
|
||||
|
||||
try {
|
||||
mService.removeListener(mServiceListener);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener.
|
||||
* This has never been a supported API. Use {@link #removeInterfaceStateListener} instead.
|
||||
* @param listener A {@link Listener} to remove.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public void removeListener(@NonNull Listener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener must not be null");
|
||||
}
|
||||
removeInterfaceStateListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface}
|
||||
* as Ethernet interfaces. The effects of this method apply to any test interfaces that are
|
||||
* already present on the system.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void setIncludeTestInterfaces(boolean include) {
|
||||
try {
|
||||
mService.setIncludeTestInterfaces(include);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request for a tethered interface.
|
||||
*/
|
||||
public static class TetheredInterfaceRequest {
|
||||
private final IEthernetManager mService;
|
||||
private final ITetheredInterfaceCallback mCb;
|
||||
|
||||
private TetheredInterfaceRequest(@NonNull IEthernetManager service,
|
||||
@NonNull ITetheredInterfaceCallback cb) {
|
||||
this.mService = service;
|
||||
this.mCb = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the request, causing the interface to revert back from tethering mode if there
|
||||
* is no other requestor.
|
||||
*/
|
||||
public void release() {
|
||||
try {
|
||||
mService.releaseTetheredInterface(mCb);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for {@link #requestTetheredInterface(TetheredInterfaceCallback)}.
|
||||
*/
|
||||
public interface TetheredInterfaceCallback {
|
||||
/**
|
||||
* Called when the tethered interface is available.
|
||||
* @param iface The name of the interface.
|
||||
*/
|
||||
void onAvailable(@NonNull String iface);
|
||||
|
||||
/**
|
||||
* Called when the tethered interface is now unavailable.
|
||||
*/
|
||||
void onUnavailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a tethered interface in tethering mode.
|
||||
*
|
||||
* <p>When this method is called and there is at least one ethernet interface available, the
|
||||
* system will designate one to act as a tethered interface. If there is already a tethered
|
||||
* interface, the existing interface will be used.
|
||||
* @param callback A callback to be called once the request has been fulfilled.
|
||||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
|
||||
})
|
||||
@NonNull
|
||||
public TetheredInterfaceRequest requestTetheredInterface(@NonNull final Executor executor,
|
||||
@NonNull final TetheredInterfaceCallback callback) {
|
||||
Objects.requireNonNull(callback, "Callback must be non-null");
|
||||
Objects.requireNonNull(executor, "Executor must be non-null");
|
||||
final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() {
|
||||
@Override
|
||||
public void onAvailable(String iface) {
|
||||
executor.execute(() -> callback.onAvailable(iface));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnavailable() {
|
||||
executor.execute(() -> callback.onUnavailable());
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
mService.requestTetheredInterface(cbInternal);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
return new TetheredInterfaceRequest(mService, cbInternal);
|
||||
}
|
||||
|
||||
private static final class NetworkInterfaceOutcomeReceiver
|
||||
extends INetworkInterfaceOutcomeReceiver.Stub {
|
||||
@NonNull
|
||||
private final Executor mExecutor;
|
||||
@NonNull
|
||||
private final OutcomeReceiver<String, EthernetNetworkManagementException> mCallback;
|
||||
|
||||
NetworkInterfaceOutcomeReceiver(
|
||||
@NonNull final Executor executor,
|
||||
@NonNull final OutcomeReceiver<String, EthernetNetworkManagementException>
|
||||
callback) {
|
||||
Objects.requireNonNull(executor, "Pass a non-null executor");
|
||||
Objects.requireNonNull(callback, "Pass a non-null callback");
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(@NonNull String iface) {
|
||||
mExecutor.execute(() -> mCallback.onResult(iface));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull EthernetNetworkManagementException e) {
|
||||
mExecutor.execute(() -> mCallback.onError(e));
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkInterfaceOutcomeReceiver makeNetworkInterfaceOutcomeReceiver(
|
||||
@Nullable final Executor executor,
|
||||
@Nullable final OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
|
||||
if (null != callback) {
|
||||
Objects.requireNonNull(executor, "Pass a non-null executor, or a null callback");
|
||||
}
|
||||
final NetworkInterfaceOutcomeReceiver proxy;
|
||||
if (null == callback) {
|
||||
proxy = null;
|
||||
} else {
|
||||
proxy = new NetworkInterfaceOutcomeReceiver(executor, callback);
|
||||
}
|
||||
return proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the configuration of an automotive device's ethernet network.
|
||||
*
|
||||
* The {@link EthernetNetworkUpdateRequest} {@code request} argument describes how to update the
|
||||
* configuration for this network.
|
||||
* Use {@link StaticIpConfiguration.Builder} to build a {@code StaticIpConfiguration} object for
|
||||
* this network to put inside the {@code request}.
|
||||
* Similarly, use {@link NetworkCapabilities.Builder} to build a {@code NetworkCapabilities}
|
||||
* object for this network to put inside the {@code request}.
|
||||
*
|
||||
* This function accepts an {@link OutcomeReceiver} that is called once the operation has
|
||||
* finished execution.
|
||||
*
|
||||
* @param iface the name of the interface to act upon.
|
||||
* @param request the {@link EthernetNetworkUpdateRequest} used to set an ethernet network's
|
||||
* {@link StaticIpConfiguration} and {@link NetworkCapabilities} values.
|
||||
* @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
|
||||
* @param callback an optional {@link OutcomeReceiver} to listen for completion of the
|
||||
* operation. On success, {@link OutcomeReceiver#onResult} is called with the
|
||||
* interface name. On error, {@link OutcomeReceiver#onError} is called with more
|
||||
* information about the error.
|
||||
* @throws SecurityException if the process doesn't hold
|
||||
* {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
|
||||
* @throws UnsupportedOperationException if called on a non-automotive device or on an
|
||||
* unsupported interface.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
|
||||
public void updateConfiguration(
|
||||
@NonNull String iface,
|
||||
@NonNull EthernetNetworkUpdateRequest request,
|
||||
@Nullable @CallbackExecutor Executor executor,
|
||||
@Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
|
||||
Objects.requireNonNull(iface, "iface must be non-null");
|
||||
Objects.requireNonNull(request, "request must be non-null");
|
||||
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
|
||||
executor, callback);
|
||||
try {
|
||||
mService.updateConfiguration(iface, request, proxy);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable a network interface.
|
||||
*
|
||||
* Enables a previously disabled network interface.
|
||||
* This function accepts an {@link OutcomeReceiver} that is called once the operation has
|
||||
* finished execution.
|
||||
*
|
||||
* @param iface the name of the interface to enable.
|
||||
* @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
|
||||
* @param callback an optional {@link OutcomeReceiver} to listen for completion of the
|
||||
* operation. On success, {@link OutcomeReceiver#onResult} is called with the
|
||||
* interface name. On error, {@link OutcomeReceiver#onError} is called with more
|
||||
* information about the error.
|
||||
* @throws SecurityException if the process doesn't hold
|
||||
* {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
public void enableInterface(
|
||||
@NonNull String iface,
|
||||
@Nullable @CallbackExecutor Executor executor,
|
||||
@Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
|
||||
Objects.requireNonNull(iface, "iface must be non-null");
|
||||
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
|
||||
executor, callback);
|
||||
try {
|
||||
mService.connectNetwork(iface, proxy);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable a network interface.
|
||||
*
|
||||
* Disables the use of a network interface to fulfill network requests. If the interface
|
||||
* currently serves a request, the network will be torn down.
|
||||
* This function accepts an {@link OutcomeReceiver} that is called once the operation has
|
||||
* finished execution.
|
||||
*
|
||||
* @param iface the name of the interface to disable.
|
||||
* @param executor an {@link Executor} to execute the callback on. Optional if callback is null.
|
||||
* @param callback an optional {@link OutcomeReceiver} to listen for completion of the
|
||||
* operation. On success, {@link OutcomeReceiver#onResult} is called with the
|
||||
* interface name. On error, {@link OutcomeReceiver#onError} is called with more
|
||||
* information about the error.
|
||||
* @throws SecurityException if the process doesn't hold
|
||||
* {@link android.Manifest.permission.MANAGE_ETHERNET_NETWORKS}.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
|
||||
@RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
public void disableInterface(
|
||||
@NonNull String iface,
|
||||
@Nullable @CallbackExecutor Executor executor,
|
||||
@Nullable OutcomeReceiver<String, EthernetNetworkManagementException> callback) {
|
||||
Objects.requireNonNull(iface, "iface must be non-null");
|
||||
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
|
||||
executor, callback);
|
||||
try {
|
||||
mService.disconnectNetwork(iface, proxy);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change ethernet setting.
|
||||
*
|
||||
* @param enabled enable or disable ethernet settings.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(anyOf = {
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_STACK,
|
||||
android.Manifest.permission.NETWORK_SETTINGS})
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void setEthernetEnabled(boolean enabled) {
|
||||
try {
|
||||
mService.setEthernetEnabled(enabled);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to changes in the state of ethernet.
|
||||
*
|
||||
* @param executor to run callbacks on.
|
||||
* @param listener to listen ethernet state changed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void addEthernetStateListener(@NonNull Executor executor,
|
||||
@NonNull IntConsumer listener) {
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(listener);
|
||||
synchronized (mListenerLock) {
|
||||
maybeAddServiceListener();
|
||||
mEthernetStateListeners.add(new ListenerInfo<IntConsumer>(executor, listener));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener.
|
||||
*
|
||||
* @param listener to listen ethernet state changed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public void removeEthernetStateListener(@NonNull IntConsumer listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
synchronized (mListenerLock) {
|
||||
mEthernetStateListeners.removeIf(l -> l.listener == listener);
|
||||
maybeRemoveServiceListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of existing Ethernet interface names regardless whether the interface
|
||||
* is available or not currently.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
@NonNull
|
||||
public List<String> getInterfaceList() {
|
||||
try {
|
||||
return mService.getInterfaceList();
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
parcelable EthernetNetworkManagementException;
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public final class EthernetNetworkManagementException
|
||||
extends RuntimeException implements Parcelable {
|
||||
|
||||
/* @hide */
|
||||
public EthernetNetworkManagementException(@NonNull final String errorMessage) {
|
||||
super(errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
final EthernetNetworkManagementException that = (EthernetNetworkManagementException) obj;
|
||||
|
||||
return Objects.equals(getMessage(), that.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<EthernetNetworkManagementException> CREATOR =
|
||||
new Parcelable.Creator<EthernetNetworkManagementException>() {
|
||||
@Override
|
||||
public EthernetNetworkManagementException[] newArray(int size) {
|
||||
return new EthernetNetworkManagementException[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public EthernetNetworkManagementException createFromParcel(@NonNull Parcel source) {
|
||||
return new EthernetNetworkManagementException(source.readString());
|
||||
}
|
||||
};
|
||||
}
|
||||
102
framework-t/src/android/net/EthernetNetworkSpecifier.java
Normal file
102
framework-t/src/android/net/EthernetNetworkSpecifier.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A {@link NetworkSpecifier} used to identify ethernet interfaces.
|
||||
*
|
||||
* @see EthernetManager
|
||||
*/
|
||||
public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable {
|
||||
|
||||
/**
|
||||
* Name of the network interface.
|
||||
*/
|
||||
@NonNull
|
||||
private final String mInterfaceName;
|
||||
|
||||
/**
|
||||
* Create a new EthernetNetworkSpecifier.
|
||||
* @param interfaceName Name of the ethernet interface the specifier refers to.
|
||||
*/
|
||||
public EthernetNetworkSpecifier(@NonNull String interfaceName) {
|
||||
if (TextUtils.isEmpty(interfaceName)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
mInterfaceName = interfaceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the ethernet interface the specifier refers to.
|
||||
*/
|
||||
@Nullable
|
||||
public String getInterfaceName() {
|
||||
// This may be null in the future to support specifiers based on data other than the
|
||||
// interface name.
|
||||
return mInterfaceName;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) {
|
||||
return equals(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (!(o instanceof EthernetNetworkSpecifier)) return false;
|
||||
return TextUtils.equals(mInterfaceName, ((EthernetNetworkSpecifier) o).mInterfaceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(mInterfaceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EthernetNetworkSpecifier (" + mInterfaceName + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeString(mInterfaceName);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<EthernetNetworkSpecifier> CREATOR =
|
||||
new Parcelable.Creator<EthernetNetworkSpecifier>() {
|
||||
public EthernetNetworkSpecifier createFromParcel(Parcel in) {
|
||||
return new EthernetNetworkSpecifier(in.readString());
|
||||
}
|
||||
public EthernetNetworkSpecifier[] newArray(int size) {
|
||||
return new EthernetNetworkSpecifier[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
parcelable EthernetNetworkUpdateRequest;
|
||||
185
framework-t/src/android/net/EthernetNetworkUpdateRequest.java
Normal file
185
framework-t/src/android/net/EthernetNetworkUpdateRequest.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a request to update an existing Ethernet interface.
|
||||
*
|
||||
* @see EthernetManager#updateConfiguration
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class EthernetNetworkUpdateRequest implements Parcelable {
|
||||
@Nullable
|
||||
private final IpConfiguration mIpConfig;
|
||||
@Nullable
|
||||
private final NetworkCapabilities mNetworkCapabilities;
|
||||
|
||||
/**
|
||||
* Setting the {@link IpConfiguration} is optional in {@link EthernetNetworkUpdateRequest}.
|
||||
* When set to null, the existing IpConfiguration is not updated.
|
||||
*
|
||||
* @return the new {@link IpConfiguration} or null.
|
||||
*/
|
||||
@Nullable
|
||||
public IpConfiguration getIpConfiguration() {
|
||||
return mIpConfig == null ? null : new IpConfiguration(mIpConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting the {@link NetworkCapabilities} is optional in {@link EthernetNetworkUpdateRequest}.
|
||||
* When set to null, the existing NetworkCapabilities are not updated.
|
||||
*
|
||||
* @return the new {@link NetworkCapabilities} or null.
|
||||
*/
|
||||
@Nullable
|
||||
public NetworkCapabilities getNetworkCapabilities() {
|
||||
return mNetworkCapabilities == null ? null : new NetworkCapabilities(mNetworkCapabilities);
|
||||
}
|
||||
|
||||
private EthernetNetworkUpdateRequest(@Nullable final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities networkCapabilities) {
|
||||
mIpConfig = ipConfig;
|
||||
mNetworkCapabilities = networkCapabilities;
|
||||
}
|
||||
|
||||
private EthernetNetworkUpdateRequest(@NonNull final Parcel source) {
|
||||
Objects.requireNonNull(source);
|
||||
mIpConfig = source.readParcelable(IpConfiguration.class.getClassLoader(),
|
||||
IpConfiguration.class);
|
||||
mNetworkCapabilities = source.readParcelable(NetworkCapabilities.class.getClassLoader(),
|
||||
NetworkCapabilities.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder used to create {@link EthernetNetworkUpdateRequest} objects.
|
||||
*/
|
||||
public static final class Builder {
|
||||
@Nullable
|
||||
private IpConfiguration mBuilderIpConfig;
|
||||
@Nullable
|
||||
private NetworkCapabilities mBuilderNetworkCapabilities;
|
||||
|
||||
public Builder(){}
|
||||
|
||||
/**
|
||||
* Constructor to populate the builder's values with an already built
|
||||
* {@link EthernetNetworkUpdateRequest}.
|
||||
* @param request the {@link EthernetNetworkUpdateRequest} to populate with.
|
||||
*/
|
||||
public Builder(@NonNull final EthernetNetworkUpdateRequest request) {
|
||||
Objects.requireNonNull(request);
|
||||
mBuilderIpConfig = null == request.mIpConfig
|
||||
? null : new IpConfiguration(request.mIpConfig);
|
||||
mBuilderNetworkCapabilities = null == request.mNetworkCapabilities
|
||||
? null : new NetworkCapabilities(request.mNetworkCapabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link IpConfiguration} to be used with the {@code Builder}.
|
||||
* @param ipConfig the {@link IpConfiguration} to set.
|
||||
* @return The builder to facilitate chaining.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setIpConfiguration(@Nullable final IpConfiguration ipConfig) {
|
||||
mBuilderIpConfig = ipConfig == null ? null : new IpConfiguration(ipConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link NetworkCapabilities} to be used with the {@code Builder}.
|
||||
* @param nc the {@link NetworkCapabilities} to set.
|
||||
* @return The builder to facilitate chaining.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setNetworkCapabilities(@Nullable final NetworkCapabilities nc) {
|
||||
mBuilderNetworkCapabilities = nc == null ? null : new NetworkCapabilities(nc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build {@link EthernetNetworkUpdateRequest} return the current update request.
|
||||
*
|
||||
* @throws IllegalStateException when both mBuilderNetworkCapabilities and mBuilderIpConfig
|
||||
* are null.
|
||||
*/
|
||||
@NonNull
|
||||
public EthernetNetworkUpdateRequest build() {
|
||||
if (mBuilderIpConfig == null && mBuilderNetworkCapabilities == null) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot construct an empty EthernetNetworkUpdateRequest");
|
||||
}
|
||||
return new EthernetNetworkUpdateRequest(mBuilderIpConfig, mBuilderNetworkCapabilities);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EthernetNetworkUpdateRequest{"
|
||||
+ "mIpConfig=" + mIpConfig
|
||||
+ ", mNetworkCapabilities=" + mNetworkCapabilities + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
EthernetNetworkUpdateRequest that = (EthernetNetworkUpdateRequest) o;
|
||||
|
||||
return Objects.equals(that.getIpConfiguration(), mIpConfig)
|
||||
&& Objects.equals(that.getNetworkCapabilities(), mNetworkCapabilities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mIpConfig, mNetworkCapabilities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(mIpConfig, flags);
|
||||
dest.writeParcelable(mNetworkCapabilities, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<EthernetNetworkUpdateRequest> CREATOR =
|
||||
new Parcelable.Creator<EthernetNetworkUpdateRequest>() {
|
||||
@Override
|
||||
public EthernetNetworkUpdateRequest[] newArray(int size) {
|
||||
return new EthernetNetworkUpdateRequest[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public EthernetNetworkUpdateRequest createFromParcel(@NonNull Parcel source) {
|
||||
return new EthernetNetworkUpdateRequest(source);
|
||||
}
|
||||
};
|
||||
}
|
||||
50
framework-t/src/android/net/IEthernetManager.aidl
Normal file
50
framework-t/src/android/net/IEthernetManager.aidl
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.net;
|
||||
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.EthernetNetworkManagementException;
|
||||
import android.net.EthernetNetworkUpdateRequest;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.ITetheredInterfaceCallback;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface that answers queries about, and allows changing
|
||||
* ethernet configuration.
|
||||
*/
|
||||
/** {@hide} */
|
||||
interface IEthernetManager
|
||||
{
|
||||
String[] getAvailableInterfaces();
|
||||
IpConfiguration getConfiguration(String iface);
|
||||
void setConfiguration(String iface, in IpConfiguration config);
|
||||
boolean isAvailable(String iface);
|
||||
void addListener(in IEthernetServiceListener listener);
|
||||
void removeListener(in IEthernetServiceListener listener);
|
||||
void setIncludeTestInterfaces(boolean include);
|
||||
void requestTetheredInterface(in ITetheredInterfaceCallback callback);
|
||||
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
|
||||
void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
|
||||
in INetworkInterfaceOutcomeReceiver listener);
|
||||
void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
|
||||
void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
|
||||
void setEthernetEnabled(boolean enabled);
|
||||
List<String> getInterfaceList();
|
||||
}
|
||||
27
framework-t/src/android/net/IEthernetServiceListener.aidl
Normal file
27
framework-t/src/android/net/IEthernetServiceListener.aidl
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.net;
|
||||
|
||||
import android.net.IpConfiguration;
|
||||
|
||||
/** @hide */
|
||||
oneway interface IEthernetServiceListener
|
||||
{
|
||||
void onEthernetStateChanged(int state);
|
||||
void onInterfaceStateChanged(String iface, int state, int role,
|
||||
in IpConfiguration configuration);
|
||||
}
|
||||
78
framework-t/src/android/net/IIpSecService.aidl
Normal file
78
framework-t/src/android/net/IIpSecService.aidl
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
** Copyright 2017, 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.net;
|
||||
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.IpSecConfig;
|
||||
import android.net.IpSecUdpEncapResponse;
|
||||
import android.net.IpSecSpiResponse;
|
||||
import android.net.IpSecTransformResponse;
|
||||
import android.net.IpSecTunnelInterfaceResponse;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
interface IIpSecService
|
||||
{
|
||||
IpSecSpiResponse allocateSecurityParameterIndex(
|
||||
in String destinationAddress, int requestedSpi, in IBinder binder);
|
||||
|
||||
void releaseSecurityParameterIndex(int resourceId);
|
||||
|
||||
IpSecUdpEncapResponse openUdpEncapsulationSocket(int port, in IBinder binder);
|
||||
|
||||
void closeUdpEncapsulationSocket(int resourceId);
|
||||
|
||||
IpSecTunnelInterfaceResponse createTunnelInterface(
|
||||
in String localAddr,
|
||||
in String remoteAddr,
|
||||
in Network underlyingNetwork,
|
||||
in IBinder binder,
|
||||
in String callingPackage);
|
||||
|
||||
void addAddressToTunnelInterface(
|
||||
int tunnelResourceId,
|
||||
in LinkAddress localAddr,
|
||||
in String callingPackage);
|
||||
|
||||
void removeAddressFromTunnelInterface(
|
||||
int tunnelResourceId,
|
||||
in LinkAddress localAddr,
|
||||
in String callingPackage);
|
||||
|
||||
void setNetworkForTunnelInterface(
|
||||
int tunnelResourceId, in Network underlyingNetwork, in String callingPackage);
|
||||
|
||||
void deleteTunnelInterface(int resourceId, in String callingPackage);
|
||||
|
||||
IpSecTransformResponse createTransform(
|
||||
in IpSecConfig c, in IBinder binder, in String callingPackage);
|
||||
|
||||
void deleteTransform(int transformId);
|
||||
|
||||
void applyTransportModeTransform(
|
||||
in ParcelFileDescriptor socket, int direction, int transformId);
|
||||
|
||||
void applyTunnelModeTransform(
|
||||
int tunnelResourceId, int direction, int transformResourceId, in String callingPackage);
|
||||
|
||||
void removeTransportModeTransforms(in ParcelFileDescriptor socket);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2021, 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.net;
|
||||
|
||||
import android.net.EthernetNetworkManagementException;
|
||||
|
||||
/** @hide */
|
||||
oneway interface INetworkInterfaceOutcomeReceiver {
|
||||
void onResult(in String iface);
|
||||
void onError(in EthernetNetworkManagementException e);
|
||||
}
|
||||
104
framework-t/src/android/net/INetworkStatsService.aidl
Normal file
104
framework-t/src/android/net/INetworkStatsService.aidl
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.net;
|
||||
|
||||
import android.net.DataUsageRequest;
|
||||
import android.net.INetworkStatsSession;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkStateSnapshot;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.UnderlyingNetworkInfo;
|
||||
import android.net.netstats.IUsageCallback;
|
||||
import android.net.netstats.provider.INetworkStatsProvider;
|
||||
import android.net.netstats.provider.INetworkStatsProviderCallback;
|
||||
import android.os.IBinder;
|
||||
import android.os.Messenger;
|
||||
|
||||
/** {@hide} */
|
||||
interface INetworkStatsService {
|
||||
|
||||
/** Start a statistics query session. */
|
||||
@UnsupportedAppUsage
|
||||
INetworkStatsSession openSession();
|
||||
|
||||
/** Start a statistics query session. If calling package is profile or device owner then it is
|
||||
* granted automatic access if apiLevel is NetworkStatsManager.API_LEVEL_DPC_ALLOWED. If
|
||||
* apiLevel is at least NetworkStatsManager.API_LEVEL_REQUIRES_PACKAGE_USAGE_STATS then
|
||||
* PACKAGE_USAGE_STATS permission is always checked. If PACKAGE_USAGE_STATS is not granted
|
||||
* READ_NETWORK_USAGE_STATS is checked for.
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
|
||||
INetworkStatsSession openSessionForUsageStats(int flags, String callingPackage);
|
||||
|
||||
/** Return data layer snapshot of UID network usage. */
|
||||
@UnsupportedAppUsage
|
||||
NetworkStats getDataLayerSnapshotForUid(int uid);
|
||||
|
||||
/** Get the transport NetworkStats for all UIDs since boot. */
|
||||
NetworkStats getUidStatsForTransport(int transport);
|
||||
|
||||
/** Return set of any ifaces associated with mobile networks since boot. */
|
||||
@UnsupportedAppUsage
|
||||
String[] getMobileIfaces();
|
||||
|
||||
/** Increment data layer count of operations performed for UID and tag. */
|
||||
void incrementOperationCount(int uid, int tag, int operationCount);
|
||||
|
||||
/** Notify {@code NetworkStatsService} about network status changed. */
|
||||
void notifyNetworkStatus(
|
||||
in Network[] defaultNetworks,
|
||||
in NetworkStateSnapshot[] snapshots,
|
||||
in String activeIface,
|
||||
in UnderlyingNetworkInfo[] underlyingNetworkInfos);
|
||||
/** Force update of statistics. */
|
||||
@UnsupportedAppUsage
|
||||
void forceUpdate();
|
||||
|
||||
/** Registers a callback on data usage. */
|
||||
DataUsageRequest registerUsageCallback(String callingPackage,
|
||||
in DataUsageRequest request, in IUsageCallback callback);
|
||||
|
||||
/** Unregisters a callback on data usage. */
|
||||
void unregisterUsageRequest(in DataUsageRequest request);
|
||||
|
||||
/** Get the uid stats information since boot */
|
||||
long getUidStats(int uid, int type);
|
||||
|
||||
/** Get the iface stats information since boot */
|
||||
long getIfaceStats(String iface, int type);
|
||||
|
||||
/** Get the total network stats information since boot */
|
||||
long getTotalStats(int type);
|
||||
|
||||
/** Registers a network stats provider */
|
||||
INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
|
||||
in INetworkStatsProvider provider);
|
||||
|
||||
/** Mark given UID as being in foreground for stats purposes. */
|
||||
void noteUidForeground(int uid, boolean uidForeground);
|
||||
|
||||
/** Advise persistence threshold; may be overridden internally. */
|
||||
void advisePersistThreshold(long thresholdBytes);
|
||||
|
||||
/**
|
||||
* Set the warning and limit to all registered custom network stats providers.
|
||||
* Note that invocation of any interface will be sent to all providers.
|
||||
*/
|
||||
void setStatsProviderWarningAndLimitAsync(String iface, long warning, long limit);
|
||||
}
|
||||
70
framework-t/src/android/net/INetworkStatsSession.aidl
Normal file
70
framework-t/src/android/net/INetworkStatsSession.aidl
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.net;
|
||||
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
|
||||
/** {@hide} */
|
||||
interface INetworkStatsSession {
|
||||
|
||||
/** Return device aggregated network layer usage summary for traffic that matches template. */
|
||||
NetworkStats getDeviceSummaryForNetwork(in NetworkTemplate template, long start, long end);
|
||||
|
||||
/** Return network layer usage summary for traffic that matches template. */
|
||||
@UnsupportedAppUsage
|
||||
NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end);
|
||||
/** Return historical network layer stats for traffic that matches template. */
|
||||
@UnsupportedAppUsage
|
||||
NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
|
||||
/**
|
||||
* Return historical network layer stats for traffic that matches template, start and end
|
||||
* timestamp.
|
||||
*/
|
||||
NetworkStatsHistory getHistoryIntervalForNetwork(in NetworkTemplate template, int fields, long start, long end);
|
||||
|
||||
/**
|
||||
* Return network layer usage summary per UID for traffic that matches template.
|
||||
*
|
||||
* <p>The resulting {@code NetworkStats#getElapsedRealtime()} contains time delta between
|
||||
* {@code start} and {@code end}.
|
||||
*
|
||||
* @param template - a predicate to filter netstats.
|
||||
* @param start - start of the range, timestamp in milliseconds since the epoch.
|
||||
* @param end - end of the range, timestamp in milliseconds since the epoch.
|
||||
* @param includeTags - includes data usage tags if true.
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
|
||||
|
||||
/** Return network layer usage summary per UID for tagged traffic that matches template. */
|
||||
NetworkStats getTaggedSummaryForAllUid(in NetworkTemplate template, long start, long end);
|
||||
|
||||
/** Return historical network layer stats for specific UID traffic that matches template. */
|
||||
@UnsupportedAppUsage
|
||||
NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields);
|
||||
/** Return historical network layer stats for specific UID traffic that matches template. */
|
||||
NetworkStatsHistory getHistoryIntervalForUid(in NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end);
|
||||
|
||||
/** Return array of uids that have stats and are accessible to the calling user */
|
||||
int[] getRelevantUids();
|
||||
|
||||
@UnsupportedAppUsage
|
||||
void close();
|
||||
|
||||
}
|
||||
23
framework-t/src/android/net/ITetheredInterfaceCallback.aidl
Normal file
23
framework-t/src/android/net/ITetheredInterfaceCallback.aidl
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.net;
|
||||
|
||||
/** @hide */
|
||||
oneway interface ITetheredInterfaceCallback {
|
||||
void onAvailable(in String iface);
|
||||
void onUnavailable();
|
||||
}
|
||||
491
framework-t/src/android/net/IpSecAlgorithm.java
Normal file
491
framework-t/src/android/net/IpSecAlgorithm.java
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.StringDef;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class represents a single algorithm that can be used by an {@link IpSecTransform}.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
|
||||
* Internet Protocol</a>
|
||||
*/
|
||||
public final class IpSecAlgorithm implements Parcelable {
|
||||
private static final String TAG = "IpSecAlgorithm";
|
||||
|
||||
/**
|
||||
* Null cipher.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String CRYPT_NULL = "ecb(cipher_null)";
|
||||
|
||||
/**
|
||||
* AES-CBC Encryption/Ciphering Algorithm.
|
||||
*
|
||||
* <p>Valid lengths for this key are {128, 192, 256}.
|
||||
*/
|
||||
public static final String CRYPT_AES_CBC = "cbc(aes)";
|
||||
|
||||
/**
|
||||
* AES-CTR Encryption/Ciphering Algorithm.
|
||||
*
|
||||
* <p>Valid lengths for keying material are {160, 224, 288}.
|
||||
*
|
||||
* <p>As per <a href="https://tools.ietf.org/html/rfc3686#section-5.1">RFC3686 (Section
|
||||
* 5.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
|
||||
* nonce. RFC compliance requires that the nonce must be unique per security association.
|
||||
*
|
||||
* <p>This algorithm may be available on the device. Caller MUST check if it is supported before
|
||||
* using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
|
||||
* included in the returned algorithm set. The returned algorithm set will not change unless the
|
||||
* device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
|
||||
* requested on an unsupported device.
|
||||
*
|
||||
* <p>@see {@link #getSupportedAlgorithms()}
|
||||
*/
|
||||
// This algorithm may be available on devices released before Android 12, and is guaranteed
|
||||
// to be available on devices first shipped with Android 12 or later.
|
||||
public static final String CRYPT_AES_CTR = "rfc3686(ctr(aes))";
|
||||
|
||||
/**
|
||||
* MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
|
||||
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
|
||||
*
|
||||
* <p>Keys for this algorithm must be 128 bits in length.
|
||||
*
|
||||
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 128.
|
||||
*/
|
||||
public static final String AUTH_HMAC_MD5 = "hmac(md5)";
|
||||
|
||||
/**
|
||||
* SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
|
||||
* new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
|
||||
*
|
||||
* <p>Keys for this algorithm must be 160 bits in length.
|
||||
*
|
||||
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 160.
|
||||
*/
|
||||
public static final String AUTH_HMAC_SHA1 = "hmac(sha1)";
|
||||
|
||||
/**
|
||||
* SHA256 HMAC Authentication/Integrity Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 256 bits in length.
|
||||
*
|
||||
* <p>Valid truncation lengths are multiples of 8 bits from 96 to 256.
|
||||
*/
|
||||
public static final String AUTH_HMAC_SHA256 = "hmac(sha256)";
|
||||
|
||||
/**
|
||||
* SHA384 HMAC Authentication/Integrity Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 384 bits in length.
|
||||
*
|
||||
* <p>Valid truncation lengths are multiples of 8 bits from 192 to 384.
|
||||
*/
|
||||
public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
|
||||
|
||||
/**
|
||||
* SHA512 HMAC Authentication/Integrity Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 512 bits in length.
|
||||
*
|
||||
* <p>Valid truncation lengths are multiples of 8 bits from 256 to 512.
|
||||
*/
|
||||
public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
|
||||
|
||||
/**
|
||||
* AES-XCBC Authentication/Integrity Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 128 bits in length.
|
||||
*
|
||||
* <p>The only valid truncation length is 96 bits.
|
||||
*
|
||||
* <p>This algorithm may be available on the device. Caller MUST check if it is supported before
|
||||
* using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
|
||||
* included in the returned algorithm set. The returned algorithm set will not change unless the
|
||||
* device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
|
||||
* requested on an unsupported device.
|
||||
*
|
||||
* <p>@see {@link #getSupportedAlgorithms()}
|
||||
*/
|
||||
// This algorithm may be available on devices released before Android 12, and is guaranteed
|
||||
// to be available on devices first shipped with Android 12 or later.
|
||||
public static final String AUTH_AES_XCBC = "xcbc(aes)";
|
||||
|
||||
/**
|
||||
* AES-CMAC Authentication/Integrity Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 128 bits in length.
|
||||
*
|
||||
* <p>The only valid truncation length is 96 bits.
|
||||
*
|
||||
* <p>This algorithm may be available on the device. Caller MUST check if it is supported before
|
||||
* using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
|
||||
* included in the returned algorithm set. The returned algorithm set will not change unless the
|
||||
* device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
|
||||
* requested on an unsupported device.
|
||||
*
|
||||
* <p>@see {@link #getSupportedAlgorithms()}
|
||||
*/
|
||||
// This algorithm may be available on devices released before Android 12, and is guaranteed
|
||||
// to be available on devices first shipped with Android 12 or later.
|
||||
public static final String AUTH_AES_CMAC = "cmac(aes)";
|
||||
|
||||
/**
|
||||
* AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
|
||||
*
|
||||
* <p>Valid lengths for keying material are {160, 224, 288}.
|
||||
*
|
||||
* <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
|
||||
* 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
|
||||
* salt. RFC compliance requires that the salt must be unique per invocation with the same key.
|
||||
*
|
||||
* <p>Valid ICV (truncation) lengths are {64, 96, 128}.
|
||||
*/
|
||||
public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
|
||||
|
||||
/**
|
||||
* ChaCha20-Poly1305 Authentication/Integrity + Encryption/Ciphering Algorithm.
|
||||
*
|
||||
* <p>Keys for this algorithm must be 288 bits in length.
|
||||
*
|
||||
* <p>As per <a href="https://tools.ietf.org/html/rfc7634#section-2">RFC7634 (Section 2)</a>,
|
||||
* keying material consists of a 256 bit key followed by a 32-bit salt. The salt is fixed per
|
||||
* security association.
|
||||
*
|
||||
* <p>The only valid ICV (truncation) length is 128 bits.
|
||||
*
|
||||
* <p>This algorithm may be available on the device. Caller MUST check if it is supported before
|
||||
* using it by calling {@link #getSupportedAlgorithms()} and checking if this algorithm is
|
||||
* included in the returned algorithm set. The returned algorithm set will not change unless the
|
||||
* device is rebooted. {@link IllegalArgumentException} will be thrown if this algorithm is
|
||||
* requested on an unsupported device.
|
||||
*
|
||||
* <p>@see {@link #getSupportedAlgorithms()}
|
||||
*/
|
||||
// This algorithm may be available on devices released before Android 12, and is guaranteed
|
||||
// to be available on devices first shipped with Android 12 or later.
|
||||
public static final String AUTH_CRYPT_CHACHA20_POLY1305 = "rfc7539esp(chacha20,poly1305)";
|
||||
|
||||
/** @hide */
|
||||
@StringDef({
|
||||
CRYPT_AES_CBC,
|
||||
CRYPT_AES_CTR,
|
||||
AUTH_HMAC_MD5,
|
||||
AUTH_HMAC_SHA1,
|
||||
AUTH_HMAC_SHA256,
|
||||
AUTH_HMAC_SHA384,
|
||||
AUTH_HMAC_SHA512,
|
||||
AUTH_AES_XCBC,
|
||||
AUTH_AES_CMAC,
|
||||
AUTH_CRYPT_AES_GCM,
|
||||
AUTH_CRYPT_CHACHA20_POLY1305
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AlgorithmName {}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public static final Map<String, Integer> ALGO_TO_REQUIRED_FIRST_SDK = new HashMap<>();
|
||||
|
||||
private static final int SDK_VERSION_ZERO = 0;
|
||||
|
||||
static {
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CBC, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_MD5, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA1, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA256, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA384, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO);
|
||||
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S);
|
||||
ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S);
|
||||
}
|
||||
|
||||
private static final Set<String> ENABLED_ALGOS =
|
||||
Collections.unmodifiableSet(loadAlgos(Resources.getSystem()));
|
||||
|
||||
private final String mName;
|
||||
private final byte[] mKey;
|
||||
private final int mTruncLenBits;
|
||||
|
||||
/**
|
||||
* Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
|
||||
* defined as constants in this class.
|
||||
*
|
||||
* <p>For algorithms that produce an integrity check value, the truncation length is a required
|
||||
* parameter. See {@link #IpSecAlgorithm(String algorithm, byte[] key, int truncLenBits)}
|
||||
*
|
||||
* @param algorithm name of the algorithm.
|
||||
* @param key key padded to a multiple of 8 bits.
|
||||
* @throws IllegalArgumentException if algorithm or key length is invalid.
|
||||
*/
|
||||
public IpSecAlgorithm(@NonNull @AlgorithmName String algorithm, @NonNull byte[] key) {
|
||||
this(algorithm, key, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
|
||||
* defined as constants in this class.
|
||||
*
|
||||
* <p>This constructor only supports algorithms that use a truncation length. i.e.
|
||||
* Authentication and Authenticated Encryption algorithms.
|
||||
*
|
||||
* @param algorithm name of the algorithm.
|
||||
* @param key key padded to a multiple of 8 bits.
|
||||
* @param truncLenBits number of bits of output hash to use.
|
||||
* @throws IllegalArgumentException if algorithm, key length or truncation length is invalid.
|
||||
*/
|
||||
public IpSecAlgorithm(
|
||||
@NonNull @AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
|
||||
mName = algorithm;
|
||||
mKey = key.clone();
|
||||
mTruncLenBits = truncLenBits;
|
||||
checkValidOrThrow(mName, mKey.length * 8, mTruncLenBits);
|
||||
}
|
||||
|
||||
/** Get the algorithm name */
|
||||
@NonNull
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/** Get the key for this algorithm */
|
||||
@NonNull
|
||||
public byte[] getKey() {
|
||||
return mKey.clone();
|
||||
}
|
||||
|
||||
/** Get the truncation length of this algorithm, in bits */
|
||||
public int getTruncationLengthBits() {
|
||||
return mTruncLenBits;
|
||||
}
|
||||
|
||||
/** Parcelable Implementation */
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Write to parcel */
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(mName);
|
||||
out.writeByteArray(mKey);
|
||||
out.writeInt(mTruncLenBits);
|
||||
}
|
||||
|
||||
/** Parcelable Creator */
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<IpSecAlgorithm> CREATOR =
|
||||
new Parcelable.Creator<IpSecAlgorithm>() {
|
||||
public IpSecAlgorithm createFromParcel(Parcel in) {
|
||||
final String name = in.readString();
|
||||
final byte[] key = in.createByteArray();
|
||||
final int truncLenBits = in.readInt();
|
||||
|
||||
return new IpSecAlgorithm(name, key, truncLenBits);
|
||||
}
|
||||
|
||||
public IpSecAlgorithm[] newArray(int size) {
|
||||
return new IpSecAlgorithm[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns supported IPsec algorithms for the current device.
|
||||
*
|
||||
* <p>Some algorithms may not be supported on old devices. Callers MUST check if an algorithm is
|
||||
* supported before using it.
|
||||
*/
|
||||
@NonNull
|
||||
public static Set<String> getSupportedAlgorithms() {
|
||||
return ENABLED_ALGOS;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public static Set<String> loadAlgos(Resources systemResources) {
|
||||
final Set<String> enabledAlgos = new HashSet<>();
|
||||
|
||||
// Load and validate the optional algorithm resource. Undefined or duplicate algorithms in
|
||||
// the resource are not allowed.
|
||||
final String[] resourceAlgos = systemResources.getStringArray(
|
||||
android.R.array.config_optionalIpSecAlgorithms);
|
||||
for (String str : resourceAlgos) {
|
||||
if (!ALGO_TO_REQUIRED_FIRST_SDK.containsKey(str) || !enabledAlgos.add(str)) {
|
||||
// This error should be caught by CTS and never be thrown to API callers
|
||||
throw new IllegalArgumentException("Invalid or repeated algorithm " + str);
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<String, Integer> entry : ALGO_TO_REQUIRED_FIRST_SDK.entrySet()) {
|
||||
if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= entry.getValue()) {
|
||||
enabledAlgos.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return enabledAlgos;
|
||||
}
|
||||
|
||||
private static void checkValidOrThrow(String name, int keyLen, int truncLen) {
|
||||
final boolean isValidLen;
|
||||
final boolean isValidTruncLen;
|
||||
|
||||
if (!getSupportedAlgorithms().contains(name)) {
|
||||
throw new IllegalArgumentException("Unsupported algorithm: " + name);
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case CRYPT_AES_CBC:
|
||||
isValidLen = keyLen == 128 || keyLen == 192 || keyLen == 256;
|
||||
isValidTruncLen = true;
|
||||
break;
|
||||
case CRYPT_AES_CTR:
|
||||
// The keying material for AES-CTR is a key plus a 32-bit salt
|
||||
isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
|
||||
isValidTruncLen = true;
|
||||
break;
|
||||
case AUTH_HMAC_MD5:
|
||||
isValidLen = keyLen == 128;
|
||||
isValidTruncLen = truncLen >= 96 && truncLen <= 128;
|
||||
break;
|
||||
case AUTH_HMAC_SHA1:
|
||||
isValidLen = keyLen == 160;
|
||||
isValidTruncLen = truncLen >= 96 && truncLen <= 160;
|
||||
break;
|
||||
case AUTH_HMAC_SHA256:
|
||||
isValidLen = keyLen == 256;
|
||||
isValidTruncLen = truncLen >= 96 && truncLen <= 256;
|
||||
break;
|
||||
case AUTH_HMAC_SHA384:
|
||||
isValidLen = keyLen == 384;
|
||||
isValidTruncLen = truncLen >= 192 && truncLen <= 384;
|
||||
break;
|
||||
case AUTH_HMAC_SHA512:
|
||||
isValidLen = keyLen == 512;
|
||||
isValidTruncLen = truncLen >= 256 && truncLen <= 512;
|
||||
break;
|
||||
case AUTH_AES_XCBC:
|
||||
isValidLen = keyLen == 128;
|
||||
isValidTruncLen = truncLen == 96;
|
||||
break;
|
||||
case AUTH_AES_CMAC:
|
||||
isValidLen = keyLen == 128;
|
||||
isValidTruncLen = truncLen == 96;
|
||||
break;
|
||||
case AUTH_CRYPT_AES_GCM:
|
||||
// The keying material for GCM is a key plus a 32-bit salt
|
||||
isValidLen = keyLen == 128 + 32 || keyLen == 192 + 32 || keyLen == 256 + 32;
|
||||
isValidTruncLen = truncLen == 64 || truncLen == 96 || truncLen == 128;
|
||||
break;
|
||||
case AUTH_CRYPT_CHACHA20_POLY1305:
|
||||
// The keying material for ChaCha20Poly1305 is a key plus a 32-bit salt
|
||||
isValidLen = keyLen == 256 + 32;
|
||||
isValidTruncLen = truncLen == 128;
|
||||
break;
|
||||
default:
|
||||
// Should never hit here.
|
||||
throw new IllegalArgumentException("Couldn't find an algorithm: " + name);
|
||||
}
|
||||
|
||||
if (!isValidLen) {
|
||||
throw new IllegalArgumentException("Invalid key material keyLength: " + keyLen);
|
||||
}
|
||||
if (!isValidTruncLen) {
|
||||
throw new IllegalArgumentException("Invalid truncation keyLength: " + truncLen);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isAuthentication() {
|
||||
switch (getName()) {
|
||||
// Fallthrough
|
||||
case AUTH_HMAC_MD5:
|
||||
case AUTH_HMAC_SHA1:
|
||||
case AUTH_HMAC_SHA256:
|
||||
case AUTH_HMAC_SHA384:
|
||||
case AUTH_HMAC_SHA512:
|
||||
case AUTH_AES_XCBC:
|
||||
case AUTH_AES_CMAC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isEncryption() {
|
||||
switch (getName()) {
|
||||
case CRYPT_AES_CBC: // fallthrough
|
||||
case CRYPT_AES_CTR:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isAead() {
|
||||
switch (getName()) {
|
||||
case AUTH_CRYPT_AES_GCM: // fallthrough
|
||||
case AUTH_CRYPT_CHACHA20_POLY1305:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append("{mName=")
|
||||
.append(mName)
|
||||
.append(", mTruncLenBits=")
|
||||
.append(mTruncLenBits)
|
||||
.append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
|
||||
if (lhs == null || rhs == null) return (lhs == rhs);
|
||||
return (lhs.mName.equals(rhs.mName)
|
||||
&& Arrays.equals(lhs.mKey, rhs.mKey)
|
||||
&& lhs.mTruncLenBits == rhs.mTruncLenBits);
|
||||
}
|
||||
};
|
||||
20
framework-t/src/android/net/IpSecConfig.aidl
Normal file
20
framework-t/src/android/net/IpSecConfig.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
/** @hide */
|
||||
parcelable IpSecConfig;
|
||||
358
framework-t/src/android/net/IpSecConfig.java
Normal file
358
framework-t/src/android/net/IpSecConfig.java
Normal file
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* This class encapsulates all the configuration parameters needed to create IPsec transforms and
|
||||
* policies.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class IpSecConfig implements Parcelable {
|
||||
private static final String TAG = "IpSecConfig";
|
||||
|
||||
// MODE_TRANSPORT or MODE_TUNNEL
|
||||
private int mMode = IpSecTransform.MODE_TRANSPORT;
|
||||
|
||||
// Preventing this from being null simplifies Java->Native binder
|
||||
private String mSourceAddress = "";
|
||||
|
||||
// Preventing this from being null simplifies Java->Native binder
|
||||
private String mDestinationAddress = "";
|
||||
|
||||
// The underlying Network that represents the "gateway" Network
|
||||
// for outbound packets. It may also be used to select packets.
|
||||
private Network mNetwork;
|
||||
|
||||
// Minimum requirements for identifying a transform
|
||||
// SPI identifying the IPsec SA in packet processing
|
||||
// and a destination IP address
|
||||
private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
|
||||
// Encryption Algorithm
|
||||
private IpSecAlgorithm mEncryption;
|
||||
|
||||
// Authentication Algorithm
|
||||
private IpSecAlgorithm mAuthentication;
|
||||
|
||||
// Authenticated Encryption Algorithm
|
||||
private IpSecAlgorithm mAuthenticatedEncryption;
|
||||
|
||||
// For tunnel mode IPv4 UDP Encapsulation
|
||||
// IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
|
||||
private int mEncapType = IpSecTransform.ENCAP_NONE;
|
||||
private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
private int mEncapRemotePort;
|
||||
|
||||
// An interval, in seconds between the NattKeepalive packets
|
||||
private int mNattKeepaliveInterval;
|
||||
|
||||
// XFRM mark and mask; defaults to 0 (no mark/mask)
|
||||
private int mMarkValue;
|
||||
private int mMarkMask;
|
||||
|
||||
// XFRM interface id
|
||||
private int mXfrmInterfaceId;
|
||||
|
||||
/** Set the mode for this IPsec transform */
|
||||
public void setMode(int mode) {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
/** Set the source IP addres for this IPsec transform */
|
||||
public void setSourceAddress(String sourceAddress) {
|
||||
mSourceAddress = sourceAddress;
|
||||
}
|
||||
|
||||
/** Set the destination IP address for this IPsec transform */
|
||||
public void setDestinationAddress(String destinationAddress) {
|
||||
mDestinationAddress = destinationAddress;
|
||||
}
|
||||
|
||||
/** Set the SPI by resource ID */
|
||||
public void setSpiResourceId(int resourceId) {
|
||||
mSpiResourceId = resourceId;
|
||||
}
|
||||
|
||||
/** Set the encryption algorithm */
|
||||
public void setEncryption(IpSecAlgorithm encryption) {
|
||||
mEncryption = encryption;
|
||||
}
|
||||
|
||||
/** Set the authentication algorithm */
|
||||
public void setAuthentication(IpSecAlgorithm authentication) {
|
||||
mAuthentication = authentication;
|
||||
}
|
||||
|
||||
/** Set the authenticated encryption algorithm */
|
||||
public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) {
|
||||
mAuthenticatedEncryption = authenticatedEncryption;
|
||||
}
|
||||
|
||||
/** Set the underlying network that will carry traffic for this transform */
|
||||
public void setNetwork(Network network) {
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
public void setEncapType(int encapType) {
|
||||
mEncapType = encapType;
|
||||
}
|
||||
|
||||
public void setEncapSocketResourceId(int resourceId) {
|
||||
mEncapSocketResourceId = resourceId;
|
||||
}
|
||||
|
||||
public void setEncapRemotePort(int port) {
|
||||
mEncapRemotePort = port;
|
||||
}
|
||||
|
||||
public void setNattKeepaliveInterval(int interval) {
|
||||
mNattKeepaliveInterval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mark value
|
||||
*
|
||||
* <p>Internal (System server) use only. Marks passed in by users will be overwritten or
|
||||
* ignored.
|
||||
*/
|
||||
public void setMarkValue(int mark) {
|
||||
mMarkValue = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mark mask
|
||||
*
|
||||
* <p>Internal (System server) use only. Marks passed in by users will be overwritten or
|
||||
* ignored.
|
||||
*/
|
||||
public void setMarkMask(int mask) {
|
||||
mMarkMask = mask;
|
||||
}
|
||||
|
||||
public void setXfrmInterfaceId(int xfrmInterfaceId) {
|
||||
mXfrmInterfaceId = xfrmInterfaceId;
|
||||
}
|
||||
|
||||
// Transport or Tunnel
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public String getSourceAddress() {
|
||||
return mSourceAddress;
|
||||
}
|
||||
|
||||
public int getSpiResourceId() {
|
||||
return mSpiResourceId;
|
||||
}
|
||||
|
||||
public String getDestinationAddress() {
|
||||
return mDestinationAddress;
|
||||
}
|
||||
|
||||
public IpSecAlgorithm getEncryption() {
|
||||
return mEncryption;
|
||||
}
|
||||
|
||||
public IpSecAlgorithm getAuthentication() {
|
||||
return mAuthentication;
|
||||
}
|
||||
|
||||
public IpSecAlgorithm getAuthenticatedEncryption() {
|
||||
return mAuthenticatedEncryption;
|
||||
}
|
||||
|
||||
public Network getNetwork() {
|
||||
return mNetwork;
|
||||
}
|
||||
|
||||
public int getEncapType() {
|
||||
return mEncapType;
|
||||
}
|
||||
|
||||
public int getEncapSocketResourceId() {
|
||||
return mEncapSocketResourceId;
|
||||
}
|
||||
|
||||
public int getEncapRemotePort() {
|
||||
return mEncapRemotePort;
|
||||
}
|
||||
|
||||
public int getNattKeepaliveInterval() {
|
||||
return mNattKeepaliveInterval;
|
||||
}
|
||||
|
||||
public int getMarkValue() {
|
||||
return mMarkValue;
|
||||
}
|
||||
|
||||
public int getMarkMask() {
|
||||
return mMarkMask;
|
||||
}
|
||||
|
||||
public int getXfrmInterfaceId() {
|
||||
return mXfrmInterfaceId;
|
||||
}
|
||||
|
||||
// Parcelable Methods
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(mMode);
|
||||
out.writeString(mSourceAddress);
|
||||
out.writeString(mDestinationAddress);
|
||||
out.writeParcelable(mNetwork, flags);
|
||||
out.writeInt(mSpiResourceId);
|
||||
out.writeParcelable(mEncryption, flags);
|
||||
out.writeParcelable(mAuthentication, flags);
|
||||
out.writeParcelable(mAuthenticatedEncryption, flags);
|
||||
out.writeInt(mEncapType);
|
||||
out.writeInt(mEncapSocketResourceId);
|
||||
out.writeInt(mEncapRemotePort);
|
||||
out.writeInt(mNattKeepaliveInterval);
|
||||
out.writeInt(mMarkValue);
|
||||
out.writeInt(mMarkMask);
|
||||
out.writeInt(mXfrmInterfaceId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public IpSecConfig() {}
|
||||
|
||||
/** Copy constructor */
|
||||
@VisibleForTesting
|
||||
public IpSecConfig(IpSecConfig c) {
|
||||
mMode = c.mMode;
|
||||
mSourceAddress = c.mSourceAddress;
|
||||
mDestinationAddress = c.mDestinationAddress;
|
||||
mNetwork = c.mNetwork;
|
||||
mSpiResourceId = c.mSpiResourceId;
|
||||
mEncryption = c.mEncryption;
|
||||
mAuthentication = c.mAuthentication;
|
||||
mAuthenticatedEncryption = c.mAuthenticatedEncryption;
|
||||
mEncapType = c.mEncapType;
|
||||
mEncapSocketResourceId = c.mEncapSocketResourceId;
|
||||
mEncapRemotePort = c.mEncapRemotePort;
|
||||
mNattKeepaliveInterval = c.mNattKeepaliveInterval;
|
||||
mMarkValue = c.mMarkValue;
|
||||
mMarkMask = c.mMarkMask;
|
||||
mXfrmInterfaceId = c.mXfrmInterfaceId;
|
||||
}
|
||||
|
||||
private IpSecConfig(Parcel in) {
|
||||
mMode = in.readInt();
|
||||
mSourceAddress = in.readString();
|
||||
mDestinationAddress = in.readString();
|
||||
mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
|
||||
mSpiResourceId = in.readInt();
|
||||
mEncryption =
|
||||
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
|
||||
mAuthentication =
|
||||
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
|
||||
mAuthenticatedEncryption =
|
||||
(IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
|
||||
mEncapType = in.readInt();
|
||||
mEncapSocketResourceId = in.readInt();
|
||||
mEncapRemotePort = in.readInt();
|
||||
mNattKeepaliveInterval = in.readInt();
|
||||
mMarkValue = in.readInt();
|
||||
mMarkMask = in.readInt();
|
||||
mXfrmInterfaceId = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder strBuilder = new StringBuilder();
|
||||
strBuilder
|
||||
.append("{mMode=")
|
||||
.append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
|
||||
.append(", mSourceAddress=")
|
||||
.append(mSourceAddress)
|
||||
.append(", mDestinationAddress=")
|
||||
.append(mDestinationAddress)
|
||||
.append(", mNetwork=")
|
||||
.append(mNetwork)
|
||||
.append(", mEncapType=")
|
||||
.append(mEncapType)
|
||||
.append(", mEncapSocketResourceId=")
|
||||
.append(mEncapSocketResourceId)
|
||||
.append(", mEncapRemotePort=")
|
||||
.append(mEncapRemotePort)
|
||||
.append(", mNattKeepaliveInterval=")
|
||||
.append(mNattKeepaliveInterval)
|
||||
.append("{mSpiResourceId=")
|
||||
.append(mSpiResourceId)
|
||||
.append(", mEncryption=")
|
||||
.append(mEncryption)
|
||||
.append(", mAuthentication=")
|
||||
.append(mAuthentication)
|
||||
.append(", mAuthenticatedEncryption=")
|
||||
.append(mAuthenticatedEncryption)
|
||||
.append(", mMarkValue=")
|
||||
.append(mMarkValue)
|
||||
.append(", mMarkMask=")
|
||||
.append(mMarkMask)
|
||||
.append(", mXfrmInterfaceId=")
|
||||
.append(mXfrmInterfaceId)
|
||||
.append("}");
|
||||
|
||||
return strBuilder.toString();
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<IpSecConfig> CREATOR =
|
||||
new Parcelable.Creator<IpSecConfig>() {
|
||||
public IpSecConfig createFromParcel(Parcel in) {
|
||||
return new IpSecConfig(in);
|
||||
}
|
||||
|
||||
public IpSecConfig[] newArray(int size) {
|
||||
return new IpSecConfig[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (!(other instanceof IpSecConfig)) return false;
|
||||
final IpSecConfig rhs = (IpSecConfig) other;
|
||||
return (mMode == rhs.mMode
|
||||
&& mSourceAddress.equals(rhs.mSourceAddress)
|
||||
&& mDestinationAddress.equals(rhs.mDestinationAddress)
|
||||
&& ((mNetwork != null && mNetwork.equals(rhs.mNetwork))
|
||||
|| (mNetwork == rhs.mNetwork))
|
||||
&& mEncapType == rhs.mEncapType
|
||||
&& mEncapSocketResourceId == rhs.mEncapSocketResourceId
|
||||
&& mEncapRemotePort == rhs.mEncapRemotePort
|
||||
&& mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
|
||||
&& mSpiResourceId == rhs.mSpiResourceId
|
||||
&& IpSecAlgorithm.equals(mEncryption, rhs.mEncryption)
|
||||
&& IpSecAlgorithm.equals(mAuthenticatedEncryption, rhs.mAuthenticatedEncryption)
|
||||
&& IpSecAlgorithm.equals(mAuthentication, rhs.mAuthentication)
|
||||
&& mMarkValue == rhs.mMarkValue
|
||||
&& mMarkMask == rhs.mMarkMask
|
||||
&& mXfrmInterfaceId == rhs.mXfrmInterfaceId);
|
||||
}
|
||||
}
|
||||
1065
framework-t/src/android/net/IpSecManager.java
Normal file
1065
framework-t/src/android/net/IpSecManager.java
Normal file
File diff suppressed because it is too large
Load Diff
20
framework-t/src/android/net/IpSecSpiResponse.aidl
Normal file
20
framework-t/src/android/net/IpSecSpiResponse.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
/** @hide */
|
||||
parcelable IpSecSpiResponse;
|
||||
78
framework-t/src/android/net/IpSecSpiResponse.java
Normal file
78
framework-t/src/android/net/IpSecSpiResponse.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* This class is used to return an SPI and corresponding status from the IpSecService to an
|
||||
* IpSecManager.SecurityParameterIndex.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class IpSecSpiResponse implements Parcelable {
|
||||
private static final String TAG = "IpSecSpiResponse";
|
||||
|
||||
public final int resourceId;
|
||||
public final int status;
|
||||
public final int spi;
|
||||
// Parcelable Methods
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(status);
|
||||
out.writeInt(resourceId);
|
||||
out.writeInt(spi);
|
||||
}
|
||||
|
||||
public IpSecSpiResponse(int inStatus, int inResourceId, int inSpi) {
|
||||
status = inStatus;
|
||||
resourceId = inResourceId;
|
||||
spi = inSpi;
|
||||
}
|
||||
|
||||
public IpSecSpiResponse(int inStatus) {
|
||||
if (inStatus == IpSecManager.Status.OK) {
|
||||
throw new IllegalArgumentException("Valid status implies other args must be provided");
|
||||
}
|
||||
status = inStatus;
|
||||
resourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
|
||||
}
|
||||
|
||||
private IpSecSpiResponse(Parcel in) {
|
||||
status = in.readInt();
|
||||
resourceId = in.readInt();
|
||||
spi = in.readInt();
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<IpSecSpiResponse> CREATOR =
|
||||
new Parcelable.Creator<IpSecSpiResponse>() {
|
||||
public IpSecSpiResponse createFromParcel(Parcel in) {
|
||||
return new IpSecSpiResponse(in);
|
||||
}
|
||||
|
||||
public IpSecSpiResponse[] newArray(int size) {
|
||||
return new IpSecSpiResponse[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
405
framework-t/src/android/net/IpSecTransform.java
Normal file
405
framework-t/src/android/net/IpSecTransform.java
Normal file
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresFeature;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class represents a transform, which roughly corresponds to an IPsec Security Association.
|
||||
*
|
||||
* <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
|
||||
* object encapsulates the properties and state of an IPsec security association. That includes,
|
||||
* but is not limited to, algorithm choice, key material, and allocated system resources.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
|
||||
* Internet Protocol</a>
|
||||
*/
|
||||
public final class IpSecTransform implements AutoCloseable {
|
||||
private static final String TAG = "IpSecTransform";
|
||||
|
||||
/** @hide */
|
||||
public static final int MODE_TRANSPORT = 0;
|
||||
|
||||
/** @hide */
|
||||
public static final int MODE_TUNNEL = 1;
|
||||
|
||||
/** @hide */
|
||||
public static final int ENCAP_NONE = 0;
|
||||
|
||||
/**
|
||||
* IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
|
||||
* header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ENCAP_ESPINUDP_NON_IKE = 1;
|
||||
|
||||
/**
|
||||
* IPsec traffic will be encapsulated within UDP as per
|
||||
* <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ENCAP_ESPINUDP = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(value = {ENCAP_NONE, ENCAP_ESPINUDP, ENCAP_ESPINUDP_NON_IKE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface EncapType {}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public IpSecTransform(Context context, IpSecConfig config) {
|
||||
mContext = context;
|
||||
mConfig = new IpSecConfig(config);
|
||||
mResourceId = INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
private IpSecManager getIpSecManager(Context context) {
|
||||
return context.getSystemService(IpSecManager.class);
|
||||
}
|
||||
/**
|
||||
* Checks the result status and throws an appropriate exception if the status is not Status.OK.
|
||||
*/
|
||||
private void checkResultStatus(int status)
|
||||
throws IOException, IpSecManager.ResourceUnavailableException,
|
||||
IpSecManager.SpiUnavailableException {
|
||||
switch (status) {
|
||||
case IpSecManager.Status.OK:
|
||||
return;
|
||||
// TODO: Pass Error string back from bundle so that errors can be more specific
|
||||
case IpSecManager.Status.RESOURCE_UNAVAILABLE:
|
||||
throw new IpSecManager.ResourceUnavailableException(
|
||||
"Failed to allocate a new IpSecTransform");
|
||||
case IpSecManager.Status.SPI_UNAVAILABLE:
|
||||
Log.wtf(TAG, "Attempting to use an SPI that was somehow not reserved");
|
||||
// Fall through
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Failed to Create a Transform with status code " + status);
|
||||
}
|
||||
}
|
||||
|
||||
private IpSecTransform activate()
|
||||
throws IOException, IpSecManager.ResourceUnavailableException,
|
||||
IpSecManager.SpiUnavailableException {
|
||||
synchronized (this) {
|
||||
try {
|
||||
IpSecTransformResponse result = getIpSecManager(mContext).createTransform(
|
||||
mConfig, new Binder(), mContext.getOpPackageName());
|
||||
int status = result.status;
|
||||
checkResultStatus(status);
|
||||
mResourceId = result.resourceId;
|
||||
Log.d(TAG, "Added Transform with Id " + mResourceId);
|
||||
mCloseGuard.open("build");
|
||||
} catch (ServiceSpecificException e) {
|
||||
throw IpSecManager.rethrowUncheckedExceptionFromServiceSpecificException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard equals.
|
||||
*/
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) return true;
|
||||
if (!(other instanceof IpSecTransform)) return false;
|
||||
final IpSecTransform rhs = (IpSecTransform) other;
|
||||
return getConfig().equals(rhs.getConfig()) && mResourceId == rhs.mResourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate this {@code IpSecTransform} and free allocated resources.
|
||||
*
|
||||
* <p>Deactivating a transform while it is still applied to a socket will result in errors on
|
||||
* that socket. Make sure to remove transforms by calling {@link
|
||||
* IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a
|
||||
* socket will not deactivate it (because one transform may be applied to multiple sockets).
|
||||
*
|
||||
* <p>It is safe to call this method on a transform that has already been deactivated.
|
||||
*/
|
||||
public void close() {
|
||||
Log.d(TAG, "Removing Transform with Id " + mResourceId);
|
||||
|
||||
// Always safe to attempt cleanup
|
||||
if (mResourceId == INVALID_RESOURCE_ID) {
|
||||
mCloseGuard.close();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
getIpSecManager(mContext).deleteTransform(mResourceId);
|
||||
} catch (Exception e) {
|
||||
// On close we swallow all random exceptions since failure to close is not
|
||||
// actionable by the user.
|
||||
Log.e(TAG, "Failed to close " + this + ", Exception=" + e);
|
||||
} finally {
|
||||
mResourceId = INVALID_RESOURCE_ID;
|
||||
mCloseGuard.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that the transform was closed properly. */
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
/* Package */
|
||||
IpSecConfig getConfig() {
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
private final IpSecConfig mConfig;
|
||||
private int mResourceId;
|
||||
private final Context mContext;
|
||||
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public int getResourceId() {
|
||||
return mResourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback class to provide status information regarding a NAT-T keepalive session
|
||||
*
|
||||
* <p>Use this callback to receive status information regarding a NAT-T keepalive session
|
||||
* by registering it when calling {@link #startNattKeepalive}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static class NattKeepaliveCallback {
|
||||
/** The specified {@code Network} is not connected. */
|
||||
public static final int ERROR_INVALID_NETWORK = 1;
|
||||
/** The hardware does not support this request. */
|
||||
public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
|
||||
/** The hardware returned an error. */
|
||||
public static final int ERROR_HARDWARE_ERROR = 3;
|
||||
|
||||
/** The requested keepalive was successfully started. */
|
||||
public void onStarted() {}
|
||||
/** The keepalive was successfully stopped. */
|
||||
public void onStopped() {}
|
||||
/** An error occurred. */
|
||||
public void onError(int error) {}
|
||||
}
|
||||
|
||||
/** This class is used to build {@link IpSecTransform} objects. */
|
||||
public static class Builder {
|
||||
private Context mContext;
|
||||
private IpSecConfig mConfig;
|
||||
|
||||
/**
|
||||
* Set the encryption algorithm.
|
||||
*
|
||||
* <p>Encryption is mutually exclusive with authenticated encryption.
|
||||
*
|
||||
* @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
|
||||
*/
|
||||
@NonNull
|
||||
public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) {
|
||||
// TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
|
||||
Objects.requireNonNull(algo);
|
||||
mConfig.setEncryption(algo);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authentication (integrity) algorithm.
|
||||
*
|
||||
* <p>Authentication is mutually exclusive with authenticated encryption.
|
||||
*
|
||||
* @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
|
||||
*/
|
||||
@NonNull
|
||||
public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) {
|
||||
// TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
|
||||
Objects.requireNonNull(algo);
|
||||
mConfig.setAuthentication(algo);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the authenticated encryption algorithm.
|
||||
*
|
||||
* <p>The Authenticated Encryption (AE) class of algorithms are also known as
|
||||
* Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode
|
||||
* algorithms (as referred to in
|
||||
* <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
|
||||
*
|
||||
* <p>Authenticated encryption is mutually exclusive with encryption and authentication.
|
||||
*
|
||||
* @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
|
||||
* be applied.
|
||||
*/
|
||||
@NonNull
|
||||
public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) {
|
||||
Objects.requireNonNull(algo);
|
||||
mConfig.setAuthenticatedEncryption(algo);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add UDP encapsulation to an IPv4 transform.
|
||||
*
|
||||
* <p>This allows IPsec traffic to pass through a NAT.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
|
||||
* ESP Packets</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
|
||||
* NAT Traversal of IKEv2</a>
|
||||
* @param localSocket a socket for sending and receiving encapsulated traffic
|
||||
* @param remotePort the UDP port number of the remote host that will send and receive
|
||||
* encapsulated traffic. In the case of IKEv2, this should be port 4500.
|
||||
*/
|
||||
@NonNull
|
||||
public IpSecTransform.Builder setIpv4Encapsulation(
|
||||
@NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
|
||||
Objects.requireNonNull(localSocket);
|
||||
mConfig.setEncapType(ENCAP_ESPINUDP);
|
||||
if (localSocket.getResourceId() == INVALID_RESOURCE_ID) {
|
||||
throw new IllegalArgumentException("Invalid UdpEncapsulationSocket");
|
||||
}
|
||||
mConfig.setEncapSocketResourceId(localSocket.getResourceId());
|
||||
mConfig.setEncapRemotePort(remotePort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a transport mode {@link IpSecTransform}.
|
||||
*
|
||||
* <p>This builds and activates a transport mode transform. Note that an active transform
|
||||
* will not affect any network traffic until it has been applied to one or more sockets.
|
||||
*
|
||||
* @see IpSecManager#applyTransportModeTransform
|
||||
* @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use
|
||||
* this transform; this address must belong to the Network used by all sockets that
|
||||
* utilize this transform; if provided, then only traffic originating from the
|
||||
* specified source address will be processed.
|
||||
* @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
|
||||
* traffic
|
||||
* @throws IllegalArgumentException indicating that a particular combination of transform
|
||||
* properties is invalid
|
||||
* @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
|
||||
* are active
|
||||
* @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
|
||||
* collides with an existing transform
|
||||
* @throws IOException indicating other errors
|
||||
*/
|
||||
@NonNull
|
||||
public IpSecTransform buildTransportModeTransform(
|
||||
@NonNull InetAddress sourceAddress,
|
||||
@NonNull IpSecManager.SecurityParameterIndex spi)
|
||||
throws IpSecManager.ResourceUnavailableException,
|
||||
IpSecManager.SpiUnavailableException, IOException {
|
||||
Objects.requireNonNull(sourceAddress);
|
||||
Objects.requireNonNull(spi);
|
||||
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
|
||||
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
|
||||
}
|
||||
mConfig.setMode(MODE_TRANSPORT);
|
||||
mConfig.setSourceAddress(sourceAddress.getHostAddress());
|
||||
mConfig.setSpiResourceId(spi.getResourceId());
|
||||
// FIXME: modifying a builder after calling build can change the built transform.
|
||||
return new IpSecTransform(mContext, mConfig).activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some
|
||||
* parameters have interdependencies that are checked at build time.
|
||||
*
|
||||
* @param sourceAddress the {@link InetAddress} that provides the source address for this
|
||||
* IPsec tunnel. This is almost certainly an address belonging to the {@link Network}
|
||||
* that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}.
|
||||
* @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
|
||||
* traffic
|
||||
* @throws IllegalArgumentException indicating that a particular combination of transform
|
||||
* properties is invalid.
|
||||
* @throws IpSecManager.ResourceUnavailableException indicating that too many transforms
|
||||
* are active
|
||||
* @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
|
||||
* collides with an existing transform
|
||||
* @throws IOException indicating other errors
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@NonNull
|
||||
@RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
|
||||
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
|
||||
public IpSecTransform buildTunnelModeTransform(
|
||||
@NonNull InetAddress sourceAddress,
|
||||
@NonNull IpSecManager.SecurityParameterIndex spi)
|
||||
throws IpSecManager.ResourceUnavailableException,
|
||||
IpSecManager.SpiUnavailableException, IOException {
|
||||
Objects.requireNonNull(sourceAddress);
|
||||
Objects.requireNonNull(spi);
|
||||
if (spi.getResourceId() == INVALID_RESOURCE_ID) {
|
||||
throw new IllegalArgumentException("Invalid SecurityParameterIndex");
|
||||
}
|
||||
mConfig.setMode(MODE_TUNNEL);
|
||||
mConfig.setSourceAddress(sourceAddress.getHostAddress());
|
||||
mConfig.setSpiResourceId(spi.getResourceId());
|
||||
return new IpSecTransform(mContext, mConfig).activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new IpSecTransform.Builder.
|
||||
*
|
||||
* @param context current context
|
||||
*/
|
||||
public Builder(@NonNull Context context) {
|
||||
Objects.requireNonNull(context);
|
||||
mContext = context;
|
||||
mConfig = new IpSecConfig();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
.append("IpSecTransform{resourceId=")
|
||||
.append(mResourceId)
|
||||
.append("}")
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
20
framework-t/src/android/net/IpSecTransformResponse.aidl
Normal file
20
framework-t/src/android/net/IpSecTransformResponse.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
/** @hide */
|
||||
parcelable IpSecTransformResponse;
|
||||
74
framework-t/src/android/net/IpSecTransformResponse.java
Normal file
74
framework-t/src/android/net/IpSecTransformResponse.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* This class is used to return an IpSecTransform resource Id and and corresponding status from the
|
||||
* IpSecService to an IpSecTransform object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class IpSecTransformResponse implements Parcelable {
|
||||
private static final String TAG = "IpSecTransformResponse";
|
||||
|
||||
public final int resourceId;
|
||||
public final int status;
|
||||
// Parcelable Methods
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(status);
|
||||
out.writeInt(resourceId);
|
||||
}
|
||||
|
||||
public IpSecTransformResponse(int inStatus) {
|
||||
if (inStatus == IpSecManager.Status.OK) {
|
||||
throw new IllegalArgumentException("Valid status implies other args must be provided");
|
||||
}
|
||||
status = inStatus;
|
||||
resourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
}
|
||||
|
||||
public IpSecTransformResponse(int inStatus, int inResourceId) {
|
||||
status = inStatus;
|
||||
resourceId = inResourceId;
|
||||
}
|
||||
|
||||
private IpSecTransformResponse(Parcel in) {
|
||||
status = in.readInt();
|
||||
resourceId = in.readInt();
|
||||
}
|
||||
|
||||
@android.annotation.NonNull
|
||||
public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
|
||||
new Parcelable.Creator<IpSecTransformResponse>() {
|
||||
public IpSecTransformResponse createFromParcel(Parcel in) {
|
||||
return new IpSecTransformResponse(in);
|
||||
}
|
||||
|
||||
public IpSecTransformResponse[] newArray(int size) {
|
||||
return new IpSecTransformResponse[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.net;
|
||||
|
||||
/** @hide */
|
||||
parcelable IpSecTunnelInterfaceResponse;
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.net;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* This class is used to return an IpSecTunnelInterface resource Id and and corresponding status
|
||||
* from the IpSecService to an IpSecTunnelInterface object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class IpSecTunnelInterfaceResponse implements Parcelable {
|
||||
private static final String TAG = "IpSecTunnelInterfaceResponse";
|
||||
|
||||
public final int resourceId;
|
||||
public final String interfaceName;
|
||||
public final int status;
|
||||
// Parcelable Methods
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(status);
|
||||
out.writeInt(resourceId);
|
||||
out.writeString(interfaceName);
|
||||
}
|
||||
|
||||
public IpSecTunnelInterfaceResponse(int inStatus) {
|
||||
if (inStatus == IpSecManager.Status.OK) {
|
||||
throw new IllegalArgumentException("Valid status implies other args must be provided");
|
||||
}
|
||||
status = inStatus;
|
||||
resourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
interfaceName = "";
|
||||
}
|
||||
|
||||
public IpSecTunnelInterfaceResponse(int inStatus, int inResourceId, String inInterfaceName) {
|
||||
status = inStatus;
|
||||
resourceId = inResourceId;
|
||||
interfaceName = inInterfaceName;
|
||||
}
|
||||
|
||||
private IpSecTunnelInterfaceResponse(Parcel in) {
|
||||
status = in.readInt();
|
||||
resourceId = in.readInt();
|
||||
interfaceName = in.readString();
|
||||
}
|
||||
|
||||
@android.annotation.NonNull
|
||||
public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
|
||||
new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
|
||||
public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
|
||||
return new IpSecTunnelInterfaceResponse(in);
|
||||
}
|
||||
|
||||
public IpSecTunnelInterfaceResponse[] newArray(int size) {
|
||||
return new IpSecTunnelInterfaceResponse[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
20
framework-t/src/android/net/IpSecUdpEncapResponse.aidl
Normal file
20
framework-t/src/android/net/IpSecUdpEncapResponse.aidl
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
/** @hide */
|
||||
parcelable IpSecUdpEncapResponse;
|
||||
98
framework-t/src/android/net/IpSecUdpEncapResponse.java
Normal file
98
framework-t/src/android/net/IpSecUdpEncapResponse.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.net;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class is used to return a UDP Socket and corresponding status from the IpSecService to an
|
||||
* IpSecManager.UdpEncapsulationSocket.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class IpSecUdpEncapResponse implements Parcelable {
|
||||
private static final String TAG = "IpSecUdpEncapResponse";
|
||||
|
||||
public final int resourceId;
|
||||
public final int port;
|
||||
public final int status;
|
||||
// There is a weird asymmetry with FileDescriptor: you can write a FileDescriptor
|
||||
// but you read a ParcelFileDescriptor. To circumvent this, when we receive a FD
|
||||
// from the user, we immediately create a ParcelFileDescriptor DUP, which we invalidate
|
||||
// on writeParcel() by setting the flag to do close-on-write.
|
||||
// TODO: tests to ensure this doesn't leak
|
||||
public final ParcelFileDescriptor fileDescriptor;
|
||||
|
||||
// Parcelable Methods
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return (fileDescriptor != null) ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeInt(status);
|
||||
out.writeInt(resourceId);
|
||||
out.writeInt(port);
|
||||
out.writeParcelable(fileDescriptor, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
|
||||
}
|
||||
|
||||
public IpSecUdpEncapResponse(int inStatus) {
|
||||
if (inStatus == IpSecManager.Status.OK) {
|
||||
throw new IllegalArgumentException("Valid status implies other args must be provided");
|
||||
}
|
||||
status = inStatus;
|
||||
resourceId = IpSecManager.INVALID_RESOURCE_ID;
|
||||
port = -1;
|
||||
fileDescriptor = null; // yes I know it's redundant, but readability
|
||||
}
|
||||
|
||||
public IpSecUdpEncapResponse(int inStatus, int inResourceId, int inPort, FileDescriptor inFd)
|
||||
throws IOException {
|
||||
if (inStatus == IpSecManager.Status.OK && inFd == null) {
|
||||
throw new IllegalArgumentException("Valid status implies FD must be non-null");
|
||||
}
|
||||
status = inStatus;
|
||||
resourceId = inResourceId;
|
||||
port = inPort;
|
||||
fileDescriptor = (status == IpSecManager.Status.OK) ? ParcelFileDescriptor.dup(inFd) : null;
|
||||
}
|
||||
|
||||
private IpSecUdpEncapResponse(Parcel in) {
|
||||
status = in.readInt();
|
||||
resourceId = in.readInt();
|
||||
port = in.readInt();
|
||||
fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
|
||||
}
|
||||
|
||||
@android.annotation.NonNull
|
||||
public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
|
||||
new Parcelable.Creator<IpSecUdpEncapResponse>() {
|
||||
public IpSecUdpEncapResponse createFromParcel(Parcel in) {
|
||||
return new IpSecUdpEncapResponse(in);
|
||||
}
|
||||
|
||||
public IpSecUdpEncapResponse[] newArray(int size) {
|
||||
return new IpSecUdpEncapResponse[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
594
framework-t/src/android/net/NetworkIdentity.java
Normal file
594
framework-t/src/android/net/NetworkIdentity.java
Normal file
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.ConnectivityManager.TYPE_WIFI;
|
||||
import static android.net.NetworkTemplate.NETWORK_TYPE_ALL;
|
||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.content.Context;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.service.NetworkIdentityProto;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.NetworkCapabilitiesUtils;
|
||||
import com.android.net.module.util.NetworkIdentityUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Network definition that includes strong identity. Analogous to combining
|
||||
* {@link NetworkCapabilities} and an IMSI.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public class NetworkIdentity {
|
||||
private static final String TAG = "NetworkIdentity";
|
||||
|
||||
/** @hide */
|
||||
// TODO: Remove this after migrating all callers to use
|
||||
// {@link NetworkTemplate#NETWORK_TYPE_ALL} instead.
|
||||
public static final int SUBTYPE_COMBINED = -1;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = {
|
||||
NetworkTemplate.OEM_MANAGED_NO,
|
||||
NetworkTemplate.OEM_MANAGED_PAID,
|
||||
NetworkTemplate.OEM_MANAGED_PRIVATE
|
||||
})
|
||||
public @interface OemManaged{}
|
||||
|
||||
/**
|
||||
* Network has no {@code NetworkCapabilities#NET_CAPABILITY_OEM_*}.
|
||||
* @hide
|
||||
*/
|
||||
public static final int OEM_NONE = 0x0;
|
||||
/**
|
||||
* Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}.
|
||||
* @hide
|
||||
*/
|
||||
public static final int OEM_PAID = 1 << 0;
|
||||
/**
|
||||
* Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}.
|
||||
* @hide
|
||||
*/
|
||||
public static final int OEM_PRIVATE = 1 << 1;
|
||||
|
||||
private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE;
|
||||
|
||||
final int mType;
|
||||
final int mRatType;
|
||||
final int mSubId;
|
||||
final String mSubscriberId;
|
||||
final String mWifiNetworkKey;
|
||||
final boolean mRoaming;
|
||||
final boolean mMetered;
|
||||
final boolean mDefaultNetwork;
|
||||
final int mOemManaged;
|
||||
|
||||
/** @hide */
|
||||
public NetworkIdentity(
|
||||
int type, int ratType, @Nullable String subscriberId, @Nullable String wifiNetworkKey,
|
||||
boolean roaming, boolean metered, boolean defaultNetwork, int oemManaged, int subId) {
|
||||
mType = type;
|
||||
mRatType = ratType;
|
||||
mSubscriberId = subscriberId;
|
||||
mWifiNetworkKey = wifiNetworkKey;
|
||||
mRoaming = roaming;
|
||||
mMetered = metered;
|
||||
mDefaultNetwork = defaultNetwork;
|
||||
mOemManaged = oemManaged;
|
||||
mSubId = subId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered,
|
||||
mDefaultNetwork, mOemManaged, mSubId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof NetworkIdentity) {
|
||||
final NetworkIdentity ident = (NetworkIdentity) obj;
|
||||
return mType == ident.mType && mRatType == ident.mRatType && mRoaming == ident.mRoaming
|
||||
&& Objects.equals(mSubscriberId, ident.mSubscriberId)
|
||||
&& Objects.equals(mWifiNetworkKey, ident.mWifiNetworkKey)
|
||||
&& mMetered == ident.mMetered
|
||||
&& mDefaultNetwork == ident.mDefaultNetwork
|
||||
&& mOemManaged == ident.mOemManaged
|
||||
&& mSubId == ident.mSubId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder("{");
|
||||
builder.append("type=").append(mType);
|
||||
builder.append(", ratType=");
|
||||
if (mRatType == NETWORK_TYPE_ALL) {
|
||||
builder.append("COMBINED");
|
||||
} else {
|
||||
builder.append(mRatType);
|
||||
}
|
||||
if (mSubscriberId != null) {
|
||||
builder.append(", subscriberId=")
|
||||
.append(NetworkIdentityUtils.scrubSubscriberId(mSubscriberId));
|
||||
}
|
||||
if (mWifiNetworkKey != null) {
|
||||
builder.append(", wifiNetworkKey=").append(mWifiNetworkKey);
|
||||
}
|
||||
if (mRoaming) {
|
||||
builder.append(", ROAMING");
|
||||
}
|
||||
builder.append(", metered=").append(mMetered);
|
||||
builder.append(", defaultNetwork=").append(mDefaultNetwork);
|
||||
builder.append(", oemManaged=").append(getOemManagedNames(mOemManaged));
|
||||
builder.append(", subId=").append(mSubId);
|
||||
return builder.append("}").toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the human readable representation of a bitfield representing the OEM managed state of a
|
||||
* network.
|
||||
*/
|
||||
static String getOemManagedNames(int oemManaged) {
|
||||
if (oemManaged == OEM_NONE) {
|
||||
return "OEM_NONE";
|
||||
}
|
||||
final int[] bitPositions = NetworkCapabilitiesUtils.unpackBits(oemManaged);
|
||||
final ArrayList<String> oemManagedNames = new ArrayList<String>();
|
||||
for (int position : bitPositions) {
|
||||
oemManagedNames.add(nameOfOemManaged(1 << position));
|
||||
}
|
||||
return String.join(",", oemManagedNames);
|
||||
}
|
||||
|
||||
private static String nameOfOemManaged(int oemManagedBit) {
|
||||
switch (oemManagedBit) {
|
||||
case OEM_PAID:
|
||||
return "OEM_PAID";
|
||||
case OEM_PRIVATE:
|
||||
return "OEM_PRIVATE";
|
||||
default:
|
||||
return "Invalid(" + oemManagedBit + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dumpDebug(ProtoOutputStream proto, long tag) {
|
||||
final long start = proto.start(tag);
|
||||
|
||||
proto.write(NetworkIdentityProto.TYPE, mType);
|
||||
|
||||
// TODO: dump mRatType as well.
|
||||
|
||||
proto.write(NetworkIdentityProto.ROAMING, mRoaming);
|
||||
proto.write(NetworkIdentityProto.METERED, mMetered);
|
||||
proto.write(NetworkIdentityProto.DEFAULT_NETWORK, mDefaultNetwork);
|
||||
proto.write(NetworkIdentityProto.OEM_MANAGED_NETWORK, mOemManaged);
|
||||
|
||||
proto.end(start);
|
||||
}
|
||||
|
||||
/** Get the network type of this instance. */
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/** Get the Radio Access Technology(RAT) type of this instance. */
|
||||
public int getRatType() {
|
||||
return mRatType;
|
||||
}
|
||||
|
||||
/** Get the Subscriber Id of this instance. */
|
||||
@Nullable
|
||||
public String getSubscriberId() {
|
||||
return mSubscriberId;
|
||||
}
|
||||
|
||||
/** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */
|
||||
@Nullable
|
||||
public String getWifiNetworkKey() {
|
||||
return mWifiNetworkKey;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
// TODO: Remove this function after all callers are removed.
|
||||
public boolean getRoaming() {
|
||||
return mRoaming;
|
||||
}
|
||||
|
||||
/** Return whether this network is roaming. */
|
||||
public boolean isRoaming() {
|
||||
return mRoaming;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
// TODO: Remove this function after all callers are removed.
|
||||
public boolean getMetered() {
|
||||
return mMetered;
|
||||
}
|
||||
|
||||
/** Return whether this network is metered. */
|
||||
public boolean isMetered() {
|
||||
return mMetered;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
// TODO: Remove this function after all callers are removed.
|
||||
public boolean getDefaultNetwork() {
|
||||
return mDefaultNetwork;
|
||||
}
|
||||
|
||||
/** Return whether this network is the default network. */
|
||||
public boolean isDefaultNetwork() {
|
||||
return mDefaultNetwork;
|
||||
}
|
||||
|
||||
/** Get the OEM managed type of this instance. */
|
||||
public int getOemManaged() {
|
||||
return mOemManaged;
|
||||
}
|
||||
|
||||
/** Get the SubId of this instance. */
|
||||
public int getSubId() {
|
||||
return mSubId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble a {@link NetworkIdentity} from the passed arguments.
|
||||
*
|
||||
* This methods builds an identity based on the capabilities of the network in the
|
||||
* snapshot and other passed arguments. The identity is used as a key to record data usage.
|
||||
*
|
||||
* @param snapshot the snapshot of network state. See {@link NetworkStateSnapshot}.
|
||||
* @param defaultNetwork whether the network is a default network.
|
||||
* @param ratType the Radio Access Technology(RAT) type of the network. Or
|
||||
* {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable.
|
||||
* See {@code TelephonyManager.NETWORK_TYPE_*}.
|
||||
* @hide
|
||||
* @deprecated See {@link NetworkIdentity.Builder}.
|
||||
*/
|
||||
// TODO: Remove this after all callers are migrated to use new Api.
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public static NetworkIdentity buildNetworkIdentity(Context context,
|
||||
@NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, int ratType) {
|
||||
final NetworkIdentity.Builder builder = new NetworkIdentity.Builder()
|
||||
.setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork)
|
||||
.setSubId(snapshot.getSubId());
|
||||
if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) {
|
||||
builder.setRatType(ratType);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a bitfield of {@code NetworkIdentity.OEM_*} based on {@link NetworkCapabilities}.
|
||||
* @hide
|
||||
*/
|
||||
public static int getOemBitfield(@NonNull NetworkCapabilities nc) {
|
||||
int oemManaged = OEM_NONE;
|
||||
|
||||
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)) {
|
||||
oemManaged |= OEM_PAID;
|
||||
}
|
||||
if (nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE)) {
|
||||
oemManaged |= OEM_PRIVATE;
|
||||
}
|
||||
|
||||
return oemManaged;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) {
|
||||
Objects.requireNonNull(right);
|
||||
int res = Integer.compare(left.mType, right.mType);
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.mRatType, right.mRatType);
|
||||
}
|
||||
if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) {
|
||||
res = left.mSubscriberId.compareTo(right.mSubscriberId);
|
||||
}
|
||||
if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) {
|
||||
res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Boolean.compare(left.mRoaming, right.mRoaming);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Boolean.compare(left.mMetered, right.mMetered);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.mOemManaged, right.mOemManaged);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.mSubId, right.mSubId);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link NetworkIdentity}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
// Need to be synchronized with ConnectivityManager.
|
||||
// TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module.
|
||||
private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST
|
||||
private static final int MIN_NETWORK_TYPE = TYPE_MOBILE;
|
||||
|
||||
private int mType;
|
||||
private int mRatType;
|
||||
private String mSubscriberId;
|
||||
private String mWifiNetworkKey;
|
||||
private boolean mRoaming;
|
||||
private boolean mMetered;
|
||||
private boolean mDefaultNetwork;
|
||||
private int mOemManaged;
|
||||
private int mSubId;
|
||||
|
||||
/**
|
||||
* Creates a new Builder.
|
||||
*/
|
||||
public Builder() {
|
||||
// Initialize with default values. Will be overwritten by setters.
|
||||
mType = ConnectivityManager.TYPE_NONE;
|
||||
mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
|
||||
mSubscriberId = null;
|
||||
mWifiNetworkKey = null;
|
||||
mRoaming = false;
|
||||
mMetered = false;
|
||||
mDefaultNetwork = false;
|
||||
mOemManaged = NetworkTemplate.OEM_MANAGED_NO;
|
||||
mSubId = INVALID_SUBSCRIPTION_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance.
|
||||
* This is a useful shorthand that will read from the snapshot and set the
|
||||
* following fields, if they are set in the snapshot :
|
||||
* - type
|
||||
* - subscriberId
|
||||
* - roaming
|
||||
* - metered
|
||||
* - oemManaged
|
||||
* - wifiNetworkKey
|
||||
*
|
||||
* @param snapshot The target {@link NetworkStateSnapshot} object.
|
||||
* @return The builder object.
|
||||
*/
|
||||
@SuppressLint("MissingGetterMatchingBuilder")
|
||||
@NonNull
|
||||
public Builder setNetworkStateSnapshot(@NonNull NetworkStateSnapshot snapshot) {
|
||||
setType(snapshot.getLegacyType());
|
||||
|
||||
setSubscriberId(snapshot.getSubscriberId());
|
||||
setRoaming(!snapshot.getNetworkCapabilities().hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING));
|
||||
setMetered(!(snapshot.getNetworkCapabilities().hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
|| snapshot.getNetworkCapabilities().hasCapability(
|
||||
NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)));
|
||||
|
||||
setOemManaged(getOemBitfield(snapshot.getNetworkCapabilities()));
|
||||
|
||||
if (mType == TYPE_WIFI) {
|
||||
final TransportInfo transportInfo = snapshot.getNetworkCapabilities()
|
||||
.getTransportInfo();
|
||||
if (transportInfo instanceof WifiInfo) {
|
||||
final WifiInfo info = (WifiInfo) transportInfo;
|
||||
setWifiNetworkKey(info.getNetworkKey());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network type of the network.
|
||||
*
|
||||
* @param type the network type. See {@link ConnectivityManager#TYPE_*}.
|
||||
*
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setType(int type) {
|
||||
// Include TYPE_NONE for compatibility, type field might not be filled by some
|
||||
// networks such as test networks.
|
||||
if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type)
|
||||
&& type != ConnectivityManager.TYPE_NONE) {
|
||||
throw new IllegalArgumentException("Invalid network type: " + type);
|
||||
}
|
||||
mType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Radio Access Technology(RAT) type of the network.
|
||||
*
|
||||
* No RAT type is specified by default. Call clearRatType to reset.
|
||||
*
|
||||
* @param ratType the Radio Access Technology(RAT) type if applicable. See
|
||||
* {@code TelephonyManager.NETWORK_TYPE_*}.
|
||||
*
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setRatType(int ratType) {
|
||||
if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType)
|
||||
&& ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN
|
||||
&& ratType != NetworkStatsManager.NETWORK_TYPE_5G_NSA) {
|
||||
throw new IllegalArgumentException("Invalid ratType " + ratType);
|
||||
}
|
||||
mRatType = ratType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the Radio Access Technology(RAT) type of the network.
|
||||
*
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder clearRatType() {
|
||||
mRatType = NetworkTemplate.NETWORK_TYPE_ALL;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Subscriber Id.
|
||||
*
|
||||
* @param subscriberId the Subscriber Id of the network. Or null if not applicable.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSubscriberId(@Nullable String subscriberId) {
|
||||
mSubscriberId = subscriberId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Wifi Network Key.
|
||||
*
|
||||
* @param wifiNetworkKey Wifi Network Key of the network,
|
||||
* see {@link WifiInfo#getNetworkKey()}.
|
||||
* Or null if not applicable.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setWifiNetworkKey(@Nullable String wifiNetworkKey) {
|
||||
mWifiNetworkKey = wifiNetworkKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this network is roaming.
|
||||
*
|
||||
* This field is false by default. Call with false to reset.
|
||||
*
|
||||
* @param roaming the roaming status of the network.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setRoaming(boolean roaming) {
|
||||
mRoaming = roaming;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this network is metered.
|
||||
*
|
||||
* This field is false by default. Call with false to reset.
|
||||
*
|
||||
* @param metered the meteredness of the network.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setMetered(boolean metered) {
|
||||
mMetered = metered;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this network is the default network.
|
||||
*
|
||||
* This field is false by default. Call with false to reset.
|
||||
*
|
||||
* @param defaultNetwork the default network status of the network.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setDefaultNetwork(boolean defaultNetwork) {
|
||||
mDefaultNetwork = defaultNetwork;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OEM managed type.
|
||||
*
|
||||
* @param oemManaged Type of OEM managed network or unmanaged networks.
|
||||
* See {@code NetworkTemplate#OEM_MANAGED_*}.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setOemManaged(@OemManaged int oemManaged) {
|
||||
// Assert input does not contain illegal oemManage bits.
|
||||
if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) {
|
||||
throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged);
|
||||
}
|
||||
mOemManaged = oemManaged;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Subscription Id.
|
||||
*
|
||||
* @param subId the Subscription Id of the network. Or INVALID_SUBSCRIPTION_ID if not
|
||||
* applicable.
|
||||
* @return this builder.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setSubId(int subId) {
|
||||
mSubId = subId;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureValidParameters() {
|
||||
// Assert non-mobile network cannot have a ratType.
|
||||
if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid ratType " + mRatType + " for type " + mType);
|
||||
}
|
||||
|
||||
// Assert non-wifi network cannot have a wifi network key.
|
||||
if (mType != TYPE_WIFI && mWifiNetworkKey != null) {
|
||||
throw new IllegalArgumentException("Invalid wifi network key for type " + mType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the instance of the {@link NetworkIdentity}.
|
||||
*
|
||||
* @return the built instance of {@link NetworkIdentity}.
|
||||
*/
|
||||
@NonNull
|
||||
public NetworkIdentity build() {
|
||||
ensureValidParameters();
|
||||
return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey,
|
||||
mRoaming, mMetered, mDefaultNetwork, mOemManaged, mSubId);
|
||||
}
|
||||
}
|
||||
}
|
||||
230
framework-t/src/android/net/NetworkIdentitySet.java
Normal file
230
framework-t/src/android/net/NetworkIdentitySet.java
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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.net;
|
||||
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.service.NetworkIdentitySetProto;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
|
||||
* active on that interface.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
|
||||
private static final int VERSION_INIT = 1;
|
||||
private static final int VERSION_ADD_ROAMING = 2;
|
||||
private static final int VERSION_ADD_NETWORK_ID = 3;
|
||||
private static final int VERSION_ADD_METERED = 4;
|
||||
private static final int VERSION_ADD_DEFAULT_NETWORK = 5;
|
||||
private static final int VERSION_ADD_OEM_MANAGED_NETWORK = 6;
|
||||
private static final int VERSION_ADD_SUB_ID = 7;
|
||||
|
||||
/**
|
||||
* Construct a {@link NetworkIdentitySet} object.
|
||||
*/
|
||||
public NetworkIdentitySet() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) {
|
||||
super(ident);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public NetworkIdentitySet(DataInput in) throws IOException {
|
||||
final int version = in.readInt();
|
||||
final int size = in.readInt();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (version <= VERSION_INIT) {
|
||||
final int ignored = in.readInt();
|
||||
}
|
||||
final int type = in.readInt();
|
||||
final int ratType = in.readInt();
|
||||
final String subscriberId = readOptionalString(in);
|
||||
final String networkId;
|
||||
if (version >= VERSION_ADD_NETWORK_ID) {
|
||||
networkId = readOptionalString(in);
|
||||
} else {
|
||||
networkId = null;
|
||||
}
|
||||
final boolean roaming;
|
||||
if (version >= VERSION_ADD_ROAMING) {
|
||||
roaming = in.readBoolean();
|
||||
} else {
|
||||
roaming = false;
|
||||
}
|
||||
|
||||
final boolean metered;
|
||||
if (version >= VERSION_ADD_METERED) {
|
||||
metered = in.readBoolean();
|
||||
} else {
|
||||
// If this is the old data and the type is mobile, treat it as metered. (Note that
|
||||
// if this is a mobile network, TYPE_MOBILE is the only possible type that could be
|
||||
// used.)
|
||||
metered = (type == TYPE_MOBILE);
|
||||
}
|
||||
|
||||
final boolean defaultNetwork;
|
||||
if (version >= VERSION_ADD_DEFAULT_NETWORK) {
|
||||
defaultNetwork = in.readBoolean();
|
||||
} else {
|
||||
defaultNetwork = true;
|
||||
}
|
||||
|
||||
final int oemNetCapabilities;
|
||||
if (version >= VERSION_ADD_OEM_MANAGED_NETWORK) {
|
||||
oemNetCapabilities = in.readInt();
|
||||
} else {
|
||||
oemNetCapabilities = NetworkIdentity.OEM_NONE;
|
||||
}
|
||||
|
||||
final int subId;
|
||||
if (version >= VERSION_ADD_SUB_ID) {
|
||||
subId = in.readInt();
|
||||
} else {
|
||||
subId = INVALID_SUBSCRIPTION_ID;
|
||||
}
|
||||
|
||||
add(new NetworkIdentity(type, ratType, subscriberId, networkId, roaming, metered,
|
||||
defaultNetwork, oemNetCapabilities, subId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to serialize this object into a {@code DataOutput}.
|
||||
* @hide
|
||||
*/
|
||||
public void writeToStream(DataOutput out) throws IOException {
|
||||
out.writeInt(VERSION_ADD_SUB_ID);
|
||||
out.writeInt(size());
|
||||
for (NetworkIdentity ident : this) {
|
||||
out.writeInt(ident.getType());
|
||||
out.writeInt(ident.getRatType());
|
||||
writeOptionalString(out, ident.getSubscriberId());
|
||||
writeOptionalString(out, ident.getWifiNetworkKey());
|
||||
out.writeBoolean(ident.isRoaming());
|
||||
out.writeBoolean(ident.isMetered());
|
||||
out.writeBoolean(ident.isDefaultNetwork());
|
||||
out.writeInt(ident.getOemManaged());
|
||||
out.writeInt(ident.getSubId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether any {@link NetworkIdentity} in this set is considered metered.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isAnyMemberMetered() {
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (NetworkIdentity ident : this) {
|
||||
if (ident.isMetered()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether any {@link NetworkIdentity} in this set is considered roaming.
|
||||
* @hide
|
||||
*/
|
||||
public boolean isAnyMemberRoaming() {
|
||||
if (isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (NetworkIdentity ident : this) {
|
||||
if (ident.isRoaming()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether any {@link NetworkIdentity} in this set is considered on the default
|
||||
* network.
|
||||
* @hide
|
||||
*/
|
||||
public boolean areAllMembersOnDefaultNetwork() {
|
||||
if (isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (NetworkIdentity ident : this) {
|
||||
if (!ident.isDefaultNetwork()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void writeOptionalString(DataOutput out, String value) throws IOException {
|
||||
if (value != null) {
|
||||
out.writeByte(1);
|
||||
out.writeUTF(value);
|
||||
} else {
|
||||
out.writeByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static String readOptionalString(DataInput in) throws IOException {
|
||||
if (in.readByte() != 0) {
|
||||
return in.readUTF();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) {
|
||||
Objects.requireNonNull(left);
|
||||
Objects.requireNonNull(right);
|
||||
if (left.isEmpty()) return -1;
|
||||
if (right.isEmpty()) return 1;
|
||||
|
||||
final NetworkIdentity leftIdent = left.iterator().next();
|
||||
final NetworkIdentity rightIdent = right.iterator().next();
|
||||
return NetworkIdentity.compare(leftIdent, rightIdent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to dump this object into proto debug file.
|
||||
* @hide
|
||||
*/
|
||||
public void dumpDebug(ProtoOutputStream proto, long tag) {
|
||||
final long start = proto.start(tag);
|
||||
|
||||
for (NetworkIdentity ident : this) {
|
||||
ident.dumpDebug(proto, NetworkIdentitySetProto.IDENTITIES);
|
||||
}
|
||||
|
||||
proto.end(start);
|
||||
}
|
||||
}
|
||||
192
framework-t/src/android/net/NetworkStateSnapshot.java
Normal file
192
framework-t/src/android/net/NetworkStateSnapshot.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.net.module.util.NetworkIdentityUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Snapshot of network state.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public final class NetworkStateSnapshot implements Parcelable {
|
||||
/** The network associated with this snapshot. */
|
||||
@NonNull
|
||||
private final Network mNetwork;
|
||||
|
||||
/** The {@link NetworkCapabilities} of the network associated with this snapshot. */
|
||||
@NonNull
|
||||
private final NetworkCapabilities mNetworkCapabilities;
|
||||
|
||||
/** The {@link LinkProperties} of the network associated with this snapshot. */
|
||||
@NonNull
|
||||
private final LinkProperties mLinkProperties;
|
||||
|
||||
/**
|
||||
* The Subscriber Id of the network associated with this snapshot. See
|
||||
* {@link android.telephony.TelephonyManager#getSubscriberId()}.
|
||||
*/
|
||||
@Nullable
|
||||
private final String mSubscriberId;
|
||||
|
||||
/**
|
||||
* The legacy type of the network associated with this snapshot. See
|
||||
* {@code ConnectivityManager#TYPE_*}.
|
||||
*/
|
||||
private final int mLegacyType;
|
||||
|
||||
public NetworkStateSnapshot(@NonNull Network network,
|
||||
@NonNull NetworkCapabilities networkCapabilities,
|
||||
@NonNull LinkProperties linkProperties,
|
||||
@Nullable String subscriberId, int legacyType) {
|
||||
mNetwork = Objects.requireNonNull(network);
|
||||
mNetworkCapabilities = Objects.requireNonNull(networkCapabilities);
|
||||
mLinkProperties = Objects.requireNonNull(linkProperties);
|
||||
mSubscriberId = subscriberId;
|
||||
mLegacyType = legacyType;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public NetworkStateSnapshot(@NonNull Parcel in) {
|
||||
mNetwork = in.readParcelable(null);
|
||||
mNetworkCapabilities = in.readParcelable(null);
|
||||
mLinkProperties = in.readParcelable(null);
|
||||
mSubscriberId = in.readString();
|
||||
mLegacyType = in.readInt();
|
||||
}
|
||||
|
||||
/** Get the network associated with this snapshot */
|
||||
@NonNull
|
||||
public Network getNetwork() {
|
||||
return mNetwork;
|
||||
}
|
||||
|
||||
/** Get {@link NetworkCapabilities} of the network associated with this snapshot. */
|
||||
@NonNull
|
||||
public NetworkCapabilities getNetworkCapabilities() {
|
||||
return mNetworkCapabilities;
|
||||
}
|
||||
|
||||
/** Get the {@link LinkProperties} of the network associated with this snapshot. */
|
||||
@NonNull
|
||||
public LinkProperties getLinkProperties() {
|
||||
return mLinkProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Subscriber Id of the network associated with this snapshot.
|
||||
* @deprecated Please use #getSubId, which doesn't return personally identifiable
|
||||
* information.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public String getSubscriberId() {
|
||||
return mSubscriberId;
|
||||
}
|
||||
|
||||
/** Get the subId of the network associated with this snapshot. */
|
||||
public int getSubId() {
|
||||
if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
|
||||
final NetworkSpecifier spec = mNetworkCapabilities.getNetworkSpecifier();
|
||||
if (spec instanceof TelephonyNetworkSpecifier) {
|
||||
return ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
|
||||
}
|
||||
}
|
||||
return INVALID_SUBSCRIPTION_ID;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the legacy type of the network associated with this snapshot.
|
||||
* @return the legacy network type. See {@code ConnectivityManager#TYPE_*}.
|
||||
*/
|
||||
public int getLegacyType() {
|
||||
return mLegacyType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeParcelable(mNetwork, flags);
|
||||
out.writeParcelable(mNetworkCapabilities, flags);
|
||||
out.writeParcelable(mLinkProperties, flags);
|
||||
out.writeString(mSubscriberId);
|
||||
out.writeInt(mLegacyType);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Creator<NetworkStateSnapshot> CREATOR =
|
||||
new Creator<NetworkStateSnapshot>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public NetworkStateSnapshot createFromParcel(@NonNull Parcel in) {
|
||||
return new NetworkStateSnapshot(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public NetworkStateSnapshot[] newArray(int size) {
|
||||
return new NetworkStateSnapshot[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof NetworkStateSnapshot)) return false;
|
||||
NetworkStateSnapshot that = (NetworkStateSnapshot) o;
|
||||
return mLegacyType == that.mLegacyType
|
||||
&& Objects.equals(mNetwork, that.mNetwork)
|
||||
&& Objects.equals(mNetworkCapabilities, that.mNetworkCapabilities)
|
||||
&& Objects.equals(mLinkProperties, that.mLinkProperties)
|
||||
&& Objects.equals(mSubscriberId, that.mSubscriberId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mNetwork,
|
||||
mNetworkCapabilities, mLinkProperties, mSubscriberId, mLegacyType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NetworkStateSnapshot{"
|
||||
+ "network=" + mNetwork
|
||||
+ ", networkCapabilities=" + mNetworkCapabilities
|
||||
+ ", linkProperties=" + mLinkProperties
|
||||
+ ", subscriberId='" + NetworkIdentityUtils.scrubSubscriberId(mSubscriberId) + '\''
|
||||
+ ", legacyType=" + mLegacyType
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
1834
framework-t/src/android/net/NetworkStats.java
Normal file
1834
framework-t/src/android/net/NetworkStats.java
Normal file
File diff suppressed because it is too large
Load Diff
208
framework-t/src/android/net/NetworkStatsAccess.java
Normal file
208
framework-t/src/android/net/NetworkStatsAccess.java
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.net;
|
||||
|
||||
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntDef;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Utility methods for controlling access to network stats APIs.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class NetworkStatsAccess {
|
||||
private NetworkStatsAccess() {}
|
||||
|
||||
/**
|
||||
* Represents an access level for the network usage history and statistics APIs.
|
||||
*
|
||||
* <p>Access levels are in increasing order; that is, it is reasonable to check access by
|
||||
* verifying that the caller's access level is at least the minimum required level.
|
||||
*/
|
||||
@IntDef({
|
||||
Level.DEFAULT,
|
||||
Level.USER,
|
||||
Level.DEVICESUMMARY,
|
||||
Level.DEVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Level {
|
||||
/**
|
||||
* Default, unprivileged access level.
|
||||
*
|
||||
* <p>Can only access usage for one's own UID.
|
||||
*
|
||||
* <p>Every app will have at least this access level.
|
||||
*/
|
||||
int DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Access level for apps which can access usage for any app running in the same user.
|
||||
*
|
||||
* <p>Granted to:
|
||||
* <ul>
|
||||
* <li>Profile owners.
|
||||
* </ul>
|
||||
*/
|
||||
int USER = 1;
|
||||
|
||||
/**
|
||||
* Access level for apps which can access usage summary of device. Device summary includes
|
||||
* usage by apps running in any profiles/users, however this access level does not
|
||||
* allow querying usage of individual apps running in other profiles/users.
|
||||
*
|
||||
* <p>Granted to:
|
||||
* <ul>
|
||||
* <li>Apps with the PACKAGE_USAGE_STATS permission granted. Note that this is an AppOps bit
|
||||
* so it is not necessarily sufficient to declare this in the manifest.
|
||||
* <li>Apps with the (signature/privileged) READ_NETWORK_USAGE_HISTORY permission.
|
||||
* </ul>
|
||||
*/
|
||||
int DEVICESUMMARY = 2;
|
||||
|
||||
/**
|
||||
* Access level for apps which can access usage for any app on the device, including apps
|
||||
* running on other users/profiles.
|
||||
*
|
||||
* <p>Granted to:
|
||||
* <ul>
|
||||
* <li>Device owners.
|
||||
* <li>Carrier-privileged applications.
|
||||
* <li>The system UID.
|
||||
* </ul>
|
||||
*/
|
||||
int DEVICE = 3;
|
||||
}
|
||||
|
||||
/** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
|
||||
public static @NetworkStatsAccess.Level int checkAccessLevel(
|
||||
Context context, int callingPid, int callingUid, String callingPackage) {
|
||||
final DevicePolicyManager mDpm = context.getSystemService(DevicePolicyManager.class);
|
||||
final TelephonyManager tm = (TelephonyManager)
|
||||
context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
boolean hasCarrierPrivileges;
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
hasCarrierPrivileges = tm != null
|
||||
&& tm.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
|
||||
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
|
||||
final boolean isDeviceOwner = mDpm != null && mDpm.isDeviceOwnerApp(callingPackage);
|
||||
final int appId = UserHandle.getAppId(callingUid);
|
||||
|
||||
final boolean isNetworkStack = context.checkPermission(
|
||||
android.Manifest.permission.NETWORK_STACK, callingPid, callingUid)
|
||||
== PERMISSION_GRANTED;
|
||||
|
||||
if (hasCarrierPrivileges || isDeviceOwner
|
||||
|| appId == Process.SYSTEM_UID || isNetworkStack) {
|
||||
// Carrier-privileged apps and device owners, and the system (including the
|
||||
// network stack) can access data usage for all apps on the device.
|
||||
return NetworkStatsAccess.Level.DEVICE;
|
||||
}
|
||||
|
||||
boolean hasAppOpsPermission = hasAppOpsPermission(context, callingUid, callingPackage);
|
||||
if (hasAppOpsPermission || context.checkCallingOrSelfPermission(
|
||||
READ_NETWORK_USAGE_HISTORY) == PackageManager.PERMISSION_GRANTED) {
|
||||
return NetworkStatsAccess.Level.DEVICESUMMARY;
|
||||
}
|
||||
|
||||
//TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
|
||||
boolean isProfileOwner = mDpm != null && (mDpm.isProfileOwnerApp(callingPackage)
|
||||
|| mDpm.isDeviceOwnerApp(callingPackage));
|
||||
if (isProfileOwner) {
|
||||
// Apps with the AppOps permission, profile owners, and apps with the privileged
|
||||
// permission can access data usage for all apps in this user/profile.
|
||||
return NetworkStatsAccess.Level.USER;
|
||||
}
|
||||
|
||||
// Everyone else gets default access (only to their own UID).
|
||||
return NetworkStatsAccess.Level.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given caller should be able to access the given UID when the caller has
|
||||
* the given {@link NetworkStatsAccess.Level}.
|
||||
*/
|
||||
public static boolean isAccessibleToUser(int uid, int callerUid,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
|
||||
final int callerUserId = UserHandle.getUserHandleForUid(callerUid).getIdentifier();
|
||||
switch (accessLevel) {
|
||||
case NetworkStatsAccess.Level.DEVICE:
|
||||
// Device-level access - can access usage for any uid.
|
||||
return true;
|
||||
case NetworkStatsAccess.Level.DEVICESUMMARY:
|
||||
// Can access usage for any app running in the same user, along
|
||||
// with some special uids (system, removed, or tethering) and
|
||||
// anonymized uids
|
||||
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|
||||
|| uid == UID_TETHERING || uid == UID_ALL
|
||||
|| userId == callerUserId;
|
||||
case NetworkStatsAccess.Level.USER:
|
||||
// User-level access - can access usage for any app running in the same user, along
|
||||
// with some special uids (system, removed, or tethering).
|
||||
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|
||||
|| uid == UID_TETHERING
|
||||
|| userId == callerUserId;
|
||||
case NetworkStatsAccess.Level.DEFAULT:
|
||||
default:
|
||||
// Default access level - can only access one's own usage.
|
||||
return uid == callerUid;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasAppOpsPermission(
|
||||
Context context, int callingUid, String callingPackage) {
|
||||
if (callingPackage != null) {
|
||||
AppOpsManager appOps = (AppOpsManager) context.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
|
||||
final int mode = appOps.noteOp(AppOpsManager.OPSTR_GET_USAGE_STATS,
|
||||
callingUid, callingPackage, null /* attributionTag */, null /* message */);
|
||||
if (mode == AppOpsManager.MODE_DEFAULT) {
|
||||
// The default behavior here is to check if PackageManager has given the app
|
||||
// permission.
|
||||
final int permissionCheck = context.checkCallingPermission(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
return permissionCheck == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return (mode == AppOpsManager.MODE_ALLOWED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
956
framework-t/src/android/net/NetworkStatsCollection.java
Normal file
956
framework-t/src/android/net/NetworkStatsCollection.java
Normal file
@@ -0,0 +1,956 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
|
||||
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
|
||||
import static android.net.NetworkStats.IFACE_ALL;
|
||||
import static android.net.NetworkStats.METERED_NO;
|
||||
import static android.net.NetworkStats.METERED_YES;
|
||||
import static android.net.NetworkStats.ROAMING_NO;
|
||||
import static android.net.NetworkStats.ROAMING_YES;
|
||||
import static android.net.NetworkStats.SET_ALL;
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
|
||||
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.net.NetworkStats.State;
|
||||
import android.net.NetworkStatsHistory.Entry;
|
||||
import android.os.Binder;
|
||||
import android.service.NetworkStatsCollectionKeyProto;
|
||||
import android.service.NetworkStatsCollectionProto;
|
||||
import android.service.NetworkStatsCollectionStatsProto;
|
||||
import android.telephony.SubscriptionPlan;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.Range;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.net.module.util.NetworkStatsUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.ProtocolException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collection of {@link NetworkStatsHistory}, stored based on combined key of
|
||||
* {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
|
||||
private static final String TAG = NetworkStatsCollection.class.getSimpleName();
|
||||
/** File header magic number: "ANET" */
|
||||
private static final int FILE_MAGIC = 0x414E4554;
|
||||
|
||||
private static final int VERSION_NETWORK_INIT = 1;
|
||||
|
||||
private static final int VERSION_UID_INIT = 1;
|
||||
private static final int VERSION_UID_WITH_IDENT = 2;
|
||||
private static final int VERSION_UID_WITH_TAG = 3;
|
||||
private static final int VERSION_UID_WITH_SET = 4;
|
||||
|
||||
private static final int VERSION_UNIFIED_INIT = 16;
|
||||
|
||||
private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
|
||||
|
||||
private final long mBucketDurationMillis;
|
||||
|
||||
private long mStartMillis;
|
||||
private long mEndMillis;
|
||||
private long mTotalBytes;
|
||||
private boolean mDirty;
|
||||
|
||||
/**
|
||||
* Construct a {@link NetworkStatsCollection} object.
|
||||
*
|
||||
* @param bucketDuration duration of the buckets in this object, in milliseconds.
|
||||
* @hide
|
||||
*/
|
||||
public NetworkStatsCollection(long bucketDurationMillis) {
|
||||
mBucketDurationMillis = bucketDurationMillis;
|
||||
reset();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void clear() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void reset() {
|
||||
mStats.clear();
|
||||
mStartMillis = Long.MAX_VALUE;
|
||||
mEndMillis = Long.MIN_VALUE;
|
||||
mTotalBytes = 0;
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public long getStartMillis() {
|
||||
return mStartMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first atomic bucket in this collection, which is more conservative
|
||||
* than {@link #mStartMillis}.
|
||||
* @hide
|
||||
*/
|
||||
public long getFirstAtomicBucketMillis() {
|
||||
if (mStartMillis == Long.MAX_VALUE) {
|
||||
return Long.MAX_VALUE;
|
||||
} else {
|
||||
return mStartMillis + mBucketDurationMillis;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public long getEndMillis() {
|
||||
return mEndMillis;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public long getTotalBytes() {
|
||||
return mTotalBytes;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isDirty() {
|
||||
return mDirty;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void clearDirty() {
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean isEmpty() {
|
||||
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public long roundUp(long time) {
|
||||
if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
|
||||
|| time == SubscriptionPlan.TIME_UNKNOWN) {
|
||||
return time;
|
||||
} else {
|
||||
final long mod = time % mBucketDurationMillis;
|
||||
if (mod > 0) {
|
||||
time -= mod;
|
||||
time += mBucketDurationMillis;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public long roundDown(long time) {
|
||||
if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
|
||||
|| time == SubscriptionPlan.TIME_UNKNOWN) {
|
||||
return time;
|
||||
} else {
|
||||
final long mod = time % mBucketDurationMillis;
|
||||
if (mod > 0) {
|
||||
time -= mod;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
|
||||
return getRelevantUids(accessLevel, Binder.getCallingUid());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
|
||||
final int callerUid) {
|
||||
final ArrayList<Integer> uids = new ArrayList<>();
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
|
||||
int j = Collections.binarySearch(uids, new Integer(key.uid));
|
||||
|
||||
if (j < 0) {
|
||||
j = ~j;
|
||||
uids.add(j, key.uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return CollectionUtils.toIntArray(uids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all {@link NetworkStatsHistory} in this collection which match
|
||||
* the requested parameters.
|
||||
* @hide
|
||||
*/
|
||||
public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
|
||||
int uid, int set, int tag, int fields, long start, long end,
|
||||
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
|
||||
if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
|
||||
throw new SecurityException("Network stats history of uid " + uid
|
||||
+ " is forbidden for caller " + callerUid);
|
||||
}
|
||||
|
||||
// 180 days of history should be enough for anyone; if we end up needing
|
||||
// more, we'll dynamically grow the history object.
|
||||
final int bucketEstimate = (int) NetworkStatsUtils.constrain(
|
||||
((end - start) / mBucketDurationMillis), 0,
|
||||
(180 * DateUtils.DAY_IN_MILLIS) / mBucketDurationMillis);
|
||||
final NetworkStatsHistory combined = new NetworkStatsHistory(
|
||||
mBucketDurationMillis, bucketEstimate, fields);
|
||||
|
||||
// shortcut when we know stats will be empty
|
||||
if (start == end) return combined;
|
||||
|
||||
// Figure out the window of time that we should be augmenting (if any)
|
||||
long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
|
||||
long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
|
||||
: SubscriptionPlan.TIME_UNKNOWN;
|
||||
// And if augmenting, we might need to collect more data to adjust with
|
||||
long collectStart = start;
|
||||
long collectEnd = end;
|
||||
|
||||
if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
|
||||
final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator();
|
||||
while (it.hasNext()) {
|
||||
final Range<ZonedDateTime> cycle = it.next();
|
||||
final long cycleStart = cycle.getLower().toInstant().toEpochMilli();
|
||||
final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli();
|
||||
if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
|
||||
augmentStart = cycleStart;
|
||||
collectStart = Long.min(collectStart, augmentStart);
|
||||
collectEnd = Long.max(collectEnd, augmentEnd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
|
||||
// Shrink augmentation window so we don't risk undercounting.
|
||||
augmentStart = roundUp(augmentStart);
|
||||
augmentEnd = roundDown(augmentEnd);
|
||||
// Grow collection window so we get all the stats needed.
|
||||
collectStart = roundDown(collectStart);
|
||||
collectEnd = roundUp(collectEnd);
|
||||
}
|
||||
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
|
||||
&& templateMatches(template, key.ident)) {
|
||||
final NetworkStatsHistory value = mStats.valueAt(i);
|
||||
combined.recordHistory(value, collectStart, collectEnd);
|
||||
}
|
||||
}
|
||||
|
||||
if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
|
||||
final NetworkStatsHistory.Entry entry = combined.getValues(
|
||||
augmentStart, augmentEnd, null);
|
||||
|
||||
// If we don't have any recorded data for this time period, give
|
||||
// ourselves something to scale with.
|
||||
if (entry.rxBytes == 0 || entry.txBytes == 0) {
|
||||
combined.recordData(augmentStart, augmentEnd,
|
||||
new NetworkStats.Entry(1, 0, 1, 0, 0));
|
||||
combined.getValues(augmentStart, augmentEnd, entry);
|
||||
}
|
||||
|
||||
final long rawBytes = (entry.rxBytes + entry.txBytes) == 0 ? 1 :
|
||||
(entry.rxBytes + entry.txBytes);
|
||||
final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes;
|
||||
final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes;
|
||||
final long targetBytes = augmentPlan.getDataUsageBytes();
|
||||
|
||||
final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes);
|
||||
final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes);
|
||||
|
||||
|
||||
// Scale all matching buckets to reach anchor target
|
||||
final long beforeTotal = combined.getTotalBytes();
|
||||
for (int i = 0; i < combined.size(); i++) {
|
||||
combined.getValues(i, entry);
|
||||
if (entry.bucketStart >= augmentStart
|
||||
&& entry.bucketStart + entry.bucketDuration <= augmentEnd) {
|
||||
entry.rxBytes = multiplySafeByRational(
|
||||
targetRxBytes, entry.rxBytes, rawRxBytes);
|
||||
entry.txBytes = multiplySafeByRational(
|
||||
targetTxBytes, entry.txBytes, rawTxBytes);
|
||||
// We purposefully clear out packet counters to indicate
|
||||
// that this data has been augmented.
|
||||
entry.rxPackets = 0;
|
||||
entry.txPackets = 0;
|
||||
combined.setValues(i, entry);
|
||||
}
|
||||
}
|
||||
|
||||
final long deltaTotal = combined.getTotalBytes() - beforeTotal;
|
||||
if (deltaTotal != 0) {
|
||||
Log.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
|
||||
}
|
||||
|
||||
// Finally we can slice data as originally requested
|
||||
final NetworkStatsHistory sliced = new NetworkStatsHistory(
|
||||
mBucketDurationMillis, bucketEstimate, fields);
|
||||
sliced.recordHistory(combined, start, end);
|
||||
return sliced;
|
||||
} else {
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize all {@link NetworkStatsHistory} in this collection which match
|
||||
* the requested parameters across the requested range.
|
||||
*
|
||||
* @param template - a predicate for filtering netstats.
|
||||
* @param start - start of the range, timestamp in milliseconds since the epoch.
|
||||
* @param end - end of the range, timestamp in milliseconds since the epoch.
|
||||
* @param accessLevel - caller access level.
|
||||
* @param callerUid - caller UID.
|
||||
* @hide
|
||||
*/
|
||||
public NetworkStats getSummary(NetworkTemplate template, long start, long end,
|
||||
@NetworkStatsAccess.Level int accessLevel, int callerUid) {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
final NetworkStats stats = new NetworkStats(end - start, 24);
|
||||
|
||||
// shortcut when we know stats will be empty
|
||||
if (start == end) return stats;
|
||||
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
NetworkStatsHistory.Entry historyEntry = null;
|
||||
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (templateMatches(template, key.ident)
|
||||
&& NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
|
||||
&& key.set < NetworkStats.SET_DEBUG_START) {
|
||||
final NetworkStatsHistory value = mStats.valueAt(i);
|
||||
historyEntry = value.getValues(start, end, now, historyEntry);
|
||||
|
||||
entry.iface = IFACE_ALL;
|
||||
entry.uid = key.uid;
|
||||
entry.set = key.set;
|
||||
entry.tag = key.tag;
|
||||
entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork()
|
||||
? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
|
||||
entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
|
||||
entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
|
||||
entry.rxBytes = historyEntry.rxBytes;
|
||||
entry.rxPackets = historyEntry.rxPackets;
|
||||
entry.txBytes = historyEntry.txBytes;
|
||||
entry.txPackets = historyEntry.txPackets;
|
||||
entry.operations = historyEntry.operations;
|
||||
|
||||
if (!entry.isEmpty()) {
|
||||
stats.combineValues(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record given {@link android.net.NetworkStats.Entry} into this collection.
|
||||
* @hide
|
||||
*/
|
||||
public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
|
||||
long end, NetworkStats.Entry entry) {
|
||||
final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
|
||||
history.recordData(start, end, entry);
|
||||
noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record given {@link NetworkStatsHistory} into this collection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(history);
|
||||
if (history.size() == 0) return;
|
||||
noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
|
||||
|
||||
NetworkStatsHistory target = mStats.get(key);
|
||||
if (target == null) {
|
||||
target = new NetworkStatsHistory(history.getBucketDuration());
|
||||
mStats.put(key, target);
|
||||
}
|
||||
target.recordEntireHistory(history);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record all {@link NetworkStatsHistory} contained in the given collection
|
||||
* into this collection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void recordCollection(@NonNull NetworkStatsCollection another) {
|
||||
Objects.requireNonNull(another);
|
||||
for (int i = 0; i < another.mStats.size(); i++) {
|
||||
final Key key = another.mStats.keyAt(i);
|
||||
final NetworkStatsHistory value = another.mStats.valueAt(i);
|
||||
recordHistory(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkStatsHistory findOrCreateHistory(
|
||||
NetworkIdentitySet ident, int uid, int set, int tag) {
|
||||
final Key key = new Key(ident, uid, set, tag);
|
||||
final NetworkStatsHistory existing = mStats.get(key);
|
||||
|
||||
// update when no existing, or when bucket duration changed
|
||||
NetworkStatsHistory updated = null;
|
||||
if (existing == null) {
|
||||
updated = new NetworkStatsHistory(mBucketDurationMillis, 10);
|
||||
} else if (existing.getBucketDuration() != mBucketDurationMillis) {
|
||||
updated = new NetworkStatsHistory(existing, mBucketDurationMillis);
|
||||
}
|
||||
|
||||
if (updated != null) {
|
||||
mStats.put(key, updated);
|
||||
return updated;
|
||||
} else {
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void read(InputStream in) throws IOException {
|
||||
read((DataInput) new DataInputStream(in));
|
||||
}
|
||||
|
||||
private void read(DataInput in) throws IOException {
|
||||
// verify file magic header intact
|
||||
final int magic = in.readInt();
|
||||
if (magic != FILE_MAGIC) {
|
||||
throw new ProtocolException("unexpected magic: " + magic);
|
||||
}
|
||||
|
||||
final int version = in.readInt();
|
||||
switch (version) {
|
||||
case VERSION_UNIFIED_INIT: {
|
||||
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
|
||||
final int identSize = in.readInt();
|
||||
for (int i = 0; i < identSize; i++) {
|
||||
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
|
||||
|
||||
final int size = in.readInt();
|
||||
for (int j = 0; j < size; j++) {
|
||||
final int uid = in.readInt();
|
||||
final int set = in.readInt();
|
||||
final int tag = in.readInt();
|
||||
|
||||
final Key key = new Key(ident, uid, set, tag);
|
||||
final NetworkStatsHistory history = new NetworkStatsHistory(in);
|
||||
recordHistory(key, history);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new ProtocolException("unexpected version: " + version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
write((DataOutput) new DataOutputStream(out));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private void write(DataOutput out) throws IOException {
|
||||
// cluster key lists grouped by ident
|
||||
final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = new HashMap<>();
|
||||
for (Key key : mStats.keySet()) {
|
||||
ArrayList<Key> keys = keysByIdent.get(key.ident);
|
||||
if (keys == null) {
|
||||
keys = new ArrayList<>();
|
||||
keysByIdent.put(key.ident, keys);
|
||||
}
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
out.writeInt(FILE_MAGIC);
|
||||
out.writeInt(VERSION_UNIFIED_INIT);
|
||||
|
||||
out.writeInt(keysByIdent.size());
|
||||
for (NetworkIdentitySet ident : keysByIdent.keySet()) {
|
||||
final ArrayList<Key> keys = keysByIdent.get(ident);
|
||||
ident.writeToStream(out);
|
||||
|
||||
out.writeInt(keys.size());
|
||||
for (Key key : keys) {
|
||||
final NetworkStatsHistory history = mStats.get(key);
|
||||
out.writeInt(key.uid);
|
||||
out.writeInt(key.set);
|
||||
out.writeInt(key.tag);
|
||||
history.writeToStream(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read legacy network summary statistics file format into the collection,
|
||||
* See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
|
||||
*
|
||||
* @deprecated
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
public void readLegacyNetwork(File file) throws IOException {
|
||||
final AtomicFile inputFile = new AtomicFile(file);
|
||||
|
||||
DataInputStream in = null;
|
||||
try {
|
||||
in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
|
||||
|
||||
// verify file magic header intact
|
||||
final int magic = in.readInt();
|
||||
if (magic != FILE_MAGIC) {
|
||||
throw new ProtocolException("unexpected magic: " + magic);
|
||||
}
|
||||
|
||||
final int version = in.readInt();
|
||||
switch (version) {
|
||||
case VERSION_NETWORK_INIT: {
|
||||
// network := size *(NetworkIdentitySet NetworkStatsHistory)
|
||||
final int size = in.readInt();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
|
||||
final NetworkStatsHistory history = new NetworkStatsHistory(in);
|
||||
|
||||
final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
|
||||
recordHistory(key, history);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new ProtocolException("unexpected version: " + version);
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// missing stats is okay, probably first boot
|
||||
} finally {
|
||||
IoUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read legacy Uid statistics file format into the collection,
|
||||
* See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
|
||||
*
|
||||
* @deprecated
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
public void readLegacyUid(File file, boolean onlyTags) throws IOException {
|
||||
final AtomicFile inputFile = new AtomicFile(file);
|
||||
|
||||
DataInputStream in = null;
|
||||
try {
|
||||
in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
|
||||
|
||||
// verify file magic header intact
|
||||
final int magic = in.readInt();
|
||||
if (magic != FILE_MAGIC) {
|
||||
throw new ProtocolException("unexpected magic: " + magic);
|
||||
}
|
||||
|
||||
final int version = in.readInt();
|
||||
switch (version) {
|
||||
case VERSION_UID_INIT: {
|
||||
// uid := size *(UID NetworkStatsHistory)
|
||||
|
||||
// drop this data version, since we don't have a good
|
||||
// mapping into NetworkIdentitySet.
|
||||
break;
|
||||
}
|
||||
case VERSION_UID_WITH_IDENT: {
|
||||
// uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
|
||||
|
||||
// drop this data version, since this version only existed
|
||||
// for a short time.
|
||||
break;
|
||||
}
|
||||
case VERSION_UID_WITH_TAG:
|
||||
case VERSION_UID_WITH_SET: {
|
||||
// uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
|
||||
final int identSize = in.readInt();
|
||||
for (int i = 0; i < identSize; i++) {
|
||||
final NetworkIdentitySet ident = new NetworkIdentitySet(in);
|
||||
|
||||
final int size = in.readInt();
|
||||
for (int j = 0; j < size; j++) {
|
||||
final int uid = in.readInt();
|
||||
final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
|
||||
: SET_DEFAULT;
|
||||
final int tag = in.readInt();
|
||||
|
||||
final Key key = new Key(ident, uid, set, tag);
|
||||
final NetworkStatsHistory history = new NetworkStatsHistory(in);
|
||||
|
||||
if ((tag == TAG_NONE) != onlyTags) {
|
||||
recordHistory(key, history);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new ProtocolException("unexpected version: " + version);
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// missing stats is okay, probably first boot
|
||||
} finally {
|
||||
IoUtils.closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any {@link NetworkStatsHistory} attributed to the requested UID,
|
||||
* moving any {@link NetworkStats#TAG_NONE} series to
|
||||
* {@link TrafficStats#UID_REMOVED}.
|
||||
* @hide
|
||||
*/
|
||||
public void removeUids(int[] uids) {
|
||||
final ArrayList<Key> knownKeys = new ArrayList<>();
|
||||
knownKeys.addAll(mStats.keySet());
|
||||
|
||||
// migrate all UID stats into special "removed" bucket
|
||||
for (Key key : knownKeys) {
|
||||
if (CollectionUtils.contains(uids, key.uid)) {
|
||||
// only migrate combined TAG_NONE history
|
||||
if (key.tag == TAG_NONE) {
|
||||
final NetworkStatsHistory uidHistory = mStats.get(key);
|
||||
final NetworkStatsHistory removedHistory = findOrCreateHistory(
|
||||
key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
|
||||
removedHistory.recordEntireHistory(uidHistory);
|
||||
}
|
||||
mStats.remove(key);
|
||||
mDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
|
||||
if (startMillis < mStartMillis) mStartMillis = startMillis;
|
||||
if (endMillis > mEndMillis) mEndMillis = endMillis;
|
||||
mTotalBytes += totalBytes;
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
private int estimateBuckets() {
|
||||
return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
|
||||
/ mBucketDurationMillis);
|
||||
}
|
||||
|
||||
private ArrayList<Key> getSortedKeys() {
|
||||
final ArrayList<Key> keys = new ArrayList<>();
|
||||
keys.addAll(mStats.keySet());
|
||||
Collections.sort(keys, (left, right) -> Key.compare(left, right));
|
||||
return keys;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dump(IndentingPrintWriter pw) {
|
||||
for (Key key : getSortedKeys()) {
|
||||
pw.print("ident="); pw.print(key.ident.toString());
|
||||
pw.print(" uid="); pw.print(key.uid);
|
||||
pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
|
||||
pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
|
||||
|
||||
final NetworkStatsHistory history = mStats.get(key);
|
||||
pw.increaseIndent();
|
||||
history.dump(pw, true);
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dumpDebug(ProtoOutputStream proto, long tag) {
|
||||
final long start = proto.start(tag);
|
||||
|
||||
for (Key key : getSortedKeys()) {
|
||||
final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
|
||||
|
||||
// Key
|
||||
final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
|
||||
key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY);
|
||||
proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
|
||||
proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
|
||||
proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
|
||||
proto.end(startKey);
|
||||
|
||||
// Value
|
||||
final NetworkStatsHistory history = mStats.get(key);
|
||||
history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY);
|
||||
proto.end(startStats);
|
||||
}
|
||||
|
||||
proto.end(start);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dumpCheckin(PrintWriter pw, long start, long end) {
|
||||
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
|
||||
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
|
||||
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth");
|
||||
dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump all contained stats that match requested parameters, but group
|
||||
* together all matching {@link NetworkTemplate} under a single prefix.
|
||||
*/
|
||||
private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
|
||||
String groupPrefix) {
|
||||
final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
|
||||
|
||||
// Walk through all history, grouping by matching network templates
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
final NetworkStatsHistory value = mStats.valueAt(i);
|
||||
|
||||
if (!templateMatches(groupTemplate, key.ident)) continue;
|
||||
if (key.set >= NetworkStats.SET_DEBUG_START) continue;
|
||||
|
||||
final Key groupKey = new Key(null, key.uid, key.set, key.tag);
|
||||
NetworkStatsHistory groupHistory = grouped.get(groupKey);
|
||||
if (groupHistory == null) {
|
||||
groupHistory = new NetworkStatsHistory(value.getBucketDuration());
|
||||
grouped.put(groupKey, groupHistory);
|
||||
}
|
||||
groupHistory.recordHistory(value, start, end);
|
||||
}
|
||||
|
||||
for (int i = 0; i < grouped.size(); i++) {
|
||||
final Key key = grouped.keyAt(i);
|
||||
final NetworkStatsHistory value = grouped.valueAt(i);
|
||||
|
||||
if (value.size() == 0) continue;
|
||||
|
||||
pw.print("c,");
|
||||
pw.print(groupPrefix); pw.print(',');
|
||||
pw.print(key.uid); pw.print(',');
|
||||
pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
|
||||
pw.print(key.tag);
|
||||
pw.println();
|
||||
|
||||
value.dumpCheckin(pw);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
|
||||
* in the given {@link NetworkIdentitySet}.
|
||||
*/
|
||||
private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
|
||||
for (NetworkIdentity ident : identSet) {
|
||||
if (template.matches(ident)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all historical stats of the collection {@link NetworkStatsCollection}.
|
||||
*
|
||||
* @return All {@link NetworkStatsHistory} in this collection.
|
||||
*/
|
||||
@NonNull
|
||||
public Map<Key, NetworkStatsHistory> getEntries() {
|
||||
return new ArrayMap(mStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link NetworkStatsCollection}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final long mBucketDurationMillis;
|
||||
private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new Builder with given bucket duration.
|
||||
*
|
||||
* @param bucketDuration Duration of the buckets of the object, in milliseconds.
|
||||
*/
|
||||
public Builder(long bucketDurationMillis) {
|
||||
mBucketDurationMillis = bucketDurationMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add association of the history with the specified key in this map.
|
||||
*
|
||||
* @param key The object used to identify a network, see {@link Key}.
|
||||
* @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
|
||||
* @return The builder object.
|
||||
*/
|
||||
@NonNull
|
||||
public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
|
||||
@NonNull NetworkStatsHistory history) {
|
||||
Objects.requireNonNull(key);
|
||||
Objects.requireNonNull(history);
|
||||
final List<Entry> historyEntries = history.getEntries();
|
||||
|
||||
final NetworkStatsHistory.Builder historyBuilder =
|
||||
new NetworkStatsHistory.Builder(mBucketDurationMillis, historyEntries.size());
|
||||
for (Entry entry : historyEntries) {
|
||||
historyBuilder.addEntry(entry);
|
||||
}
|
||||
|
||||
mEntries.put(key, historyBuilder.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the instance of the {@link NetworkStatsCollection}.
|
||||
*
|
||||
* @return the built instance of {@link NetworkStatsCollection}.
|
||||
*/
|
||||
@NonNull
|
||||
public NetworkStatsCollection build() {
|
||||
final NetworkStatsCollection collection =
|
||||
new NetworkStatsCollection(mBucketDurationMillis);
|
||||
for (int i = 0; i < mEntries.size(); i++) {
|
||||
collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the identifier that associate with the {@link NetworkStatsHistory} object to identify
|
||||
* a certain record in the {@link NetworkStatsCollection} object.
|
||||
*/
|
||||
public static final class Key {
|
||||
/** @hide */
|
||||
public final NetworkIdentitySet ident;
|
||||
/** @hide */
|
||||
public final int uid;
|
||||
/** @hide */
|
||||
public final int set;
|
||||
/** @hide */
|
||||
public final int tag;
|
||||
|
||||
private final int mHashCode;
|
||||
|
||||
/**
|
||||
* Construct a {@link Key} object.
|
||||
*
|
||||
* @param ident a Set of {@link NetworkIdentity} that associated with the record.
|
||||
* @param uid Uid of the record.
|
||||
* @param set Set of the record, see {@code NetworkStats#SET_*}.
|
||||
* @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}.
|
||||
*/
|
||||
public Key(@NonNull Set<NetworkIdentity> ident, int uid, @State int set, int tag) {
|
||||
this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) {
|
||||
this.ident = Objects.requireNonNull(ident);
|
||||
this.uid = uid;
|
||||
this.set = set;
|
||||
this.tag = tag;
|
||||
mHashCode = Objects.hash(ident, uid, set, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj instanceof Key) {
|
||||
final Key key = (Key) obj;
|
||||
return uid == key.uid && set == key.set && tag == key.tag
|
||||
&& Objects.equals(ident, key.ident);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static int compare(@NonNull Key left, @NonNull Key right) {
|
||||
Objects.requireNonNull(left);
|
||||
Objects.requireNonNull(right);
|
||||
int res = 0;
|
||||
if (left.ident != null && right.ident != null) {
|
||||
res = NetworkIdentitySet.compare(left.ident, right.ident);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.uid, right.uid);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.set, right.set);
|
||||
}
|
||||
if (res == 0) {
|
||||
res = Integer.compare(left.tag, right.tag);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
framework-t/src/android/net/NetworkStatsHistory.aidl
Normal file
19
framework-t/src/android/net/NetworkStatsHistory.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2011, 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.net;
|
||||
|
||||
parcelable NetworkStatsHistory;
|
||||
1162
framework-t/src/android/net/NetworkStatsHistory.java
Normal file
1162
framework-t/src/android/net/NetworkStatsHistory.java
Normal file
File diff suppressed because it is too large
Load Diff
1119
framework-t/src/android/net/NetworkTemplate.java
Normal file
1119
framework-t/src/android/net/NetworkTemplate.java
Normal file
File diff suppressed because it is too large
Load Diff
1148
framework-t/src/android/net/TrafficStats.java
Normal file
1148
framework-t/src/android/net/TrafficStats.java
Normal file
File diff suppressed because it is too large
Load Diff
19
framework-t/src/android/net/UnderlyingNetworkInfo.aidl
Normal file
19
framework-t/src/android/net/UnderlyingNetworkInfo.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.net;
|
||||
|
||||
parcelable UnderlyingNetworkInfo;
|
||||
135
framework-t/src/android/net/UnderlyingNetworkInfo.java
Normal file
135
framework-t/src/android/net/UnderlyingNetworkInfo.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A lightweight container used to carry information on the networks that underly a given
|
||||
* virtual network.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = MODULE_LIBRARIES)
|
||||
public final class UnderlyingNetworkInfo implements Parcelable {
|
||||
/** The owner of this network. */
|
||||
private final int mOwnerUid;
|
||||
|
||||
/** The interface name of this network. */
|
||||
@NonNull
|
||||
private final String mIface;
|
||||
|
||||
/** The names of the interfaces underlying this network. */
|
||||
@NonNull
|
||||
private final List<String> mUnderlyingIfaces;
|
||||
|
||||
public UnderlyingNetworkInfo(int ownerUid, @NonNull String iface,
|
||||
@NonNull List<String> underlyingIfaces) {
|
||||
Objects.requireNonNull(iface);
|
||||
Objects.requireNonNull(underlyingIfaces);
|
||||
mOwnerUid = ownerUid;
|
||||
mIface = iface;
|
||||
mUnderlyingIfaces = Collections.unmodifiableList(new ArrayList<>(underlyingIfaces));
|
||||
}
|
||||
|
||||
private UnderlyingNetworkInfo(@NonNull Parcel in) {
|
||||
mOwnerUid = in.readInt();
|
||||
mIface = in.readString();
|
||||
List<String> underlyingIfaces = new ArrayList<>();
|
||||
in.readList(underlyingIfaces, null /*classLoader*/);
|
||||
mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
|
||||
}
|
||||
|
||||
/** Get the owner of this network. */
|
||||
public int getOwnerUid() {
|
||||
return mOwnerUid;
|
||||
}
|
||||
|
||||
/** Get the interface name of this network. */
|
||||
@NonNull
|
||||
public String getInterface() {
|
||||
return mIface;
|
||||
}
|
||||
|
||||
/** Get the names of the interfaces underlying this network. */
|
||||
@NonNull
|
||||
public List<String> getUnderlyingInterfaces() {
|
||||
return mUnderlyingIfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UnderlyingNetworkInfo{"
|
||||
+ "ownerUid=" + mOwnerUid
|
||||
+ ", iface='" + mIface + '\''
|
||||
+ ", underlyingIfaces='" + mUnderlyingIfaces.toString() + '\''
|
||||
+ '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mOwnerUid);
|
||||
dest.writeString(mIface);
|
||||
dest.writeList(mUnderlyingIfaces);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<UnderlyingNetworkInfo> CREATOR =
|
||||
new Parcelable.Creator<UnderlyingNetworkInfo>() {
|
||||
@NonNull
|
||||
@Override
|
||||
public UnderlyingNetworkInfo createFromParcel(@NonNull Parcel in) {
|
||||
return new UnderlyingNetworkInfo(in);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UnderlyingNetworkInfo[] newArray(int size) {
|
||||
return new UnderlyingNetworkInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof UnderlyingNetworkInfo)) return false;
|
||||
final UnderlyingNetworkInfo that = (UnderlyingNetworkInfo) o;
|
||||
return mOwnerUid == that.getOwnerUid()
|
||||
&& Objects.equals(mIface, that.getInterface())
|
||||
&& Objects.equals(mUnderlyingIfaces, that.getUnderlyingInterfaces());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mOwnerUid, mIface, mUnderlyingIfaces);
|
||||
}
|
||||
}
|
||||
29
framework-t/src/android/net/netstats/IUsageCallback.aidl
Normal file
29
framework-t/src/android/net/netstats/IUsageCallback.aidl
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.net.netstats;
|
||||
|
||||
import android.net.DataUsageRequest;
|
||||
|
||||
/**
|
||||
* Interface for NetworkStatsService to notify events to the callers of registerUsageCallback.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IUsageCallback {
|
||||
void onThresholdReached(in DataUsageRequest request);
|
||||
void onCallbackReleased(in DataUsageRequest request);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.net.netstats.provider;
|
||||
|
||||
/**
|
||||
* Interface for NetworkStatsService to query network statistics and set data limits.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface INetworkStatsProvider {
|
||||
void onRequestStatsUpdate(int token);
|
||||
void onSetAlert(long quotaBytes);
|
||||
void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.net.netstats.provider;
|
||||
|
||||
import android.net.NetworkStats;
|
||||
|
||||
/**
|
||||
* Interface for implementor of {@link INetworkStatsProviderCallback} to push events
|
||||
* such as network statistics update or notify limit reached.
|
||||
* @hide
|
||||
*/
|
||||
oneway interface INetworkStatsProviderCallback {
|
||||
void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
|
||||
void notifyAlertReached();
|
||||
void notifyWarningReached();
|
||||
void notifyLimitReached();
|
||||
void unregister();
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.net.netstats.provider;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.net.NetworkStats;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* A base class that allows external modules to implement a custom network statistics provider.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public abstract class NetworkStatsProvider {
|
||||
/**
|
||||
* A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit}
|
||||
* indicates there is no limit.
|
||||
*/
|
||||
public static final int QUOTA_UNLIMITED = -1;
|
||||
|
||||
@NonNull private final INetworkStatsProvider mProviderBinder =
|
||||
new INetworkStatsProvider.Stub() {
|
||||
|
||||
@Override
|
||||
public void onRequestStatsUpdate(int token) {
|
||||
NetworkStatsProvider.this.onRequestStatsUpdate(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlert(long quotaBytes) {
|
||||
NetworkStatsProvider.this.onSetAlert(quotaBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {
|
||||
NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes);
|
||||
}
|
||||
};
|
||||
|
||||
// The binder given by the service when successfully registering. Only null before registering,
|
||||
// never null once non-null.
|
||||
@Nullable
|
||||
private INetworkStatsProviderCallback mProviderCbBinder;
|
||||
|
||||
/**
|
||||
* Return the binder invoked by the service and redirect function calls to the overridden
|
||||
* methods.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public INetworkStatsProvider getProviderBinder() {
|
||||
return mProviderBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the binder that was returned by the service when successfully registering. Note that
|
||||
* the provider cannot be re-registered. Hence this method can only be called once per provider.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setProviderCallbackBinder(@NonNull INetworkStatsProviderCallback binder) {
|
||||
if (mProviderCbBinder != null) {
|
||||
throw new IllegalArgumentException("provider is already registered");
|
||||
}
|
||||
mProviderCbBinder = binder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the binder that was returned by the service when successfully registering. Or null if the
|
||||
* provider was never registered.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
public INetworkStatsProviderCallback getProviderCallbackBinder() {
|
||||
return mProviderCbBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the binder that was returned by the service when successfully registering. Throw an
|
||||
* {@link IllegalStateException} if the provider is not registered.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public INetworkStatsProviderCallback getProviderCallbackBinderOrThrow() {
|
||||
if (mProviderCbBinder == null) {
|
||||
throw new IllegalStateException("the provider is not registered");
|
||||
}
|
||||
return mProviderCbBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the system of new network statistics.
|
||||
*
|
||||
* Send the network statistics recorded since the last call to {@link #notifyStatsUpdated}. Must
|
||||
* be called as soon as possible after {@link NetworkStatsProvider#onRequestStatsUpdate(int)}
|
||||
* being called. Responding later increases the probability stats will be dropped. The
|
||||
* provider can also call this whenever it wants to reports new stats for any reason.
|
||||
* Note that the system will not necessarily immediately propagate the statistics to
|
||||
* reflect the update.
|
||||
*
|
||||
* @param token the token under which these stats were gathered. Providers can call this method
|
||||
* with the current token as often as they want, until the token changes.
|
||||
* {@see NetworkStatsProvider#onRequestStatsUpdate()}
|
||||
* @param ifaceStats the {@link NetworkStats} per interface to be reported.
|
||||
* The provider should not include any traffic that is already counted by
|
||||
* kernel interface counters.
|
||||
* @param uidStats the same stats as above, but counts {@link NetworkStats}
|
||||
* per uid.
|
||||
*/
|
||||
public void notifyStatsUpdated(int token, @NonNull NetworkStats ifaceStats,
|
||||
@NonNull NetworkStats uidStats) {
|
||||
try {
|
||||
getProviderCallbackBinderOrThrow().notifyStatsUpdated(token, ifaceStats, uidStats);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify system that the quota set by {@code onSetAlert} has been reached.
|
||||
*/
|
||||
public void notifyAlertReached() {
|
||||
try {
|
||||
getProviderCallbackBinderOrThrow().notifyAlertReached();
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
|
||||
*/
|
||||
public void notifyWarningReached() {
|
||||
try {
|
||||
// Reuse the code path to notify warning reached with limit reached
|
||||
// since framework handles them in the same way.
|
||||
getProviderCallbackBinderOrThrow().notifyWarningReached();
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify system that the limit set by {@link #onSetLimit} or limit set by
|
||||
* {@link #onSetWarningAndLimit} has been reached.
|
||||
*/
|
||||
public void notifyLimitReached() {
|
||||
try {
|
||||
getProviderCallbackBinderOrThrow().notifyLimitReached();
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@code NetworkStatsService} when it requires to know updated stats.
|
||||
* The provider MUST respond by calling {@link #notifyStatsUpdated} as soon as possible.
|
||||
* Responding later increases the probability stats will be dropped. Memory allowing, the
|
||||
* system will try to take stats into account up to one minute after calling
|
||||
* {@link #onRequestStatsUpdate}.
|
||||
*
|
||||
* @param token a positive number identifying the new state of the system under which
|
||||
* {@link NetworkStats} have to be gathered from now on. When this is called,
|
||||
* custom implementations of providers MUST tally and report the latest stats with
|
||||
* the previous token, under which stats were being gathered so far.
|
||||
*/
|
||||
public abstract void onRequestStatsUpdate(int token);
|
||||
|
||||
/**
|
||||
* Called by {@code NetworkStatsService} when setting the interface quota for the specified
|
||||
* upstream interface. When this is called, the custom implementation should block all egress
|
||||
* packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have
|
||||
* been reached, and MUST respond to it by calling
|
||||
* {@link NetworkStatsProvider#notifyLimitReached()}.
|
||||
*
|
||||
* @param iface the interface requiring the operation.
|
||||
* @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
|
||||
* from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
|
||||
*/
|
||||
public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
|
||||
|
||||
/**
|
||||
* Called by {@code NetworkStatsService} when setting the interface quotas for the specified
|
||||
* upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system
|
||||
* will not call {@link #onSetLimit}. When this method is called, the implementation
|
||||
* should behave as follows:
|
||||
* 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on
|
||||
* {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}.
|
||||
* 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on
|
||||
* {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}.
|
||||
*
|
||||
* @param iface the interface requiring the operation.
|
||||
* @param warningBytes the warning defined as the number of bytes, starting from zero and
|
||||
* counting from now. A value of {@link #QUOTA_UNLIMITED} indicates
|
||||
* there is no warning.
|
||||
* @param limitBytes the limit defined as the number of bytes, starting from zero and counting
|
||||
* from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
|
||||
*/
|
||||
public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
|
||||
// Backward compatibility for those who didn't override this function.
|
||||
onSetLimit(iface, limitBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
|
||||
* MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes
|
||||
* have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should
|
||||
* not block all egress packets.
|
||||
*
|
||||
* @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
|
||||
* from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert.
|
||||
*/
|
||||
public abstract void onSetAlert(long quotaBytes);
|
||||
}
|
||||
30
framework-t/src/android/net/nsd/INsdManager.aidl
Normal file
30
framework-t/src/android/net/nsd/INsdManager.aidl
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2021, 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.net.nsd;
|
||||
|
||||
import android.net.nsd.INsdManagerCallback;
|
||||
import android.net.nsd.INsdServiceConnector;
|
||||
import android.os.Messenger;
|
||||
|
||||
/**
|
||||
* Interface that NsdService implements to connect NsdManager clients.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface INsdManager {
|
||||
INsdServiceConnector connect(INsdManagerCallback cb);
|
||||
}
|
||||
39
framework-t/src/android/net/nsd/INsdManagerCallback.aidl
Normal file
39
framework-t/src/android/net/nsd/INsdManagerCallback.aidl
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2021, 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.net.nsd;
|
||||
|
||||
import android.os.Messenger;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
|
||||
/**
|
||||
* Callbacks from NsdService to NsdManager
|
||||
* @hide
|
||||
*/
|
||||
oneway interface INsdManagerCallback {
|
||||
void onDiscoverServicesStarted(int listenerKey, in NsdServiceInfo info);
|
||||
void onDiscoverServicesFailed(int listenerKey, int error);
|
||||
void onServiceFound(int listenerKey, in NsdServiceInfo info);
|
||||
void onServiceLost(int listenerKey, in NsdServiceInfo info);
|
||||
void onStopDiscoveryFailed(int listenerKey, int error);
|
||||
void onStopDiscoverySucceeded(int listenerKey);
|
||||
void onRegisterServiceFailed(int listenerKey, int error);
|
||||
void onRegisterServiceSucceeded(int listenerKey, in NsdServiceInfo info);
|
||||
void onUnregisterServiceFailed(int listenerKey, int error);
|
||||
void onUnregisterServiceSucceeded(int listenerKey);
|
||||
void onResolveServiceFailed(int listenerKey, int error);
|
||||
void onResolveServiceSucceeded(int listenerKey, in NsdServiceInfo info);
|
||||
}
|
||||
35
framework-t/src/android/net/nsd/INsdServiceConnector.aidl
Normal file
35
framework-t/src/android/net/nsd/INsdServiceConnector.aidl
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2021, 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.net.nsd;
|
||||
|
||||
import android.net.nsd.INsdManagerCallback;
|
||||
import android.net.nsd.NsdServiceInfo;
|
||||
import android.os.Messenger;
|
||||
|
||||
/**
|
||||
* Interface that NsdService implements for each NsdManager client.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface INsdServiceConnector {
|
||||
void registerService(int listenerKey, in NsdServiceInfo serviceInfo);
|
||||
void unregisterService(int listenerKey);
|
||||
void discoverServices(int listenerKey, in NsdServiceInfo serviceInfo);
|
||||
void stopDiscovery(int listenerKey);
|
||||
void resolveService(int listenerKey, in NsdServiceInfo serviceInfo);
|
||||
void startDaemon();
|
||||
}
|
||||
1083
framework-t/src/android/net/nsd/NsdManager.java
Normal file
1083
framework-t/src/android/net/nsd/NsdManager.java
Normal file
File diff suppressed because it is too large
Load Diff
418
framework-t/src/android/net/nsd/NsdServiceInfo.java
Normal file
418
framework-t/src/android/net/nsd/NsdServiceInfo.java
Normal file
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.net.nsd;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.net.Network;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class representing service information for network service discovery
|
||||
* {@see NsdManager}
|
||||
*/
|
||||
public final class NsdServiceInfo implements Parcelable {
|
||||
|
||||
private static final String TAG = "NsdServiceInfo";
|
||||
|
||||
private String mServiceName;
|
||||
|
||||
private String mServiceType;
|
||||
|
||||
private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<>();
|
||||
|
||||
private InetAddress mHost;
|
||||
|
||||
private int mPort;
|
||||
|
||||
@Nullable
|
||||
private Network mNetwork;
|
||||
|
||||
public NsdServiceInfo() {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public NsdServiceInfo(String sn, String rt) {
|
||||
mServiceName = sn;
|
||||
mServiceType = rt;
|
||||
}
|
||||
|
||||
/** Get the service name */
|
||||
public String getServiceName() {
|
||||
return mServiceName;
|
||||
}
|
||||
|
||||
/** Set the service name */
|
||||
public void setServiceName(String s) {
|
||||
mServiceName = s;
|
||||
}
|
||||
|
||||
/** Get the service type */
|
||||
public String getServiceType() {
|
||||
return mServiceType;
|
||||
}
|
||||
|
||||
/** Set the service type */
|
||||
public void setServiceType(String s) {
|
||||
mServiceType = s;
|
||||
}
|
||||
|
||||
/** Get the host address. The host address is valid for a resolved service. */
|
||||
public InetAddress getHost() {
|
||||
return mHost;
|
||||
}
|
||||
|
||||
/** Set the host address */
|
||||
public void setHost(InetAddress s) {
|
||||
mHost = s;
|
||||
}
|
||||
|
||||
/** Get port number. The port number is valid for a resolved service. */
|
||||
public int getPort() {
|
||||
return mPort;
|
||||
}
|
||||
|
||||
/** Set port number */
|
||||
public void setPort(int p) {
|
||||
mPort = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack txt information from a base-64 encoded byte array.
|
||||
*
|
||||
* @param rawRecords The raw base64 encoded records string read from netd.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setTxtRecords(@NonNull String rawRecords) {
|
||||
byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
|
||||
|
||||
// There can be multiple TXT records after each other. Each record has to following format:
|
||||
//
|
||||
// byte type required meaning
|
||||
// ------------------- ------------------- -------- ----------------------------------
|
||||
// 0 unsigned 8 bit yes size of record excluding this byte
|
||||
// 1 - n ASCII but not '=' yes key
|
||||
// n + 1 '=' optional separator of key and value
|
||||
// n + 2 - record size uninterpreted bytes optional value
|
||||
//
|
||||
// Example legal records:
|
||||
// [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
|
||||
// [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
|
||||
// [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
|
||||
//
|
||||
// Example corrupted records
|
||||
// [3, =, 1, 2] <- key is empty
|
||||
// [3, 0, =, 2] <- key contains non-ASCII character. We handle this by replacing the
|
||||
// invalid characters instead of skipping the record.
|
||||
// [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
|
||||
// handle this by reducing the length of the record as needed.
|
||||
int pos = 0;
|
||||
while (pos < txtRecordsRawBytes.length) {
|
||||
// recordLen is an unsigned 8 bit value
|
||||
int recordLen = txtRecordsRawBytes[pos] & 0xff;
|
||||
pos += 1;
|
||||
|
||||
try {
|
||||
if (recordLen == 0) {
|
||||
throw new IllegalArgumentException("Zero sized txt record");
|
||||
} else if (pos + recordLen > txtRecordsRawBytes.length) {
|
||||
Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
|
||||
recordLen = txtRecordsRawBytes.length - pos;
|
||||
}
|
||||
|
||||
// Decode key-value records
|
||||
String key = null;
|
||||
byte[] value = null;
|
||||
int valueLen = 0;
|
||||
for (int i = pos; i < pos + recordLen; i++) {
|
||||
if (key == null) {
|
||||
if (txtRecordsRawBytes[i] == '=') {
|
||||
key = new String(txtRecordsRawBytes, pos, i - pos,
|
||||
StandardCharsets.US_ASCII);
|
||||
}
|
||||
} else {
|
||||
if (value == null) {
|
||||
value = new byte[recordLen - key.length() - 1];
|
||||
}
|
||||
value[valueLen] = txtRecordsRawBytes[i];
|
||||
valueLen++;
|
||||
}
|
||||
}
|
||||
|
||||
// If '=' was not found we have a boolean record
|
||||
if (key == null) {
|
||||
key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
// Empty keys are not allowed (RFC6763 6.4)
|
||||
throw new IllegalArgumentException("Invalid txt record (key is empty)");
|
||||
}
|
||||
|
||||
if (getAttributes().containsKey(key)) {
|
||||
// When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
|
||||
throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
|
||||
}
|
||||
|
||||
setAttribute(key, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
|
||||
}
|
||||
|
||||
pos += recordLen;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@UnsupportedAppUsage
|
||||
public void setAttribute(String key, byte[] value) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
throw new IllegalArgumentException("Key cannot be empty");
|
||||
}
|
||||
|
||||
// Key must be printable US-ASCII, excluding =.
|
||||
for (int i = 0; i < key.length(); ++i) {
|
||||
char character = key.charAt(i);
|
||||
if (character < 0x20 || character > 0x7E) {
|
||||
throw new IllegalArgumentException("Key strings must be printable US-ASCII");
|
||||
} else if (character == 0x3D) {
|
||||
throw new IllegalArgumentException("Key strings must not include '='");
|
||||
}
|
||||
}
|
||||
|
||||
// Key length + value length must be < 255.
|
||||
if (key.length() + (value == null ? 0 : value.length) >= 255) {
|
||||
throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
|
||||
}
|
||||
|
||||
// Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
|
||||
if (key.length() > 9) {
|
||||
Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
|
||||
}
|
||||
|
||||
// Check against total TXT record size limits.
|
||||
// Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
|
||||
int txtRecordSize = getTxtRecordSize();
|
||||
int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
|
||||
if (futureSize > 1300) {
|
||||
throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
|
||||
} else if (futureSize > 400) {
|
||||
Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
|
||||
}
|
||||
|
||||
mTxtRecord.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a service attribute as a key/value pair.
|
||||
*
|
||||
* <p> Service attributes are included as DNS-SD TXT record pairs.
|
||||
*
|
||||
* <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
|
||||
* be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
|
||||
*
|
||||
* <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
|
||||
* {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
|
||||
* first value.
|
||||
*/
|
||||
public void setAttribute(String key, String value) {
|
||||
try {
|
||||
setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException("Value must be UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove an attribute by key */
|
||||
public void removeAttribute(String key) {
|
||||
mTxtRecord.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve attributes as a map of String keys to byte[] values. The attributes map is only
|
||||
* valid for a resolved service.
|
||||
*
|
||||
* <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
|
||||
* {@link #removeAttribute}.
|
||||
*/
|
||||
public Map<String, byte[]> getAttributes() {
|
||||
return Collections.unmodifiableMap(mTxtRecord);
|
||||
}
|
||||
|
||||
private int getTxtRecordSize() {
|
||||
int txtRecordSize = 0;
|
||||
for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
|
||||
txtRecordSize += 2; // One for the length byte, one for the = between key and value.
|
||||
txtRecordSize += entry.getKey().length();
|
||||
byte[] value = entry.getValue();
|
||||
txtRecordSize += value == null ? 0 : value.length;
|
||||
}
|
||||
return txtRecordSize;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public @NonNull byte[] getTxtRecord() {
|
||||
int txtRecordSize = getTxtRecordSize();
|
||||
if (txtRecordSize == 0) {
|
||||
return new byte[]{};
|
||||
}
|
||||
|
||||
byte[] txtRecord = new byte[txtRecordSize];
|
||||
int ptr = 0;
|
||||
for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
byte[] value = entry.getValue();
|
||||
|
||||
// One byte to record the length of this key/value pair.
|
||||
txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
|
||||
|
||||
// The key, in US-ASCII.
|
||||
// Note: use the StandardCharsets const here because it doesn't raise exceptions and we
|
||||
// already know the key is ASCII at this point.
|
||||
System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
|
||||
key.length());
|
||||
ptr += key.length();
|
||||
|
||||
// US-ASCII '=' character.
|
||||
txtRecord[ptr++] = (byte)'=';
|
||||
|
||||
// The value, as any raw bytes.
|
||||
if (value != null) {
|
||||
System.arraycopy(value, 0, txtRecord, ptr, value.length);
|
||||
ptr += value.length;
|
||||
}
|
||||
}
|
||||
return txtRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the network where the service can be found.
|
||||
*
|
||||
* This is never null if this {@link NsdServiceInfo} was obtained from
|
||||
* {@link NsdManager#discoverServices} or {@link NsdManager#resolveService}.
|
||||
*/
|
||||
@Nullable
|
||||
public Network getNetwork() {
|
||||
return mNetwork;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network where the service can be found.
|
||||
* @param network The network, or null to search for, or to announce, the service on all
|
||||
* connected networks.
|
||||
*/
|
||||
public void setNetwork(@Nullable Network network) {
|
||||
mNetwork = network;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("name: ").append(mServiceName)
|
||||
.append(", type: ").append(mServiceType)
|
||||
.append(", host: ").append(mHost)
|
||||
.append(", port: ").append(mPort)
|
||||
.append(", network: ").append(mNetwork);
|
||||
|
||||
byte[] txtRecord = getTxtRecord();
|
||||
sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(mServiceName);
|
||||
dest.writeString(mServiceType);
|
||||
if (mHost != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeByteArray(mHost.getAddress());
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeInt(mPort);
|
||||
|
||||
// TXT record key/value pairs.
|
||||
dest.writeInt(mTxtRecord.size());
|
||||
for (String key : mTxtRecord.keySet()) {
|
||||
byte[] value = mTxtRecord.get(key);
|
||||
if (value != null) {
|
||||
dest.writeInt(1);
|
||||
dest.writeInt(value.length);
|
||||
dest.writeByteArray(value);
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeString(key);
|
||||
}
|
||||
|
||||
dest.writeParcelable(mNetwork, 0);
|
||||
}
|
||||
|
||||
/** Implement the Parcelable interface */
|
||||
public static final @android.annotation.NonNull Creator<NsdServiceInfo> CREATOR =
|
||||
new Creator<NsdServiceInfo>() {
|
||||
public NsdServiceInfo createFromParcel(Parcel in) {
|
||||
NsdServiceInfo info = new NsdServiceInfo();
|
||||
info.mServiceName = in.readString();
|
||||
info.mServiceType = in.readString();
|
||||
|
||||
if (in.readInt() == 1) {
|
||||
try {
|
||||
info.mHost = InetAddress.getByAddress(in.createByteArray());
|
||||
} catch (java.net.UnknownHostException e) {}
|
||||
}
|
||||
|
||||
info.mPort = in.readInt();
|
||||
|
||||
// TXT record key/value pairs.
|
||||
int recordCount = in.readInt();
|
||||
for (int i = 0; i < recordCount; ++i) {
|
||||
byte[] valueArray = null;
|
||||
if (in.readInt() == 1) {
|
||||
int valueLength = in.readInt();
|
||||
valueArray = new byte[valueLength];
|
||||
in.readByteArray(valueArray);
|
||||
}
|
||||
info.mTxtRecord.put(in.readString(), valueArray);
|
||||
}
|
||||
info.mNetwork = in.readParcelable(null, Network.class);
|
||||
return info;
|
||||
}
|
||||
|
||||
public NsdServiceInfo[] newArray(int size) {
|
||||
return new NsdServiceInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,25 @@ package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
// Include build rules from Sources.bp
|
||||
// sc-mainline-prod only: do not include Sources.bp
|
||||
// build = ["Sources.bp"]
|
||||
|
||||
filegroup {
|
||||
name: "service-connectivity-tiramisu-sources",
|
||||
srcs: [
|
||||
// sc-mainline-prod only: Building T sources is disabled on this branch.
|
||||
// "src/**/*.java",
|
||||
"src/com/android/server/ConnectivityServiceInitializer.java",
|
||||
// filegroup contains empty stubs on sc-mainline-prod.
|
||||
":services.connectivity-tiramisu-updatable-sources",
|
||||
],
|
||||
visibility: ["//visibility:private"],
|
||||
}
|
||||
// The above filegroup can be used to specify different sources depending
|
||||
// on the branch, while minimizing merge conflicts in the rest of the
|
||||
// build rules.
|
||||
|
||||
// This builds T+ services depending on framework-connectivity-t
|
||||
// hidden symbols separately from the S+ services, to ensure that S+
|
||||
// services cannot accidentally depend on T+ hidden symbols from
|
||||
@@ -29,9 +48,7 @@ java_library {
|
||||
// TODO(b/210962470): Bump this to at least S, and then T.
|
||||
min_sdk_version: "30",
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
":ethernet-service-updatable-sources",
|
||||
":services.connectivity-tiramisu-updatable-sources",
|
||||
":service-connectivity-tiramisu-sources",
|
||||
],
|
||||
libs: [
|
||||
"framework-annotations-lib",
|
||||
|
||||
95
service-t/Sources.bp
Normal file
95
service-t/Sources.bp
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// Copyright (C) 2021 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.
|
||||
//
|
||||
|
||||
// NetworkStats related libraries.
|
||||
|
||||
filegroup {
|
||||
name: "services.connectivity-netstats-sources",
|
||||
srcs: [
|
||||
"src/com/android/server/net/NetworkIdentity*.java",
|
||||
"src/com/android/server/net/NetworkStats*.java",
|
||||
"src/com/android/server/net/BpfInterfaceMapUpdater.java",
|
||||
"src/com/android/server/net/InterfaceMapValue.java",
|
||||
"src/com/android/server/net/CookieTagMapKey.java",
|
||||
"src/com/android/server/net/CookieTagMapValue.java",
|
||||
"src/com/android/server/net/StatsMapKey.java",
|
||||
"src/com/android/server/net/StatsMapValue.java",
|
||||
"src/com/android/server/net/UidStatsMapKey.java",
|
||||
],
|
||||
path: "src",
|
||||
visibility: [
|
||||
"//visibility:private",
|
||||
],
|
||||
}
|
||||
|
||||
// For test code only.
|
||||
filegroup {
|
||||
name: "lib_networkStatsFactory_native",
|
||||
srcs: [
|
||||
"jni/com_android_server_net_NetworkStatsFactory.cpp",
|
||||
],
|
||||
path: "jni",
|
||||
visibility: [
|
||||
"//packages/modules/Connectivity:__subpackages__",
|
||||
],
|
||||
}
|
||||
|
||||
filegroup {
|
||||
name: "services.connectivity-netstats-jni-sources",
|
||||
srcs: [
|
||||
"jni/com_android_server_net_NetworkStatsFactory.cpp",
|
||||
"jni/com_android_server_net_NetworkStatsService.cpp",
|
||||
],
|
||||
path: "jni",
|
||||
visibility: [
|
||||
"//packages/modules/Connectivity:__subpackages__",
|
||||
],
|
||||
}
|
||||
|
||||
// Connectivity-T common libraries.
|
||||
|
||||
// TODO: remove this empty filegroup.
|
||||
filegroup {
|
||||
name: "services.connectivity-tiramisu-sources",
|
||||
srcs: [],
|
||||
path: "src",
|
||||
visibility: ["//frameworks/base/services/core"],
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libcom_android_net_module_util_jni",
|
||||
min_sdk_version: "30",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
"-Wthread-safety",
|
||||
],
|
||||
srcs: [
|
||||
"jni/onload.cpp",
|
||||
],
|
||||
stl: "libc++_static",
|
||||
static_libs: [
|
||||
"libnet_utils_device_common_bpfjni",
|
||||
],
|
||||
shared_libs: [
|
||||
"liblog",
|
||||
"libnativehelper",
|
||||
],
|
||||
apex_available: [
|
||||
"//apex_available:platform",
|
||||
],
|
||||
}
|
||||
362
service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
Normal file
362
service-t/jni/com_android_server_net_NetworkStatsFactory.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "NetworkStats"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <nativehelper/ScopedUtfChars.h>
|
||||
#include <nativehelper/ScopedLocalRef.h>
|
||||
#include <nativehelper/ScopedPrimitiveArray.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/misc.h>
|
||||
|
||||
#include "android-base/unique_fd.h"
|
||||
#include "bpf/BpfUtils.h"
|
||||
#include "netdbpf/BpfNetworkStats.h"
|
||||
|
||||
using android::bpf::parseBpfNetworkStatsDetail;
|
||||
using android::bpf::stats_line;
|
||||
|
||||
namespace android {
|
||||
|
||||
static jclass gStringClass;
|
||||
|
||||
static struct {
|
||||
jfieldID size;
|
||||
jfieldID capacity;
|
||||
jfieldID iface;
|
||||
jfieldID uid;
|
||||
jfieldID set;
|
||||
jfieldID tag;
|
||||
jfieldID metered;
|
||||
jfieldID roaming;
|
||||
jfieldID defaultNetwork;
|
||||
jfieldID rxBytes;
|
||||
jfieldID rxPackets;
|
||||
jfieldID txBytes;
|
||||
jfieldID txPackets;
|
||||
jfieldID operations;
|
||||
} gNetworkStatsClassInfo;
|
||||
|
||||
static jobjectArray get_string_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
|
||||
{
|
||||
if (!grow) {
|
||||
jobjectArray array = (jobjectArray)env->GetObjectField(obj, field);
|
||||
if (array != NULL) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
return env->NewObjectArray(size, gStringClass, NULL);
|
||||
}
|
||||
|
||||
static jintArray get_int_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
|
||||
{
|
||||
if (!grow) {
|
||||
jintArray array = (jintArray)env->GetObjectField(obj, field);
|
||||
if (array != NULL) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
return env->NewIntArray(size);
|
||||
}
|
||||
|
||||
static jlongArray get_long_array(JNIEnv* env, jobject obj, jfieldID field, int size, bool grow)
|
||||
{
|
||||
if (!grow) {
|
||||
jlongArray array = (jlongArray)env->GetObjectField(obj, field);
|
||||
if (array != NULL) {
|
||||
return array;
|
||||
}
|
||||
}
|
||||
return env->NewLongArray(size);
|
||||
}
|
||||
|
||||
static int legacyReadNetworkStatsDetail(std::vector<stats_line>* lines,
|
||||
const std::vector<std::string>& limitIfaces,
|
||||
int limitTag, int limitUid, const char* path) {
|
||||
FILE* fp = fopen(path, "re");
|
||||
if (fp == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int lastIdx = 1;
|
||||
int idx;
|
||||
char buffer[384];
|
||||
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
|
||||
stats_line s;
|
||||
int64_t rawTag;
|
||||
char* pos = buffer;
|
||||
char* endPos;
|
||||
// First field is the index.
|
||||
idx = (int)strtol(pos, &endPos, 10);
|
||||
//ALOGI("Index #%d: %s", idx, buffer);
|
||||
if (pos == endPos) {
|
||||
// Skip lines that don't start with in index. In particular,
|
||||
// this will skip the initial header line.
|
||||
continue;
|
||||
}
|
||||
if (idx != lastIdx + 1) {
|
||||
ALOGE("inconsistent idx=%d after lastIdx=%d: %s", idx, lastIdx, buffer);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
lastIdx = idx;
|
||||
pos = endPos;
|
||||
// Skip whitespace.
|
||||
while (*pos == ' ') {
|
||||
pos++;
|
||||
}
|
||||
// Next field is iface.
|
||||
int ifaceIdx = 0;
|
||||
while (*pos != ' ' && *pos != 0 && ifaceIdx < (int)(sizeof(s.iface)-1)) {
|
||||
s.iface[ifaceIdx] = *pos;
|
||||
ifaceIdx++;
|
||||
pos++;
|
||||
}
|
||||
if (*pos != ' ') {
|
||||
ALOGE("bad iface: %s", buffer);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
s.iface[ifaceIdx] = 0;
|
||||
if (limitIfaces.size() > 0) {
|
||||
// Is this an iface the caller is interested in?
|
||||
int i = 0;
|
||||
while (i < (int)limitIfaces.size()) {
|
||||
if (limitIfaces[i] == s.iface) {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i >= (int)limitIfaces.size()) {
|
||||
// Nothing matched; skip this line.
|
||||
//ALOGI("skipping due to iface: %s", buffer);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore whitespace
|
||||
while (*pos == ' ') pos++;
|
||||
|
||||
// Find end of tag field
|
||||
endPos = pos;
|
||||
while (*endPos != ' ') endPos++;
|
||||
|
||||
// Three digit field is always 0x0, otherwise parse
|
||||
if (endPos - pos == 3) {
|
||||
rawTag = 0;
|
||||
} else {
|
||||
if (sscanf(pos, "%" PRIx64, &rawTag) != 1) {
|
||||
ALOGE("bad tag: %s", pos);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
s.tag = rawTag >> 32;
|
||||
if (limitTag != -1 && s.tag != static_cast<uint32_t>(limitTag)) {
|
||||
//ALOGI("skipping due to tag: %s", buffer);
|
||||
continue;
|
||||
}
|
||||
pos = endPos;
|
||||
|
||||
// Ignore whitespace
|
||||
while (*pos == ' ') pos++;
|
||||
|
||||
// Parse remaining fields.
|
||||
if (sscanf(pos, "%u %u %" PRIu64 " %" PRIu64 " %" PRIu64 " %" PRIu64,
|
||||
&s.uid, &s.set, &s.rxBytes, &s.rxPackets,
|
||||
&s.txBytes, &s.txPackets) == 6) {
|
||||
if (limitUid != -1 && static_cast<uint32_t>(limitUid) != s.uid) {
|
||||
//ALOGI("skipping due to uid: %s", buffer);
|
||||
continue;
|
||||
}
|
||||
lines->push_back(s);
|
||||
} else {
|
||||
//ALOGI("skipping due to bad remaining fields: %s", pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0) {
|
||||
ALOGE("Failed to close netstats file");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int statsLinesToNetworkStats(JNIEnv* env, jclass clazz, jobject stats,
|
||||
std::vector<stats_line>& lines) {
|
||||
int size = lines.size();
|
||||
|
||||
bool grow = size > env->GetIntField(stats, gNetworkStatsClassInfo.capacity);
|
||||
|
||||
ScopedLocalRef<jobjectArray> iface(env, get_string_array(env, stats,
|
||||
gNetworkStatsClassInfo.iface, size, grow));
|
||||
if (iface.get() == NULL) return -1;
|
||||
ScopedIntArrayRW uid(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.uid, size, grow));
|
||||
if (uid.get() == NULL) return -1;
|
||||
ScopedIntArrayRW set(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.set, size, grow));
|
||||
if (set.get() == NULL) return -1;
|
||||
ScopedIntArrayRW tag(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.tag, size, grow));
|
||||
if (tag.get() == NULL) return -1;
|
||||
ScopedIntArrayRW metered(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.metered, size, grow));
|
||||
if (metered.get() == NULL) return -1;
|
||||
ScopedIntArrayRW roaming(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.roaming, size, grow));
|
||||
if (roaming.get() == NULL) return -1;
|
||||
ScopedIntArrayRW defaultNetwork(env, get_int_array(env, stats,
|
||||
gNetworkStatsClassInfo.defaultNetwork, size, grow));
|
||||
if (defaultNetwork.get() == NULL) return -1;
|
||||
ScopedLongArrayRW rxBytes(env, get_long_array(env, stats,
|
||||
gNetworkStatsClassInfo.rxBytes, size, grow));
|
||||
if (rxBytes.get() == NULL) return -1;
|
||||
ScopedLongArrayRW rxPackets(env, get_long_array(env, stats,
|
||||
gNetworkStatsClassInfo.rxPackets, size, grow));
|
||||
if (rxPackets.get() == NULL) return -1;
|
||||
ScopedLongArrayRW txBytes(env, get_long_array(env, stats,
|
||||
gNetworkStatsClassInfo.txBytes, size, grow));
|
||||
if (txBytes.get() == NULL) return -1;
|
||||
ScopedLongArrayRW txPackets(env, get_long_array(env, stats,
|
||||
gNetworkStatsClassInfo.txPackets, size, grow));
|
||||
if (txPackets.get() == NULL) return -1;
|
||||
ScopedLongArrayRW operations(env, get_long_array(env, stats,
|
||||
gNetworkStatsClassInfo.operations, size, grow));
|
||||
if (operations.get() == NULL) return -1;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
ScopedLocalRef<jstring> ifaceString(env, env->NewStringUTF(lines[i].iface));
|
||||
env->SetObjectArrayElement(iface.get(), i, ifaceString.get());
|
||||
|
||||
uid[i] = lines[i].uid;
|
||||
set[i] = lines[i].set;
|
||||
tag[i] = lines[i].tag;
|
||||
// Metered, roaming and defaultNetwork are populated in Java-land.
|
||||
rxBytes[i] = lines[i].rxBytes;
|
||||
rxPackets[i] = lines[i].rxPackets;
|
||||
txBytes[i] = lines[i].txBytes;
|
||||
txPackets[i] = lines[i].txPackets;
|
||||
}
|
||||
|
||||
env->SetIntField(stats, gNetworkStatsClassInfo.size, size);
|
||||
if (grow) {
|
||||
env->SetIntField(stats, gNetworkStatsClassInfo.capacity, size);
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.iface, iface.get());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.metered, metered.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.roaming, roaming.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.defaultNetwork,
|
||||
defaultNetwork.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.txPackets, txPackets.getJavaArray());
|
||||
env->SetObjectField(stats, gNetworkStatsClassInfo.operations, operations.getJavaArray());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats, jstring path,
|
||||
jint limitUid, jobjectArray limitIfacesObj, jint limitTag,
|
||||
jboolean useBpfStats) {
|
||||
|
||||
std::vector<std::string> limitIfaces;
|
||||
if (limitIfacesObj != NULL && env->GetArrayLength(limitIfacesObj) > 0) {
|
||||
int num = env->GetArrayLength(limitIfacesObj);
|
||||
for (int i = 0; i < num; i++) {
|
||||
jstring string = (jstring)env->GetObjectArrayElement(limitIfacesObj, i);
|
||||
ScopedUtfChars string8(env, string);
|
||||
if (string8.c_str() != NULL) {
|
||||
limitIfaces.push_back(std::string(string8.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<stats_line> lines;
|
||||
|
||||
|
||||
if (useBpfStats) {
|
||||
if (parseBpfNetworkStatsDetail(&lines, limitIfaces, limitTag, limitUid) < 0)
|
||||
return -1;
|
||||
} else {
|
||||
ScopedUtfChars path8(env, path);
|
||||
if (path8.c_str() == NULL) {
|
||||
ALOGE("the qtaguid legacy path is invalid: %s", path8.c_str());
|
||||
return -1;
|
||||
}
|
||||
if (legacyReadNetworkStatsDetail(&lines, limitIfaces, limitTag,
|
||||
limitUid, path8.c_str()) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return statsLinesToNetworkStats(env, clazz, stats, lines);
|
||||
}
|
||||
|
||||
static int readNetworkStatsDev(JNIEnv* env, jclass clazz, jobject stats) {
|
||||
std::vector<stats_line> lines;
|
||||
|
||||
if (parseBpfNetworkStatsDev(&lines) < 0)
|
||||
return -1;
|
||||
|
||||
return statsLinesToNetworkStats(env, clazz, stats, lines);
|
||||
}
|
||||
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
{ "nativeReadNetworkStatsDetail",
|
||||
"(Landroid/net/NetworkStats;Ljava/lang/String;I[Ljava/lang/String;IZ)I",
|
||||
(void*) readNetworkStatsDetail },
|
||||
{ "nativeReadNetworkStatsDev", "(Landroid/net/NetworkStats;)I",
|
||||
(void*) readNetworkStatsDev },
|
||||
};
|
||||
|
||||
int register_android_server_net_NetworkStatsFactory(JNIEnv* env) {
|
||||
int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods,
|
||||
NELEM(gMethods));
|
||||
gStringClass = env->FindClass("java/lang/String");
|
||||
gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass));
|
||||
|
||||
jclass clazz = env->FindClass("android/net/NetworkStats");
|
||||
gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
|
||||
gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I");
|
||||
gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
|
||||
gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
|
||||
gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
|
||||
gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
|
||||
gNetworkStatsClassInfo.metered = env->GetFieldID(clazz, "metered", "[I");
|
||||
gNetworkStatsClassInfo.roaming = env->GetFieldID(clazz, "roaming", "[I");
|
||||
gNetworkStatsClassInfo.defaultNetwork = env->GetFieldID(clazz, "defaultNetwork", "[I");
|
||||
gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
|
||||
gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
|
||||
gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
|
||||
gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
|
||||
gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
116
service-t/jni/com_android_server_net_NetworkStatsService.cpp
Normal file
116
service-t/jni/com_android_server_net_NetworkStatsService.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "NetworkStatsNative"
|
||||
|
||||
#include <cutils/qtaguid.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <jni.h>
|
||||
#include <nativehelper/ScopedUtfChars.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/misc.h>
|
||||
|
||||
#include "bpf/BpfUtils.h"
|
||||
#include "netdbpf/BpfNetworkStats.h"
|
||||
|
||||
using android::bpf::bpfGetUidStats;
|
||||
using android::bpf::bpfGetIfaceStats;
|
||||
|
||||
namespace android {
|
||||
|
||||
// NOTE: keep these in sync with TrafficStats.java
|
||||
static const uint64_t UNKNOWN = -1;
|
||||
|
||||
enum StatsType {
|
||||
RX_BYTES = 0,
|
||||
RX_PACKETS = 1,
|
||||
TX_BYTES = 2,
|
||||
TX_PACKETS = 3,
|
||||
TCP_RX_PACKETS = 4,
|
||||
TCP_TX_PACKETS = 5
|
||||
};
|
||||
|
||||
static uint64_t getStatsType(Stats* stats, StatsType type) {
|
||||
switch (type) {
|
||||
case RX_BYTES:
|
||||
return stats->rxBytes;
|
||||
case RX_PACKETS:
|
||||
return stats->rxPackets;
|
||||
case TX_BYTES:
|
||||
return stats->txBytes;
|
||||
case TX_PACKETS:
|
||||
return stats->txPackets;
|
||||
case TCP_RX_PACKETS:
|
||||
return stats->tcpRxPackets;
|
||||
case TCP_TX_PACKETS:
|
||||
return stats->tcpTxPackets;
|
||||
default:
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
|
||||
Stats stats = {};
|
||||
|
||||
if (bpfGetIfaceStats(NULL, &stats) == 0) {
|
||||
return getStatsType(&stats, (StatsType) type);
|
||||
} else {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
|
||||
ScopedUtfChars iface8(env, iface);
|
||||
if (iface8.c_str() == NULL) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
Stats stats = {};
|
||||
|
||||
if (bpfGetIfaceStats(iface8.c_str(), &stats) == 0) {
|
||||
return getStatsType(&stats, (StatsType) type);
|
||||
} else {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) {
|
||||
Stats stats = {};
|
||||
|
||||
if (bpfGetUidStats(uid, &stats) == 0) {
|
||||
return getStatsType(&stats, (StatsType) type);
|
||||
} else {
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static const JNINativeMethod gMethods[] = {
|
||||
{"nativeGetTotalStat", "(I)J", (void*)getTotalStat},
|
||||
{"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat},
|
||||
{"nativeGetUidStat", "(II)J", (void*)getUidStat},
|
||||
};
|
||||
|
||||
int register_android_server_net_NetworkStatsService(JNIEnv* env) {
|
||||
return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods,
|
||||
NELEM(gMethods));
|
||||
}
|
||||
|
||||
}
|
||||
38
service-t/jni/onload.cpp
Normal file
38
service-t/jni/onload.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
#include <log/log.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name);
|
||||
|
||||
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
|
||||
JNIEnv *env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
ALOGE("GetEnv failed");
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (register_com_android_net_module_util_BpfMap(env,
|
||||
"com/android/net/module/util/BpfMap") < 0) return JNI_ERR;
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2007 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 com.android.server;
|
||||
|
||||
interface INativeDaemonConnectorCallbacks {
|
||||
|
||||
void onDaemonConnected();
|
||||
boolean onCheckHoldWakeLock(int code);
|
||||
boolean onEvent(int code, String raw, String[] cooked);
|
||||
}
|
||||
1878
service-t/src/com/android/server/IpSecService.java
Normal file
1878
service-t/src/com/android/server/IpSecService.java
Normal file
File diff suppressed because it is too large
Load Diff
704
service-t/src/com/android/server/NativeDaemonConnector.java
Normal file
704
service-t/src/com/android/server/NativeDaemonConnector.java
Normal file
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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 com.android.server;
|
||||
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.SystemClock;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Generic connector class for interfacing with a native daemon which uses the
|
||||
* {@code libsysutils} FrameworkListener protocol.
|
||||
*/
|
||||
final class NativeDaemonConnector implements Runnable, Handler.Callback {
|
||||
private final static boolean VDBG = false;
|
||||
|
||||
private final String TAG;
|
||||
|
||||
private String mSocket;
|
||||
private OutputStream mOutputStream;
|
||||
private LocalLog mLocalLog;
|
||||
|
||||
private volatile boolean mDebug = false;
|
||||
private volatile Object mWarnIfHeld;
|
||||
|
||||
private final ResponseQueue mResponseQueue;
|
||||
|
||||
private final PowerManager.WakeLock mWakeLock;
|
||||
|
||||
private final Looper mLooper;
|
||||
|
||||
private INativeDaemonConnectorCallbacks mCallbacks;
|
||||
private Handler mCallbackHandler;
|
||||
|
||||
private AtomicInteger mSequenceNumber;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
|
||||
private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
|
||||
|
||||
/** Lock held whenever communicating with native daemon. */
|
||||
private final Object mDaemonLock = new Object();
|
||||
|
||||
private final int BUFFER_SIZE = 4096;
|
||||
|
||||
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
|
||||
int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
|
||||
mCallbacks = callbacks;
|
||||
mSocket = socket;
|
||||
mResponseQueue = new ResponseQueue(responseQueueSize);
|
||||
mWakeLock = wl;
|
||||
if (mWakeLock != null) {
|
||||
mWakeLock.setReferenceCounted(true);
|
||||
}
|
||||
mSequenceNumber = new AtomicInteger(0);
|
||||
TAG = logTag != null ? logTag : "NativeDaemonConnector";
|
||||
mLocalLog = new LocalLog(maxLogSize);
|
||||
final HandlerThread thread = new HandlerThread(TAG);
|
||||
thread.start();
|
||||
mLooper = thread.getLooper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Set debugging mode, which causes messages to also be written to both
|
||||
* {@link Log} in addition to internal log.
|
||||
*/
|
||||
public void setDebug(boolean debug) {
|
||||
mDebug = debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
|
||||
* Inaccurate across 49.7 days of uptime, but only used for debugging.
|
||||
*/
|
||||
private int uptimeMillisInt() {
|
||||
return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Yell loudly if someone tries making future {@link #execute(Command)}
|
||||
* calls while holding a lock on the given object.
|
||||
*/
|
||||
public void setWarnIfHeld(Object warnIfHeld) {
|
||||
if (mWarnIfHeld != null) {
|
||||
throw new IllegalStateException("warnIfHeld is already set.");
|
||||
}
|
||||
mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mCallbackHandler = new Handler(mLooper, this);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
listenToSocket();
|
||||
} catch (Exception e) {
|
||||
loge("Error in NativeDaemonConnector: " + e);
|
||||
SystemClock.sleep(5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
final String event = (String) msg.obj;
|
||||
final int start = uptimeMillisInt();
|
||||
final int sent = msg.arg1;
|
||||
try {
|
||||
if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
|
||||
log(String.format("Unhandled event '%s'", event));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
loge("Error handling '" + event + "': " + e);
|
||||
} finally {
|
||||
if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
|
||||
mWakeLock.release();
|
||||
}
|
||||
final int end = uptimeMillisInt();
|
||||
if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
|
||||
loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
|
||||
}
|
||||
if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
|
||||
loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private LocalSocketAddress determineSocketAddress() {
|
||||
// If we're testing, set up a socket in a namespace that's accessible to test code.
|
||||
// In order to ensure that unprivileged apps aren't able to impersonate native daemons on
|
||||
// production devices, even if said native daemons ill-advisedly pick a socket name that
|
||||
// starts with __test__, only allow this on debug builds.
|
||||
if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
|
||||
return new LocalSocketAddress(mSocket);
|
||||
} else {
|
||||
return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
|
||||
}
|
||||
}
|
||||
|
||||
private void listenToSocket() throws IOException {
|
||||
LocalSocket socket = null;
|
||||
|
||||
try {
|
||||
socket = new LocalSocket();
|
||||
LocalSocketAddress address = determineSocketAddress();
|
||||
|
||||
socket.connect(address);
|
||||
|
||||
InputStream inputStream = socket.getInputStream();
|
||||
synchronized (mDaemonLock) {
|
||||
mOutputStream = socket.getOutputStream();
|
||||
}
|
||||
|
||||
mCallbacks.onDaemonConnected();
|
||||
|
||||
FileDescriptor[] fdList = null;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int start = 0;
|
||||
|
||||
while (true) {
|
||||
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
|
||||
if (count < 0) {
|
||||
loge("got " + count + " reading with start = " + start);
|
||||
break;
|
||||
}
|
||||
fdList = socket.getAncillaryFileDescriptors();
|
||||
|
||||
// Add our starting point to the count and reset the start.
|
||||
count += start;
|
||||
start = 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (buffer[i] == 0) {
|
||||
// Note - do not log this raw message since it may contain
|
||||
// sensitive data
|
||||
final String rawEvent = new String(
|
||||
buffer, start, i - start, StandardCharsets.UTF_8);
|
||||
|
||||
boolean releaseWl = false;
|
||||
try {
|
||||
final NativeDaemonEvent event =
|
||||
NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
|
||||
|
||||
log("RCV <- {" + event + "}");
|
||||
|
||||
if (event.isClassUnsolicited()) {
|
||||
// TODO: migrate to sending NativeDaemonEvent instances
|
||||
if (mCallbacks.onCheckHoldWakeLock(event.getCode())
|
||||
&& mWakeLock != null) {
|
||||
mWakeLock.acquire();
|
||||
releaseWl = true;
|
||||
}
|
||||
Message msg = mCallbackHandler.obtainMessage(
|
||||
event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
|
||||
if (mCallbackHandler.sendMessage(msg)) {
|
||||
releaseWl = false;
|
||||
}
|
||||
} else {
|
||||
mResponseQueue.add(event.getCmdNumber(), event);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
log("Problem parsing message " + e);
|
||||
} finally {
|
||||
if (releaseWl) {
|
||||
mWakeLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (start == 0) {
|
||||
log("RCV incomplete");
|
||||
}
|
||||
|
||||
// We should end at the amount we read. If not, compact then
|
||||
// buffer and read again.
|
||||
if (start != count) {
|
||||
final int remaining = BUFFER_SIZE - start;
|
||||
System.arraycopy(buffer, start, buffer, 0, remaining);
|
||||
start = remaining;
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
loge("Communications error: " + ex);
|
||||
throw ex;
|
||||
} finally {
|
||||
synchronized (mDaemonLock) {
|
||||
if (mOutputStream != null) {
|
||||
try {
|
||||
loge("closing stream for " + mSocket);
|
||||
mOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
loge("Failed closing output stream: " + e);
|
||||
}
|
||||
mOutputStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
loge("Failed closing socket: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around argument that indicates it's sensitive and shouldn't be
|
||||
* logged.
|
||||
*/
|
||||
public static class SensitiveArg {
|
||||
private final Object mArg;
|
||||
|
||||
public SensitiveArg(Object arg) {
|
||||
mArg = arg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.valueOf(mArg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make command for daemon, escaping arguments as needed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
|
||||
String cmd, Object... args) {
|
||||
if (cmd.indexOf('\0') >= 0) {
|
||||
throw new IllegalArgumentException("Unexpected command: " + cmd);
|
||||
}
|
||||
if (cmd.indexOf(' ') >= 0) {
|
||||
throw new IllegalArgumentException("Arguments must be separate from command");
|
||||
}
|
||||
|
||||
rawBuilder.append(sequenceNumber).append(' ').append(cmd);
|
||||
logBuilder.append(sequenceNumber).append(' ').append(cmd);
|
||||
for (Object arg : args) {
|
||||
final String argString = String.valueOf(arg);
|
||||
if (argString.indexOf('\0') >= 0) {
|
||||
throw new IllegalArgumentException("Unexpected argument: " + arg);
|
||||
}
|
||||
|
||||
rawBuilder.append(' ');
|
||||
logBuilder.append(' ');
|
||||
|
||||
appendEscaped(rawBuilder, argString);
|
||||
if (arg instanceof SensitiveArg) {
|
||||
logBuilder.append("[scrubbed]");
|
||||
} else {
|
||||
appendEscaped(logBuilder, argString);
|
||||
}
|
||||
}
|
||||
|
||||
rawBuilder.append('\0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that waits until all asychronous notifications sent by the native daemon have
|
||||
* been processed. This method must not be called on the notification thread or an
|
||||
* exception will be thrown.
|
||||
*/
|
||||
public void waitForCallbacks() {
|
||||
if (Thread.currentThread() == mLooper.getThread()) {
|
||||
throw new IllegalStateException("Must not call this method on callback thread");
|
||||
}
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
mCallbackHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue the given command to the native daemon and return a single expected
|
||||
* response.
|
||||
*
|
||||
* @throws NativeDaemonConnectorException when problem communicating with
|
||||
* native daemon, or if the response matches
|
||||
* {@link NativeDaemonEvent#isClassClientError()} or
|
||||
* {@link NativeDaemonEvent#isClassServerError()}.
|
||||
*/
|
||||
public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
|
||||
return execute(cmd.mCmd, cmd.mArguments.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue the given command to the native daemon and return a single expected
|
||||
* response. Any arguments must be separated from base command so they can
|
||||
* be properly escaped.
|
||||
*
|
||||
* @throws NativeDaemonConnectorException when problem communicating with
|
||||
* native daemon, or if the response matches
|
||||
* {@link NativeDaemonEvent#isClassClientError()} or
|
||||
* {@link NativeDaemonEvent#isClassServerError()}.
|
||||
*/
|
||||
public NativeDaemonEvent execute(String cmd, Object... args)
|
||||
throws NativeDaemonConnectorException {
|
||||
return execute(DEFAULT_TIMEOUT, cmd, args);
|
||||
}
|
||||
|
||||
public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
|
||||
throws NativeDaemonConnectorException {
|
||||
final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
|
||||
if (events.length != 1) {
|
||||
throw new NativeDaemonConnectorException(
|
||||
"Expected exactly one response, but received " + events.length);
|
||||
}
|
||||
return events[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue the given command to the native daemon and return any
|
||||
* {@link NativeDaemonEvent#isClassContinue()} responses, including the
|
||||
* final terminal response.
|
||||
*
|
||||
* @throws NativeDaemonConnectorException when problem communicating with
|
||||
* native daemon, or if the response matches
|
||||
* {@link NativeDaemonEvent#isClassClientError()} or
|
||||
* {@link NativeDaemonEvent#isClassServerError()}.
|
||||
*/
|
||||
public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
|
||||
return executeForList(cmd.mCmd, cmd.mArguments.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue the given command to the native daemon and return any
|
||||
* {@link NativeDaemonEvent#isClassContinue()} responses, including the
|
||||
* final terminal response. Any arguments must be separated from base
|
||||
* command so they can be properly escaped.
|
||||
*
|
||||
* @throws NativeDaemonConnectorException when problem communicating with
|
||||
* native daemon, or if the response matches
|
||||
* {@link NativeDaemonEvent#isClassClientError()} or
|
||||
* {@link NativeDaemonEvent#isClassServerError()}.
|
||||
*/
|
||||
public NativeDaemonEvent[] executeForList(String cmd, Object... args)
|
||||
throws NativeDaemonConnectorException {
|
||||
return executeForList(DEFAULT_TIMEOUT, cmd, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue the given command to the native daemon and return any {@linke
|
||||
* NativeDaemonEvent@isClassContinue()} responses, including the final
|
||||
* terminal response. Note that the timeout does not count time in deep
|
||||
* sleep. Any arguments must be separated from base command so they can be
|
||||
* properly escaped.
|
||||
*
|
||||
* @throws NativeDaemonConnectorException when problem communicating with
|
||||
* native daemon, or if the response matches
|
||||
* {@link NativeDaemonEvent#isClassClientError()} or
|
||||
* {@link NativeDaemonEvent#isClassServerError()}.
|
||||
*/
|
||||
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
|
||||
throws NativeDaemonConnectorException {
|
||||
if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
|
||||
Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
|
||||
+ Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
|
||||
}
|
||||
|
||||
final long startTime = SystemClock.elapsedRealtime();
|
||||
|
||||
final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
|
||||
|
||||
final StringBuilder rawBuilder = new StringBuilder();
|
||||
final StringBuilder logBuilder = new StringBuilder();
|
||||
final int sequenceNumber = mSequenceNumber.incrementAndGet();
|
||||
|
||||
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
|
||||
|
||||
final String rawCmd = rawBuilder.toString();
|
||||
final String logCmd = logBuilder.toString();
|
||||
|
||||
log("SND -> {" + logCmd + "}");
|
||||
|
||||
synchronized (mDaemonLock) {
|
||||
if (mOutputStream == null) {
|
||||
throw new NativeDaemonConnectorException("missing output stream");
|
||||
} else {
|
||||
try {
|
||||
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new NativeDaemonConnectorException("problem sending command", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NativeDaemonEvent event = null;
|
||||
do {
|
||||
event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
|
||||
if (event == null) {
|
||||
loge("timed-out waiting for response to " + logCmd);
|
||||
throw new NativeDaemonTimeoutException(logCmd, event);
|
||||
}
|
||||
if (VDBG) log("RMV <- {" + event + "}");
|
||||
events.add(event);
|
||||
} while (event.isClassContinue());
|
||||
|
||||
final long endTime = SystemClock.elapsedRealtime();
|
||||
if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
|
||||
loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
|
||||
}
|
||||
|
||||
if (event.isClassClientError()) {
|
||||
throw new NativeDaemonArgumentException(logCmd, event);
|
||||
}
|
||||
if (event.isClassServerError()) {
|
||||
throw new NativeDaemonFailureException(logCmd, event);
|
||||
}
|
||||
|
||||
return events.toArray(new NativeDaemonEvent[events.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the given argument to {@link StringBuilder}, escaping as needed,
|
||||
* and surrounding with quotes when it contains spaces.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static void appendEscaped(StringBuilder builder, String arg) {
|
||||
final boolean hasSpaces = arg.indexOf(' ') >= 0;
|
||||
if (hasSpaces) {
|
||||
builder.append('"');
|
||||
}
|
||||
|
||||
final int length = arg.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
final char c = arg.charAt(i);
|
||||
|
||||
if (c == '"') {
|
||||
builder.append("\\\"");
|
||||
} else if (c == '\\') {
|
||||
builder.append("\\\\");
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSpaces) {
|
||||
builder.append('"');
|
||||
}
|
||||
}
|
||||
|
||||
private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
|
||||
public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
|
||||
super(command, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IllegalArgumentException rethrowAsParcelableException() {
|
||||
throw new IllegalArgumentException(getMessage(), this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
|
||||
public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
|
||||
super(command, event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command builder that handles argument list building. Any arguments must
|
||||
* be separated from base command so they can be properly escaped.
|
||||
*/
|
||||
public static class Command {
|
||||
private String mCmd;
|
||||
private ArrayList<Object> mArguments = new ArrayList<>();
|
||||
|
||||
public Command(String cmd, Object... args) {
|
||||
mCmd = cmd;
|
||||
for (Object arg : args) {
|
||||
appendArg(arg);
|
||||
}
|
||||
}
|
||||
|
||||
public Command appendArg(Object arg) {
|
||||
mArguments.add(arg);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
mLocalLog.dump(fd, pw, args);
|
||||
pw.println();
|
||||
mResponseQueue.dump(fd, pw, args);
|
||||
}
|
||||
|
||||
private void log(String logstring) {
|
||||
if (mDebug) Log.d(TAG, logstring);
|
||||
mLocalLog.log(logstring);
|
||||
}
|
||||
|
||||
private void loge(String logstring) {
|
||||
Log.e(TAG, logstring);
|
||||
mLocalLog.log(logstring);
|
||||
}
|
||||
|
||||
private static class ResponseQueue {
|
||||
|
||||
private static class PendingCmd {
|
||||
public final int cmdNum;
|
||||
public final String logCmd;
|
||||
|
||||
public BlockingQueue<NativeDaemonEvent> responses =
|
||||
new ArrayBlockingQueue<NativeDaemonEvent>(10);
|
||||
|
||||
// The availableResponseCount member is used to track when we can remove this
|
||||
// instance from the ResponseQueue.
|
||||
// This is used under the protection of a sync of the mPendingCmds object.
|
||||
// A positive value means we've had more writers retreive this object while
|
||||
// a negative value means we've had more readers. When we've had an equal number
|
||||
// (it goes to zero) we can remove this object from the mPendingCmds list.
|
||||
// Note that we may have more responses for this command (and more readers
|
||||
// coming), but that would result in a new PendingCmd instance being created
|
||||
// and added with the same cmdNum.
|
||||
// Also note that when this goes to zero it just means a parity of readers and
|
||||
// writers have retrieved this object - not that they are done using it. The
|
||||
// responses queue may well have more responses yet to be read or may get more
|
||||
// responses added to it. But all those readers/writers have retreived and
|
||||
// hold references to this instance already so it can be removed from
|
||||
// mPendingCmds queue.
|
||||
public int availableResponseCount;
|
||||
|
||||
public PendingCmd(int cmdNum, String logCmd) {
|
||||
this.cmdNum = cmdNum;
|
||||
this.logCmd = logCmd;
|
||||
}
|
||||
}
|
||||
|
||||
private final LinkedList<PendingCmd> mPendingCmds;
|
||||
private int mMaxCount;
|
||||
|
||||
ResponseQueue(int maxCount) {
|
||||
mPendingCmds = new LinkedList<PendingCmd>();
|
||||
mMaxCount = maxCount;
|
||||
}
|
||||
|
||||
public void add(int cmdNum, NativeDaemonEvent response) {
|
||||
PendingCmd found = null;
|
||||
synchronized (mPendingCmds) {
|
||||
for (PendingCmd pendingCmd : mPendingCmds) {
|
||||
if (pendingCmd.cmdNum == cmdNum) {
|
||||
found = pendingCmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
// didn't find it - make sure our queue isn't too big before adding
|
||||
while (mPendingCmds.size() >= mMaxCount) {
|
||||
Log.e("NativeDaemonConnector.ResponseQueue",
|
||||
"more buffered than allowed: " + mPendingCmds.size() +
|
||||
" >= " + mMaxCount);
|
||||
// let any waiter timeout waiting for this
|
||||
PendingCmd pendingCmd = mPendingCmds.remove();
|
||||
Log.e("NativeDaemonConnector.ResponseQueue",
|
||||
"Removing request: " + pendingCmd.logCmd + " (" +
|
||||
pendingCmd.cmdNum + ")");
|
||||
}
|
||||
found = new PendingCmd(cmdNum, null);
|
||||
mPendingCmds.add(found);
|
||||
}
|
||||
found.availableResponseCount++;
|
||||
// if a matching remove call has already retrieved this we can remove this
|
||||
// instance from our list
|
||||
if (found.availableResponseCount == 0) mPendingCmds.remove(found);
|
||||
}
|
||||
try {
|
||||
found.responses.put(response);
|
||||
} catch (InterruptedException e) { }
|
||||
}
|
||||
|
||||
// note that the timeout does not count time in deep sleep. If you don't want
|
||||
// the device to sleep, hold a wakelock
|
||||
public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
|
||||
PendingCmd found = null;
|
||||
synchronized (mPendingCmds) {
|
||||
for (PendingCmd pendingCmd : mPendingCmds) {
|
||||
if (pendingCmd.cmdNum == cmdNum) {
|
||||
found = pendingCmd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == null) {
|
||||
found = new PendingCmd(cmdNum, logCmd);
|
||||
mPendingCmds.add(found);
|
||||
}
|
||||
found.availableResponseCount--;
|
||||
// if a matching add call has already retrieved this we can remove this
|
||||
// instance from our list
|
||||
if (found.availableResponseCount == 0) mPendingCmds.remove(found);
|
||||
}
|
||||
NativeDaemonEvent result = null;
|
||||
try {
|
||||
result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {}
|
||||
if (result == null) {
|
||||
Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
pw.println("Pending requests:");
|
||||
synchronized (mPendingCmds) {
|
||||
for (PendingCmd pendingCmd : mPendingCmds) {
|
||||
pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 com.android.server;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
/**
|
||||
* An exception that indicates there was an error with a
|
||||
* {@link NativeDaemonConnector} operation.
|
||||
*/
|
||||
public class NativeDaemonConnectorException extends Exception {
|
||||
private String mCmd;
|
||||
private NativeDaemonEvent mEvent;
|
||||
|
||||
public NativeDaemonConnectorException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
|
||||
super("command '" + cmd + "' failed with '" + event + "'");
|
||||
mCmd = cmd;
|
||||
mEvent = event;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return mEvent != null ? mEvent.getCode() : -1;
|
||||
}
|
||||
|
||||
public String getCmd() {
|
||||
return mCmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rethrow as a {@link RuntimeException} subclass that is handled by
|
||||
* {@link Parcel#writeException(Exception)}.
|
||||
*/
|
||||
public IllegalArgumentException rethrowAsParcelableException() {
|
||||
throw new IllegalStateException(getMessage(), this);
|
||||
}
|
||||
}
|
||||
267
service-t/src/com/android/server/NativeDaemonEvent.java
Normal file
267
service-t/src/com/android/server/NativeDaemonEvent.java
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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 com.android.server;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Parsed event from native side of {@link NativeDaemonConnector}.
|
||||
*/
|
||||
public class NativeDaemonEvent {
|
||||
|
||||
// TODO: keep class ranges in sync with ResponseCode.h
|
||||
// TODO: swap client and server error ranges to roughly mirror HTTP spec
|
||||
|
||||
private final int mCmdNumber;
|
||||
private final int mCode;
|
||||
private final String mMessage;
|
||||
private final String mRawEvent;
|
||||
private final String mLogMessage;
|
||||
private String[] mParsed;
|
||||
private FileDescriptor[] mFdList;
|
||||
|
||||
private NativeDaemonEvent(int cmdNumber, int code, String message,
|
||||
String rawEvent, String logMessage, FileDescriptor[] fdList) {
|
||||
mCmdNumber = cmdNumber;
|
||||
mCode = code;
|
||||
mMessage = message;
|
||||
mRawEvent = rawEvent;
|
||||
mLogMessage = logMessage;
|
||||
mParsed = null;
|
||||
mFdList = fdList;
|
||||
}
|
||||
|
||||
static public final String SENSITIVE_MARKER = "{{sensitive}}";
|
||||
|
||||
public int getCmdNumber() {
|
||||
return mCmdNumber;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public FileDescriptor[] getFileDescriptors() {
|
||||
return mFdList;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String getRawEvent() {
|
||||
return mRawEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mLogMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if event represents a partial response which is continued in
|
||||
* additional subsequent events.
|
||||
*/
|
||||
public boolean isClassContinue() {
|
||||
return mCode >= 100 && mCode < 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if event represents a command success.
|
||||
*/
|
||||
public boolean isClassOk() {
|
||||
return mCode >= 200 && mCode < 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if event represents a remote native daemon error.
|
||||
*/
|
||||
public boolean isClassServerError() {
|
||||
return mCode >= 400 && mCode < 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if event represents a command syntax or argument error.
|
||||
*/
|
||||
public boolean isClassClientError() {
|
||||
return mCode >= 500 && mCode < 600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if event represents an unsolicited event from native daemon.
|
||||
*/
|
||||
public boolean isClassUnsolicited() {
|
||||
return isClassUnsolicited(mCode);
|
||||
}
|
||||
|
||||
private static boolean isClassUnsolicited(int code) {
|
||||
return code >= 600 && code < 700;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify this event matches the given code.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #getCode()} doesn't match.
|
||||
*/
|
||||
public void checkCode(int code) {
|
||||
if (mCode != code) {
|
||||
throw new IllegalStateException("Expected " + code + " but was: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given raw event into {@link NativeDaemonEvent} instance.
|
||||
*
|
||||
* @throws IllegalArgumentException when line doesn't match format expected
|
||||
* from native side.
|
||||
*/
|
||||
public static NativeDaemonEvent parseRawEvent(String rawEvent, FileDescriptor[] fdList) {
|
||||
final String[] parsed = rawEvent.split(" ");
|
||||
if (parsed.length < 2) {
|
||||
throw new IllegalArgumentException("Insufficient arguments");
|
||||
}
|
||||
|
||||
int skiplength = 0;
|
||||
|
||||
final int code;
|
||||
try {
|
||||
code = Integer.parseInt(parsed[0]);
|
||||
skiplength = parsed[0].length() + 1;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("problem parsing code", e);
|
||||
}
|
||||
|
||||
int cmdNumber = -1;
|
||||
if (isClassUnsolicited(code) == false) {
|
||||
if (parsed.length < 3) {
|
||||
throw new IllegalArgumentException("Insufficient arguemnts");
|
||||
}
|
||||
try {
|
||||
cmdNumber = Integer.parseInt(parsed[1]);
|
||||
skiplength += parsed[1].length() + 1;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("problem parsing cmdNumber", e);
|
||||
}
|
||||
}
|
||||
|
||||
String logMessage = rawEvent;
|
||||
if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) {
|
||||
skiplength += parsed[2].length() + 1;
|
||||
logMessage = parsed[0] + " " + parsed[1] + " {}";
|
||||
}
|
||||
|
||||
final String message = rawEvent.substring(skiplength);
|
||||
|
||||
return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage, fdList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the given {@link NativeDaemonEvent} list, returning
|
||||
* {@link #getMessage()} for any events matching the requested code.
|
||||
*/
|
||||
public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
|
||||
final ArrayList<String> result = new ArrayList<>();
|
||||
for (NativeDaemonEvent event : events) {
|
||||
if (event.getCode() == matchCode) {
|
||||
result.add(event.getMessage());
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the Nth field of the event.
|
||||
*
|
||||
* This ignores and code or cmdNum, the first return value is given for N=0.
|
||||
* Also understands "\"quoted\" multiword responses" and tries them as a single field
|
||||
*/
|
||||
public String getField(int n) {
|
||||
if (mParsed == null) {
|
||||
mParsed = unescapeArgs(mRawEvent);
|
||||
}
|
||||
n += 2; // skip code and command#
|
||||
if (n > mParsed.length) return null;
|
||||
return mParsed[n];
|
||||
}
|
||||
|
||||
public static String[] unescapeArgs(String rawEvent) {
|
||||
final boolean DEBUG_ROUTINE = false;
|
||||
final String LOGTAG = "unescapeArgs";
|
||||
final ArrayList<String> parsed = new ArrayList<String>();
|
||||
final int length = rawEvent.length();
|
||||
int current = 0;
|
||||
int wordEnd = -1;
|
||||
boolean quoted = false;
|
||||
|
||||
if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
|
||||
if (rawEvent.charAt(current) == '\"') {
|
||||
quoted = true;
|
||||
current++;
|
||||
}
|
||||
while (current < length) {
|
||||
// find the end of the word
|
||||
char terminator = quoted ? '\"' : ' ';
|
||||
wordEnd = current;
|
||||
while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
|
||||
if (rawEvent.charAt(wordEnd) == '\\') {
|
||||
// skip the escaped char
|
||||
++wordEnd;
|
||||
}
|
||||
++wordEnd;
|
||||
}
|
||||
if (wordEnd > length) wordEnd = length;
|
||||
String word = rawEvent.substring(current, wordEnd);
|
||||
current += word.length();
|
||||
if (!quoted) {
|
||||
word = word.trim();
|
||||
} else {
|
||||
current++; // skip the trailing quote
|
||||
}
|
||||
// unescape stuff within the word
|
||||
word = word.replace("\\\\", "\\");
|
||||
word = word.replace("\\\"", "\"");
|
||||
|
||||
if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
|
||||
parsed.add(word);
|
||||
|
||||
// find the beginning of the next word - either of these options
|
||||
int nextSpace = rawEvent.indexOf(' ', current);
|
||||
int nextQuote = rawEvent.indexOf(" \"", current);
|
||||
if (DEBUG_ROUTINE) {
|
||||
Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
|
||||
}
|
||||
if (nextQuote > -1 && nextQuote <= nextSpace) {
|
||||
quoted = true;
|
||||
current = nextQuote + 2;
|
||||
} else {
|
||||
quoted = false;
|
||||
if (nextSpace > -1) {
|
||||
current = nextSpace + 1;
|
||||
}
|
||||
} // else we just start the next word after the current and read til the end
|
||||
if (DEBUG_ROUTINE) {
|
||||
Log.e(LOGTAG, "next loop - current=" + current
|
||||
+ ", length=" + length + ", quoted=" + quoted);
|
||||
}
|
||||
}
|
||||
return parsed.toArray(new String[parsed.size()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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 com.android.server;
|
||||
|
||||
/**
|
||||
* An exception that indicates there was a timeout with a
|
||||
* {@link NativeDaemonConnector} operation.
|
||||
*/
|
||||
public class NativeDaemonTimeoutException extends NativeDaemonConnectorException {
|
||||
public NativeDaemonTimeoutException(String command, NativeDaemonEvent event) {
|
||||
super(command, event);
|
||||
}
|
||||
}
|
||||
|
||||
1146
service-t/src/com/android/server/NsdService.java
Normal file
1146
service-t/src/com/android/server/NsdService.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.ethernet;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.IpConfiguration;
|
||||
import android.os.Environment;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.server.net.IpConfigStore;
|
||||
|
||||
|
||||
/**
|
||||
* This class provides an API to store and manage Ethernet network configuration.
|
||||
*/
|
||||
public class EthernetConfigStore {
|
||||
private static final String ipConfigFile = Environment.getDataDirectory() +
|
||||
"/misc/ethernet/ipconfig.txt";
|
||||
|
||||
private IpConfigStore mStore = new IpConfigStore();
|
||||
private ArrayMap<String, IpConfiguration> mIpConfigurations;
|
||||
private IpConfiguration mIpConfigurationForDefaultInterface;
|
||||
private final Object mSync = new Object();
|
||||
|
||||
public EthernetConfigStore() {
|
||||
mIpConfigurations = new ArrayMap<>(0);
|
||||
}
|
||||
|
||||
public void read() {
|
||||
synchronized (mSync) {
|
||||
ArrayMap<String, IpConfiguration> configs =
|
||||
IpConfigStore.readIpConfigurations(ipConfigFile);
|
||||
|
||||
// This configuration may exist in old file versions when there was only a single active
|
||||
// Ethernet interface.
|
||||
if (configs.containsKey("0")) {
|
||||
mIpConfigurationForDefaultInterface = configs.remove("0");
|
||||
}
|
||||
|
||||
mIpConfigurations = configs;
|
||||
}
|
||||
}
|
||||
|
||||
public void write(String iface, IpConfiguration config) {
|
||||
boolean modified;
|
||||
|
||||
synchronized (mSync) {
|
||||
if (config == null) {
|
||||
modified = mIpConfigurations.remove(iface) != null;
|
||||
} else {
|
||||
IpConfiguration oldConfig = mIpConfigurations.put(iface, config);
|
||||
modified = !config.equals(oldConfig);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
mStore.writeIpConfigurations(ipConfigFile, mIpConfigurations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayMap<String, IpConfiguration> getIpConfigurations() {
|
||||
synchronized (mSync) {
|
||||
return new ArrayMap<>(mIpConfigurations);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IpConfiguration getIpConfigurationForDefaultInterface() {
|
||||
synchronized (mSync) {
|
||||
return mIpConfigurationForDefaultInterface == null
|
||||
? null : new IpConfiguration(mIpConfigurationForDefaultInterface);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.ethernet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.NetworkAgent;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkScore;
|
||||
import android.os.Looper;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
public class EthernetNetworkAgent extends NetworkAgent {
|
||||
|
||||
private static final String TAG = "EthernetNetworkAgent";
|
||||
|
||||
public interface Callbacks {
|
||||
void onNetworkUnwanted();
|
||||
}
|
||||
|
||||
private final Callbacks mCallbacks;
|
||||
|
||||
EthernetNetworkAgent(
|
||||
@NonNull Context context,
|
||||
@NonNull Looper looper,
|
||||
@NonNull NetworkCapabilities nc,
|
||||
@NonNull LinkProperties lp,
|
||||
@NonNull NetworkAgentConfig config,
|
||||
@Nullable NetworkProvider provider,
|
||||
@NonNull Callbacks cb) {
|
||||
super(context, looper, TAG, nc, lp, new NetworkScore.Builder().build(), config, provider);
|
||||
mCallbacks = cb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkUnwanted() {
|
||||
mCallbacks.onNetworkUnwanted();
|
||||
}
|
||||
|
||||
// sendLinkProperties is final in NetworkAgent, so it cannot be mocked.
|
||||
public void sendLinkPropertiesImpl(LinkProperties lp) {
|
||||
sendLinkProperties(lp);
|
||||
}
|
||||
|
||||
public Callbacks getCallbacks() {
|
||||
return mCallbacks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.ethernet;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityResources;
|
||||
import android.net.EthernetManager;
|
||||
import android.net.EthernetNetworkSpecifier;
|
||||
import android.net.EthernetNetworkManagementException;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkFactory;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.NetworkSpecifier;
|
||||
import android.net.ip.IIpClient;
|
||||
import android.net.ip.IpClientCallbacks;
|
||||
import android.net.ip.IpClientManager;
|
||||
import android.net.ip.IpClientUtil;
|
||||
import android.net.shared.ProvisioningConfiguration;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.net.module.util.InterfaceParams;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* {@link NetworkFactory} that represents Ethernet networks.
|
||||
*
|
||||
* This class reports a static network score of 70 when it is tracking an interface and that
|
||||
* interface's link is up, and a score of 0 otherwise.
|
||||
*/
|
||||
public class EthernetNetworkFactory extends NetworkFactory {
|
||||
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
|
||||
final static boolean DBG = true;
|
||||
|
||||
private final static int NETWORK_SCORE = 70;
|
||||
private static final String NETWORK_TYPE = "Ethernet";
|
||||
private static final String LEGACY_TCP_BUFFER_SIZES =
|
||||
"524288,1048576,3145728,524288,1048576,2097152";
|
||||
|
||||
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
|
||||
new ConcurrentHashMap<>();
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
final Dependencies mDeps;
|
||||
|
||||
public static class Dependencies {
|
||||
public void makeIpClient(Context context, String iface, IpClientCallbacks callbacks) {
|
||||
IpClientUtil.makeIpClient(context, iface, callbacks);
|
||||
}
|
||||
|
||||
public IpClientManager makeIpClientManager(@NonNull final IIpClient ipClient) {
|
||||
return new IpClientManager(ipClient, TAG);
|
||||
}
|
||||
|
||||
public EthernetNetworkAgent makeEthernetNetworkAgent(Context context, Looper looper,
|
||||
NetworkCapabilities nc, LinkProperties lp, NetworkAgentConfig config,
|
||||
NetworkProvider provider, EthernetNetworkAgent.Callbacks cb) {
|
||||
return new EthernetNetworkAgent(context, looper, nc, lp, config, provider, cb);
|
||||
}
|
||||
|
||||
public InterfaceParams getNetworkInterfaceByName(String name) {
|
||||
return InterfaceParams.getByName(name);
|
||||
}
|
||||
|
||||
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||
private String getPlatformTcpBufferSizes(Context context) {
|
||||
final Resources r = context.getResources();
|
||||
final int resId = r.getIdentifier("config_ethernet_tcp_buffers", "string",
|
||||
context.getPackageName());
|
||||
return r.getString(resId);
|
||||
}
|
||||
|
||||
public String getTcpBufferSizesFromResource(Context context) {
|
||||
final String tcpBufferSizes;
|
||||
final String platformTcpBufferSizes = getPlatformTcpBufferSizes(context);
|
||||
if (!LEGACY_TCP_BUFFER_SIZES.equals(platformTcpBufferSizes)) {
|
||||
// Platform resource is not the historical default: use the overlay.
|
||||
tcpBufferSizes = platformTcpBufferSizes;
|
||||
} else {
|
||||
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||
tcpBufferSizes = resources.get().getString(R.string.config_ethernet_tcp_buffers);
|
||||
}
|
||||
return tcpBufferSizes;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConfigurationException extends AndroidRuntimeException {
|
||||
public ConfigurationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public EthernetNetworkFactory(Handler handler, Context context) {
|
||||
this(handler, context, new Dependencies());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
EthernetNetworkFactory(Handler handler, Context context, Dependencies deps) {
|
||||
super(handler.getLooper(), context, NETWORK_TYPE, createDefaultNetworkCapabilities());
|
||||
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
mDeps = deps;
|
||||
|
||||
setScoreFilter(NETWORK_SCORE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean acceptRequest(NetworkRequest request) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "acceptRequest, request: " + request);
|
||||
}
|
||||
|
||||
return networkForRequest(request) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void needNetworkFor(NetworkRequest networkRequest) {
|
||||
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||
|
||||
if (network == null) {
|
||||
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++network.refCount == 1) {
|
||||
network.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseNetworkFor(NetworkRequest networkRequest) {
|
||||
NetworkInterfaceState network = networkForRequest(networkRequest);
|
||||
if (network == null) {
|
||||
Log.e(TAG, "releaseNetworkFor, failed to get a network for " + networkRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
if (--network.refCount == 0) {
|
||||
network.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of available interface names. The array is sorted: unrestricted interfaces
|
||||
* goes first, then sorted by name.
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected String[] getAvailableInterfaces(boolean includeRestricted) {
|
||||
return mTrackingInterfaces.values()
|
||||
.stream()
|
||||
.filter(iface -> !iface.isRestricted() || includeRestricted)
|
||||
.sorted((iface1, iface2) -> {
|
||||
int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
|
||||
return r == 0 ? iface1.name.compareTo(iface2.name) : r;
|
||||
})
|
||||
.map(iface -> iface.name)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected void addInterface(@NonNull final String ifaceName, @NonNull final String hwAddress,
|
||||
@NonNull final IpConfiguration ipConfig,
|
||||
@NonNull final NetworkCapabilities capabilities) {
|
||||
if (mTrackingInterfaces.containsKey(ifaceName)) {
|
||||
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
final NetworkCapabilities nc = new NetworkCapabilities.Builder(capabilities)
|
||||
.setNetworkSpecifier(new EthernetNetworkSpecifier(ifaceName))
|
||||
.build();
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + nc);
|
||||
}
|
||||
|
||||
final NetworkInterfaceState iface = new NetworkInterfaceState(
|
||||
ifaceName, hwAddress, mHandler, mContext, ipConfig, nc, this, mDeps);
|
||||
mTrackingInterfaces.put(ifaceName, iface);
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected int getInterfaceState(@NonNull String iface) {
|
||||
final NetworkInterfaceState interfaceState = mTrackingInterfaces.get(iface);
|
||||
if (interfaceState == null) {
|
||||
return EthernetManager.STATE_ABSENT;
|
||||
} else if (!interfaceState.mLinkUp) {
|
||||
return EthernetManager.STATE_LINK_DOWN;
|
||||
} else {
|
||||
return EthernetManager.STATE_LINK_UP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a network's configuration and restart it if necessary.
|
||||
*
|
||||
* @param ifaceName the interface name of the network to be updated.
|
||||
* @param ipConfig the desired {@link IpConfiguration} for the given network or null. If
|
||||
* {@code null} is passed, the existing IpConfiguration is not updated.
|
||||
* @param capabilities the desired {@link NetworkCapabilities} for the given network. If
|
||||
* {@code null} is passed, then the network's current
|
||||
* {@link NetworkCapabilities} will be used in support of existing APIs as
|
||||
* the public API does not allow this.
|
||||
* @param listener an optional {@link INetworkInterfaceOutcomeReceiver} to notify callers of
|
||||
* completion.
|
||||
*/
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected void updateInterface(@NonNull final String ifaceName,
|
||||
@Nullable final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities capabilities,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (!hasInterface(ifaceName)) {
|
||||
maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
|
||||
return;
|
||||
}
|
||||
|
||||
final NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||
iface.updateInterface(ipConfig, capabilities, listener);
|
||||
mTrackingInterfaces.put(ifaceName, iface);
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
private static NetworkCapabilities mixInCapabilities(NetworkCapabilities nc,
|
||||
NetworkCapabilities addedNc) {
|
||||
final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(nc);
|
||||
for (int transport : addedNc.getTransportTypes()) builder.addTransportType(transport);
|
||||
for (int capability : addedNc.getCapabilities()) builder.addCapability(capability);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void updateCapabilityFilter() {
|
||||
NetworkCapabilities capabilitiesFilter = createDefaultNetworkCapabilities();
|
||||
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
|
||||
capabilitiesFilter = mixInCapabilities(capabilitiesFilter, iface.mCapabilities);
|
||||
}
|
||||
|
||||
if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
|
||||
setCapabilityFilter(capabilitiesFilter);
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createDefaultNetworkCapabilities() {
|
||||
return NetworkCapabilities.Builder
|
||||
.withoutDefaultCapabilities()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET).build();
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected void removeInterface(String interfaceName) {
|
||||
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
|
||||
if (iface != null) {
|
||||
iface.maybeSendNetworkManagementCallbackForAbort();
|
||||
iface.stop();
|
||||
}
|
||||
|
||||
updateCapabilityFilter();
|
||||
}
|
||||
|
||||
/** Returns true if state has been modified */
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
protected boolean updateInterfaceLinkState(@NonNull final String ifaceName, final boolean up,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (!hasInterface(ifaceName)) {
|
||||
maybeSendNetworkManagementCallbackForUntracked(ifaceName, listener);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
|
||||
}
|
||||
|
||||
NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
|
||||
return iface.updateLinkState(up, listener);
|
||||
}
|
||||
|
||||
private void maybeSendNetworkManagementCallbackForUntracked(
|
||||
String ifaceName, INetworkInterfaceOutcomeReceiver listener) {
|
||||
maybeSendNetworkManagementCallback(listener, null,
|
||||
new EthernetNetworkManagementException(
|
||||
ifaceName + " can't be updated as it is not available."));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected boolean hasInterface(String ifaceName) {
|
||||
return mTrackingInterfaces.containsKey(ifaceName);
|
||||
}
|
||||
|
||||
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
|
||||
String requestedIface = null;
|
||||
|
||||
NetworkSpecifier specifier = request.getNetworkSpecifier();
|
||||
if (specifier instanceof EthernetNetworkSpecifier) {
|
||||
requestedIface = ((EthernetNetworkSpecifier) specifier)
|
||||
.getInterfaceName();
|
||||
}
|
||||
|
||||
NetworkInterfaceState network = null;
|
||||
if (!TextUtils.isEmpty(requestedIface)) {
|
||||
NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
|
||||
if (n != null && request.canBeSatisfiedBy(n.mCapabilities)) {
|
||||
network = n;
|
||||
}
|
||||
} else {
|
||||
for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
|
||||
if (request.canBeSatisfiedBy(n.mCapabilities) && n.mLinkUp) {
|
||||
network = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DBG) {
|
||||
Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
|
||||
}
|
||||
|
||||
return network;
|
||||
}
|
||||
|
||||
private static void maybeSendNetworkManagementCallback(
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener,
|
||||
@Nullable final String iface,
|
||||
@Nullable final EthernetNetworkManagementException e) {
|
||||
if (null == listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (iface != null) {
|
||||
listener.onResult(iface);
|
||||
} else {
|
||||
listener.onError(e);
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Log.e(TAG, "Can't send onComplete for network management callback", re);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class NetworkInterfaceState {
|
||||
final String name;
|
||||
|
||||
private final String mHwAddress;
|
||||
private final Handler mHandler;
|
||||
private final Context mContext;
|
||||
private final NetworkFactory mNetworkFactory;
|
||||
private final Dependencies mDeps;
|
||||
|
||||
private static String sTcpBufferSizes = null; // Lazy initialized.
|
||||
|
||||
private boolean mLinkUp;
|
||||
private int mLegacyType;
|
||||
private LinkProperties mLinkProperties = new LinkProperties();
|
||||
|
||||
private volatile @Nullable IpClientManager mIpClient;
|
||||
private @NonNull NetworkCapabilities mCapabilities;
|
||||
private @Nullable EthernetIpClientCallback mIpClientCallback;
|
||||
private @Nullable EthernetNetworkAgent mNetworkAgent;
|
||||
private @Nullable IpConfiguration mIpConfig;
|
||||
|
||||
/**
|
||||
* A map of TRANSPORT_* types to legacy transport types available for each type an ethernet
|
||||
* interface could propagate.
|
||||
*
|
||||
* There are no legacy type equivalents to LOWPAN or WIFI_AWARE. These types are set to
|
||||
* TYPE_NONE to match the behavior of their own network factories.
|
||||
*/
|
||||
private static final SparseArray<Integer> sTransports = new SparseArray();
|
||||
static {
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||
ConnectivityManager.TYPE_ETHERNET);
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_BLUETOOTH,
|
||||
ConnectivityManager.TYPE_BLUETOOTH);
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI, ConnectivityManager.TYPE_WIFI);
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||
ConnectivityManager.TYPE_MOBILE);
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_LOWPAN, ConnectivityManager.TYPE_NONE);
|
||||
sTransports.put(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
|
||||
ConnectivityManager.TYPE_NONE);
|
||||
}
|
||||
|
||||
long refCount = 0;
|
||||
|
||||
private class EthernetIpClientCallback extends IpClientCallbacks {
|
||||
private final ConditionVariable mIpClientStartCv = new ConditionVariable(false);
|
||||
private final ConditionVariable mIpClientShutdownCv = new ConditionVariable(false);
|
||||
@Nullable INetworkInterfaceOutcomeReceiver mNetworkManagementListener;
|
||||
|
||||
EthernetIpClientCallback(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
mNetworkManagementListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIpClientCreated(IIpClient ipClient) {
|
||||
mIpClient = mDeps.makeIpClientManager(ipClient);
|
||||
mIpClientStartCv.open();
|
||||
}
|
||||
|
||||
private void awaitIpClientStart() {
|
||||
mIpClientStartCv.block();
|
||||
}
|
||||
|
||||
private void awaitIpClientShutdown() {
|
||||
mIpClientShutdownCv.block();
|
||||
}
|
||||
|
||||
// At the time IpClient is stopped, an IpClient event may have already been posted on
|
||||
// the back of the handler and is awaiting execution. Once that event is executed, the
|
||||
// associated callback object may not be valid anymore
|
||||
// (NetworkInterfaceState#mIpClientCallback points to a different object / null).
|
||||
private boolean isCurrentCallback() {
|
||||
return this == mIpClientCallback;
|
||||
}
|
||||
|
||||
private void handleIpEvent(final @NonNull Runnable r) {
|
||||
mHandler.post(() -> {
|
||||
if (!isCurrentCallback()) {
|
||||
Log.i(TAG, "Ignoring stale IpClientCallbacks " + this);
|
||||
return;
|
||||
}
|
||||
r.run();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvisioningSuccess(LinkProperties newLp) {
|
||||
handleIpEvent(() -> onIpLayerStarted(newLp, mNetworkManagementListener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvisioningFailure(LinkProperties newLp) {
|
||||
// This cannot happen due to provisioning timeout, because our timeout is 0. It can
|
||||
// happen due to errors while provisioning or on provisioning loss.
|
||||
handleIpEvent(() -> onIpLayerStopped(mNetworkManagementListener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkPropertiesChange(LinkProperties newLp) {
|
||||
handleIpEvent(() -> updateLinkProperties(newLp));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReachabilityLost(String logMsg) {
|
||||
handleIpEvent(() -> updateNeighborLostEvent(logMsg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit() {
|
||||
mIpClient = null;
|
||||
mIpClientShutdownCv.open();
|
||||
}
|
||||
}
|
||||
|
||||
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
|
||||
@NonNull IpConfiguration ipConfig, @NonNull NetworkCapabilities capabilities,
|
||||
NetworkFactory networkFactory, Dependencies deps) {
|
||||
name = ifaceName;
|
||||
mIpConfig = Objects.requireNonNull(ipConfig);
|
||||
mCapabilities = Objects.requireNonNull(capabilities);
|
||||
mLegacyType = getLegacyType(mCapabilities);
|
||||
mHandler = handler;
|
||||
mContext = context;
|
||||
mNetworkFactory = networkFactory;
|
||||
mDeps = deps;
|
||||
mHwAddress = hwAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the legacy transport type from a NetworkCapabilities transport type. Defaults
|
||||
* to legacy TYPE_NONE if there is no known conversion
|
||||
*/
|
||||
private static int getLegacyType(int transport) {
|
||||
return sTransports.get(transport, ConnectivityManager.TYPE_NONE);
|
||||
}
|
||||
|
||||
private static int getLegacyType(@NonNull final NetworkCapabilities capabilities) {
|
||||
final int[] transportTypes = capabilities.getTransportTypes();
|
||||
if (transportTypes.length > 0) {
|
||||
return getLegacyType(transportTypes[0]);
|
||||
}
|
||||
|
||||
// Should never happen as transport is always one of ETHERNET or a valid override
|
||||
throw new ConfigurationException("Network Capabilities do not have an associated "
|
||||
+ "transport type.");
|
||||
}
|
||||
|
||||
private void setCapabilities(@NonNull final NetworkCapabilities capabilities) {
|
||||
mCapabilities = new NetworkCapabilities(capabilities);
|
||||
mLegacyType = getLegacyType(mCapabilities);
|
||||
}
|
||||
|
||||
void updateInterface(@Nullable final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities capabilities,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "updateInterface, iface: " + name
|
||||
+ ", ipConfig: " + ipConfig + ", old ipConfig: " + mIpConfig
|
||||
+ ", capabilities: " + capabilities + ", old capabilities: " + mCapabilities
|
||||
+ ", listener: " + listener
|
||||
);
|
||||
}
|
||||
|
||||
if (null != ipConfig){
|
||||
mIpConfig = ipConfig;
|
||||
}
|
||||
if (null != capabilities) {
|
||||
setCapabilities(capabilities);
|
||||
}
|
||||
// Send an abort callback if a request is filed before the previous one has completed.
|
||||
maybeSendNetworkManagementCallbackForAbort();
|
||||
// TODO: Update this logic to only do a restart if required. Although a restart may
|
||||
// be required due to the capabilities or ipConfiguration values, not all
|
||||
// capabilities changes require a restart.
|
||||
restart(listener);
|
||||
}
|
||||
|
||||
boolean isRestricted() {
|
||||
return !mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
private void start() {
|
||||
start(null);
|
||||
}
|
||||
|
||||
private void start(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (mIpClient != null) {
|
||||
if (DBG) Log.d(TAG, "IpClient already started");
|
||||
return;
|
||||
}
|
||||
if (DBG) {
|
||||
Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
|
||||
}
|
||||
|
||||
mIpClientCallback = new EthernetIpClientCallback(listener);
|
||||
mDeps.makeIpClient(mContext, name, mIpClientCallback);
|
||||
mIpClientCallback.awaitIpClientStart();
|
||||
|
||||
if (sTcpBufferSizes == null) {
|
||||
sTcpBufferSizes = mDeps.getTcpBufferSizesFromResource(mContext);
|
||||
}
|
||||
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
|
||||
}
|
||||
|
||||
void onIpLayerStarted(@NonNull final LinkProperties linkProperties,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (mNetworkAgent != null) {
|
||||
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
mLinkProperties = linkProperties;
|
||||
|
||||
// Create our NetworkAgent.
|
||||
final NetworkAgentConfig config = new NetworkAgentConfig.Builder()
|
||||
.setLegacyType(mLegacyType)
|
||||
.setLegacyTypeName(NETWORK_TYPE)
|
||||
.setLegacyExtraInfo(mHwAddress)
|
||||
.build();
|
||||
mNetworkAgent = mDeps.makeEthernetNetworkAgent(mContext, mHandler.getLooper(),
|
||||
mCapabilities, mLinkProperties, config, mNetworkFactory.getProvider(),
|
||||
new EthernetNetworkAgent.Callbacks() {
|
||||
@Override
|
||||
public void onNetworkUnwanted() {
|
||||
// if mNetworkAgent is null, we have already called stop.
|
||||
if (mNetworkAgent == null) return;
|
||||
|
||||
if (this == mNetworkAgent.getCallbacks()) {
|
||||
stop();
|
||||
} else {
|
||||
Log.d(TAG, "Ignoring unwanted as we have a more modern " +
|
||||
"instance");
|
||||
}
|
||||
}
|
||||
});
|
||||
mNetworkAgent.register();
|
||||
mNetworkAgent.markConnected();
|
||||
realizeNetworkManagementCallback(name, null);
|
||||
}
|
||||
|
||||
void onIpLayerStopped(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
// There is no point in continuing if the interface is gone as stop() will be triggered
|
||||
// by removeInterface() when processed on the handler thread and start() won't
|
||||
// work for a non-existent interface.
|
||||
if (null == mDeps.getNetworkInterfaceByName(name)) {
|
||||
if (DBG) Log.d(TAG, name + " is no longer available.");
|
||||
// Send a callback in case a provisioning request was in progress.
|
||||
maybeSendNetworkManagementCallbackForAbort();
|
||||
return;
|
||||
}
|
||||
restart(listener);
|
||||
}
|
||||
|
||||
private void maybeSendNetworkManagementCallbackForAbort() {
|
||||
realizeNetworkManagementCallback(null,
|
||||
new EthernetNetworkManagementException(
|
||||
"The IP provisioning request has been aborted."));
|
||||
}
|
||||
|
||||
// Must be called on the handler thread
|
||||
private void realizeNetworkManagementCallback(@Nullable final String iface,
|
||||
@Nullable final EthernetNetworkManagementException e) {
|
||||
ensureRunningOnEthernetHandlerThread();
|
||||
if (null == mIpClientCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
EthernetNetworkFactory.maybeSendNetworkManagementCallback(
|
||||
mIpClientCallback.mNetworkManagementListener, iface, e);
|
||||
// Only send a single callback per listener.
|
||||
mIpClientCallback.mNetworkManagementListener = null;
|
||||
}
|
||||
|
||||
private void ensureRunningOnEthernetHandlerThread() {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on the Ethernet thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
void updateLinkProperties(LinkProperties linkProperties) {
|
||||
mLinkProperties = linkProperties;
|
||||
if (mNetworkAgent != null) {
|
||||
mNetworkAgent.sendLinkPropertiesImpl(linkProperties);
|
||||
}
|
||||
}
|
||||
|
||||
void updateNeighborLostEvent(String logMsg) {
|
||||
Log.i(TAG, "updateNeighborLostEvent " + logMsg);
|
||||
// Reachability lost will be seen only if the gateway is not reachable.
|
||||
// Since ethernet FW doesn't have the mechanism to scan for new networks
|
||||
// like WiFi, simply restart.
|
||||
// If there is a better network, that will become default and apps
|
||||
// will be able to use internet. If ethernet gets connected again,
|
||||
// and has backhaul connectivity, it will become default.
|
||||
restart();
|
||||
}
|
||||
|
||||
/** Returns true if state has been modified */
|
||||
boolean updateLinkState(final boolean up,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (mLinkUp == up) {
|
||||
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, null,
|
||||
new EthernetNetworkManagementException(
|
||||
"No changes with requested link state " + up + " for " + name));
|
||||
return false;
|
||||
}
|
||||
mLinkUp = up;
|
||||
|
||||
if (!up) { // was up, goes down
|
||||
// Send an abort on a provisioning request callback if necessary before stopping.
|
||||
maybeSendNetworkManagementCallbackForAbort();
|
||||
stop();
|
||||
// If only setting the interface down, send a callback to signal completion.
|
||||
EthernetNetworkFactory.maybeSendNetworkManagementCallback(listener, name, null);
|
||||
} else { // was down, goes up
|
||||
stop();
|
||||
start(listener);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
// Invalidate all previous start requests
|
||||
if (mIpClient != null) {
|
||||
mIpClient.shutdown();
|
||||
mIpClientCallback.awaitIpClientShutdown();
|
||||
mIpClient = null;
|
||||
}
|
||||
mIpClientCallback = null;
|
||||
|
||||
if (mNetworkAgent != null) {
|
||||
mNetworkAgent.unregister();
|
||||
mNetworkAgent = null;
|
||||
}
|
||||
mLinkProperties.clear();
|
||||
}
|
||||
|
||||
private static void provisionIpClient(@NonNull final IpClientManager ipClient,
|
||||
@NonNull final IpConfiguration config, @NonNull final String tcpBufferSizes) {
|
||||
if (config.getProxySettings() == ProxySettings.STATIC ||
|
||||
config.getProxySettings() == ProxySettings.PAC) {
|
||||
ipClient.setHttpProxy(config.getHttpProxy());
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(tcpBufferSizes)) {
|
||||
ipClient.setTcpBufferSizes(tcpBufferSizes);
|
||||
}
|
||||
|
||||
ipClient.startProvisioning(createProvisioningConfiguration(config));
|
||||
}
|
||||
|
||||
private static ProvisioningConfiguration createProvisioningConfiguration(
|
||||
@NonNull final IpConfiguration config) {
|
||||
if (config.getIpAssignment() == IpAssignment.STATIC) {
|
||||
return new ProvisioningConfiguration.Builder()
|
||||
.withStaticConfiguration(config.getStaticIpConfiguration())
|
||||
.build();
|
||||
}
|
||||
return new ProvisioningConfiguration.Builder()
|
||||
.withProvisioningTimeoutMs(0)
|
||||
.build();
|
||||
}
|
||||
|
||||
void restart() {
|
||||
restart(null);
|
||||
}
|
||||
|
||||
void restart(@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (DBG) Log.d(TAG, "reconnecting Ethernet");
|
||||
stop();
|
||||
start(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{ "
|
||||
+ "refCount: " + refCount + ", "
|
||||
+ "iface: " + name + ", "
|
||||
+ "up: " + mLinkUp + ", "
|
||||
+ "hwAddress: " + mHwAddress + ", "
|
||||
+ "networkCapabilities: " + mCapabilities + ", "
|
||||
+ "networkAgent: " + mNetworkAgent + ", "
|
||||
+ "ipClient: " + mIpClient + ","
|
||||
+ "linkProperties: " + mLinkProperties
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||
super.dump(fd, pw, args);
|
||||
pw.println(getClass().getSimpleName());
|
||||
pw.println("Tracking interfaces:");
|
||||
pw.increaseIndent();
|
||||
for (String iface: mTrackingInterfaces.keySet()) {
|
||||
NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
|
||||
pw.println(iface + ":" + ifaceState);
|
||||
pw.increaseIndent();
|
||||
if (null == ifaceState.mIpClient) {
|
||||
pw.println("IpClient is null");
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.ethernet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.INetd;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
// TODO: consider renaming EthernetServiceImpl to EthernetService and deleting this file.
|
||||
public final class EthernetService {
|
||||
private static final String TAG = "EthernetService";
|
||||
private static final String THREAD_NAME = "EthernetServiceThread";
|
||||
|
||||
private static INetd getNetd(Context context) {
|
||||
final INetd netd =
|
||||
INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE));
|
||||
Objects.requireNonNull(netd, "could not get netd instance");
|
||||
return netd;
|
||||
}
|
||||
|
||||
public static EthernetServiceImpl create(Context context) {
|
||||
final HandlerThread handlerThread = new HandlerThread(THREAD_NAME);
|
||||
handlerThread.start();
|
||||
final Handler handler = new Handler(handlerThread.getLooper());
|
||||
final EthernetNetworkFactory factory = new EthernetNetworkFactory(handler, context);
|
||||
return new EthernetServiceImpl(context, handler,
|
||||
new EthernetTracker(context, handler, factory, getNetd(context)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.ethernet;
|
||||
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.IEthernetManager;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.ITetheredInterfaceCallback;
|
||||
import android.net.EthernetNetworkUpdateRequest;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.PrintWriterPrinter;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* EthernetServiceImpl handles remote Ethernet operation requests by implementing
|
||||
* the IEthernetManager interface.
|
||||
*/
|
||||
public class EthernetServiceImpl extends IEthernetManager.Stub {
|
||||
private static final String TAG = "EthernetServiceImpl";
|
||||
|
||||
@VisibleForTesting
|
||||
final AtomicBoolean mStarted = new AtomicBoolean(false);
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final EthernetTracker mTracker;
|
||||
|
||||
EthernetServiceImpl(@NonNull final Context context, @NonNull final Handler handler,
|
||||
@NonNull final EthernetTracker tracker) {
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mTracker = tracker;
|
||||
}
|
||||
|
||||
private void enforceAutomotiveDevice(final @NonNull String methodName) {
|
||||
PermissionUtils.enforceSystemFeature(mContext, PackageManager.FEATURE_AUTOMOTIVE,
|
||||
methodName + " is only available on automotive devices.");
|
||||
}
|
||||
|
||||
private boolean checkUseRestrictedNetworksPermission() {
|
||||
return PermissionUtils.checkAnyPermissionOf(mContext,
|
||||
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Log.i(TAG, "Starting Ethernet service");
|
||||
mTracker.start();
|
||||
mStarted.set(true);
|
||||
}
|
||||
|
||||
private void throwIfEthernetNotStarted() {
|
||||
if (!mStarted.get()) {
|
||||
throw new IllegalStateException("System isn't ready to change ethernet configurations");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAvailableInterfaces() throws RemoteException {
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ethernet configuration
|
||||
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
|
||||
*/
|
||||
@Override
|
||||
public IpConfiguration getConfiguration(String iface) {
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||
}
|
||||
|
||||
return new IpConfiguration(mTracker.getIpConfiguration(iface));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Ethernet configuration
|
||||
*/
|
||||
@Override
|
||||
public void setConfiguration(String iface, IpConfiguration config) {
|
||||
throwIfEthernetNotStarted();
|
||||
|
||||
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||
}
|
||||
|
||||
// TODO: this does not check proxy settings, gateways, etc.
|
||||
// Fix this by making IpConfiguration a complete representation of static configuration.
|
||||
mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether given interface is available.
|
||||
*/
|
||||
@Override
|
||||
public boolean isAvailable(String iface) {
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
if (mTracker.isRestrictedInterface(iface)) {
|
||||
PermissionUtils.enforceRestrictedNetworkPermission(mContext, TAG);
|
||||
}
|
||||
|
||||
return mTracker.isTrackingInterface(iface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener.
|
||||
* @param listener A {@link IEthernetServiceListener} to add.
|
||||
*/
|
||||
public void addListener(IEthernetServiceListener listener) throws RemoteException {
|
||||
Objects.requireNonNull(listener, "listener must not be null");
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener.
|
||||
* @param listener A {@link IEthernetServiceListener} to remove.
|
||||
*/
|
||||
public void removeListener(IEthernetServiceListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener must not be null");
|
||||
}
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
mTracker.removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeTestInterfaces(boolean include) {
|
||||
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||
android.Manifest.permission.NETWORK_SETTINGS);
|
||||
mTracker.setIncludeTestInterfaces(include);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||
Objects.requireNonNull(callback, "callback must not be null");
|
||||
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||
android.Manifest.permission.NETWORK_SETTINGS);
|
||||
mTracker.requestTetheredInterface(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||
Objects.requireNonNull(callback, "callback must not be null");
|
||||
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||
android.Manifest.permission.NETWORK_SETTINGS);
|
||||
mTracker.releaseTetheredInterface(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
||||
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
pw.println("Permission Denial: can't dump EthernetService from pid="
|
||||
+ Binder.getCallingPid()
|
||||
+ ", uid=" + Binder.getCallingUid());
|
||||
return;
|
||||
}
|
||||
|
||||
pw.println("Current Ethernet state: ");
|
||||
pw.increaseIndent();
|
||||
mTracker.dump(fd, pw, args);
|
||||
pw.decreaseIndent();
|
||||
|
||||
pw.println("Handler:");
|
||||
pw.increaseIndent();
|
||||
mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
private void enforceNetworkManagementPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS,
|
||||
"EthernetServiceImpl");
|
||||
}
|
||||
|
||||
private void enforceManageTestNetworksPermission() {
|
||||
mContext.enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.MANAGE_TEST_NETWORKS,
|
||||
"EthernetServiceImpl");
|
||||
}
|
||||
|
||||
private void maybeValidateTestCapabilities(final String iface,
|
||||
@Nullable final NetworkCapabilities nc) {
|
||||
if (!mTracker.isValidTestInterface(iface)) {
|
||||
return;
|
||||
}
|
||||
// For test interfaces, only null or capabilities that include TRANSPORT_TEST are
|
||||
// allowed.
|
||||
if (nc != null && !nc.hasTransport(TRANSPORT_TEST)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Updates to test interfaces must have NetworkCapabilities.TRANSPORT_TEST.");
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceAdminPermission(final String iface, boolean enforceAutomotive,
|
||||
final String logMessage) {
|
||||
if (mTracker.isValidTestInterface(iface)) {
|
||||
enforceManageTestNetworksPermission();
|
||||
} else {
|
||||
enforceNetworkManagementPermission();
|
||||
if (enforceAutomotive) {
|
||||
enforceAutomotiveDevice(logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfiguration(@NonNull final String iface,
|
||||
@NonNull final EthernetNetworkUpdateRequest request,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
Objects.requireNonNull(iface);
|
||||
Objects.requireNonNull(request);
|
||||
throwIfEthernetNotStarted();
|
||||
|
||||
// TODO: validate that iface is listed in overlay config_ethernet_interfaces
|
||||
// only automotive devices are allowed to set the NetworkCapabilities using this API
|
||||
enforceAdminPermission(iface, request.getNetworkCapabilities() != null,
|
||||
"updateConfiguration() with non-null capabilities");
|
||||
maybeValidateTestCapabilities(iface, request.getNetworkCapabilities());
|
||||
|
||||
mTracker.updateConfiguration(
|
||||
iface, request.getIpConfiguration(), request.getNetworkCapabilities(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectNetwork(@NonNull final String iface,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
|
||||
Objects.requireNonNull(iface);
|
||||
throwIfEthernetNotStarted();
|
||||
|
||||
enforceAdminPermission(iface, true, "connectNetwork()");
|
||||
|
||||
mTracker.connectNetwork(iface, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnectNetwork(@NonNull final String iface,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
|
||||
Objects.requireNonNull(iface);
|
||||
throwIfEthernetNotStarted();
|
||||
|
||||
enforceAdminPermission(iface, true, "connectNetwork()");
|
||||
|
||||
mTracker.disconnectNetwork(iface, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEthernetEnabled(boolean enabled) {
|
||||
PermissionUtils.enforceNetworkStackPermissionOr(mContext,
|
||||
android.Manifest.permission.NETWORK_SETTINGS);
|
||||
|
||||
mTracker.setEthernetEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getInterfaceList() {
|
||||
PermissionUtils.enforceAccessNetworkStatePermission(mContext, TAG);
|
||||
return mTracker.getInterfaceList();
|
||||
}
|
||||
}
|
||||
942
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
942
service-t/src/com/android/server/ethernet/EthernetTracker.java
Normal file
@@ -0,0 +1,942 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.ethernet;
|
||||
|
||||
import static android.net.EthernetManager.ETHERNET_STATE_DISABLED;
|
||||
import static android.net.EthernetManager.ETHERNET_STATE_ENABLED;
|
||||
import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityResources;
|
||||
import android.net.EthernetManager;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.INetd;
|
||||
import android.net.ITetheredInterfaceCallback;
|
||||
import android.net.InterfaceConfigurationParcel;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteCallbackList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||
import com.android.net.module.util.NetdUtils;
|
||||
import com.android.net.module.util.PermissionUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Tracks Ethernet interfaces and manages interface configurations.
|
||||
*
|
||||
* <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
|
||||
* in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
|
||||
* not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
|
||||
* Interfaces could have associated {@link android.net.IpConfiguration}.
|
||||
* Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
|
||||
* connected over USB). This class supports multiple interfaces. When an interface appears on the
|
||||
* system (or is present at boot time) this class will start tracking it and bring it up. Only
|
||||
* interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
|
||||
* tracked.
|
||||
*
|
||||
* <p>All public or package private methods must be thread-safe unless stated otherwise.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public class EthernetTracker {
|
||||
private static final int INTERFACE_MODE_CLIENT = 1;
|
||||
private static final int INTERFACE_MODE_SERVER = 2;
|
||||
|
||||
private static final String TAG = EthernetTracker.class.getSimpleName();
|
||||
private static final boolean DBG = EthernetNetworkFactory.DBG;
|
||||
|
||||
private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+";
|
||||
private static final String LEGACY_IFACE_REGEXP = "eth\\d";
|
||||
|
||||
/**
|
||||
* Interface names we track. This is a product-dependent regular expression, plus,
|
||||
* if setIncludeTestInterfaces is true, any test interfaces.
|
||||
*/
|
||||
private volatile String mIfaceMatch;
|
||||
/**
|
||||
* Track test interfaces if true, don't track otherwise.
|
||||
*/
|
||||
private boolean mIncludeTestInterfaces = false;
|
||||
|
||||
/** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
|
||||
private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
|
||||
new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final Context mContext;
|
||||
private final INetd mNetd;
|
||||
private final Handler mHandler;
|
||||
private final EthernetNetworkFactory mFactory;
|
||||
private final EthernetConfigStore mConfigStore;
|
||||
private final Dependencies mDeps;
|
||||
|
||||
private final RemoteCallbackList<IEthernetServiceListener> mListeners =
|
||||
new RemoteCallbackList<>();
|
||||
private final TetheredInterfaceRequestList mTetheredInterfaceRequests =
|
||||
new TetheredInterfaceRequestList();
|
||||
|
||||
// Used only on the handler thread
|
||||
private String mDefaultInterface;
|
||||
private int mDefaultInterfaceMode = INTERFACE_MODE_CLIENT;
|
||||
// Tracks whether clients were notified that the tethered interface is available
|
||||
private boolean mTetheredInterfaceWasAvailable = false;
|
||||
private volatile IpConfiguration mIpConfigForDefaultInterface;
|
||||
|
||||
private int mEthernetState = ETHERNET_STATE_ENABLED;
|
||||
|
||||
private class TetheredInterfaceRequestList extends
|
||||
RemoteCallbackList<ITetheredInterfaceCallback> {
|
||||
@Override
|
||||
public void onCallbackDied(ITetheredInterfaceCallback cb, Object cookie) {
|
||||
mHandler.post(EthernetTracker.this::maybeUntetherDefaultInterface);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Dependencies {
|
||||
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||
private String getPlatformRegexResource(Context context) {
|
||||
final Resources r = context.getResources();
|
||||
final int resId =
|
||||
r.getIdentifier("config_ethernet_iface_regex", "string", context.getPackageName());
|
||||
return r.getString(resId);
|
||||
}
|
||||
|
||||
// TODO: remove legacy resource fallback after migrating its overlays.
|
||||
private String[] getPlatformInterfaceConfigs(Context context) {
|
||||
final Resources r = context.getResources();
|
||||
final int resId = r.getIdentifier("config_ethernet_interfaces", "array",
|
||||
context.getPackageName());
|
||||
return r.getStringArray(resId);
|
||||
}
|
||||
|
||||
public String getInterfaceRegexFromResource(Context context) {
|
||||
final String platformRegex = getPlatformRegexResource(context);
|
||||
final String match;
|
||||
if (!LEGACY_IFACE_REGEXP.equals(platformRegex)) {
|
||||
// Platform resource is not the historical default: use the overlay
|
||||
match = platformRegex;
|
||||
} else {
|
||||
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||
match = resources.get().getString(
|
||||
com.android.connectivity.resources.R.string.config_ethernet_iface_regex);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
public String[] getInterfaceConfigFromResource(Context context) {
|
||||
final String[] platformInterfaceConfigs = getPlatformInterfaceConfigs(context);
|
||||
final String[] interfaceConfigs;
|
||||
if (platformInterfaceConfigs.length != 0) {
|
||||
// Platform resource is not the historical default: use the overlay
|
||||
interfaceConfigs = platformInterfaceConfigs;
|
||||
} else {
|
||||
final ConnectivityResources resources = new ConnectivityResources(context);
|
||||
interfaceConfigs = resources.get().getStringArray(
|
||||
com.android.connectivity.resources.R.array.config_ethernet_interfaces);
|
||||
}
|
||||
return interfaceConfigs;
|
||||
}
|
||||
}
|
||||
|
||||
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
|
||||
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
|
||||
this(context, handler, factory, netd, new Dependencies());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
|
||||
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd,
|
||||
@NonNull final Dependencies deps) {
|
||||
mContext = context;
|
||||
mHandler = handler;
|
||||
mFactory = factory;
|
||||
mNetd = netd;
|
||||
mDeps = deps;
|
||||
|
||||
// Interface match regex.
|
||||
updateIfaceMatchRegexp();
|
||||
|
||||
// Read default Ethernet interface configuration from resources
|
||||
final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
|
||||
for (String strConfig : interfaceConfigs) {
|
||||
parseEthernetConfig(strConfig);
|
||||
}
|
||||
|
||||
mConfigStore = new EthernetConfigStore();
|
||||
}
|
||||
|
||||
void start() {
|
||||
mFactory.register();
|
||||
mConfigStore.read();
|
||||
|
||||
// Default interface is just the first one we want to track.
|
||||
mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
|
||||
final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
|
||||
for (int i = 0; i < configs.size(); i++) {
|
||||
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
|
||||
}
|
||||
|
||||
try {
|
||||
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||
mNetd.registerUnsolicitedEventListener(new InterfaceObserver());
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
Log.e(TAG, "Could not register InterfaceObserver " + e);
|
||||
}
|
||||
|
||||
mHandler.post(this::trackAvailableInterfaces);
|
||||
}
|
||||
|
||||
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
|
||||
}
|
||||
writeIpConfiguration(iface, ipConfiguration);
|
||||
mHandler.post(() -> {
|
||||
mFactory.updateInterface(iface, ipConfiguration, null, null);
|
||||
broadcastInterfaceStateChange(iface);
|
||||
});
|
||||
}
|
||||
|
||||
private void writeIpConfiguration(@NonNull final String iface,
|
||||
@NonNull final IpConfiguration ipConfig) {
|
||||
mConfigStore.write(iface, ipConfig);
|
||||
mIpConfigurations.put(iface, ipConfig);
|
||||
}
|
||||
|
||||
private IpConfiguration getIpConfigurationForCallback(String iface, int state) {
|
||||
return (state == EthernetManager.STATE_ABSENT) ? null : getOrCreateIpConfiguration(iface);
|
||||
}
|
||||
|
||||
private void ensureRunningOnEthernetServiceThread() {
|
||||
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on EthernetService thread: "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the link state or IpConfiguration change of existing Ethernet interfaces to all
|
||||
* listeners.
|
||||
*/
|
||||
protected void broadcastInterfaceStateChange(@NonNull String iface) {
|
||||
ensureRunningOnEthernetServiceThread();
|
||||
final int state = mFactory.getInterfaceState(iface);
|
||||
final int role = getInterfaceRole(iface);
|
||||
final IpConfiguration config = getIpConfigurationForCallback(iface, state);
|
||||
final int n = mListeners.beginBroadcast();
|
||||
for (int i = 0; i < n; i++) {
|
||||
try {
|
||||
mListeners.getBroadcastItem(i).onInterfaceStateChanged(iface, state, role, config);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unicast the interface state or IpConfiguration change of existing Ethernet interfaces to a
|
||||
* specific listener.
|
||||
*/
|
||||
protected void unicastInterfaceStateChange(@NonNull IEthernetServiceListener listener,
|
||||
@NonNull String iface) {
|
||||
ensureRunningOnEthernetServiceThread();
|
||||
final int state = mFactory.getInterfaceState(iface);
|
||||
final int role = getInterfaceRole(iface);
|
||||
final IpConfiguration config = getIpConfigurationForCallback(iface, state);
|
||||
try {
|
||||
listener.onInterfaceStateChanged(iface, state, role, config);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected void updateConfiguration(@NonNull final String iface,
|
||||
@Nullable final IpConfiguration ipConfig,
|
||||
@Nullable final NetworkCapabilities capabilities,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "updateConfiguration, iface: " + iface + ", capabilities: " + capabilities
|
||||
+ ", ipConfig: " + ipConfig);
|
||||
}
|
||||
|
||||
final IpConfiguration localIpConfig = ipConfig == null
|
||||
? null : new IpConfiguration(ipConfig);
|
||||
if (ipConfig != null) {
|
||||
writeIpConfiguration(iface, localIpConfig);
|
||||
}
|
||||
|
||||
if (null != capabilities) {
|
||||
mNetworkCapabilities.put(iface, capabilities);
|
||||
}
|
||||
mHandler.post(() -> {
|
||||
mFactory.updateInterface(iface, localIpConfig, capabilities, listener);
|
||||
broadcastInterfaceStateChange(iface);
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected void connectNetwork(@NonNull final String iface,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
mHandler.post(() -> updateInterfaceState(iface, true, listener));
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected void disconnectNetwork(@NonNull final String iface,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
mHandler.post(() -> updateInterfaceState(iface, false, listener));
|
||||
}
|
||||
|
||||
IpConfiguration getIpConfiguration(String iface) {
|
||||
return mIpConfigurations.get(iface);
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected boolean isTrackingInterface(String iface) {
|
||||
return mFactory.hasInterface(iface);
|
||||
}
|
||||
|
||||
String[] getInterfaces(boolean includeRestricted) {
|
||||
return mFactory.getAvailableInterfaces(includeRestricted);
|
||||
}
|
||||
|
||||
List<String> getInterfaceList() {
|
||||
final List<String> interfaceList = new ArrayList<String>();
|
||||
final String[] ifaces;
|
||||
try {
|
||||
ifaces = mNetd.interfaceGetList();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||
return interfaceList;
|
||||
}
|
||||
final String ifaceMatch = mIfaceMatch;
|
||||
for (String iface : ifaces) {
|
||||
if (iface.matches(ifaceMatch)) interfaceList.add(iface);
|
||||
}
|
||||
return interfaceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given interface was configured as restricted (doesn't have
|
||||
* NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
|
||||
*/
|
||||
boolean isRestrictedInterface(String iface) {
|
||||
final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||
return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
|
||||
}
|
||||
|
||||
void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
|
||||
mHandler.post(() -> {
|
||||
if (!mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks))) {
|
||||
// Remote process has already died
|
||||
return;
|
||||
}
|
||||
for (String iface : getInterfaces(canUseRestrictedNetworks)) {
|
||||
unicastInterfaceStateChange(listener, iface);
|
||||
}
|
||||
|
||||
unicastEthernetStateChange(listener, mEthernetState);
|
||||
});
|
||||
}
|
||||
|
||||
void removeListener(IEthernetServiceListener listener) {
|
||||
mHandler.post(() -> mListeners.unregister(listener));
|
||||
}
|
||||
|
||||
public void setIncludeTestInterfaces(boolean include) {
|
||||
mHandler.post(() -> {
|
||||
mIncludeTestInterfaces = include;
|
||||
updateIfaceMatchRegexp();
|
||||
mHandler.post(() -> trackAvailableInterfaces());
|
||||
});
|
||||
}
|
||||
|
||||
public void requestTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||
mHandler.post(() -> {
|
||||
if (!mTetheredInterfaceRequests.register(callback)) {
|
||||
// Remote process has already died
|
||||
return;
|
||||
}
|
||||
if (mDefaultInterfaceMode == INTERFACE_MODE_SERVER) {
|
||||
if (mTetheredInterfaceWasAvailable) {
|
||||
notifyTetheredInterfaceAvailable(callback, mDefaultInterface);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaultInterfaceMode(INTERFACE_MODE_SERVER);
|
||||
});
|
||||
}
|
||||
|
||||
public void releaseTetheredInterface(ITetheredInterfaceCallback callback) {
|
||||
mHandler.post(() -> {
|
||||
mTetheredInterfaceRequests.unregister(callback);
|
||||
maybeUntetherDefaultInterface();
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyTetheredInterfaceAvailable(ITetheredInterfaceCallback cb, String iface) {
|
||||
try {
|
||||
cb.onAvailable(iface);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error sending tethered interface available callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyTetheredInterfaceUnavailable(ITetheredInterfaceCallback cb) {
|
||||
try {
|
||||
cb.onUnavailable();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error sending tethered interface available callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUntetherDefaultInterface() {
|
||||
if (mTetheredInterfaceRequests.getRegisteredCallbackCount() > 0) return;
|
||||
if (mDefaultInterfaceMode == INTERFACE_MODE_CLIENT) return;
|
||||
setDefaultInterfaceMode(INTERFACE_MODE_CLIENT);
|
||||
}
|
||||
|
||||
private void setDefaultInterfaceMode(int mode) {
|
||||
Log.d(TAG, "Setting default interface mode to " + mode);
|
||||
mDefaultInterfaceMode = mode;
|
||||
if (mDefaultInterface != null) {
|
||||
removeInterface(mDefaultInterface);
|
||||
addInterface(mDefaultInterface);
|
||||
}
|
||||
}
|
||||
|
||||
private int getInterfaceRole(final String iface) {
|
||||
if (!mFactory.hasInterface(iface)) return EthernetManager.ROLE_NONE;
|
||||
final int mode = getInterfaceMode(iface);
|
||||
return (mode == INTERFACE_MODE_CLIENT)
|
||||
? EthernetManager.ROLE_CLIENT
|
||||
: EthernetManager.ROLE_SERVER;
|
||||
}
|
||||
|
||||
private int getInterfaceMode(final String iface) {
|
||||
if (iface.equals(mDefaultInterface)) {
|
||||
return mDefaultInterfaceMode;
|
||||
}
|
||||
return INTERFACE_MODE_CLIENT;
|
||||
}
|
||||
|
||||
private void removeInterface(String iface) {
|
||||
mFactory.removeInterface(iface);
|
||||
maybeUpdateServerModeInterfaceState(iface, false);
|
||||
}
|
||||
|
||||
private void stopTrackingInterface(String iface) {
|
||||
removeInterface(iface);
|
||||
if (iface.equals(mDefaultInterface)) {
|
||||
mDefaultInterface = null;
|
||||
}
|
||||
broadcastInterfaceStateChange(iface);
|
||||
}
|
||||
|
||||
private void addInterface(String iface) {
|
||||
InterfaceConfigurationParcel config = null;
|
||||
// Bring up the interface so we get link status indications.
|
||||
try {
|
||||
PermissionUtils.enforceNetworkStackPermission(mContext);
|
||||
NetdUtils.setInterfaceUp(mNetd, iface);
|
||||
config = NetdUtils.getInterfaceConfigParcel(mNetd, iface);
|
||||
} catch (IllegalStateException e) {
|
||||
// Either the system is crashing or the interface has disappeared. Just ignore the
|
||||
// error; we haven't modified any state because we only do that if our calls succeed.
|
||||
Log.e(TAG, "Error upping interface " + iface, e);
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
Log.e(TAG, "Null interface config parcelable for " + iface + ". Bailing out.");
|
||||
return;
|
||||
}
|
||||
|
||||
final String hwAddress = config.hwAddr;
|
||||
|
||||
NetworkCapabilities nc = mNetworkCapabilities.get(iface);
|
||||
if (nc == null) {
|
||||
// Try to resolve using mac address
|
||||
nc = mNetworkCapabilities.get(hwAddress);
|
||||
if (nc == null) {
|
||||
final boolean isTestIface = iface.matches(TEST_IFACE_REGEXP);
|
||||
nc = createDefaultNetworkCapabilities(isTestIface);
|
||||
}
|
||||
}
|
||||
|
||||
final int mode = getInterfaceMode(iface);
|
||||
if (mode == INTERFACE_MODE_CLIENT) {
|
||||
IpConfiguration ipConfiguration = getOrCreateIpConfiguration(iface);
|
||||
Log.d(TAG, "Tracking interface in client mode: " + iface);
|
||||
mFactory.addInterface(iface, hwAddress, ipConfiguration, nc);
|
||||
} else {
|
||||
maybeUpdateServerModeInterfaceState(iface, true);
|
||||
}
|
||||
|
||||
// Note: if the interface already has link (e.g., if we crashed and got
|
||||
// restarted while it was running), we need to fake a link up notification so we
|
||||
// start configuring it.
|
||||
if (NetdUtils.hasFlag(config, "running")) {
|
||||
updateInterfaceState(iface, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInterfaceState(String iface, boolean up) {
|
||||
updateInterfaceState(iface, up, null /* listener */);
|
||||
}
|
||||
|
||||
private void updateInterfaceState(@NonNull final String iface, final boolean up,
|
||||
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
|
||||
final int mode = getInterfaceMode(iface);
|
||||
final boolean factoryLinkStateUpdated = (mode == INTERFACE_MODE_CLIENT)
|
||||
&& mFactory.updateInterfaceLinkState(iface, up, listener);
|
||||
|
||||
if (factoryLinkStateUpdated) {
|
||||
broadcastInterfaceStateChange(iface);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateServerModeInterfaceState(String iface, boolean available) {
|
||||
if (available == mTetheredInterfaceWasAvailable || !iface.equals(mDefaultInterface)) return;
|
||||
|
||||
Log.d(TAG, (available ? "Tracking" : "No longer tracking")
|
||||
+ " interface in server mode: " + iface);
|
||||
|
||||
final int pendingCbs = mTetheredInterfaceRequests.beginBroadcast();
|
||||
for (int i = 0; i < pendingCbs; i++) {
|
||||
ITetheredInterfaceCallback item = mTetheredInterfaceRequests.getBroadcastItem(i);
|
||||
if (available) {
|
||||
notifyTetheredInterfaceAvailable(item, iface);
|
||||
} else {
|
||||
notifyTetheredInterfaceUnavailable(item);
|
||||
}
|
||||
}
|
||||
mTetheredInterfaceRequests.finishBroadcast();
|
||||
mTetheredInterfaceWasAvailable = available;
|
||||
}
|
||||
|
||||
private void maybeTrackInterface(String iface) {
|
||||
if (!iface.matches(mIfaceMatch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't already track this interface, and if this interface matches
|
||||
// our regex, start tracking it.
|
||||
if (mFactory.hasInterface(iface) || iface.equals(mDefaultInterface)) {
|
||||
if (DBG) Log.w(TAG, "Ignoring already-tracked interface " + iface);
|
||||
return;
|
||||
}
|
||||
if (DBG) Log.i(TAG, "maybeTrackInterface: " + iface);
|
||||
|
||||
// TODO: avoid making an interface default if it has configured NetworkCapabilities.
|
||||
if (mDefaultInterface == null) {
|
||||
mDefaultInterface = iface;
|
||||
}
|
||||
|
||||
if (mIpConfigForDefaultInterface != null) {
|
||||
updateIpConfiguration(iface, mIpConfigForDefaultInterface);
|
||||
mIpConfigForDefaultInterface = null;
|
||||
}
|
||||
|
||||
addInterface(iface);
|
||||
|
||||
broadcastInterfaceStateChange(iface);
|
||||
}
|
||||
|
||||
private void trackAvailableInterfaces() {
|
||||
try {
|
||||
final String[] ifaces = mNetd.interfaceGetList();
|
||||
for (String iface : ifaces) {
|
||||
maybeTrackInterface(iface);
|
||||
}
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
Log.e(TAG, "Could not get list of interfaces " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
|
||||
|
||||
@Override
|
||||
public void onInterfaceLinkStateChanged(String iface, boolean up) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
|
||||
}
|
||||
mHandler.post(() -> updateInterfaceState(iface, up));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterfaceAdded(String iface) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
|
||||
}
|
||||
mHandler.post(() -> maybeTrackInterface(iface));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterfaceRemoved(String iface) {
|
||||
if (DBG) {
|
||||
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
|
||||
}
|
||||
mHandler.post(() -> stopTrackingInterface(iface));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ListenerInfo {
|
||||
|
||||
boolean canUseRestrictedNetworks = false;
|
||||
|
||||
ListenerInfo(boolean canUseRestrictedNetworks) {
|
||||
this.canUseRestrictedNetworks = canUseRestrictedNetworks;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an Ethernet interface configuration
|
||||
*
|
||||
* @param configString represents an Ethernet configuration in the following format: {@code
|
||||
* <interface name|mac address>;[Network Capabilities];[IP config];[Override Transport]}
|
||||
*/
|
||||
private void parseEthernetConfig(String configString) {
|
||||
final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
|
||||
NetworkCapabilities nc = createNetworkCapabilities(
|
||||
!TextUtils.isEmpty(config.mCapabilities) /* clear default capabilities */,
|
||||
config.mCapabilities, config.mTransport).build();
|
||||
mNetworkCapabilities.put(config.mIface, nc);
|
||||
|
||||
if (null != config.mIpConfig) {
|
||||
IpConfiguration ipConfig = parseStaticIpConfiguration(config.mIpConfig);
|
||||
mIpConfigurations.put(config.mIface, ipConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static EthernetTrackerConfig createEthernetTrackerConfig(@NonNull final String configString) {
|
||||
Objects.requireNonNull(configString, "EthernetTrackerConfig requires non-null config");
|
||||
return new EthernetTrackerConfig(configString.split(";", /* limit of tokens */ 4));
|
||||
}
|
||||
|
||||
private static NetworkCapabilities createDefaultNetworkCapabilities(boolean isTestIface) {
|
||||
NetworkCapabilities.Builder builder = createNetworkCapabilities(
|
||||
false /* clear default capabilities */, null, null)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
|
||||
if (isTestIface) {
|
||||
builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST);
|
||||
} else {
|
||||
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a static list of network capabilities
|
||||
*
|
||||
* @param clearDefaultCapabilities Indicates whether or not to clear any default capabilities
|
||||
* @param commaSeparatedCapabilities A comma separated string list of integer encoded
|
||||
* NetworkCapability.NET_CAPABILITY_* values
|
||||
* @param overrideTransport A string representing a single integer encoded override transport
|
||||
* type. Must be one of the NetworkCapability.TRANSPORT_*
|
||||
* values. TRANSPORT_VPN is not supported. Errors with input
|
||||
* will cause the override to be ignored.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static NetworkCapabilities.Builder createNetworkCapabilities(
|
||||
boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities,
|
||||
@Nullable String overrideTransport) {
|
||||
|
||||
final NetworkCapabilities.Builder builder = clearDefaultCapabilities
|
||||
? NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||
: new NetworkCapabilities.Builder();
|
||||
|
||||
// Determine the transport type. If someone has tried to define an override transport then
|
||||
// attempt to add it. Since we can only have one override, all errors with it will
|
||||
// gracefully default back to TRANSPORT_ETHERNET and warn the user. VPN is not allowed as an
|
||||
// override type. Wifi Aware and LoWPAN are currently unsupported as well.
|
||||
int transport = NetworkCapabilities.TRANSPORT_ETHERNET;
|
||||
if (!TextUtils.isEmpty(overrideTransport)) {
|
||||
try {
|
||||
int parsedTransport = Integer.valueOf(overrideTransport);
|
||||
if (parsedTransport == NetworkCapabilities.TRANSPORT_VPN
|
||||
|| parsedTransport == NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
||||
|| parsedTransport == NetworkCapabilities.TRANSPORT_LOWPAN) {
|
||||
Log.e(TAG, "Override transport '" + parsedTransport + "' is not supported. "
|
||||
+ "Defaulting to TRANSPORT_ETHERNET");
|
||||
} else {
|
||||
transport = parsedTransport;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.e(TAG, "Override transport type '" + overrideTransport + "' "
|
||||
+ "could not be parsed. Defaulting to TRANSPORT_ETHERNET");
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the transport. If the user supplied a valid number that is not a valid transport
|
||||
// then adding will throw an exception. Default back to TRANSPORT_ETHERNET if that happens
|
||||
try {
|
||||
builder.addTransportType(transport);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Log.e(TAG, transport + " is not a valid NetworkCapability.TRANSPORT_* value. "
|
||||
+ "Defaulting to TRANSPORT_ETHERNET");
|
||||
builder.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||
}
|
||||
|
||||
builder.setLinkUpstreamBandwidthKbps(100 * 1000);
|
||||
builder.setLinkDownstreamBandwidthKbps(100 * 1000);
|
||||
|
||||
if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
|
||||
for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
|
||||
if (!TextUtils.isEmpty(strNetworkCapability)) {
|
||||
try {
|
||||
builder.addCapability(Integer.valueOf(strNetworkCapability));
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.e(TAG, "Capability '" + strNetworkCapability + "' could not be parsed");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
Log.e(TAG, strNetworkCapability + " is not a valid "
|
||||
+ "NetworkCapability.NET_CAPABILITY_* value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ethernet networks have no way to update the following capabilities, so they always
|
||||
// have them.
|
||||
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
|
||||
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
|
||||
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses static IP configuration.
|
||||
*
|
||||
* @param staticIpConfig represents static IP configuration in the following format: {@code
|
||||
* ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
|
||||
* domains=<comma-sep-domains>}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
|
||||
final StaticIpConfiguration.Builder staticIpConfigBuilder =
|
||||
new StaticIpConfiguration.Builder();
|
||||
|
||||
for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
|
||||
if (TextUtils.isEmpty(keyValueAsString)) continue;
|
||||
|
||||
String[] pair = keyValueAsString.split("=");
|
||||
if (pair.length != 2) {
|
||||
throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
|
||||
+ " in " + staticIpConfig);
|
||||
}
|
||||
|
||||
String key = pair[0];
|
||||
String value = pair[1];
|
||||
|
||||
switch (key) {
|
||||
case "ip":
|
||||
staticIpConfigBuilder.setIpAddress(new LinkAddress(value));
|
||||
break;
|
||||
case "domains":
|
||||
staticIpConfigBuilder.setDomains(value);
|
||||
break;
|
||||
case "gateway":
|
||||
staticIpConfigBuilder.setGateway(InetAddress.parseNumericAddress(value));
|
||||
break;
|
||||
case "dns": {
|
||||
ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
|
||||
for (String address: value.split(",")) {
|
||||
dnsAddresses.add(InetAddress.parseNumericAddress(address));
|
||||
}
|
||||
staticIpConfigBuilder.setDnsServers(dnsAddresses);
|
||||
break;
|
||||
}
|
||||
default : {
|
||||
throw new IllegalArgumentException("Unexpected key: " + key
|
||||
+ " in " + staticIpConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createIpConfiguration(staticIpConfigBuilder.build());
|
||||
}
|
||||
|
||||
private static IpConfiguration createIpConfiguration(
|
||||
@NonNull final StaticIpConfiguration staticIpConfig) {
|
||||
return new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
|
||||
}
|
||||
|
||||
private IpConfiguration getOrCreateIpConfiguration(String iface) {
|
||||
IpConfiguration ret = mIpConfigurations.get(iface);
|
||||
if (ret != null) return ret;
|
||||
ret = new IpConfiguration();
|
||||
ret.setIpAssignment(IpAssignment.DHCP);
|
||||
ret.setProxySettings(ProxySettings.NONE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void updateIfaceMatchRegexp() {
|
||||
final String match = mDeps.getInterfaceRegexFromResource(mContext);
|
||||
mIfaceMatch = mIncludeTestInterfaces
|
||||
? "(" + match + "|" + TEST_IFACE_REGEXP + ")"
|
||||
: match;
|
||||
Log.d(TAG, "Interface match regexp set to '" + mIfaceMatch + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a given interface is valid for testing.
|
||||
*
|
||||
* @param iface the name of the interface to validate.
|
||||
* @return {@code true} if test interfaces are enabled and the given {@code iface} has a test
|
||||
* interface prefix, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isValidTestInterface(@NonNull final String iface) {
|
||||
return mIncludeTestInterfaces && iface.matches(TEST_IFACE_REGEXP);
|
||||
}
|
||||
|
||||
private void postAndWaitForRunnable(Runnable r) {
|
||||
final ConditionVariable cv = new ConditionVariable();
|
||||
if (mHandler.post(() -> {
|
||||
r.run();
|
||||
cv.open();
|
||||
})) {
|
||||
cv.block(2000L);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
protected void setEthernetEnabled(boolean enabled) {
|
||||
mHandler.post(() -> {
|
||||
int newState = enabled ? ETHERNET_STATE_ENABLED : ETHERNET_STATE_DISABLED;
|
||||
if (mEthernetState == newState) return;
|
||||
|
||||
mEthernetState = newState;
|
||||
|
||||
if (enabled) {
|
||||
trackAvailableInterfaces();
|
||||
} else {
|
||||
// TODO: maybe also disable server mode interface as well.
|
||||
untrackFactoryInterfaces();
|
||||
}
|
||||
broadcastEthernetStateChange(mEthernetState);
|
||||
});
|
||||
}
|
||||
|
||||
private void untrackFactoryInterfaces() {
|
||||
for (String iface : mFactory.getAvailableInterfaces(true /* includeRestricted */)) {
|
||||
stopTrackingInterface(iface);
|
||||
}
|
||||
}
|
||||
|
||||
private void unicastEthernetStateChange(@NonNull IEthernetServiceListener listener,
|
||||
int state) {
|
||||
ensureRunningOnEthernetServiceThread();
|
||||
try {
|
||||
listener.onEthernetStateChanged(state);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastEthernetStateChange(int state) {
|
||||
ensureRunningOnEthernetServiceThread();
|
||||
final int n = mListeners.beginBroadcast();
|
||||
for (int i = 0; i < n; i++) {
|
||||
try {
|
||||
mListeners.getBroadcastItem(i).onEthernetStateChanged(state);
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing here.
|
||||
}
|
||||
}
|
||||
mListeners.finishBroadcast();
|
||||
}
|
||||
|
||||
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
|
||||
postAndWaitForRunnable(() -> {
|
||||
pw.println(getClass().getSimpleName());
|
||||
pw.println("Ethernet interface name filter: " + mIfaceMatch);
|
||||
pw.println("Default interface: " + mDefaultInterface);
|
||||
pw.println("Default interface mode: " + mDefaultInterfaceMode);
|
||||
pw.println("Tethered interface requests: "
|
||||
+ mTetheredInterfaceRequests.getRegisteredCallbackCount());
|
||||
pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
|
||||
pw.println("IP Configurations:");
|
||||
pw.increaseIndent();
|
||||
for (String iface : mIpConfigurations.keySet()) {
|
||||
pw.println(iface + ": " + mIpConfigurations.get(iface));
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
pw.println("Network Capabilities:");
|
||||
pw.increaseIndent();
|
||||
for (String iface : mNetworkCapabilities.keySet()) {
|
||||
pw.println(iface + ": " + mNetworkCapabilities.get(iface));
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
pw.println();
|
||||
|
||||
mFactory.dump(fd, pw, args);
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class EthernetTrackerConfig {
|
||||
final String mIface;
|
||||
final String mCapabilities;
|
||||
final String mIpConfig;
|
||||
final String mTransport;
|
||||
|
||||
EthernetTrackerConfig(@NonNull final String[] tokens) {
|
||||
Objects.requireNonNull(tokens, "EthernetTrackerConfig requires non-null tokens");
|
||||
mIface = tokens[0];
|
||||
mCapabilities = tokens.length > 1 ? tokens[1] : null;
|
||||
mIpConfig = tokens.length > 2 && !TextUtils.isEmpty(tokens[2]) ? tokens[2] : null;
|
||||
mTransport = tokens.length > 3 ? tokens[3] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
139
service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
Normal file
139
service-t/src/com/android/server/net/BpfInterfaceMapUpdater.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.INetd;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.system.ErrnoException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
|
||||
import com.android.net.module.util.BpfMap;
|
||||
import com.android.net.module.util.IBpfMap;
|
||||
import com.android.net.module.util.InterfaceParams;
|
||||
import com.android.net.module.util.Struct.U32;
|
||||
|
||||
/**
|
||||
* Monitor interface added (without removed) and right interface name and its index to bpf map.
|
||||
*/
|
||||
public class BpfInterfaceMapUpdater {
|
||||
private static final String TAG = BpfInterfaceMapUpdater.class.getSimpleName();
|
||||
// This is current path but may be changed soon.
|
||||
private static final String IFACE_INDEX_NAME_MAP_PATH =
|
||||
"/sys/fs/bpf/map_netd_iface_index_name_map";
|
||||
private final IBpfMap<U32, InterfaceMapValue> mBpfMap;
|
||||
private final INetd mNetd;
|
||||
private final Handler mHandler;
|
||||
private final Dependencies mDeps;
|
||||
|
||||
public BpfInterfaceMapUpdater(Context ctx, Handler handler) {
|
||||
this(ctx, handler, new Dependencies());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public BpfInterfaceMapUpdater(Context ctx, Handler handler, Dependencies deps) {
|
||||
mDeps = deps;
|
||||
mBpfMap = deps.getInterfaceMap();
|
||||
mNetd = deps.getINetd(ctx);
|
||||
mHandler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependencies of BpfInerfaceMapUpdater, for injection in tests.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class Dependencies {
|
||||
/** Create BpfMap for updating interface and index mapping. */
|
||||
public IBpfMap<U32, InterfaceMapValue> getInterfaceMap() {
|
||||
try {
|
||||
return new BpfMap<>(IFACE_INDEX_NAME_MAP_PATH, BpfMap.BPF_F_RDWR,
|
||||
U32.class, InterfaceMapValue.class);
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Cannot create interface map: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get InterfaceParams for giving interface name. */
|
||||
public InterfaceParams getInterfaceParams(String ifaceName) {
|
||||
return InterfaceParams.getByName(ifaceName);
|
||||
}
|
||||
|
||||
/** Get INetd binder object. */
|
||||
public INetd getINetd(Context ctx) {
|
||||
return INetd.Stub.asInterface((IBinder) ctx.getSystemService(Context.NETD_SERVICE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening interface update event.
|
||||
* Query current interface names before listening.
|
||||
*/
|
||||
public void start() {
|
||||
mHandler.post(() -> {
|
||||
if (mBpfMap == null) {
|
||||
Log.wtf(TAG, "Fail to start: Null bpf map");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: use a NetlinkMonitor and listen for RTM_NEWLINK messages instead.
|
||||
mNetd.registerUnsolicitedEventListener(new InterfaceChangeObserver());
|
||||
} catch (RemoteException e) {
|
||||
Log.wtf(TAG, "Unable to register netd UnsolicitedEventListener, " + e);
|
||||
}
|
||||
|
||||
final String[] ifaces;
|
||||
try {
|
||||
// TODO: use a netlink dump to get the current interface list.
|
||||
ifaces = mNetd.interfaceGetList();
|
||||
} catch (RemoteException | ServiceSpecificException e) {
|
||||
Log.wtf(TAG, "Unable to query interface names by netd, " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
for (String ifaceName : ifaces) {
|
||||
addInterface(ifaceName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addInterface(String ifaceName) {
|
||||
final InterfaceParams iface = mDeps.getInterfaceParams(ifaceName);
|
||||
if (iface == null) {
|
||||
Log.e(TAG, "Unable to get InterfaceParams for " + ifaceName);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mBpfMap.updateEntry(new U32(iface.index), new InterfaceMapValue(ifaceName));
|
||||
} catch (ErrnoException e) {
|
||||
Log.e(TAG, "Unable to update entry for " + ifaceName + ", " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private class InterfaceChangeObserver extends BaseNetdUnsolicitedEventListener {
|
||||
@Override
|
||||
public void onInterfaceAdded(String ifName) {
|
||||
mHandler.post(() -> addInterface(ifName));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
service-t/src/com/android/server/net/CookieTagMapKey.java
Normal file
33
service-t/src/com/android/server/net/CookieTagMapKey.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* Key for cookie tag map.
|
||||
*/
|
||||
public class CookieTagMapKey extends Struct {
|
||||
@Field(order = 0, type = Type.S64)
|
||||
public final long socketCookie;
|
||||
|
||||
public CookieTagMapKey(final long socketCookie) {
|
||||
this.socketCookie = socketCookie;
|
||||
}
|
||||
}
|
||||
37
service-t/src/com/android/server/net/CookieTagMapValue.java
Normal file
37
service-t/src/com/android/server/net/CookieTagMapValue.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* Value for cookie tag map.
|
||||
*/
|
||||
public class CookieTagMapValue extends Struct {
|
||||
@Field(order = 0, type = Type.U32)
|
||||
public final long uid;
|
||||
|
||||
@Field(order = 1, type = Type.U32)
|
||||
public final long tag;
|
||||
|
||||
public CookieTagMapValue(final long uid, final long tag) {
|
||||
this.uid = uid;
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
35
service-t/src/com/android/server/net/InterfaceMapValue.java
Normal file
35
service-t/src/com/android/server/net/InterfaceMapValue.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* The value of bpf interface index map which is used for NetworkStatsService.
|
||||
*/
|
||||
public class InterfaceMapValue extends Struct {
|
||||
@Field(order = 0, type = Type.ByteArray, arraysize = 16)
|
||||
public final byte[] interfaceName;
|
||||
|
||||
public InterfaceMapValue(String iface) {
|
||||
final byte[] ifaceArray = iface.getBytes();
|
||||
interfaceName = new byte[16];
|
||||
// All array bytes after the interface name, if any, must be 0.
|
||||
System.arraycopy(ifaceArray, 0, interfaceName, 0, ifaceArray.length);
|
||||
}
|
||||
}
|
||||
449
service-t/src/com/android/server/net/IpConfigStore.java
Normal file
449
service-t/src/com/android/server/net/IpConfigStore.java
Normal file
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.net;
|
||||
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.net.Uri;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.ProxyUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class provides an API to store and manage L3 network IP configuration.
|
||||
*/
|
||||
public class IpConfigStore {
|
||||
private static final String TAG = "IpConfigStore";
|
||||
private static final boolean DBG = false;
|
||||
|
||||
protected final DelayedDiskWrite mWriter;
|
||||
|
||||
/* IP and proxy configuration keys */
|
||||
protected static final String ID_KEY = "id";
|
||||
protected static final String IP_ASSIGNMENT_KEY = "ipAssignment";
|
||||
protected static final String LINK_ADDRESS_KEY = "linkAddress";
|
||||
protected static final String GATEWAY_KEY = "gateway";
|
||||
protected static final String DNS_KEY = "dns";
|
||||
protected static final String PROXY_SETTINGS_KEY = "proxySettings";
|
||||
protected static final String PROXY_HOST_KEY = "proxyHost";
|
||||
protected static final String PROXY_PORT_KEY = "proxyPort";
|
||||
protected static final String PROXY_PAC_FILE = "proxyPac";
|
||||
protected static final String EXCLUSION_LIST_KEY = "exclusionList";
|
||||
protected static final String EOS = "eos";
|
||||
|
||||
protected static final int IPCONFIG_FILE_VERSION = 3;
|
||||
|
||||
public IpConfigStore(DelayedDiskWrite writer) {
|
||||
mWriter = writer;
|
||||
}
|
||||
|
||||
public IpConfigStore() {
|
||||
this(new DelayedDiskWrite());
|
||||
}
|
||||
|
||||
private static boolean writeConfig(DataOutputStream out, String configKey,
|
||||
IpConfiguration config) throws IOException {
|
||||
return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the IP configuration with the given parameters to {@link DataOutputStream}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static boolean writeConfig(DataOutputStream out, String configKey,
|
||||
IpConfiguration config, int version) throws IOException {
|
||||
boolean written = false;
|
||||
|
||||
try {
|
||||
switch (config.getIpAssignment()) {
|
||||
case STATIC:
|
||||
out.writeUTF(IP_ASSIGNMENT_KEY);
|
||||
out.writeUTF(config.getIpAssignment().toString());
|
||||
StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
|
||||
if (staticIpConfiguration != null) {
|
||||
if (staticIpConfiguration.getIpAddress() != null) {
|
||||
LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
|
||||
out.writeUTF(LINK_ADDRESS_KEY);
|
||||
out.writeUTF(ipAddress.getAddress().getHostAddress());
|
||||
out.writeInt(ipAddress.getPrefixLength());
|
||||
}
|
||||
if (staticIpConfiguration.getGateway() != null) {
|
||||
out.writeUTF(GATEWAY_KEY);
|
||||
out.writeInt(0); // Default route.
|
||||
out.writeInt(1); // Have a gateway.
|
||||
out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
|
||||
}
|
||||
for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
|
||||
out.writeUTF(DNS_KEY);
|
||||
out.writeUTF(inetAddr.getHostAddress());
|
||||
}
|
||||
}
|
||||
written = true;
|
||||
break;
|
||||
case DHCP:
|
||||
out.writeUTF(IP_ASSIGNMENT_KEY);
|
||||
out.writeUTF(config.getIpAssignment().toString());
|
||||
written = true;
|
||||
break;
|
||||
case UNASSIGNED:
|
||||
/* Ignore */
|
||||
break;
|
||||
default:
|
||||
loge("Ignore invalid ip assignment while writing");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (config.getProxySettings()) {
|
||||
case STATIC:
|
||||
ProxyInfo proxyProperties = config.getHttpProxy();
|
||||
String exclusionList = ProxyUtils.exclusionListAsString(
|
||||
proxyProperties.getExclusionList());
|
||||
out.writeUTF(PROXY_SETTINGS_KEY);
|
||||
out.writeUTF(config.getProxySettings().toString());
|
||||
out.writeUTF(PROXY_HOST_KEY);
|
||||
out.writeUTF(proxyProperties.getHost());
|
||||
out.writeUTF(PROXY_PORT_KEY);
|
||||
out.writeInt(proxyProperties.getPort());
|
||||
if (exclusionList != null) {
|
||||
out.writeUTF(EXCLUSION_LIST_KEY);
|
||||
out.writeUTF(exclusionList);
|
||||
}
|
||||
written = true;
|
||||
break;
|
||||
case PAC:
|
||||
ProxyInfo proxyPacProperties = config.getHttpProxy();
|
||||
out.writeUTF(PROXY_SETTINGS_KEY);
|
||||
out.writeUTF(config.getProxySettings().toString());
|
||||
out.writeUTF(PROXY_PAC_FILE);
|
||||
out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
|
||||
written = true;
|
||||
break;
|
||||
case NONE:
|
||||
out.writeUTF(PROXY_SETTINGS_KEY);
|
||||
out.writeUTF(config.getProxySettings().toString());
|
||||
written = true;
|
||||
break;
|
||||
case UNASSIGNED:
|
||||
/* Ignore */
|
||||
break;
|
||||
default:
|
||||
loge("Ignore invalid proxy settings while writing");
|
||||
break;
|
||||
}
|
||||
|
||||
if (written) {
|
||||
out.writeUTF(ID_KEY);
|
||||
if (version < 3) {
|
||||
out.writeInt(Integer.valueOf(configKey));
|
||||
} else {
|
||||
out.writeUTF(configKey);
|
||||
}
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
loge("Failure in writing " + config + e);
|
||||
}
|
||||
out.writeUTF(EOS);
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
|
||||
* New method uses string as network identifier which could be interface name or MAC address or
|
||||
* other token.
|
||||
*/
|
||||
@Deprecated
|
||||
public void writeIpAndProxyConfigurationsToFile(String filePath,
|
||||
final SparseArray<IpConfiguration> networks) {
|
||||
mWriter.write(filePath, out -> {
|
||||
out.writeInt(IPCONFIG_FILE_VERSION);
|
||||
for (int i = 0; i < networks.size(); i++) {
|
||||
writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the IP configuration associated to the target networks to the destination path.
|
||||
*/
|
||||
public void writeIpConfigurations(String filePath,
|
||||
ArrayMap<String, IpConfiguration> networks) {
|
||||
mWriter.write(filePath, out -> {
|
||||
out.writeInt(IPCONFIG_FILE_VERSION);
|
||||
for (int i = 0; i < networks.size(); i++) {
|
||||
writeConfig(out, networks.keyAt(i), networks.valueAt(i));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the IP configuration from the destination path to {@link BufferedInputStream}.
|
||||
*/
|
||||
public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
|
||||
BufferedInputStream bufferedInputStream;
|
||||
try {
|
||||
bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
// Return an empty array here because callers expect an empty array when the file is
|
||||
// not present.
|
||||
loge("Error opening configuration file: " + e);
|
||||
return new ArrayMap<>(0);
|
||||
}
|
||||
return readIpConfigurations(bufferedInputStream);
|
||||
}
|
||||
|
||||
/** @deprecated use {@link #readIpConfigurations(String)} */
|
||||
@Deprecated
|
||||
public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
|
||||
BufferedInputStream bufferedInputStream;
|
||||
try {
|
||||
bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
// Return an empty array here because callers expect an empty array when the file is
|
||||
// not present.
|
||||
loge("Error opening configuration file: " + e);
|
||||
return new SparseArray<>();
|
||||
}
|
||||
return readIpAndProxyConfigurations(bufferedInputStream);
|
||||
}
|
||||
|
||||
/** @deprecated use {@link #readIpConfigurations(InputStream)} */
|
||||
@Deprecated
|
||||
public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
|
||||
InputStream inputStream) {
|
||||
ArrayMap<String, IpConfiguration> networks = readIpConfigurations(inputStream);
|
||||
if (networks == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SparseArray<IpConfiguration> networksById = new SparseArray<>();
|
||||
for (int i = 0; i < networks.size(); i++) {
|
||||
int id = Integer.valueOf(networks.keyAt(i));
|
||||
networksById.put(id, networks.valueAt(i));
|
||||
}
|
||||
|
||||
return networksById;
|
||||
}
|
||||
|
||||
/** Returns a map of network identity token and {@link IpConfiguration}. */
|
||||
public static ArrayMap<String, IpConfiguration> readIpConfigurations(
|
||||
InputStream inputStream) {
|
||||
ArrayMap<String, IpConfiguration> networks = new ArrayMap<>();
|
||||
DataInputStream in = null;
|
||||
try {
|
||||
in = new DataInputStream(inputStream);
|
||||
|
||||
int version = in.readInt();
|
||||
if (version != 3 && version != 2 && version != 1) {
|
||||
loge("Bad version on IP configuration file, ignore read");
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
String uniqueToken = null;
|
||||
// Default is DHCP with no proxy
|
||||
IpAssignment ipAssignment = IpAssignment.DHCP;
|
||||
ProxySettings proxySettings = ProxySettings.NONE;
|
||||
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
|
||||
LinkAddress linkAddress = null;
|
||||
InetAddress gatewayAddress = null;
|
||||
String proxyHost = null;
|
||||
String pacFileUrl = null;
|
||||
int proxyPort = -1;
|
||||
String exclusionList = null;
|
||||
String key;
|
||||
final List<InetAddress> dnsServers = new ArrayList<>();
|
||||
|
||||
do {
|
||||
key = in.readUTF();
|
||||
try {
|
||||
if (key.equals(ID_KEY)) {
|
||||
if (version < 3) {
|
||||
int id = in.readInt();
|
||||
uniqueToken = String.valueOf(id);
|
||||
} else {
|
||||
uniqueToken = in.readUTF();
|
||||
}
|
||||
} else if (key.equals(IP_ASSIGNMENT_KEY)) {
|
||||
ipAssignment = IpAssignment.valueOf(in.readUTF());
|
||||
} else if (key.equals(LINK_ADDRESS_KEY)) {
|
||||
LinkAddress parsedLinkAddress =
|
||||
new LinkAddress(
|
||||
InetAddresses.parseNumericAddress(in.readUTF()),
|
||||
in.readInt());
|
||||
if (parsedLinkAddress.getAddress() instanceof Inet4Address
|
||||
&& linkAddress == null) {
|
||||
linkAddress = parsedLinkAddress;
|
||||
} else {
|
||||
loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
|
||||
}
|
||||
} else if (key.equals(GATEWAY_KEY)) {
|
||||
LinkAddress dest = null;
|
||||
InetAddress gateway = null;
|
||||
if (version == 1) {
|
||||
// only supported default gateways - leave the dest/prefix empty
|
||||
gateway = InetAddresses.parseNumericAddress(in.readUTF());
|
||||
if (gatewayAddress == null) {
|
||||
gatewayAddress = gateway;
|
||||
} else {
|
||||
loge("Duplicate gateway: " + gateway.getHostAddress());
|
||||
}
|
||||
} else {
|
||||
if (in.readInt() == 1) {
|
||||
dest =
|
||||
new LinkAddress(
|
||||
InetAddresses.parseNumericAddress(in.readUTF()),
|
||||
in.readInt());
|
||||
}
|
||||
if (in.readInt() == 1) {
|
||||
gateway = InetAddresses.parseNumericAddress(in.readUTF());
|
||||
}
|
||||
// If the destination is a default IPv4 route, use the gateway
|
||||
// address unless already set. If there is no destination, assume
|
||||
// it is default route and use the gateway address in all cases.
|
||||
if (dest == null) {
|
||||
gatewayAddress = gateway;
|
||||
} else if (dest.getAddress() instanceof Inet4Address
|
||||
&& dest.getPrefixLength() == 0 && gatewayAddress == null) {
|
||||
gatewayAddress = gateway;
|
||||
} else {
|
||||
loge("Non-IPv4 default or duplicate route: "
|
||||
+ dest.getAddress());
|
||||
}
|
||||
}
|
||||
} else if (key.equals(DNS_KEY)) {
|
||||
dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
|
||||
} else if (key.equals(PROXY_SETTINGS_KEY)) {
|
||||
proxySettings = ProxySettings.valueOf(in.readUTF());
|
||||
} else if (key.equals(PROXY_HOST_KEY)) {
|
||||
proxyHost = in.readUTF();
|
||||
} else if (key.equals(PROXY_PORT_KEY)) {
|
||||
proxyPort = in.readInt();
|
||||
} else if (key.equals(PROXY_PAC_FILE)) {
|
||||
pacFileUrl = in.readUTF();
|
||||
} else if (key.equals(EXCLUSION_LIST_KEY)) {
|
||||
exclusionList = in.readUTF();
|
||||
} else if (key.equals(EOS)) {
|
||||
break;
|
||||
} else {
|
||||
loge("Ignore unknown key " + key + "while reading");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
loge("Ignore invalid address while reading" + e);
|
||||
}
|
||||
} while (true);
|
||||
|
||||
staticIpConfiguration = new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(linkAddress)
|
||||
.setGateway(gatewayAddress)
|
||||
.setDnsServers(dnsServers)
|
||||
.build();
|
||||
|
||||
if (uniqueToken != null) {
|
||||
IpConfiguration config = new IpConfiguration();
|
||||
networks.put(uniqueToken, config);
|
||||
|
||||
switch (ipAssignment) {
|
||||
case STATIC:
|
||||
config.setStaticIpConfiguration(staticIpConfiguration);
|
||||
config.setIpAssignment(ipAssignment);
|
||||
break;
|
||||
case DHCP:
|
||||
config.setIpAssignment(ipAssignment);
|
||||
break;
|
||||
case UNASSIGNED:
|
||||
loge("BUG: Found UNASSIGNED IP on file, use DHCP");
|
||||
config.setIpAssignment(IpAssignment.DHCP);
|
||||
break;
|
||||
default:
|
||||
loge("Ignore invalid ip assignment while reading.");
|
||||
config.setIpAssignment(IpAssignment.UNASSIGNED);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (proxySettings) {
|
||||
case STATIC:
|
||||
ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
|
||||
ProxyUtils.exclusionStringAsList(exclusionList));
|
||||
config.setProxySettings(proxySettings);
|
||||
config.setHttpProxy(proxyInfo);
|
||||
break;
|
||||
case PAC:
|
||||
ProxyInfo proxyPacProperties =
|
||||
ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
|
||||
config.setProxySettings(proxySettings);
|
||||
config.setHttpProxy(proxyPacProperties);
|
||||
break;
|
||||
case NONE:
|
||||
config.setProxySettings(proxySettings);
|
||||
break;
|
||||
case UNASSIGNED:
|
||||
loge("BUG: Found UNASSIGNED proxy on file, use NONE");
|
||||
config.setProxySettings(ProxySettings.NONE);
|
||||
break;
|
||||
default:
|
||||
loge("Ignore invalid proxy settings while reading");
|
||||
config.setProxySettings(ProxySettings.UNASSIGNED);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (DBG) log("Missing id while parsing configuration");
|
||||
}
|
||||
}
|
||||
} catch (EOFException ignore) {
|
||||
} catch (IOException e) {
|
||||
loge("Error parsing configuration: " + e);
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
}
|
||||
|
||||
return networks;
|
||||
}
|
||||
|
||||
protected static void loge(String s) {
|
||||
Log.e(TAG, s);
|
||||
}
|
||||
|
||||
protected static void log(String s) {
|
||||
Log.d(TAG, s);
|
||||
}
|
||||
}
|
||||
505
service-t/src/com/android/server/net/NetworkStatsFactory.java
Normal file
505
service-t/src/com/android/server/net/NetworkStatsFactory.java
Normal file
@@ -0,0 +1,505 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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 com.android.server.net;
|
||||
|
||||
import static android.net.NetworkStats.INTERFACES_ALL;
|
||||
import static android.net.NetworkStats.SET_ALL;
|
||||
import static android.net.NetworkStats.TAG_ALL;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.UnderlyingNetworkInfo;
|
||||
import android.os.ServiceSpecificException;
|
||||
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.ProcFileReader;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
import com.android.server.BpfNetMaps;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Creates {@link NetworkStats} instances by parsing various {@code /proc/}
|
||||
* files as needed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class NetworkStatsFactory {
|
||||
static {
|
||||
System.loadLibrary("service-connectivity");
|
||||
}
|
||||
|
||||
private static final String TAG = "NetworkStatsFactory";
|
||||
|
||||
private static final boolean USE_NATIVE_PARSING = true;
|
||||
private static final boolean VALIDATE_NATIVE_STATS = false;
|
||||
|
||||
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
|
||||
private final File mStatsXtIfaceAll;
|
||||
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_fmt}. */
|
||||
private final File mStatsXtIfaceFmt;
|
||||
/** Path to {@code /proc/net/xt_qtaguid/stats}. */
|
||||
private final File mStatsXtUid;
|
||||
|
||||
private final boolean mUseBpfStats;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final BpfNetMaps mBpfNetMaps;
|
||||
|
||||
/**
|
||||
* Guards persistent data access in this class
|
||||
*
|
||||
* <p>In order to prevent deadlocks, critical sections protected by this lock SHALL NOT call out
|
||||
* to other code that will acquire other locks within the system server. See b/134244752.
|
||||
*/
|
||||
private final Object mPersistentDataLock = new Object();
|
||||
|
||||
/** Set containing info about active VPNs and their underlying networks. */
|
||||
private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0];
|
||||
|
||||
// A persistent snapshot of cumulative stats since device start
|
||||
@GuardedBy("mPersistentDataLock")
|
||||
private NetworkStats mPersistSnapshot;
|
||||
|
||||
// The persistent snapshot of tun and 464xlat adjusted stats since device start
|
||||
@GuardedBy("mPersistentDataLock")
|
||||
private NetworkStats mTunAnd464xlatAdjustedStats;
|
||||
|
||||
/**
|
||||
* (Stacked interface) -> (base interface) association for all connected ifaces since boot.
|
||||
*
|
||||
* Because counters must never roll backwards, once a given interface is stacked on top of an
|
||||
* underlying interface, the stacked interface can never be stacked on top of
|
||||
* another interface. */
|
||||
private final ConcurrentHashMap<String, String> mStackedIfaces
|
||||
= new ConcurrentHashMap<>();
|
||||
|
||||
/** Informs the factory of a new stacked interface. */
|
||||
public void noteStackedIface(String stackedIface, String baseIface) {
|
||||
if (stackedIface != null && baseIface != null) {
|
||||
mStackedIfaces.put(stackedIface, baseIface);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set active VPN information for data usage migration purposes
|
||||
*
|
||||
* <p>Traffic on TUN-based VPNs inherently all appear to be originated from the VPN providing
|
||||
* app's UID. This method is used to support migration of VPN data usage, ensuring data is
|
||||
* accurately billed to the real owner of the traffic.
|
||||
*
|
||||
* @param vpnArray The snapshot of the currently-running VPNs.
|
||||
*/
|
||||
public void updateUnderlyingNetworkInfos(UnderlyingNetworkInfo[] vpnArray) {
|
||||
mUnderlyingNetworkInfos = vpnArray.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of interfaces containing specified ifaces and stacked interfaces.
|
||||
*
|
||||
* <p>The added stacked interfaces are ifaces stacked on top of the specified ones, or ifaces
|
||||
* on which the specified ones are stacked. Stacked interfaces are those noted with
|
||||
* {@link #noteStackedIface(String, String)}, but only interfaces noted before this method
|
||||
* is called are guaranteed to be included.
|
||||
*/
|
||||
public String[] augmentWithStackedInterfaces(@Nullable String[] requiredIfaces) {
|
||||
if (requiredIfaces == NetworkStats.INTERFACES_ALL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HashSet<String> relatedIfaces = new HashSet<>(Arrays.asList(requiredIfaces));
|
||||
// ConcurrentHashMap's EntrySet iterators are "guaranteed to traverse
|
||||
// elements as they existed upon construction exactly once, and may
|
||||
// (but are not guaranteed to) reflect any modifications subsequent to construction".
|
||||
// This is enough here.
|
||||
for (Map.Entry<String, String> entry : mStackedIfaces.entrySet()) {
|
||||
if (relatedIfaces.contains(entry.getKey())) {
|
||||
relatedIfaces.add(entry.getValue());
|
||||
} else if (relatedIfaces.contains(entry.getValue())) {
|
||||
relatedIfaces.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
String[] outArray = new String[relatedIfaces.size()];
|
||||
return relatedIfaces.toArray(outArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}.
|
||||
* @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map)
|
||||
*/
|
||||
public void apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic) {
|
||||
NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, mStackedIfaces);
|
||||
}
|
||||
|
||||
public NetworkStatsFactory(@NonNull Context ctx) {
|
||||
this(ctx, new File("/proc/"), true);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public NetworkStatsFactory(@NonNull Context ctx, File procRoot, boolean useBpfStats) {
|
||||
mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
|
||||
mStatsXtIfaceFmt = new File(procRoot, "net/xt_qtaguid/iface_stat_fmt");
|
||||
mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
|
||||
mUseBpfStats = useBpfStats;
|
||||
mBpfNetMaps = new BpfNetMaps();
|
||||
synchronized (mPersistentDataLock) {
|
||||
mPersistSnapshot = new NetworkStats(SystemClock.elapsedRealtime(), -1);
|
||||
mTunAnd464xlatAdjustedStats = new NetworkStats(SystemClock.elapsedRealtime(), -1);
|
||||
}
|
||||
mContext = ctx;
|
||||
}
|
||||
|
||||
public NetworkStats readBpfNetworkStatsDev() throws IOException {
|
||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
|
||||
if (nativeReadNetworkStatsDev(stats) != 0) {
|
||||
throw new IOException("Failed to parse bpf iface stats");
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return interface-level summary {@link NetworkStats} measured
|
||||
* using {@code /proc/net/dev} style hooks, which may include non IP layer
|
||||
* traffic. Values monotonically increase since device boot, and may include
|
||||
* details about inactive interfaces.
|
||||
*
|
||||
* @throws IllegalStateException when problem parsing stats.
|
||||
*/
|
||||
public NetworkStats readNetworkStatsSummaryDev() throws IOException {
|
||||
|
||||
// Return xt_bpf stats if switched to bpf module.
|
||||
if (mUseBpfStats)
|
||||
return readBpfNetworkStatsDev();
|
||||
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
|
||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
|
||||
ProcFileReader reader = null;
|
||||
try {
|
||||
reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceAll));
|
||||
|
||||
while (reader.hasMoreData()) {
|
||||
entry.iface = reader.nextString();
|
||||
entry.uid = UID_ALL;
|
||||
entry.set = SET_ALL;
|
||||
entry.tag = TAG_NONE;
|
||||
|
||||
final boolean active = reader.nextInt() != 0;
|
||||
|
||||
// always include snapshot values
|
||||
entry.rxBytes = reader.nextLong();
|
||||
entry.rxPackets = reader.nextLong();
|
||||
entry.txBytes = reader.nextLong();
|
||||
entry.txPackets = reader.nextLong();
|
||||
|
||||
// fold in active numbers, but only when active
|
||||
if (active) {
|
||||
entry.rxBytes += reader.nextLong();
|
||||
entry.rxPackets += reader.nextLong();
|
||||
entry.txBytes += reader.nextLong();
|
||||
entry.txPackets += reader.nextLong();
|
||||
}
|
||||
|
||||
stats.insertEntry(entry);
|
||||
reader.finishLine();
|
||||
}
|
||||
} catch (NullPointerException|NumberFormatException e) {
|
||||
throw protocolExceptionWithCause("problem parsing stats", e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(reader);
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return interface-level summary {@link NetworkStats}. Designed
|
||||
* to return only IP layer traffic. Values monotonically increase since
|
||||
* device boot, and may include details about inactive interfaces.
|
||||
*
|
||||
* @throws IllegalStateException when problem parsing stats.
|
||||
*/
|
||||
public NetworkStats readNetworkStatsSummaryXt() throws IOException {
|
||||
|
||||
// Return xt_bpf stats if qtaguid module is replaced.
|
||||
if (mUseBpfStats)
|
||||
return readBpfNetworkStatsDev();
|
||||
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
|
||||
// return null when kernel doesn't support
|
||||
if (!mStatsXtIfaceFmt.exists()) return null;
|
||||
|
||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
|
||||
ProcFileReader reader = null;
|
||||
try {
|
||||
// open and consume header line
|
||||
reader = new ProcFileReader(new FileInputStream(mStatsXtIfaceFmt));
|
||||
reader.finishLine();
|
||||
|
||||
while (reader.hasMoreData()) {
|
||||
entry.iface = reader.nextString();
|
||||
entry.uid = UID_ALL;
|
||||
entry.set = SET_ALL;
|
||||
entry.tag = TAG_NONE;
|
||||
|
||||
entry.rxBytes = reader.nextLong();
|
||||
entry.rxPackets = reader.nextLong();
|
||||
entry.txBytes = reader.nextLong();
|
||||
entry.txPackets = reader.nextLong();
|
||||
|
||||
stats.insertEntry(entry);
|
||||
reader.finishLine();
|
||||
}
|
||||
} catch (NullPointerException|NumberFormatException e) {
|
||||
throw protocolExceptionWithCause("problem parsing stats", e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(reader);
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
public NetworkStats readNetworkStatsDetail() throws IOException {
|
||||
return readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
|
||||
}
|
||||
|
||||
@GuardedBy("mPersistentDataLock")
|
||||
private void requestSwapActiveStatsMapLocked() throws IOException {
|
||||
try {
|
||||
// Do a active map stats swap. Once the swap completes, this code
|
||||
// can read and clean the inactive map without races.
|
||||
mBpfNetMaps.swapActiveStatsMap();
|
||||
} catch (ServiceSpecificException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the detailed UID stats based on the provided parameters
|
||||
*
|
||||
* @param limitUid the UID to limit this query to
|
||||
* @param limitIfaces the interfaces to limit this query to. Use {@link
|
||||
* NetworkStats.INTERFACES_ALL} to select all interfaces
|
||||
* @param limitTag the tags to limit this query to
|
||||
* @return the NetworkStats instance containing network statistics at the present time.
|
||||
*/
|
||||
public NetworkStats readNetworkStatsDetail(
|
||||
int limitUid, String[] limitIfaces, int limitTag) throws IOException {
|
||||
// In order to prevent deadlocks, anything protected by this lock MUST NOT call out to other
|
||||
// code that will acquire other locks within the system server. See b/134244752.
|
||||
synchronized (mPersistentDataLock) {
|
||||
// Take a reference. If this gets swapped out, we still have the old reference.
|
||||
final UnderlyingNetworkInfo[] vpnArray = mUnderlyingNetworkInfos;
|
||||
// Take a defensive copy. mPersistSnapshot is mutated in some cases below
|
||||
final NetworkStats prev = mPersistSnapshot.clone();
|
||||
|
||||
if (USE_NATIVE_PARSING) {
|
||||
final NetworkStats stats =
|
||||
new NetworkStats(SystemClock.elapsedRealtime(), 0 /* initialSize */);
|
||||
if (mUseBpfStats) {
|
||||
requestSwapActiveStatsMapLocked();
|
||||
// Stats are always read from the inactive map, so they must be read after the
|
||||
// swap
|
||||
if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
|
||||
INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
|
||||
throw new IOException("Failed to parse network stats");
|
||||
}
|
||||
|
||||
// BPF stats are incremental; fold into mPersistSnapshot.
|
||||
mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
|
||||
mPersistSnapshot.combineAllValues(stats);
|
||||
} else {
|
||||
if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), UID_ALL,
|
||||
INTERFACES_ALL, TAG_ALL, mUseBpfStats) != 0) {
|
||||
throw new IOException("Failed to parse network stats");
|
||||
}
|
||||
if (VALIDATE_NATIVE_STATS) {
|
||||
final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid,
|
||||
UID_ALL, INTERFACES_ALL, TAG_ALL);
|
||||
assertEquals(javaStats, stats);
|
||||
}
|
||||
|
||||
mPersistSnapshot = stats;
|
||||
}
|
||||
} else {
|
||||
mPersistSnapshot = javaReadNetworkStatsDetail(mStatsXtUid, UID_ALL, INTERFACES_ALL,
|
||||
TAG_ALL);
|
||||
}
|
||||
|
||||
NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
|
||||
|
||||
// Filter return values
|
||||
adjustedStats.filter(limitUid, limitIfaces, limitTag);
|
||||
return adjustedStats;
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mPersistentDataLock")
|
||||
private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
|
||||
NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
|
||||
// Calculate delta from last snapshot
|
||||
final NetworkStats delta = uidDetailStats.subtract(previousStats);
|
||||
|
||||
// Apply 464xlat adjustments before VPN adjustments. If VPNs are using v4 on a v6 only
|
||||
// network, the overhead is their fault.
|
||||
// No locking here: apply464xlatAdjustments behaves fine with an add-only
|
||||
// ConcurrentHashMap.
|
||||
delta.apply464xlatAdjustments(mStackedIfaces);
|
||||
|
||||
// Migrate data usage over a VPN to the TUN network.
|
||||
for (UnderlyingNetworkInfo info : vpnArray) {
|
||||
delta.migrateTun(info.getOwnerUid(), info.getInterface(),
|
||||
info.getUnderlyingInterfaces());
|
||||
// Filter out debug entries as that may lead to over counting.
|
||||
delta.filterDebugEntries();
|
||||
}
|
||||
|
||||
// Update mTunAnd464xlatAdjustedStats with migrated delta.
|
||||
mTunAnd464xlatAdjustedStats.combineAllValues(delta);
|
||||
mTunAnd464xlatAdjustedStats.setElapsedRealtime(uidDetailStats.getElapsedRealtime());
|
||||
|
||||
return mTunAnd464xlatAdjustedStats.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return {@link NetworkStats} with UID-level details. Values are
|
||||
* expected to monotonically increase since device boot.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid,
|
||||
String[] limitIfaces, int limitTag)
|
||||
throws IOException {
|
||||
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
|
||||
|
||||
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
|
||||
int idx = 1;
|
||||
int lastIdx = 1;
|
||||
|
||||
ProcFileReader reader = null;
|
||||
try {
|
||||
// open and consume header line
|
||||
reader = new ProcFileReader(new FileInputStream(detailPath));
|
||||
reader.finishLine();
|
||||
|
||||
while (reader.hasMoreData()) {
|
||||
idx = reader.nextInt();
|
||||
if (idx != lastIdx + 1) {
|
||||
throw new ProtocolException(
|
||||
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
|
||||
}
|
||||
lastIdx = idx;
|
||||
|
||||
entry.iface = reader.nextString();
|
||||
entry.tag = kernelToTag(reader.nextString());
|
||||
entry.uid = reader.nextInt();
|
||||
entry.set = reader.nextInt();
|
||||
entry.rxBytes = reader.nextLong();
|
||||
entry.rxPackets = reader.nextLong();
|
||||
entry.txBytes = reader.nextLong();
|
||||
entry.txPackets = reader.nextLong();
|
||||
|
||||
if ((limitIfaces == null || CollectionUtils.contains(limitIfaces, entry.iface))
|
||||
&& (limitUid == UID_ALL || limitUid == entry.uid)
|
||||
&& (limitTag == TAG_ALL || limitTag == entry.tag)) {
|
||||
stats.insertEntry(entry);
|
||||
}
|
||||
|
||||
reader.finishLine();
|
||||
}
|
||||
} catch (NullPointerException|NumberFormatException e) {
|
||||
throw protocolExceptionWithCause("problem parsing idx " + idx, e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(reader);
|
||||
StrictMode.setThreadPolicy(savedPolicy);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public void assertEquals(NetworkStats expected, NetworkStats actual) {
|
||||
if (expected.size() != actual.size()) {
|
||||
throw new AssertionError(
|
||||
"Expected size " + expected.size() + ", actual size " + actual.size());
|
||||
}
|
||||
|
||||
NetworkStats.Entry expectedRow = null;
|
||||
NetworkStats.Entry actualRow = null;
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
expectedRow = expected.getValues(i, expectedRow);
|
||||
actualRow = actual.getValues(i, actualRow);
|
||||
if (!expectedRow.equals(actualRow)) {
|
||||
throw new AssertionError(
|
||||
"Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
|
||||
* format like {@code 0x7fffffff00000000}.
|
||||
*/
|
||||
public static int kernelToTag(String string) {
|
||||
int length = string.length();
|
||||
if (length > 10) {
|
||||
return Long.decode(string.substring(0, length - 8)).intValue();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse statistics from file into given {@link NetworkStats} object. Values
|
||||
* are expected to monotonically increase since device boot.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static native int nativeReadNetworkStatsDetail(NetworkStats stats, String path,
|
||||
int limitUid, String[] limitIfaces, int limitTag, boolean useBpfStats);
|
||||
|
||||
@VisibleForTesting
|
||||
public static native int nativeReadNetworkStatsDev(NetworkStats stats);
|
||||
|
||||
private static ProtocolException protocolExceptionWithCause(String message, Throwable cause) {
|
||||
ProtocolException pe = new ProtocolException(message);
|
||||
pe.initCause(cause);
|
||||
return pe;
|
||||
}
|
||||
}
|
||||
451
service-t/src/com/android/server/net/NetworkStatsObservers.java
Normal file
451
service-t/src/com/android/server/net/NetworkStatsObservers.java
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 com.android.server.net;
|
||||
|
||||
import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
|
||||
|
||||
import android.app.usage.NetworkStatsManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.DataUsageRequest;
|
||||
import android.net.NetworkIdentitySet;
|
||||
import android.net.NetworkStack;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsAccess;
|
||||
import android.net.NetworkStatsCollection;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.netstats.IUsageCallback;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Manages observers of {@link NetworkStats}. Allows observers to be notified when
|
||||
* data usage has been reported in {@link NetworkStatsService}. An observer can set
|
||||
* a threshold of how much data it cares about to be notified.
|
||||
*/
|
||||
class NetworkStatsObservers {
|
||||
private static final String TAG = "NetworkStatsObservers";
|
||||
private static final boolean LOGV = false;
|
||||
|
||||
private static final int MSG_REGISTER = 1;
|
||||
private static final int MSG_UNREGISTER = 2;
|
||||
private static final int MSG_UPDATE_STATS = 3;
|
||||
|
||||
// All access to this map must be done from the handler thread.
|
||||
// indexed by DataUsageRequest#requestId
|
||||
private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
|
||||
|
||||
// Sequence number of DataUsageRequests
|
||||
private final AtomicInteger mNextDataUsageRequestId = new AtomicInteger();
|
||||
|
||||
// Lazily instantiated when an observer is registered.
|
||||
private volatile Handler mHandler;
|
||||
|
||||
/**
|
||||
* Creates a wrapper that contains the caller context and a normalized request.
|
||||
* The request should be returned to the caller app, and the wrapper should be sent to this
|
||||
* object through #addObserver by the service handler.
|
||||
*
|
||||
* <p>It will register the observer asynchronously, so it is safe to call from any thread.
|
||||
*
|
||||
* @return the normalized request wrapped within {@link RequestInfo}.
|
||||
*/
|
||||
public DataUsageRequest register(Context context, DataUsageRequest inputRequest,
|
||||
IUsageCallback callback, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
|
||||
DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
|
||||
RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
|
||||
accessLevel);
|
||||
|
||||
if (LOGV) Log.v(TAG, "Registering observer for " + request);
|
||||
getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a data usage observer.
|
||||
*
|
||||
* <p>It will unregister the observer asynchronously, so it is safe to call from any thread.
|
||||
*/
|
||||
public void unregister(DataUsageRequest request, int callingUid) {
|
||||
getHandler().sendMessage(mHandler.obtainMessage(MSG_UNREGISTER, callingUid, 0 /* ignore */,
|
||||
request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates data usage statistics of registered observers and notifies if limits are reached.
|
||||
*
|
||||
* <p>It will update stats asynchronously, so it is safe to call from any thread.
|
||||
*/
|
||||
public void updateStats(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
|
||||
ArrayMap<String, NetworkIdentitySet> activeIfaces,
|
||||
ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
|
||||
long currentTime) {
|
||||
StatsContext statsContext = new StatsContext(xtSnapshot, uidSnapshot, activeIfaces,
|
||||
activeUidIfaces, currentTime);
|
||||
getHandler().sendMessage(mHandler.obtainMessage(MSG_UPDATE_STATS, statsContext));
|
||||
}
|
||||
|
||||
private Handler getHandler() {
|
||||
if (mHandler == null) {
|
||||
synchronized (this) {
|
||||
if (mHandler == null) {
|
||||
if (LOGV) Log.v(TAG, "Creating handler");
|
||||
mHandler = new Handler(getHandlerLooperLocked(), mHandlerCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected Looper getHandlerLooperLocked() {
|
||||
HandlerThread handlerThread = new HandlerThread(TAG);
|
||||
handlerThread.start();
|
||||
return handlerThread.getLooper();
|
||||
}
|
||||
|
||||
private Handler.Callback mHandlerCallback = new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_REGISTER: {
|
||||
handleRegister((RequestInfo) msg.obj);
|
||||
return true;
|
||||
}
|
||||
case MSG_UNREGISTER: {
|
||||
handleUnregister((DataUsageRequest) msg.obj, msg.arg1 /* callingUid */);
|
||||
return true;
|
||||
}
|
||||
case MSG_UPDATE_STATS: {
|
||||
handleUpdateStats((StatsContext) msg.obj);
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a {@link RequestInfo} as an observer.
|
||||
* Should only be called from the handler thread otherwise there will be a race condition
|
||||
* on mDataUsageRequests.
|
||||
*/
|
||||
private void handleRegister(RequestInfo requestInfo) {
|
||||
mDataUsageRequests.put(requestInfo.mRequest.requestId, requestInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link DataUsageRequest} if the calling uid is authorized.
|
||||
* Should only be called from the handler thread otherwise there will be a race condition
|
||||
* on mDataUsageRequests.
|
||||
*/
|
||||
private void handleUnregister(DataUsageRequest request, int callingUid) {
|
||||
RequestInfo requestInfo;
|
||||
requestInfo = mDataUsageRequests.get(request.requestId);
|
||||
if (requestInfo == null) {
|
||||
if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
|
||||
return;
|
||||
}
|
||||
if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
|
||||
Log.w(TAG, "Caller uid " + callingUid + " is not owner of " + request);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOGV) Log.v(TAG, "Unregistering " + request);
|
||||
mDataUsageRequests.remove(request.requestId);
|
||||
requestInfo.unlinkDeathRecipient();
|
||||
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
|
||||
}
|
||||
|
||||
private void handleUpdateStats(StatsContext statsContext) {
|
||||
if (mDataUsageRequests.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < mDataUsageRequests.size(); i++) {
|
||||
RequestInfo requestInfo = mDataUsageRequests.valueAt(i);
|
||||
requestInfo.updateStats(statsContext);
|
||||
}
|
||||
}
|
||||
|
||||
private DataUsageRequest buildRequest(Context context, DataUsageRequest request,
|
||||
int callingUid) {
|
||||
// For non-NETWORK_STACK permission uid, cap the minimum threshold to a safe default to
|
||||
// avoid too many callbacks.
|
||||
final long thresholdInBytes = (context.checkPermission(
|
||||
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, Process.myPid(), callingUid)
|
||||
== PackageManager.PERMISSION_GRANTED ? request.thresholdInBytes
|
||||
: Math.max(MIN_THRESHOLD_BYTES, request.thresholdInBytes));
|
||||
if (thresholdInBytes > request.thresholdInBytes) {
|
||||
Log.w(TAG, "Threshold was too low for " + request
|
||||
+ ". Overriding to a safer default of " + thresholdInBytes + " bytes");
|
||||
}
|
||||
return new DataUsageRequest(mNextDataUsageRequestId.incrementAndGet(),
|
||||
request.template, thresholdInBytes);
|
||||
}
|
||||
|
||||
private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
|
||||
int callingUid, @NetworkStatsAccess.Level int accessLevel) {
|
||||
if (accessLevel <= NetworkStatsAccess.Level.USER) {
|
||||
return new UserUsageRequestInfo(this, request, callback, callingUid,
|
||||
accessLevel);
|
||||
} else {
|
||||
// Safety check in case a new access level is added and we forgot to update this
|
||||
if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
|
||||
throw new IllegalArgumentException(
|
||||
"accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
|
||||
}
|
||||
return new NetworkUsageRequestInfo(this, request, callback, callingUid,
|
||||
accessLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks information relevant to a data usage observer.
|
||||
* It will notice when the calling process dies so we can self-expire.
|
||||
*/
|
||||
private abstract static class RequestInfo implements IBinder.DeathRecipient {
|
||||
private final NetworkStatsObservers mStatsObserver;
|
||||
protected final DataUsageRequest mRequest;
|
||||
private final IUsageCallback mCallback;
|
||||
protected final int mCallingUid;
|
||||
protected final @NetworkStatsAccess.Level int mAccessLevel;
|
||||
protected NetworkStatsRecorder mRecorder;
|
||||
protected NetworkStatsCollection mCollection;
|
||||
|
||||
RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
|
||||
IUsageCallback callback, int callingUid,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
mStatsObserver = statsObserver;
|
||||
mRequest = request;
|
||||
mCallback = callback;
|
||||
mCallingUid = callingUid;
|
||||
mAccessLevel = accessLevel;
|
||||
|
||||
try {
|
||||
mCallback.asBinder().linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
binderDied();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
if (LOGV) {
|
||||
Log.v(TAG, "RequestInfo binderDied(" + mRequest + ", " + mCallback + ")");
|
||||
}
|
||||
mStatsObserver.unregister(mRequest, Process.SYSTEM_UID);
|
||||
callCallback(NetworkStatsManager.CALLBACK_RELEASED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RequestInfo from uid:" + mCallingUid
|
||||
+ " for " + mRequest + " accessLevel:" + mAccessLevel;
|
||||
}
|
||||
|
||||
private void unlinkDeathRecipient() {
|
||||
mCallback.asBinder().unlinkToDeath(this, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stats given the samples and interface to identity mappings.
|
||||
*/
|
||||
private void updateStats(StatsContext statsContext) {
|
||||
if (mRecorder == null) {
|
||||
// First run; establish baseline stats
|
||||
resetRecorder();
|
||||
recordSample(statsContext);
|
||||
return;
|
||||
}
|
||||
recordSample(statsContext);
|
||||
|
||||
if (checkStats()) {
|
||||
resetRecorder();
|
||||
callCallback(NetworkStatsManager.CALLBACK_LIMIT_REACHED);
|
||||
}
|
||||
}
|
||||
|
||||
private void callCallback(int callbackType) {
|
||||
try {
|
||||
if (LOGV) {
|
||||
Log.v(TAG, "sending notification " + callbackTypeToName(callbackType)
|
||||
+ " for " + mRequest);
|
||||
}
|
||||
switch (callbackType) {
|
||||
case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
|
||||
mCallback.onThresholdReached(mRequest);
|
||||
break;
|
||||
case NetworkStatsManager.CALLBACK_RELEASED:
|
||||
mCallback.onCallbackReleased(mRequest);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// May occur naturally in the race of binder death.
|
||||
Log.w(TAG, "RemoteException caught trying to send a callback msg for " + mRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetRecorder() {
|
||||
mRecorder = new NetworkStatsRecorder();
|
||||
mCollection = mRecorder.getSinceBoot();
|
||||
}
|
||||
|
||||
protected abstract boolean checkStats();
|
||||
|
||||
protected abstract void recordSample(StatsContext statsContext);
|
||||
|
||||
private String callbackTypeToName(int callbackType) {
|
||||
switch (callbackType) {
|
||||
case NetworkStatsManager.CALLBACK_LIMIT_REACHED:
|
||||
return "LIMIT_REACHED";
|
||||
case NetworkStatsManager.CALLBACK_RELEASED:
|
||||
return "RELEASED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class NetworkUsageRequestInfo extends RequestInfo {
|
||||
NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
|
||||
IUsageCallback callback, int callingUid,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
super(statsObserver, request, callback, callingUid, accessLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkStats() {
|
||||
long bytesSoFar = getTotalBytesForNetwork(mRequest.template);
|
||||
if (LOGV) {
|
||||
Log.v(TAG, bytesSoFar + " bytes so far since notification for "
|
||||
+ mRequest.template);
|
||||
}
|
||||
if (bytesSoFar > mRequest.thresholdInBytes) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recordSample(StatsContext statsContext) {
|
||||
// Recorder does not need to be locked in this context since only the handler
|
||||
// thread will update it. We pass a null VPN array because usage is aggregated by uid
|
||||
// for this snapshot, so VPN traffic can't be reattributed to responsible apps.
|
||||
mRecorder.recordSnapshotLocked(statsContext.mXtSnapshot, statsContext.mActiveIfaces,
|
||||
statsContext.mCurrentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads stats matching the given template. {@link NetworkStatsCollection} will aggregate
|
||||
* over all buckets, which in this case should be only one since we built it big enough
|
||||
* that it will outlive the caller. If it doesn't, then there will be multiple buckets.
|
||||
*/
|
||||
private long getTotalBytesForNetwork(NetworkTemplate template) {
|
||||
NetworkStats stats = mCollection.getSummary(template,
|
||||
Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
|
||||
mAccessLevel, mCallingUid);
|
||||
return stats.getTotalBytes();
|
||||
}
|
||||
}
|
||||
|
||||
private static class UserUsageRequestInfo extends RequestInfo {
|
||||
UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
|
||||
IUsageCallback callback, int callingUid,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
super(statsObserver, request, callback, callingUid, accessLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkStats() {
|
||||
int[] uidsToMonitor = mCollection.getRelevantUids(mAccessLevel, mCallingUid);
|
||||
|
||||
for (int i = 0; i < uidsToMonitor.length; i++) {
|
||||
long bytesSoFar = getTotalBytesForNetworkUid(mRequest.template, uidsToMonitor[i]);
|
||||
if (bytesSoFar > mRequest.thresholdInBytes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recordSample(StatsContext statsContext) {
|
||||
// Recorder does not need to be locked in this context since only the handler
|
||||
// thread will update it. We pass the VPN info so VPN traffic is reattributed to
|
||||
// responsible apps.
|
||||
mRecorder.recordSnapshotLocked(statsContext.mUidSnapshot, statsContext.mActiveUidIfaces,
|
||||
statsContext.mCurrentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all stats matching the given template and uid. Ther history will likely only
|
||||
* contain one bucket per ident since we build it big enough that it will outlive the
|
||||
* caller lifetime.
|
||||
*/
|
||||
private long getTotalBytesForNetworkUid(NetworkTemplate template, int uid) {
|
||||
try {
|
||||
NetworkStatsHistory history = mCollection.getHistory(template, null, uid,
|
||||
NetworkStats.SET_ALL, NetworkStats.TAG_NONE,
|
||||
NetworkStatsHistory.FIELD_ALL,
|
||||
Long.MIN_VALUE /* start */, Long.MAX_VALUE /* end */,
|
||||
mAccessLevel, mCallingUid);
|
||||
return history.getTotalBytes();
|
||||
} catch (SecurityException e) {
|
||||
if (LOGV) {
|
||||
Log.w(TAG, "CallerUid " + mCallingUid + " may have lost access to uid "
|
||||
+ uid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class StatsContext {
|
||||
NetworkStats mXtSnapshot;
|
||||
NetworkStats mUidSnapshot;
|
||||
ArrayMap<String, NetworkIdentitySet> mActiveIfaces;
|
||||
ArrayMap<String, NetworkIdentitySet> mActiveUidIfaces;
|
||||
long mCurrentTime;
|
||||
|
||||
StatsContext(NetworkStats xtSnapshot, NetworkStats uidSnapshot,
|
||||
ArrayMap<String, NetworkIdentitySet> activeIfaces,
|
||||
ArrayMap<String, NetworkIdentitySet> activeUidIfaces,
|
||||
long currentTime) {
|
||||
mXtSnapshot = xtSnapshot;
|
||||
mUidSnapshot = uidSnapshot;
|
||||
mActiveIfaces = activeIfaces;
|
||||
mActiveUidIfaces = activeUidIfaces;
|
||||
mCurrentTime = currentTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
507
service-t/src/com/android/server/net/NetworkStatsRecorder.java
Normal file
507
service-t/src/com/android/server/net/NetworkStatsRecorder.java
Normal file
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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 com.android.server.net;
|
||||
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.TrafficStats.KB_IN_BYTES;
|
||||
import static android.net.TrafficStats.MB_IN_BYTES;
|
||||
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
|
||||
|
||||
import android.net.NetworkIdentitySet;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStats.NonMonotonicObserver;
|
||||
import android.net.NetworkStatsAccess;
|
||||
import android.net.NetworkStatsCollection;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.TrafficStats;
|
||||
import android.os.Binder;
|
||||
import android.os.DropBoxManager;
|
||||
import android.service.NetworkStatsRecorderProto;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.net.module.util.NetworkStatsUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Logic to record deltas between periodic {@link NetworkStats} snapshots into
|
||||
* {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
|
||||
* Keeps pending changes in memory until they pass a specific threshold, in
|
||||
* bytes. Uses {@link FileRotator} for persistence logic if present.
|
||||
* <p>
|
||||
* Not inherently thread safe.
|
||||
*/
|
||||
public class NetworkStatsRecorder {
|
||||
private static final String TAG = "NetworkStatsRecorder";
|
||||
private static final boolean LOGD = false;
|
||||
private static final boolean LOGV = false;
|
||||
|
||||
private static final String TAG_NETSTATS_DUMP = "netstats_dump";
|
||||
|
||||
/** Dump before deleting in {@link #recoverFromWtf()}. */
|
||||
private static final boolean DUMP_BEFORE_DELETE = true;
|
||||
|
||||
private final FileRotator mRotator;
|
||||
private final NonMonotonicObserver<String> mObserver;
|
||||
private final DropBoxManager mDropBox;
|
||||
private final String mCookie;
|
||||
|
||||
private final long mBucketDuration;
|
||||
private final boolean mOnlyTags;
|
||||
|
||||
private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
|
||||
private NetworkStats mLastSnapshot;
|
||||
|
||||
private final NetworkStatsCollection mPending;
|
||||
private final NetworkStatsCollection mSinceBoot;
|
||||
|
||||
private final CombiningRewriter mPendingRewriter;
|
||||
|
||||
private WeakReference<NetworkStatsCollection> mComplete;
|
||||
|
||||
/**
|
||||
* Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
|
||||
*/
|
||||
public NetworkStatsRecorder() {
|
||||
mRotator = null;
|
||||
mObserver = null;
|
||||
mDropBox = null;
|
||||
mCookie = null;
|
||||
|
||||
// set the bucket big enough to have all data in one bucket, but allow some
|
||||
// slack to avoid overflow
|
||||
mBucketDuration = YEAR_IN_MILLIS;
|
||||
mOnlyTags = false;
|
||||
|
||||
mPending = null;
|
||||
mSinceBoot = new NetworkStatsCollection(mBucketDuration);
|
||||
|
||||
mPendingRewriter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persisted recorder.
|
||||
*/
|
||||
public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
|
||||
DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
|
||||
mRotator = Objects.requireNonNull(rotator, "missing FileRotator");
|
||||
mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver");
|
||||
mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager");
|
||||
mCookie = cookie;
|
||||
|
||||
mBucketDuration = bucketDuration;
|
||||
mOnlyTags = onlyTags;
|
||||
|
||||
mPending = new NetworkStatsCollection(bucketDuration);
|
||||
mSinceBoot = new NetworkStatsCollection(bucketDuration);
|
||||
|
||||
mPendingRewriter = new CombiningRewriter(mPending);
|
||||
}
|
||||
|
||||
public void setPersistThreshold(long thresholdBytes) {
|
||||
if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes);
|
||||
mPersistThresholdBytes = NetworkStatsUtils.constrain(
|
||||
thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
|
||||
}
|
||||
|
||||
public void resetLocked() {
|
||||
mLastSnapshot = null;
|
||||
if (mPending != null) {
|
||||
mPending.reset();
|
||||
}
|
||||
if (mSinceBoot != null) {
|
||||
mSinceBoot.reset();
|
||||
}
|
||||
if (mComplete != null) {
|
||||
mComplete.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
|
||||
return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
|
||||
NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotal(null);
|
||||
}
|
||||
|
||||
public NetworkStatsCollection getSinceBoot() {
|
||||
return mSinceBoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load complete history represented by {@link FileRotator}. Caches
|
||||
* internally as a {@link WeakReference}, and updated with future
|
||||
* {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
|
||||
* as reference is valid.
|
||||
*/
|
||||
public NetworkStatsCollection getOrLoadCompleteLocked() {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
|
||||
if (res == null) {
|
||||
res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
|
||||
mComplete = new WeakReference<NetworkStatsCollection>(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
|
||||
if (res == null) {
|
||||
res = loadLocked(start, end);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private NetworkStatsCollection loadLocked(long start, long end) {
|
||||
if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
|
||||
final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
|
||||
try {
|
||||
mRotator.readMatching(res, start, end);
|
||||
res.recordCollection(mPending);
|
||||
} catch (IOException e) {
|
||||
Log.wtf(TAG, "problem completely reading network stats", e);
|
||||
recoverFromWtf();
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.wtf(TAG, "problem completely reading network stats", e);
|
||||
recoverFromWtf();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record any delta that occurred since last {@link NetworkStats} snapshot, using the given
|
||||
* {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is
|
||||
* not counted as delta.
|
||||
*/
|
||||
public void recordSnapshotLocked(NetworkStats snapshot,
|
||||
Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
|
||||
final HashSet<String> unknownIfaces = new HashSet<>();
|
||||
|
||||
// skip recording when snapshot missing
|
||||
if (snapshot == null) return;
|
||||
|
||||
// assume first snapshot is bootstrap and don't record
|
||||
if (mLastSnapshot == null) {
|
||||
mLastSnapshot = snapshot;
|
||||
return;
|
||||
}
|
||||
|
||||
final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
|
||||
|
||||
final NetworkStats delta = NetworkStats.subtract(
|
||||
snapshot, mLastSnapshot, mObserver, mCookie);
|
||||
final long end = currentTimeMillis;
|
||||
final long start = end - delta.getElapsedRealtime();
|
||||
|
||||
NetworkStats.Entry entry = null;
|
||||
for (int i = 0; i < delta.size(); i++) {
|
||||
entry = delta.getValues(i, entry);
|
||||
|
||||
// As a last-ditch check, report any negative values and
|
||||
// clamp them so recording below doesn't croak.
|
||||
if (entry.isNegative()) {
|
||||
if (mObserver != null) {
|
||||
mObserver.foundNonMonotonic(delta, i, mCookie);
|
||||
}
|
||||
entry.rxBytes = Math.max(entry.rxBytes, 0);
|
||||
entry.rxPackets = Math.max(entry.rxPackets, 0);
|
||||
entry.txBytes = Math.max(entry.txBytes, 0);
|
||||
entry.txPackets = Math.max(entry.txPackets, 0);
|
||||
entry.operations = Math.max(entry.operations, 0);
|
||||
}
|
||||
|
||||
final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
|
||||
if (ident == null) {
|
||||
unknownIfaces.add(entry.iface);
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip when no delta occurred
|
||||
if (entry.isEmpty()) continue;
|
||||
|
||||
// only record tag data when requested
|
||||
if ((entry.tag == TAG_NONE) != mOnlyTags) {
|
||||
if (mPending != null) {
|
||||
mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
|
||||
}
|
||||
|
||||
// also record against boot stats when present
|
||||
if (mSinceBoot != null) {
|
||||
mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
|
||||
}
|
||||
|
||||
// also record against complete dataset when present
|
||||
if (complete != null) {
|
||||
complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mLastSnapshot = snapshot;
|
||||
|
||||
if (LOGV && unknownIfaces.size() > 0) {
|
||||
Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider persisting any pending deltas, if they are beyond
|
||||
* {@link #mPersistThresholdBytes}.
|
||||
*/
|
||||
public void maybePersistLocked(long currentTimeMillis) {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
final long pendingBytes = mPending.getTotalBytes();
|
||||
if (pendingBytes >= mPersistThresholdBytes) {
|
||||
forcePersistLocked(currentTimeMillis);
|
||||
} else {
|
||||
mRotator.maybeRotate(currentTimeMillis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force persisting any pending deltas.
|
||||
*/
|
||||
public void forcePersistLocked(long currentTimeMillis) {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
if (mPending.isDirty()) {
|
||||
if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie);
|
||||
try {
|
||||
mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
|
||||
mRotator.maybeRotate(currentTimeMillis);
|
||||
mPending.reset();
|
||||
} catch (IOException e) {
|
||||
Log.wtf(TAG, "problem persisting pending stats", e);
|
||||
recoverFromWtf();
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.wtf(TAG, "problem persisting pending stats", e);
|
||||
recoverFromWtf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given UID from all {@link FileRotator} history, migrating it
|
||||
* to {@link TrafficStats#UID_REMOVED}.
|
||||
*/
|
||||
public void removeUidsLocked(int[] uids) {
|
||||
if (mRotator != null) {
|
||||
try {
|
||||
// Rewrite all persisted data to migrate UID stats
|
||||
mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
|
||||
} catch (IOException e) {
|
||||
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
|
||||
recoverFromWtf();
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
|
||||
recoverFromWtf();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any pending stats
|
||||
if (mPending != null) {
|
||||
mPending.removeUids(uids);
|
||||
}
|
||||
if (mSinceBoot != null) {
|
||||
mSinceBoot.removeUids(uids);
|
||||
}
|
||||
|
||||
// Clear UID from current stats snapshot
|
||||
if (mLastSnapshot != null) {
|
||||
mLastSnapshot.removeUids(uids);
|
||||
}
|
||||
|
||||
final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
|
||||
if (complete != null) {
|
||||
complete.removeUids(uids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewriter that will combine current {@link NetworkStatsCollection} values
|
||||
* with anything read from disk, and write combined set to disk. Clears the
|
||||
* original {@link NetworkStatsCollection} when finished writing.
|
||||
*/
|
||||
private static class CombiningRewriter implements FileRotator.Rewriter {
|
||||
private final NetworkStatsCollection mCollection;
|
||||
|
||||
public CombiningRewriter(NetworkStatsCollection collection) {
|
||||
mCollection = Objects.requireNonNull(collection, "missing NetworkStatsCollection");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// ignored
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws IOException {
|
||||
mCollection.read(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWrite() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
mCollection.write(out);
|
||||
mCollection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewriter that will remove any {@link NetworkStatsHistory} attributed to
|
||||
* the requested UID, only writing data back when modified.
|
||||
*/
|
||||
public static class RemoveUidRewriter implements FileRotator.Rewriter {
|
||||
private final NetworkStatsCollection mTemp;
|
||||
private final int[] mUids;
|
||||
|
||||
public RemoveUidRewriter(long bucketDuration, int[] uids) {
|
||||
mTemp = new NetworkStatsCollection(bucketDuration);
|
||||
mUids = uids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
mTemp.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(InputStream in) throws IOException {
|
||||
mTemp.read(in);
|
||||
mTemp.clearDirty();
|
||||
mTemp.removeUids(mUids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWrite() {
|
||||
return mTemp.isDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(OutputStream out) throws IOException {
|
||||
mTemp.write(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void importLegacyNetworkLocked(File file) throws IOException {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
|
||||
// legacy file still exists; start empty to avoid double importing
|
||||
mRotator.deleteAll();
|
||||
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
|
||||
collection.readLegacyNetwork(file);
|
||||
|
||||
final long startMillis = collection.getStartMillis();
|
||||
final long endMillis = collection.getEndMillis();
|
||||
|
||||
if (!collection.isEmpty()) {
|
||||
// process legacy data, creating active file at starting time, then
|
||||
// using end time to possibly trigger rotation.
|
||||
mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
|
||||
mRotator.maybeRotate(endMillis);
|
||||
}
|
||||
}
|
||||
|
||||
public void importLegacyUidLocked(File file) throws IOException {
|
||||
Objects.requireNonNull(mRotator, "missing FileRotator");
|
||||
|
||||
// legacy file still exists; start empty to avoid double importing
|
||||
mRotator.deleteAll();
|
||||
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
|
||||
collection.readLegacyUid(file, mOnlyTags);
|
||||
|
||||
final long startMillis = collection.getStartMillis();
|
||||
final long endMillis = collection.getEndMillis();
|
||||
|
||||
if (!collection.isEmpty()) {
|
||||
// process legacy data, creating active file at starting time, then
|
||||
// using end time to possibly trigger rotation.
|
||||
mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
|
||||
mRotator.maybeRotate(endMillis);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
|
||||
if (mPending != null) {
|
||||
pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
|
||||
}
|
||||
if (fullHistory) {
|
||||
pw.println("Complete history:");
|
||||
getOrLoadCompleteLocked().dump(pw);
|
||||
} else {
|
||||
pw.println("History since boot:");
|
||||
mSinceBoot.dump(pw);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpDebugLocked(ProtoOutputStream proto, long tag) {
|
||||
final long start = proto.start(tag);
|
||||
if (mPending != null) {
|
||||
proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES,
|
||||
mPending.getTotalBytes());
|
||||
}
|
||||
getOrLoadCompleteLocked().dumpDebug(proto,
|
||||
NetworkStatsRecorderProto.COMPLETE_HISTORY);
|
||||
proto.end(start);
|
||||
}
|
||||
|
||||
public void dumpCheckin(PrintWriter pw, long start, long end) {
|
||||
// Only load and dump stats from the requested window
|
||||
getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover from {@link FileRotator} failure by dumping state to
|
||||
* {@link DropBoxManager} and deleting contents.
|
||||
*/
|
||||
private void recoverFromWtf() {
|
||||
if (DUMP_BEFORE_DELETE) {
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
mRotator.dumpAll(os);
|
||||
} catch (IOException e) {
|
||||
// ignore partial contents
|
||||
os.reset();
|
||||
} finally {
|
||||
IoUtils.closeQuietly(os);
|
||||
}
|
||||
mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
|
||||
}
|
||||
|
||||
mRotator.deleteAll();
|
||||
}
|
||||
}
|
||||
2528
service-t/src/com/android/server/net/NetworkStatsService.java
Normal file
2528
service-t/src/com/android/server/net/NetworkStatsService.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 com.android.server.net;
|
||||
|
||||
import static android.app.usage.NetworkStatsManager.NETWORK_TYPE_5G_NSA;
|
||||
import static android.app.usage.NetworkStatsManager.getCollapsedRatType;
|
||||
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED;
|
||||
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
|
||||
import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyCallback;
|
||||
import android.telephony.TelephonyDisplayInfo;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.net.module.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Helper class that watches for events that are triggered per subscription.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
public class NetworkStatsSubscriptionsMonitor extends
|
||||
SubscriptionManager.OnSubscriptionsChangedListener {
|
||||
|
||||
/**
|
||||
* Interface that this monitor uses to delegate event handling to NetworkStatsService.
|
||||
*/
|
||||
public interface Delegate {
|
||||
/**
|
||||
* Notify that the collapsed RAT type has been changed for any subscription. The method
|
||||
* will also be triggered for any existing sub when start and stop monitoring.
|
||||
*
|
||||
* @param subscriberId IMSI of the subscription.
|
||||
* @param collapsedRatType collapsed RAT type.
|
||||
* @see android.app.usage.NetworkStatsManager#getCollapsedRatType(int).
|
||||
*/
|
||||
void onCollapsedRatTypeChanged(@NonNull String subscriberId, int collapsedRatType);
|
||||
}
|
||||
private final Delegate mDelegate;
|
||||
|
||||
/**
|
||||
* Receivers that watches for {@link TelephonyDisplayInfo} changes for each subscription, to
|
||||
* monitor the transitioning between Radio Access Technology(RAT) types for each sub.
|
||||
*/
|
||||
@NonNull
|
||||
private final CopyOnWriteArrayList<RatTypeListener> mRatListeners =
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
@NonNull
|
||||
private final SubscriptionManager mSubscriptionManager;
|
||||
@NonNull
|
||||
private final TelephonyManager mTeleManager;
|
||||
|
||||
@NonNull
|
||||
private final Executor mExecutor;
|
||||
|
||||
NetworkStatsSubscriptionsMonitor(@NonNull Context context,
|
||||
@NonNull Executor executor, @NonNull Delegate delegate) {
|
||||
super();
|
||||
mSubscriptionManager = (SubscriptionManager) context.getSystemService(
|
||||
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
|
||||
mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
mExecutor = executor;
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscriptionsChanged() {
|
||||
// Collect active subId list, hidden subId such as opportunistic subscriptions are
|
||||
// also needed to track CBRS.
|
||||
final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
|
||||
|
||||
// IMSI is needed for every newly added sub. Listener stores subscriberId into it to
|
||||
// prevent binder call to telephony when querying RAT. Keep listener registration with empty
|
||||
// IMSI is meaningless since the RAT type changed is ambiguous for multi-SIM if reported
|
||||
// with empty IMSI. So filter the subs w/o a valid IMSI to prevent such registration.
|
||||
final List<Pair<Integer, String>> filteredNewSubs = new ArrayList<>();
|
||||
for (final int subId : newSubs) {
|
||||
final String subscriberId =
|
||||
mTeleManager.createForSubscriptionId(subId).getSubscriberId();
|
||||
if (!TextUtils.isEmpty(subscriberId)) {
|
||||
filteredNewSubs.add(new Pair(subId, subscriberId));
|
||||
}
|
||||
}
|
||||
|
||||
for (final Pair<Integer, String> sub : filteredNewSubs) {
|
||||
// Fully match listener with subId and IMSI, since in some rare cases, IMSI might be
|
||||
// suddenly change regardless of subId, such as switch IMSI feature in modem side.
|
||||
// If that happens, register new listener with new IMSI and remove old one later.
|
||||
if (CollectionUtils.any(mRatListeners, it -> it.equalsKey(sub.first, sub.second))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final RatTypeListener listener = new RatTypeListener(this, sub.first, sub.second);
|
||||
mRatListeners.add(listener);
|
||||
|
||||
// Register listener to the telephony manager that associated with specific sub.
|
||||
mTeleManager.createForSubscriptionId(sub.first)
|
||||
.registerTelephonyCallback(mExecutor, listener);
|
||||
Log.d(NetworkStatsService.TAG, "RAT type listener registered for sub " + sub.first);
|
||||
}
|
||||
|
||||
for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
|
||||
// If there is no subId and IMSI matched the listener, removes it.
|
||||
if (!CollectionUtils.any(filteredNewSubs,
|
||||
it -> listener.equalsKey(it.first, it.second))) {
|
||||
handleRemoveRatTypeListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) {
|
||||
final ArrayList<Integer> ret = new ArrayList<>();
|
||||
final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList();
|
||||
for (int id : ids) ret.add(id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collapsed RatType for the given subscriberId.
|
||||
*
|
||||
* @param subscriberId the target subscriberId
|
||||
* @return collapsed RatType for the given subscriberId
|
||||
*/
|
||||
public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
|
||||
final int index = CollectionUtils.indexOf(mRatListeners,
|
||||
it -> TextUtils.equals(subscriberId, it.mSubscriberId));
|
||||
return index != -1 ? mRatListeners.get(index).mLastCollapsedRatType
|
||||
: TelephonyManager.NETWORK_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring events that triggered per subscription.
|
||||
*/
|
||||
public void start() {
|
||||
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister subscription changes and all listeners for each subscription.
|
||||
*/
|
||||
public void stop() {
|
||||
mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
|
||||
|
||||
for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
|
||||
handleRemoveRatTypeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
|
||||
mTeleManager.createForSubscriptionId(listener.mSubId)
|
||||
.unregisterTelephonyCallback(listener);
|
||||
Log.d(NetworkStatsService.TAG, "RAT type listener unregistered for sub " + listener.mSubId);
|
||||
mRatListeners.remove(listener);
|
||||
|
||||
// Removal of subscriptions doesn't generate RAT changed event, fire it for every
|
||||
// RatTypeListener.
|
||||
mDelegate.onCollapsedRatTypeChanged(
|
||||
listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN);
|
||||
}
|
||||
|
||||
static class RatTypeListener extends TelephonyCallback
|
||||
implements TelephonyCallback.DisplayInfoListener {
|
||||
// Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}.
|
||||
@NonNull
|
||||
private final int mSubId;
|
||||
|
||||
// IMSI to identifying the corresponding network from {@link NetworkState}.
|
||||
// See {@link TelephonyManager#getSubscriberId}.
|
||||
@NonNull
|
||||
private final String mSubscriberId;
|
||||
|
||||
private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
|
||||
@NonNull
|
||||
private final NetworkStatsSubscriptionsMonitor mMonitor;
|
||||
|
||||
RatTypeListener(@NonNull NetworkStatsSubscriptionsMonitor monitor, int subId,
|
||||
@NonNull String subscriberId) {
|
||||
mSubId = subId;
|
||||
mSubscriberId = subscriberId;
|
||||
mMonitor = monitor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(TelephonyDisplayInfo displayInfo) {
|
||||
// In 5G SA (Stand Alone) mode, the primary cell itself will be 5G hence telephony
|
||||
// would report RAT = 5G_NR.
|
||||
// However, in 5G NSA (Non Stand Alone) mode, the primary cell is still LTE and
|
||||
// network allocates a secondary 5G cell so telephony reports RAT = LTE along with
|
||||
// NR state as connected. In such case, attributes the data usage to NR.
|
||||
// See b/160727498.
|
||||
final boolean is5GNsa = displayInfo.getNetworkType() == NETWORK_TYPE_LTE
|
||||
&& (displayInfo.getOverrideNetworkType() == OVERRIDE_NETWORK_TYPE_NR_NSA
|
||||
|| displayInfo.getOverrideNetworkType() == OVERRIDE_NETWORK_TYPE_NR_ADVANCED);
|
||||
|
||||
final int networkType =
|
||||
(is5GNsa ? NETWORK_TYPE_5G_NSA : displayInfo.getNetworkType());
|
||||
final int collapsedRatType = getCollapsedRatType(networkType);
|
||||
if (collapsedRatType == mLastCollapsedRatType) return;
|
||||
|
||||
if (NetworkStatsService.LOGD) {
|
||||
Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): "
|
||||
+ mLastCollapsedRatType + " -> " + collapsedRatType);
|
||||
}
|
||||
mLastCollapsedRatType = collapsedRatType;
|
||||
mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getSubId() {
|
||||
return mSubId;
|
||||
}
|
||||
|
||||
boolean equalsKey(int subId, @NonNull String subscriberId) {
|
||||
return mSubId == subId && TextUtils.equals(mSubscriberId, subscriberId);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
service-t/src/com/android/server/net/StatsMapKey.java
Normal file
46
service-t/src/com/android/server/net/StatsMapKey.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* Key for both stats maps.
|
||||
*/
|
||||
public class StatsMapKey extends Struct {
|
||||
@Field(order = 0, type = Type.U32)
|
||||
public final long uid;
|
||||
|
||||
@Field(order = 1, type = Type.U32)
|
||||
public final long tag;
|
||||
|
||||
@Field(order = 2, type = Type.U32)
|
||||
public final long counterSet;
|
||||
|
||||
@Field(order = 3, type = Type.U32)
|
||||
public final long ifaceIndex;
|
||||
|
||||
public StatsMapKey(final long uid, final long tag, final long counterSet,
|
||||
final long ifaceIndex) {
|
||||
this.uid = uid;
|
||||
this.tag = tag;
|
||||
this.counterSet = counterSet;
|
||||
this.ifaceIndex = ifaceIndex;
|
||||
}
|
||||
}
|
||||
46
service-t/src/com/android/server/net/StatsMapValue.java
Normal file
46
service-t/src/com/android/server/net/StatsMapValue.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* Value used for both stats maps and uid stats map.
|
||||
*/
|
||||
public class StatsMapValue extends Struct {
|
||||
@Field(order = 0, type = Type.U63)
|
||||
public final long rxPackets;
|
||||
|
||||
@Field(order = 1, type = Type.U63)
|
||||
public final long rxBytes;
|
||||
|
||||
@Field(order = 2, type = Type.U63)
|
||||
public final long txPackets;
|
||||
|
||||
@Field(order = 3, type = Type.U63)
|
||||
public final long txBytes;
|
||||
|
||||
public StatsMapValue(final long rxPackets, final long rxBytes, final long txPackets,
|
||||
final long txBytes) {
|
||||
this.rxPackets = rxPackets;
|
||||
this.rxBytes = rxBytes;
|
||||
this.txPackets = txPackets;
|
||||
this.txBytes = txBytes;
|
||||
}
|
||||
}
|
||||
33
service-t/src/com/android/server/net/UidStatsMapKey.java
Normal file
33
service-t/src/com/android/server/net/UidStatsMapKey.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.net;
|
||||
|
||||
import com.android.net.module.util.Struct;
|
||||
import com.android.net.module.util.Struct.Field;
|
||||
import com.android.net.module.util.Struct.Type;
|
||||
|
||||
/**
|
||||
* Key for uid stats map.
|
||||
*/
|
||||
public class UidStatsMapKey extends Struct {
|
||||
@Field(order = 0, type = Type.U32)
|
||||
public final long uid;
|
||||
|
||||
public UidStatsMapKey(final long uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
114
service/src/com/android/server/net/DelayedDiskWrite.java
Normal file
114
service/src/com/android/server/net/DelayedDiskWrite.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 com.android.server.net;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This class provides APIs to do a delayed data write to a given {@link OutputStream}.
|
||||
*/
|
||||
public class DelayedDiskWrite {
|
||||
private static final String TAG = "DelayedDiskWrite";
|
||||
|
||||
private HandlerThread mDiskWriteHandlerThread;
|
||||
private Handler mDiskWriteHandler;
|
||||
/* Tracks multiple writes on the same thread */
|
||||
private int mWriteSequence = 0;
|
||||
|
||||
/**
|
||||
* Used to do a delayed data write to a given {@link OutputStream}.
|
||||
*/
|
||||
public interface Writer {
|
||||
/**
|
||||
* write data to a given {@link OutputStream}.
|
||||
*/
|
||||
void onWriteCalled(DataOutputStream out) throws IOException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a delayed data write to a given output stream opened from filePath.
|
||||
*/
|
||||
public void write(final String filePath, final Writer w) {
|
||||
write(filePath, w, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a delayed data write to a given output stream opened from filePath.
|
||||
*/
|
||||
public void write(final String filePath, final Writer w, final boolean open) {
|
||||
if (TextUtils.isEmpty(filePath)) {
|
||||
throw new IllegalArgumentException("empty file path");
|
||||
}
|
||||
|
||||
/* Do a delayed write to disk on a separate handler thread */
|
||||
synchronized (this) {
|
||||
if (++mWriteSequence == 1) {
|
||||
mDiskWriteHandlerThread = new HandlerThread("DelayedDiskWriteThread");
|
||||
mDiskWriteHandlerThread.start();
|
||||
mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper());
|
||||
}
|
||||
}
|
||||
|
||||
mDiskWriteHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
doWrite(filePath, w, open);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doWrite(String filePath, Writer w, boolean open) {
|
||||
DataOutputStream out = null;
|
||||
try {
|
||||
if (open) {
|
||||
out = new DataOutputStream(new BufferedOutputStream(
|
||||
new FileOutputStream(filePath)));
|
||||
}
|
||||
w.onWriteCalled(out);
|
||||
} catch (IOException e) {
|
||||
loge("Error writing data file " + filePath);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
|
||||
// Quit if no more writes sent
|
||||
synchronized (this) {
|
||||
if (--mWriteSequence == 0) {
|
||||
mDiskWriteHandler.getLooper().quit();
|
||||
mDiskWriteHandler = null;
|
||||
mDiskWriteHandlerThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loge(String s) {
|
||||
Log.e(TAG, s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ filegroup {
|
||||
"java/com/android/server/IpSecServiceParameterizedTest.java",
|
||||
"java/com/android/server/IpSecServiceRefcountedResourceTest.java",
|
||||
"java/com/android/server/IpSecServiceTest.java",
|
||||
"java/com/android/server/NativeDaemonConnectorTest.java",
|
||||
"java/com/android/server/NetworkManagementServiceTest.java",
|
||||
"java/com/android/server/NsdServiceTest.java",
|
||||
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
|
||||
@@ -83,8 +84,10 @@ filegroup {
|
||||
"java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
|
||||
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
|
||||
"java/com/android/server/connectivity/VpnTest.java",
|
||||
"java/com/android/server/ethernet/*.java",
|
||||
"java/com/android/server/net/ipmemorystore/*.java",
|
||||
"java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
|
||||
"java/com/android/server/net/IpConfigStoreTest.java",
|
||||
"java/com/android/server/net/NetworkStats*.java",
|
||||
"java/com/android/server/net/TestableUsageCallback.kt",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2011 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 com.android.server;
|
||||
|
||||
import static com.android.server.NativeDaemonConnector.appendEscaped;
|
||||
import static com.android.server.NativeDaemonConnector.makeCommand;
|
||||
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
||||
import com.android.server.NativeDaemonConnector.SensitiveArg;
|
||||
|
||||
/**
|
||||
* Tests for {@link NativeDaemonConnector}.
|
||||
*/
|
||||
@MediumTest
|
||||
public class NativeDaemonConnectorTest extends AndroidTestCase {
|
||||
private static final String TAG = "NativeDaemonConnectorTest";
|
||||
|
||||
public void testArgumentNormal() throws Exception {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "");
|
||||
assertEquals("", builder.toString());
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "foo");
|
||||
assertEquals("foo", builder.toString());
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "foo\"bar");
|
||||
assertEquals("foo\\\"bar", builder.toString());
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "foo\\bar\\\"baz");
|
||||
assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString());
|
||||
}
|
||||
|
||||
public void testArgumentWithSpaces() throws Exception {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "foo bar");
|
||||
assertEquals("\"foo bar\"", builder.toString());
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "foo\"bar\\baz foo");
|
||||
assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString());
|
||||
}
|
||||
|
||||
public void testArgumentWithUtf() throws Exception {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.setLength(0);
|
||||
appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
|
||||
assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
|
||||
}
|
||||
|
||||
public void testSensitiveArgs() throws Exception {
|
||||
final StringBuilder rawBuilder = new StringBuilder();
|
||||
final StringBuilder logBuilder = new StringBuilder();
|
||||
|
||||
rawBuilder.setLength(0);
|
||||
logBuilder.setLength(0);
|
||||
makeCommand(rawBuilder, logBuilder, 1, "foo", "bar", "baz");
|
||||
assertEquals("1 foo bar baz\0", rawBuilder.toString());
|
||||
assertEquals("1 foo bar baz", logBuilder.toString());
|
||||
|
||||
rawBuilder.setLength(0);
|
||||
logBuilder.setLength(0);
|
||||
makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("bar"), "baz");
|
||||
assertEquals("1 foo bar baz\0", rawBuilder.toString());
|
||||
assertEquals("1 foo [scrubbed] baz", logBuilder.toString());
|
||||
|
||||
rawBuilder.setLength(0);
|
||||
logBuilder.setLength(0);
|
||||
makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("foo bar"), "baz baz",
|
||||
new SensitiveArg("wat"));
|
||||
assertEquals("1 foo \"foo bar\" \"baz baz\" wat\0", rawBuilder.toString());
|
||||
assertEquals("1 foo [scrubbed] \"baz baz\" [scrubbed]", logBuilder.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,783 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 com.android.server.ethernet;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.test.MockAnswerUtil.AnswerWithArguments;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.EthernetNetworkSpecifier;
|
||||
import android.net.EthernetNetworkManagementException;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkAgentConfig;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkProvider;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.net.ip.IpClientCallbacks;
|
||||
import android.net.ip.IpClientManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.test.TestLooper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.net.module.util.InterfaceParams;
|
||||
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class EthernetNetworkFactoryTest {
|
||||
private static final int TIMEOUT_MS = 2_000;
|
||||
private static final String TEST_IFACE = "test123";
|
||||
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||
private static final String IP_ADDR = "192.0.2.2/25";
|
||||
private static final LinkAddress LINK_ADDR = new LinkAddress(IP_ADDR);
|
||||
private static final String HW_ADDR = "01:02:03:04:05:06";
|
||||
private TestLooper mLooper;
|
||||
private Handler mHandler;
|
||||
private EthernetNetworkFactory mNetFactory = null;
|
||||
private IpClientCallbacks mIpClientCallbacks;
|
||||
@Mock private Context mContext;
|
||||
@Mock private Resources mResources;
|
||||
@Mock private EthernetNetworkFactory.Dependencies mDeps;
|
||||
@Mock private IpClientManager mIpClient;
|
||||
@Mock private EthernetNetworkAgent mNetworkAgent;
|
||||
@Mock private InterfaceParams mInterfaceParams;
|
||||
@Mock private Network mMockNetwork;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
setupNetworkAgentMock();
|
||||
setupIpClientMock();
|
||||
setupContext();
|
||||
}
|
||||
|
||||
//TODO: Move away from usage of TestLooper in order to move this logic back into @Before.
|
||||
private void initEthernetNetworkFactory() {
|
||||
mLooper = new TestLooper();
|
||||
mHandler = new Handler(mLooper.getLooper());
|
||||
mNetFactory = new EthernetNetworkFactory(mHandler, mContext, mDeps);
|
||||
}
|
||||
|
||||
private void setupNetworkAgentMock() {
|
||||
when(mDeps.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any()))
|
||||
.thenAnswer(new AnswerWithArguments() {
|
||||
public EthernetNetworkAgent answer(
|
||||
Context context,
|
||||
Looper looper,
|
||||
NetworkCapabilities nc,
|
||||
LinkProperties lp,
|
||||
NetworkAgentConfig config,
|
||||
NetworkProvider provider,
|
||||
EthernetNetworkAgent.Callbacks cb) {
|
||||
when(mNetworkAgent.getCallbacks()).thenReturn(cb);
|
||||
when(mNetworkAgent.getNetwork())
|
||||
.thenReturn(mMockNetwork);
|
||||
return mNetworkAgent;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void setupIpClientMock() throws Exception {
|
||||
doAnswer(inv -> {
|
||||
// these tests only support one concurrent IpClient, so make sure we do not accidentally
|
||||
// create a mess.
|
||||
assertNull("An IpClient has already been created.", mIpClientCallbacks);
|
||||
|
||||
mIpClientCallbacks = inv.getArgument(2);
|
||||
mIpClientCallbacks.onIpClientCreated(null);
|
||||
mLooper.dispatchAll();
|
||||
return null;
|
||||
}).when(mDeps).makeIpClient(any(Context.class), anyString(), any());
|
||||
|
||||
doAnswer(inv -> {
|
||||
mIpClientCallbacks.onQuit();
|
||||
mLooper.dispatchAll();
|
||||
mIpClientCallbacks = null;
|
||||
return null;
|
||||
}).when(mIpClient).shutdown();
|
||||
|
||||
when(mDeps.makeIpClientManager(any())).thenReturn(mIpClient);
|
||||
}
|
||||
|
||||
private void triggerOnProvisioningSuccess() {
|
||||
mIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private void triggerOnProvisioningFailure() {
|
||||
mIpClientCallbacks.onProvisioningFailure(new LinkProperties());
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private void triggerOnReachabilityLost() {
|
||||
mIpClientCallbacks.onReachabilityLost("ReachabilityLost");
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private void setupContext() {
|
||||
when(mDeps.getTcpBufferSizesFromResource(eq(mContext))).thenReturn("");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
// looper is shared with the network agents, so there may still be messages to dispatch on
|
||||
// tear down.
|
||||
mLooper.dispatchAll();
|
||||
}
|
||||
|
||||
private NetworkCapabilities createDefaultFilterCaps() {
|
||||
return NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.build();
|
||||
}
|
||||
|
||||
private NetworkCapabilities.Builder createInterfaceCapsBuilder(final int transportType) {
|
||||
return new NetworkCapabilities.Builder()
|
||||
.addTransportType(transportType)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
|
||||
}
|
||||
|
||||
private NetworkRequest.Builder createDefaultRequestBuilder() {
|
||||
return new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
}
|
||||
|
||||
private NetworkRequest createDefaultRequest() {
|
||||
return createDefaultRequestBuilder().build();
|
||||
}
|
||||
|
||||
private IpConfiguration createDefaultIpConfig() {
|
||||
IpConfiguration ipConfig = new IpConfiguration();
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE);
|
||||
return ipConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link IpConfiguration} with an associated {@link StaticIpConfiguration}.
|
||||
*
|
||||
* @return {@link IpConfiguration} with its {@link StaticIpConfiguration} set.
|
||||
*/
|
||||
private IpConfiguration createStaticIpConfig() {
|
||||
final IpConfiguration ipConfig = new IpConfiguration();
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
|
||||
ipConfig.setStaticIpConfiguration(
|
||||
new StaticIpConfiguration.Builder().setIpAddress(LINK_ADDR).build());
|
||||
return ipConfig;
|
||||
}
|
||||
|
||||
// creates an interface with provisioning in progress (since updating the interface link state
|
||||
// automatically starts the provisioning process)
|
||||
private void createInterfaceUndergoingProvisioning(String iface) {
|
||||
// Default to the ethernet transport type.
|
||||
createInterfaceUndergoingProvisioning(iface, NetworkCapabilities.TRANSPORT_ETHERNET);
|
||||
}
|
||||
|
||||
private void createInterfaceUndergoingProvisioning(
|
||||
@NonNull final String iface, final int transportType) {
|
||||
final IpConfiguration ipConfig = createDefaultIpConfig();
|
||||
mNetFactory.addInterface(iface, HW_ADDR, ipConfig,
|
||||
createInterfaceCapsBuilder(transportType).build());
|
||||
assertTrue(mNetFactory.updateInterfaceLinkState(iface, true, NULL_LISTENER));
|
||||
verifyStart(ipConfig);
|
||||
clearInvocations(mDeps);
|
||||
clearInvocations(mIpClient);
|
||||
}
|
||||
|
||||
// creates a provisioned interface
|
||||
private void createAndVerifyProvisionedInterface(String iface) throws Exception {
|
||||
// Default to the ethernet transport type.
|
||||
createAndVerifyProvisionedInterface(iface, NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||
ConnectivityManager.TYPE_ETHERNET);
|
||||
}
|
||||
|
||||
private void createVerifyAndRemoveProvisionedInterface(final int transportType,
|
||||
final int expectedLegacyType) throws Exception {
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE, transportType,
|
||||
expectedLegacyType);
|
||||
mNetFactory.removeInterface(TEST_IFACE);
|
||||
}
|
||||
|
||||
private void createAndVerifyProvisionedInterface(
|
||||
@NonNull final String iface, final int transportType, final int expectedLegacyType)
|
||||
throws Exception {
|
||||
createInterfaceUndergoingProvisioning(iface, transportType);
|
||||
triggerOnProvisioningSuccess();
|
||||
// provisioning succeeded, verify that the network agent is created, registered, marked
|
||||
// as connected and legacy type are correctly set.
|
||||
final ArgumentCaptor<NetworkCapabilities> ncCaptor = ArgumentCaptor.forClass(
|
||||
NetworkCapabilities.class);
|
||||
verify(mDeps).makeEthernetNetworkAgent(any(), any(), ncCaptor.capture(), any(),
|
||||
argThat(x -> x.getLegacyType() == expectedLegacyType), any(), any());
|
||||
assertEquals(
|
||||
new EthernetNetworkSpecifier(iface), ncCaptor.getValue().getNetworkSpecifier());
|
||||
verifyNetworkAgentRegistersAndConnects();
|
||||
clearInvocations(mDeps);
|
||||
clearInvocations(mNetworkAgent);
|
||||
}
|
||||
|
||||
// creates an unprovisioned interface
|
||||
private void createUnprovisionedInterface(String iface) throws Exception {
|
||||
// To create an unprovisioned interface, provision and then "stop" it, i.e. stop its
|
||||
// NetworkAgent and IpClient. One way this can be done is by provisioning an interface and
|
||||
// then calling onNetworkUnwanted.
|
||||
createAndVerifyProvisionedInterface(iface);
|
||||
|
||||
mNetworkAgent.getCallbacks().onNetworkUnwanted();
|
||||
mLooper.dispatchAll();
|
||||
verifyStop();
|
||||
|
||||
clearInvocations(mIpClient);
|
||||
clearInvocations(mNetworkAgent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptRequest() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||
assertTrue(mNetFactory.acceptRequest(createDefaultRequest()));
|
||||
|
||||
NetworkRequest wifiRequest = createDefaultRequestBuilder()
|
||||
.removeTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
|
||||
assertFalse(mNetFactory.acceptRequest(wifiRequest));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceLinkStateForActiveProvisioningInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
// verify that the IpClient gets shut down when interface state changes to down.
|
||||
final boolean ret =
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||
|
||||
assertTrue(ret);
|
||||
verify(mIpClient).shutdown();
|
||||
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
final boolean ret =
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||
|
||||
assertTrue(ret);
|
||||
verifyStop();
|
||||
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createUnprovisionedInterface(TEST_IFACE);
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
final boolean ret =
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */, listener);
|
||||
|
||||
assertTrue(ret);
|
||||
// There should not be an active IPClient or NetworkAgent.
|
||||
verify(mDeps, never()).makeIpClient(any(), any(), any());
|
||||
verify(mDeps, never())
|
||||
.makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
|
||||
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
// if interface was never added, link state cannot be updated.
|
||||
final boolean ret =
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
|
||||
|
||||
assertFalse(ret);
|
||||
verifyNoStopOrStart();
|
||||
listener.expectOnErrorWithMessage("can't be updated as it is not available");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
final boolean ret =
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */, listener);
|
||||
|
||||
assertFalse(ret);
|
||||
verifyNoStopOrStart();
|
||||
listener.expectOnErrorWithMessage("No changes");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeedNetworkForOnProvisionedInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeedNetworkForOnUnprovisionedInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createUnprovisionedInterface(TEST_IFACE);
|
||||
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||
verify(mIpClient).startProvisioning(any());
|
||||
|
||||
triggerOnProvisioningSuccess();
|
||||
verifyNetworkAgentRegistersAndConnects();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNeedNetworkForOnInterfaceUndergoingProvisioning() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createInterfaceUndergoingProvisioning(TEST_IFACE);
|
||||
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
|
||||
triggerOnProvisioningSuccess();
|
||||
verifyNetworkAgentRegistersAndConnects();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisioningLoss() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
triggerOnProvisioningFailure();
|
||||
verifyStop();
|
||||
// provisioning loss should trigger a retry, since the interface is still there
|
||||
verify(mIpClient).startProvisioning(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisioningLossForDisappearedInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
// mocked method returns null by default, but just to be explicit in the test:
|
||||
when(mDeps.getNetworkInterfaceByName(eq(TEST_IFACE))).thenReturn(null);
|
||||
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
triggerOnProvisioningFailure();
|
||||
|
||||
// the interface disappeared and getNetworkInterfaceByName returns null, we should not retry
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
verifyNoStopOrStart();
|
||||
}
|
||||
|
||||
private void verifyNoStopOrStart() {
|
||||
verify(mNetworkAgent, never()).register();
|
||||
verify(mIpClient, never()).shutdown();
|
||||
verify(mNetworkAgent, never()).unregister();
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpClientIsNotStartedWhenLinkIsDown() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createUnprovisionedInterface(TEST_IFACE);
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER);
|
||||
|
||||
mNetFactory.needNetworkFor(createDefaultRequest());
|
||||
|
||||
verify(mDeps, never()).makeIpClient(any(), any(), any());
|
||||
|
||||
// BUG(b/191854824): requesting a network with a specifier (Android Auto use case) should
|
||||
// not start an IpClient when the link is down, but fixing this may make matters worse by
|
||||
// tiggering b/197548738.
|
||||
NetworkRequest specificNetRequest = new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
|
||||
.build();
|
||||
mNetFactory.needNetworkFor(specificNetRequest);
|
||||
mNetFactory.releaseNetworkFor(specificNetRequest);
|
||||
|
||||
mNetFactory.updateInterfaceLinkState(TEST_IFACE, true, NULL_LISTENER);
|
||||
// TODO: change to once when b/191854824 is fixed.
|
||||
verify(mDeps, times(2)).makeIpClient(any(), eq(TEST_IFACE), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLinkPropertiesChanged() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
LinkProperties lp = new LinkProperties();
|
||||
mIpClientCallbacks.onLinkPropertiesChange(lp);
|
||||
mLooper.dispatchAll();
|
||||
verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkUnwanted() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
mNetworkAgent.getCallbacks().onNetworkUnwanted();
|
||||
mLooper.dispatchAll();
|
||||
verifyStop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkUnwantedWithStaleNetworkAgent() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
// ensures provisioning is restarted after provisioning loss
|
||||
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
EthernetNetworkAgent.Callbacks oldCbs = mNetworkAgent.getCallbacks();
|
||||
// replace network agent in EthernetNetworkFactory
|
||||
// Loss of provisioning will restart the ip client and network agent.
|
||||
triggerOnProvisioningFailure();
|
||||
verify(mDeps).makeIpClient(any(), any(), any());
|
||||
|
||||
triggerOnProvisioningSuccess();
|
||||
verify(mDeps).makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
|
||||
|
||||
// verify that unwanted is ignored
|
||||
clearInvocations(mIpClient);
|
||||
clearInvocations(mNetworkAgent);
|
||||
oldCbs.onNetworkUnwanted();
|
||||
verify(mIpClient, never()).shutdown();
|
||||
verify(mNetworkAgent, never()).unregister();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportOverrideIsCorrectlySet() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
// createProvisionedInterface() has verifications in place for transport override
|
||||
// functionality which for EthernetNetworkFactory is network score and legacy type mappings.
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_ETHERNET,
|
||||
ConnectivityManager.TYPE_ETHERNET);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_BLUETOOTH,
|
||||
ConnectivityManager.TYPE_BLUETOOTH);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI,
|
||||
ConnectivityManager.TYPE_WIFI);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_CELLULAR,
|
||||
ConnectivityManager.TYPE_MOBILE);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_LOWPAN,
|
||||
ConnectivityManager.TYPE_NONE);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_WIFI_AWARE,
|
||||
ConnectivityManager.TYPE_NONE);
|
||||
createVerifyAndRemoveProvisionedInterface(NetworkCapabilities.TRANSPORT_TEST,
|
||||
ConnectivityManager.TYPE_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReachabilityLoss() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
triggerOnReachabilityLost();
|
||||
|
||||
// Reachability loss should trigger a stop and start, since the interface is still there
|
||||
verifyRestart(createDefaultIpConfig());
|
||||
}
|
||||
|
||||
private IpClientCallbacks getStaleIpClientCallbacks() throws Exception {
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
final IpClientCallbacks staleIpClientCallbacks = mIpClientCallbacks;
|
||||
mNetFactory.removeInterface(TEST_IFACE);
|
||||
verifyStop();
|
||||
assertNotSame(mIpClientCallbacks, staleIpClientCallbacks);
|
||||
return staleIpClientCallbacks;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreOnIpLayerStartedCallbackForStaleCallback() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||
|
||||
staleIpClientCallbacks.onProvisioningSuccess(new LinkProperties());
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
verify(mNetworkAgent, never()).register();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreOnIpLayerStoppedCallbackForStaleCallback() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
|
||||
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||
|
||||
staleIpClientCallbacks.onProvisioningFailure(new LinkProperties());
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreLinkPropertiesCallbackForStaleCallback() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||
final LinkProperties lp = new LinkProperties();
|
||||
|
||||
staleIpClientCallbacks.onLinkPropertiesChange(lp);
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(mNetworkAgent, never()).sendLinkPropertiesImpl(eq(lp));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreNeighborLossCallbackForStaleCallback() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
final IpClientCallbacks staleIpClientCallbacks = getStaleIpClientCallbacks();
|
||||
|
||||
staleIpClientCallbacks.onReachabilityLost("Neighbor Lost");
|
||||
mLooper.dispatchAll();
|
||||
|
||||
verify(mIpClient, never()).startProvisioning(any());
|
||||
verify(mNetworkAgent, never()).register();
|
||||
}
|
||||
|
||||
private void verifyRestart(@NonNull final IpConfiguration ipConfig) {
|
||||
verifyStop();
|
||||
verifyStart(ipConfig);
|
||||
}
|
||||
|
||||
private void verifyStart(@NonNull final IpConfiguration ipConfig) {
|
||||
verify(mDeps).makeIpClient(any(Context.class), anyString(), any());
|
||||
verify(mIpClient).startProvisioning(
|
||||
argThat(x -> Objects.equals(x.mStaticIpConfig, ipConfig.getStaticIpConfiguration()))
|
||||
);
|
||||
}
|
||||
|
||||
private void verifyStop() {
|
||||
verify(mIpClient).shutdown();
|
||||
verify(mNetworkAgent).unregister();
|
||||
}
|
||||
|
||||
private void verifyNetworkAgentRegistersAndConnects() {
|
||||
verify(mNetworkAgent).register();
|
||||
verify(mNetworkAgent).markConnected();
|
||||
}
|
||||
|
||||
private static final class TestNetworkManagementListener
|
||||
implements INetworkInterfaceOutcomeReceiver {
|
||||
private final CompletableFuture<String> mResult = new CompletableFuture<>();
|
||||
private final CompletableFuture<EthernetNetworkManagementException> mError =
|
||||
new CompletableFuture<>();
|
||||
|
||||
@Override
|
||||
public void onResult(@NonNull String iface) {
|
||||
mResult.complete(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull EthernetNetworkManagementException exception) {
|
||||
mError.complete(exception);
|
||||
}
|
||||
|
||||
String expectOnResult() throws Exception {
|
||||
return mResult.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
EthernetNetworkManagementException expectOnError() throws Exception {
|
||||
return mError.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
void expectOnErrorWithMessage(String msg) throws Exception {
|
||||
assertTrue(expectOnError().getMessage().contains(msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceCallsListenerCorrectlyOnSuccess() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||
triggerOnProvisioningSuccess();
|
||||
|
||||
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||
}
|
||||
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||
@Test
|
||||
public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||
TEST_IFACE,
|
||||
() -> mNetFactory.removeInterface(TEST_IFACE));
|
||||
}
|
||||
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||
@Test
|
||||
public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||
TEST_IFACE,
|
||||
() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
|
||||
}
|
||||
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
|
||||
@Test
|
||||
public void testUpdateInterfaceCallsListenerCorrectlyOnConcurrentRequests() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||
final TestNetworkManagementListener successfulListener =
|
||||
new TestNetworkManagementListener();
|
||||
|
||||
// If two calls come in before the first one completes, the first listener will be aborted
|
||||
// and the second one will be successful.
|
||||
verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||
TEST_IFACE,
|
||||
() -> {
|
||||
mNetFactory.updateInterface(
|
||||
TEST_IFACE, ipConfiguration, capabilities, successfulListener);
|
||||
triggerOnProvisioningSuccess();
|
||||
});
|
||||
|
||||
assertEquals(successfulListener.expectOnResult(), TEST_IFACE);
|
||||
}
|
||||
|
||||
private void verifyNetworkManagementCallIsAbortedWhenInterrupted(
|
||||
@NonNull final String iface,
|
||||
@NonNull final Runnable interruptingRunnable) throws Exception {
|
||||
createAndVerifyProvisionedInterface(iface);
|
||||
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||
final TestNetworkManagementListener failedListener = new TestNetworkManagementListener();
|
||||
|
||||
// An active update request will be aborted on interrupt prior to provisioning completion.
|
||||
mNetFactory.updateInterface(iface, ipConfiguration, capabilities, failedListener);
|
||||
interruptingRunnable.run();
|
||||
|
||||
failedListener.expectOnErrorWithMessage("aborted");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceRestartsAgentCorrectly() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||
triggerOnProvisioningSuccess();
|
||||
|
||||
assertEquals(listener.expectOnResult(), TEST_IFACE);
|
||||
verify(mDeps).makeEthernetNetworkAgent(any(), any(),
|
||||
eq(capabilities), any(), any(), any(), any());
|
||||
verifyRestart(ipConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceForNonExistingInterface() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
// No interface exists due to not calling createAndVerifyProvisionedInterface(...).
|
||||
final NetworkCapabilities capabilities = createDefaultFilterCaps();
|
||||
final IpConfiguration ipConfiguration = createStaticIpConfig();
|
||||
final TestNetworkManagementListener listener = new TestNetworkManagementListener();
|
||||
|
||||
mNetFactory.updateInterface(TEST_IFACE, ipConfiguration, capabilities, listener);
|
||||
|
||||
verifyNoStopOrStart();
|
||||
listener.expectOnErrorWithMessage("can't be updated as it is not available");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateInterfaceWithNullIpConfiguration() throws Exception {
|
||||
initEthernetNetworkFactory();
|
||||
createAndVerifyProvisionedInterface(TEST_IFACE);
|
||||
|
||||
final IpConfiguration initialIpConfig = createStaticIpConfig();
|
||||
mNetFactory.updateInterface(TEST_IFACE, initialIpConfig, null /*capabilities*/,
|
||||
null /*listener*/);
|
||||
triggerOnProvisioningSuccess();
|
||||
verifyRestart(initialIpConfig);
|
||||
|
||||
// TODO: have verifyXyz functions clear invocations.
|
||||
clearInvocations(mDeps);
|
||||
clearInvocations(mIpClient);
|
||||
clearInvocations(mNetworkAgent);
|
||||
|
||||
|
||||
// verify that sending a null ipConfig does not update the current ipConfig.
|
||||
mNetFactory.updateInterface(TEST_IFACE, null /*ipConfig*/, null /*capabilities*/,
|
||||
null /*listener*/);
|
||||
triggerOnProvisioningSuccess();
|
||||
verifyRestart(initialIpConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.ethernet;
|
||||
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.EthernetNetworkUpdateRequest;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.os.Handler;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class EthernetServiceImplTest {
|
||||
private static final String TEST_IFACE = "test123";
|
||||
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
|
||||
new EthernetNetworkUpdateRequest.Builder()
|
||||
.setIpConfiguration(new IpConfiguration())
|
||||
.setNetworkCapabilities(new NetworkCapabilities.Builder().build())
|
||||
.build();
|
||||
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_CAPABILITIES =
|
||||
new EthernetNetworkUpdateRequest.Builder()
|
||||
.setIpConfiguration(new IpConfiguration())
|
||||
.build();
|
||||
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG =
|
||||
new EthernetNetworkUpdateRequest.Builder()
|
||||
.setNetworkCapabilities(new NetworkCapabilities.Builder().build())
|
||||
.build();
|
||||
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||
private EthernetServiceImpl mEthernetServiceImpl;
|
||||
@Mock private Context mContext;
|
||||
@Mock private Handler mHandler;
|
||||
@Mock private EthernetTracker mEthernetTracker;
|
||||
@Mock private PackageManager mPackageManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||
mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker);
|
||||
mEthernetServiceImpl.mStarted.set(true);
|
||||
toggleAutomotiveFeature(true);
|
||||
shouldTrackIface(TEST_IFACE, true);
|
||||
}
|
||||
|
||||
private void toggleAutomotiveFeature(final boolean isEnabled) {
|
||||
doReturn(isEnabled)
|
||||
.when(mPackageManager).hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||
}
|
||||
|
||||
private void shouldTrackIface(@NonNull final String iface, final boolean shouldTrack) {
|
||||
doReturn(shouldTrack).when(mEthernetTracker).isTrackingInterface(iface);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetConfigurationRejectsWhenEthNotStarted() {
|
||||
mEthernetServiceImpl.mStarted.set(false);
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
mEthernetServiceImpl.setConfiguration("" /* iface */, new IpConfiguration());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationRejectsWhenEthNotStarted() {
|
||||
mEthernetServiceImpl.mStarted.set(false);
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(
|
||||
"" /* iface */, UPDATE_REQUEST, null /* listener */);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkRejectsWhenEthNotStarted() {
|
||||
mEthernetServiceImpl.mStarted.set(false);
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkRejectsWhenEthNotStarted() {
|
||||
mEthernetServiceImpl.mStarted.set(false);
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationRejectsNullIface() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(null, UPDATE_REQUEST, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkRejectsNullIface() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkRejectsNullIface() {
|
||||
assertThrows(NullPointerException.class, () -> {
|
||||
mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationWithCapabilitiesRejectsWithoutAutomotiveFeature() {
|
||||
toggleAutomotiveFeature(false);
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationWithCapabilitiesWithAutomotiveFeature() {
|
||||
toggleAutomotiveFeature(false);
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_CAPABILITIES,
|
||||
NULL_LISTENER);
|
||||
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getIpConfiguration()),
|
||||
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
|
||||
toggleAutomotiveFeature(false);
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
|
||||
toggleAutomotiveFeature(false);
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
private void denyManageEthPermission() {
|
||||
doThrow(new SecurityException("")).when(mContext)
|
||||
.enforceCallingOrSelfPermission(
|
||||
eq(Manifest.permission.MANAGE_ETHERNET_NETWORKS), anyString());
|
||||
}
|
||||
|
||||
private void denyManageTestNetworksPermission() {
|
||||
doThrow(new SecurityException("")).when(mContext)
|
||||
.enforceCallingOrSelfPermission(
|
||||
eq(Manifest.permission.MANAGE_TEST_NETWORKS), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationRejectsWithoutManageEthPermission() {
|
||||
denyManageEthPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkRejectsWithoutManageEthPermission() {
|
||||
denyManageEthPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
|
||||
denyManageEthPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
private void enableTestInterface() {
|
||||
when(mEthernetTracker.isValidTestInterface(eq(TEST_IFACE))).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationRejectsTestRequestWithoutTestPermission() {
|
||||
enableTestInterface();
|
||||
denyManageTestNetworksPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkRejectsTestRequestWithoutTestPermission() {
|
||||
enableTestInterface();
|
||||
denyManageTestNetworksPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() {
|
||||
enableTestInterface();
|
||||
denyManageTestNetworksPermission();
|
||||
assertThrows(SecurityException.class, () -> {
|
||||
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfiguration() {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||
verify(mEthernetTracker).updateConfiguration(
|
||||
eq(TEST_IFACE),
|
||||
eq(UPDATE_REQUEST.getIpConfiguration()),
|
||||
eq(UPDATE_REQUEST.getNetworkCapabilities()), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetwork() {
|
||||
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetwork() {
|
||||
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationAcceptsTestRequestWithNullCapabilities() {
|
||||
enableTestInterface();
|
||||
final EthernetNetworkUpdateRequest request =
|
||||
new EthernetNetworkUpdateRequest
|
||||
.Builder()
|
||||
.setIpConfiguration(new IpConfiguration()).build();
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
|
||||
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||
eq(request.getIpConfiguration()),
|
||||
eq(request.getNetworkCapabilities()), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationAcceptsRequestWithNullIpConfiguration() {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_IP_CONFIG,
|
||||
NULL_LISTENER);
|
||||
verify(mEthernetTracker).updateConfiguration(eq(TEST_IFACE),
|
||||
eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getIpConfiguration()),
|
||||
eq(UPDATE_REQUEST_WITHOUT_IP_CONFIG.getNetworkCapabilities()), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationRejectsInvalidTestRequest() {
|
||||
enableTestInterface();
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST, NULL_LISTENER);
|
||||
});
|
||||
}
|
||||
|
||||
private EthernetNetworkUpdateRequest createTestNetworkUpdateRequest() {
|
||||
final NetworkCapabilities nc = new NetworkCapabilities
|
||||
.Builder(UPDATE_REQUEST.getNetworkCapabilities())
|
||||
.addTransportType(TRANSPORT_TEST).build();
|
||||
|
||||
return new EthernetNetworkUpdateRequest
|
||||
.Builder(UPDATE_REQUEST)
|
||||
.setNetworkCapabilities(nc).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfigurationForTestRequestDoesNotRequireAutoOrEthernetPermission() {
|
||||
enableTestInterface();
|
||||
toggleAutomotiveFeature(false);
|
||||
denyManageEthPermission();
|
||||
final EthernetNetworkUpdateRequest request = createTestNetworkUpdateRequest();
|
||||
|
||||
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, request, NULL_LISTENER);
|
||||
verify(mEthernetTracker).updateConfiguration(
|
||||
eq(TEST_IFACE),
|
||||
eq(request.getIpConfiguration()),
|
||||
eq(request.getNetworkCapabilities()), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
|
||||
enableTestInterface();
|
||||
toggleAutomotiveFeature(false);
|
||||
denyManageEthPermission();
|
||||
|
||||
mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
|
||||
enableTestInterface();
|
||||
toggleAutomotiveFeature(false);
|
||||
denyManageEthPermission();
|
||||
|
||||
mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
private void denyPermissions(String... permissions) {
|
||||
for (String permission: permissions) {
|
||||
doReturn(PackageManager.PERMISSION_DENIED).when(mContext)
|
||||
.checkCallingOrSelfPermission(eq(permission));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetEthernetEnabled() {
|
||||
denyPermissions(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK);
|
||||
mEthernetServiceImpl.setEthernetEnabled(true);
|
||||
verify(mEthernetTracker).setEthernetEnabled(true);
|
||||
reset(mEthernetTracker);
|
||||
|
||||
denyPermissions(Manifest.permission.NETWORK_STACK);
|
||||
mEthernetServiceImpl.setEthernetEnabled(false);
|
||||
verify(mEthernetTracker).setEthernetEnabled(false);
|
||||
reset(mEthernetTracker);
|
||||
|
||||
denyPermissions(Manifest.permission.NETWORK_SETTINGS);
|
||||
try {
|
||||
mEthernetServiceImpl.setEthernetEnabled(true);
|
||||
fail("Should get SecurityException");
|
||||
} catch (SecurityException e) { }
|
||||
verify(mEthernetTracker, never()).setEthernetEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.ethernet;
|
||||
|
||||
import static android.net.TestNetworkManager.TEST_TAP_PREFIX;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.EthernetManager;
|
||||
import android.net.InetAddresses;
|
||||
import android.net.INetworkInterfaceOutcomeReceiver;
|
||||
import android.net.IEthernetServiceListener;
|
||||
import android.net.INetd;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.InterfaceConfigurationParcel;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.testutils.HandlerUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class EthernetTrackerTest {
|
||||
private static final String TEST_IFACE = "test123";
|
||||
private static final int TIMEOUT_MS = 1_000;
|
||||
private static final String THREAD_NAME = "EthernetServiceThread";
|
||||
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
|
||||
private EthernetTracker tracker;
|
||||
private HandlerThread mHandlerThread;
|
||||
@Mock private Context mContext;
|
||||
@Mock private EthernetNetworkFactory mFactory;
|
||||
@Mock private INetd mNetd;
|
||||
@Mock private EthernetTracker.Dependencies mDeps;
|
||||
|
||||
@Before
|
||||
public void setUp() throws RemoteException {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
initMockResources();
|
||||
when(mFactory.updateInterfaceLinkState(anyString(), anyBoolean(), any())).thenReturn(false);
|
||||
when(mNetd.interfaceGetList()).thenReturn(new String[0]);
|
||||
mHandlerThread = new HandlerThread(THREAD_NAME);
|
||||
mHandlerThread.start();
|
||||
tracker = new EthernetTracker(mContext, mHandlerThread.getThreadHandler(), mFactory, mNetd,
|
||||
mDeps);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
mHandlerThread.quitSafely();
|
||||
}
|
||||
|
||||
private void initMockResources() {
|
||||
when(mDeps.getInterfaceRegexFromResource(eq(mContext))).thenReturn("");
|
||||
when(mDeps.getInterfaceConfigFromResource(eq(mContext))).thenReturn(new String[0]);
|
||||
}
|
||||
|
||||
private void waitForIdle() {
|
||||
HandlerUtils.waitForIdle(mHandlerThread, TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Creation of various valid static IP configurations
|
||||
*/
|
||||
@Test
|
||||
public void createStaticIpConfiguration() {
|
||||
// Empty gives default StaticIPConfiguration object
|
||||
assertStaticConfiguration(new StaticIpConfiguration(), "");
|
||||
|
||||
// Setting only the IP address properly cascades and assumes defaults
|
||||
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(new LinkAddress("192.0.2.10/24")).build(), "ip=192.0.2.10/24");
|
||||
|
||||
final ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
|
||||
dnsAddresses.add(InetAddresses.parseNumericAddress("4.4.4.4"));
|
||||
dnsAddresses.add(InetAddresses.parseNumericAddress("8.8.8.8"));
|
||||
// Setting other fields properly cascades them
|
||||
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(new LinkAddress("192.0.2.10/24"))
|
||||
.setDnsServers(dnsAddresses)
|
||||
.setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
|
||||
.setDomains("android").build(),
|
||||
"ip=192.0.2.10/24 dns=4.4.4.4,8.8.8.8 gateway=192.0.2.1 domains=android");
|
||||
|
||||
// Verify order doesn't matter
|
||||
assertStaticConfiguration(new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(new LinkAddress("192.0.2.10/24"))
|
||||
.setDnsServers(dnsAddresses)
|
||||
.setGateway(InetAddresses.parseNumericAddress("192.0.2.1"))
|
||||
.setDomains("android").build(),
|
||||
"domains=android ip=192.0.2.10/24 gateway=192.0.2.1 dns=4.4.4.4,8.8.8.8 ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Attempt creation of various bad static IP configurations
|
||||
*/
|
||||
@Test
|
||||
public void createStaticIpConfiguration_Bad() {
|
||||
assertStaticConfigurationFails("ip=192.0.2.1/24 gateway= blah=20.20.20.20"); // Unknown key
|
||||
assertStaticConfigurationFails("ip=192.0.2.1"); // mask is missing
|
||||
assertStaticConfigurationFails("ip=a.b.c"); // not a valid ip address
|
||||
assertStaticConfigurationFails("dns=4.4.4.4,1.2.3.A"); // not valid ip address in dns
|
||||
assertStaticConfigurationFails("="); // Key and value is empty
|
||||
assertStaticConfigurationFails("ip="); // Value is empty
|
||||
assertStaticConfigurationFails("ip=192.0.2.1/24 gateway="); // Gateway is empty
|
||||
}
|
||||
|
||||
private void assertStaticConfigurationFails(String config) {
|
||||
try {
|
||||
EthernetTracker.parseStaticIpConfiguration(config);
|
||||
fail("Expected to fail: " + config);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStaticConfiguration(StaticIpConfiguration expectedStaticIpConfig,
|
||||
String configAsString) {
|
||||
final IpConfiguration expectedIpConfiguration = new IpConfiguration();
|
||||
expectedIpConfiguration.setIpAssignment(IpAssignment.STATIC);
|
||||
expectedIpConfiguration.setProxySettings(ProxySettings.NONE);
|
||||
expectedIpConfiguration.setStaticIpConfiguration(expectedStaticIpConfig);
|
||||
|
||||
assertEquals(expectedIpConfiguration,
|
||||
EthernetTracker.parseStaticIpConfiguration(configAsString));
|
||||
}
|
||||
|
||||
private NetworkCapabilities.Builder makeEthernetCapabilitiesBuilder(boolean clearAll) {
|
||||
final NetworkCapabilities.Builder builder =
|
||||
clearAll ? NetworkCapabilities.Builder.withoutDefaultCapabilities()
|
||||
: new NetworkCapabilities.Builder();
|
||||
return builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test: Attempt to create a capabilties with various valid sets of capabilities/transports
|
||||
*/
|
||||
@Test
|
||||
public void createNetworkCapabilities() {
|
||||
|
||||
// Particularly common expected results
|
||||
NetworkCapabilities defaultEthernetCleared =
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.build();
|
||||
|
||||
NetworkCapabilities ethernetClearedWithCommonCaps =
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addCapability(12)
|
||||
.addCapability(13)
|
||||
.addCapability(14)
|
||||
.addCapability(15)
|
||||
.build();
|
||||
|
||||
// Empty capabilities and transports lists with a "please clear defaults" should
|
||||
// yield an empty capabilities set with TRANPORT_ETHERNET
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "");
|
||||
|
||||
// Empty capabilities and transports without the clear defaults flag should return the
|
||||
// default capabilities set with TRANSPORT_ETHERNET
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(false /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.build(),
|
||||
false, "", "");
|
||||
|
||||
// A list of capabilities without the clear defaults flag should return the default
|
||||
// capabilities, mixed with the desired capabilities, and TRANSPORT_ETHERNET
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(false /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addCapability(11)
|
||||
.addCapability(12)
|
||||
.build(),
|
||||
false, "11,12", "");
|
||||
|
||||
// Adding a list of capabilities with a clear defaults will leave exactly those capabilities
|
||||
// with a default TRANSPORT_ETHERNET since no overrides are specified
|
||||
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15", "");
|
||||
|
||||
// Adding any invalid capabilities to the list will cause them to be ignored
|
||||
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,65,73", "");
|
||||
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "12,13,14,15,abcdefg", "");
|
||||
|
||||
// Adding a valid override transport will remove the default TRANSPORT_ETHERNET transport
|
||||
// and apply only the override to the capabiltities object
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(0)
|
||||
.build(),
|
||||
true, "", "0");
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(1)
|
||||
.build(),
|
||||
true, "", "1");
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(2)
|
||||
.build(),
|
||||
true, "", "2");
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addTransportType(3)
|
||||
.build(),
|
||||
true, "", "3");
|
||||
|
||||
// "4" is TRANSPORT_VPN, which is unsupported. Should default back to TRANPORT_ETHERNET
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "4");
|
||||
|
||||
// "5" is TRANSPORT_WIFI_AWARE, which is currently supported due to no legacy TYPE_NONE
|
||||
// conversion. When that becomes available, this test must be updated
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "5");
|
||||
|
||||
// "6" is TRANSPORT_LOWPAN, which is currently supported due to no legacy TYPE_NONE
|
||||
// conversion. When that becomes available, this test must be updated
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "6");
|
||||
|
||||
// Adding an invalid override transport will leave the transport as TRANSPORT_ETHERNET
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared,true, "", "100");
|
||||
assertParsedNetworkCapabilities(defaultEthernetCleared, true, "", "abcdefg");
|
||||
|
||||
// Ensure the adding of both capabilities and transports work
|
||||
assertParsedNetworkCapabilities(
|
||||
makeEthernetCapabilitiesBuilder(true /* clearAll */)
|
||||
.setLinkUpstreamBandwidthKbps(100000)
|
||||
.setLinkDownstreamBandwidthKbps(100000)
|
||||
.addCapability(12)
|
||||
.addCapability(13)
|
||||
.addCapability(14)
|
||||
.addCapability(15)
|
||||
.addTransportType(3)
|
||||
.build(),
|
||||
true, "12,13,14,15", "3");
|
||||
|
||||
// Ensure order does not matter for capability list
|
||||
assertParsedNetworkCapabilities(ethernetClearedWithCommonCaps, true, "13,12,15,14", "");
|
||||
}
|
||||
|
||||
private void assertParsedNetworkCapabilities(NetworkCapabilities expectedNetworkCapabilities,
|
||||
boolean clearCapabilties, String configCapabiltiies,String configTransports) {
|
||||
assertEquals(expectedNetworkCapabilities,
|
||||
EthernetTracker.createNetworkCapabilities(clearCapabilties, configCapabiltiies,
|
||||
configTransports).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEthernetTrackerConfigReturnsCorrectValue() {
|
||||
final String capabilities = "2";
|
||||
final String ipConfig = "3";
|
||||
final String transport = "4";
|
||||
final String configString = String.join(";", TEST_IFACE, capabilities, ipConfig, transport);
|
||||
|
||||
final EthernetTracker.EthernetTrackerConfig config =
|
||||
EthernetTracker.createEthernetTrackerConfig(configString);
|
||||
|
||||
assertEquals(TEST_IFACE, config.mIface);
|
||||
assertEquals(capabilities, config.mCapabilities);
|
||||
assertEquals(ipConfig, config.mIpConfig);
|
||||
assertEquals(transport, config.mTransport);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateEthernetTrackerConfigThrowsNpeWithNullInput() {
|
||||
assertThrows(NullPointerException.class,
|
||||
() -> EthernetTracker.createEthernetTrackerConfig(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConfiguration() {
|
||||
final NetworkCapabilities capabilities = new NetworkCapabilities.Builder().build();
|
||||
final LinkAddress linkAddr = new LinkAddress("192.0.2.2/25");
|
||||
final StaticIpConfiguration staticIpConfig =
|
||||
new StaticIpConfiguration.Builder().setIpAddress(linkAddr).build();
|
||||
final IpConfiguration ipConfig =
|
||||
new IpConfiguration.Builder().setStaticIpConfiguration(staticIpConfig).build();
|
||||
final INetworkInterfaceOutcomeReceiver listener = null;
|
||||
|
||||
tracker.updateConfiguration(TEST_IFACE, ipConfig, capabilities, listener);
|
||||
waitForIdle();
|
||||
|
||||
verify(mFactory).updateInterface(
|
||||
eq(TEST_IFACE), eq(ipConfig), eq(capabilities), eq(listener));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectNetworkCorrectlyCallsFactory() {
|
||||
tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
waitForIdle();
|
||||
|
||||
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
|
||||
eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisconnectNetworkCorrectlyCallsFactory() {
|
||||
tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
|
||||
waitForIdle();
|
||||
|
||||
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
|
||||
eq(NULL_LISTENER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidTestInterfaceIsFalseWhenTestInterfacesAreNotIncluded() {
|
||||
final String validIfaceName = TEST_TAP_PREFIX + "123";
|
||||
tracker.setIncludeTestInterfaces(false);
|
||||
waitForIdle();
|
||||
|
||||
final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
|
||||
|
||||
assertFalse(isValidTestInterface);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidTestInterfaceIsFalseWhenTestInterfaceNameIsInvalid() {
|
||||
final String invalidIfaceName = "123" + TEST_TAP_PREFIX;
|
||||
tracker.setIncludeTestInterfaces(true);
|
||||
waitForIdle();
|
||||
|
||||
final boolean isValidTestInterface = tracker.isValidTestInterface(invalidIfaceName);
|
||||
|
||||
assertFalse(isValidTestInterface);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsValidTestInterfaceIsTrueWhenTestInterfacesIncludedAndValidName() {
|
||||
final String validIfaceName = TEST_TAP_PREFIX + "123";
|
||||
tracker.setIncludeTestInterfaces(true);
|
||||
waitForIdle();
|
||||
|
||||
final boolean isValidTestInterface = tracker.isValidTestInterface(validIfaceName);
|
||||
|
||||
assertTrue(isValidTestInterface);
|
||||
}
|
||||
|
||||
public static class EthernetStateListener extends IEthernetServiceListener.Stub {
|
||||
@Override
|
||||
public void onEthernetStateChanged(int state) { }
|
||||
|
||||
@Override
|
||||
public void onInterfaceStateChanged(String iface, int state, int role,
|
||||
IpConfiguration configuration) { }
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListenEthernetStateChange() throws Exception {
|
||||
final String testIface = "testtap123";
|
||||
final String testHwAddr = "11:22:33:44:55:66";
|
||||
final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
|
||||
ifaceParcel.ifName = testIface;
|
||||
ifaceParcel.hwAddr = testHwAddr;
|
||||
ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
|
||||
|
||||
tracker.setIncludeTestInterfaces(true);
|
||||
waitForIdle();
|
||||
|
||||
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
|
||||
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
|
||||
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
|
||||
doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
|
||||
|
||||
final EthernetStateListener listener = spy(new EthernetStateListener());
|
||||
tracker.addListener(listener, true /* canUseRestrictedNetworks */);
|
||||
// Check default state.
|
||||
waitForIdle();
|
||||
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
|
||||
anyInt(), any());
|
||||
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
|
||||
reset(listener);
|
||||
|
||||
doReturn(EthernetManager.STATE_ABSENT).when(mFactory).getInterfaceState(eq(testIface));
|
||||
tracker.setEthernetEnabled(false);
|
||||
waitForIdle();
|
||||
verify(mFactory).removeInterface(eq(testIface));
|
||||
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_DISABLED));
|
||||
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_ABSENT),
|
||||
anyInt(), any());
|
||||
reset(listener);
|
||||
|
||||
doReturn(EthernetManager.STATE_LINK_UP).when(mFactory).getInterfaceState(eq(testIface));
|
||||
tracker.setEthernetEnabled(true);
|
||||
waitForIdle();
|
||||
verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
|
||||
verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
|
||||
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
|
||||
anyInt(), any());
|
||||
}
|
||||
}
|
||||
159
tests/unit/java/com/android/server/net/IpConfigStoreTest.java
Normal file
159
tests/unit/java/com/android/server/net/IpConfigStoreTest.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.net;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.net.InetAddresses;
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.IpConfiguration.IpAssignment;
|
||||
import android.net.IpConfiguration.ProxySettings;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.StaticIpConfiguration;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link IpConfigStore}
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IpConfigStoreTest {
|
||||
private static final int KEY_CONFIG = 17;
|
||||
private static final String IFACE_1 = "eth0";
|
||||
private static final String IFACE_2 = "eth1";
|
||||
private static final String IP_ADDR_1 = "192.168.1.10/24";
|
||||
private static final String IP_ADDR_2 = "192.168.1.20/24";
|
||||
private static final String DNS_IP_ADDR_1 = "1.2.3.4";
|
||||
private static final String DNS_IP_ADDR_2 = "5.6.7.8";
|
||||
|
||||
@Test
|
||||
public void backwardCompatibility2to3() throws IOException {
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
DataOutputStream outputStream = new DataOutputStream(byteStream);
|
||||
|
||||
final IpConfiguration expectedConfig =
|
||||
newIpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
|
||||
|
||||
// Emulate writing to old format.
|
||||
writeDhcpConfigV2(outputStream, KEY_CONFIG, expectedConfig);
|
||||
|
||||
InputStream in = new ByteArrayInputStream(byteStream.toByteArray());
|
||||
ArrayMap<String, IpConfiguration> configurations = IpConfigStore.readIpConfigurations(in);
|
||||
|
||||
assertNotNull(configurations);
|
||||
assertEquals(1, configurations.size());
|
||||
IpConfiguration actualConfig = configurations.get(String.valueOf(KEY_CONFIG));
|
||||
assertNotNull(actualConfig);
|
||||
assertEquals(expectedConfig, actualConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void staticIpMultiNetworks() throws Exception {
|
||||
final ArrayList<InetAddress> dnsServers = new ArrayList<>();
|
||||
dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_1));
|
||||
dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_2));
|
||||
final StaticIpConfiguration staticIpConfiguration1 = new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(new LinkAddress(IP_ADDR_1))
|
||||
.setDnsServers(dnsServers).build();
|
||||
final StaticIpConfiguration staticIpConfiguration2 = new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(new LinkAddress(IP_ADDR_2))
|
||||
.setDnsServers(dnsServers).build();
|
||||
|
||||
ProxyInfo proxyInfo =
|
||||
ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
|
||||
|
||||
IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
|
||||
ProxySettings.STATIC, staticIpConfiguration1, proxyInfo);
|
||||
IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
|
||||
ProxySettings.STATIC, staticIpConfiguration2, proxyInfo);
|
||||
|
||||
ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
|
||||
expectedNetworks.put(IFACE_1, expectedConfig1);
|
||||
expectedNetworks.put(IFACE_2, expectedConfig2);
|
||||
|
||||
MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
|
||||
IpConfigStore store = new IpConfigStore(writer);
|
||||
store.writeIpConfigurations("file/path/not/used/", expectedNetworks);
|
||||
|
||||
InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
|
||||
ArrayMap<String, IpConfiguration> actualNetworks = IpConfigStore.readIpConfigurations(in);
|
||||
assertNotNull(actualNetworks);
|
||||
assertEquals(2, actualNetworks.size());
|
||||
assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
|
||||
assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2));
|
||||
}
|
||||
|
||||
private IpConfiguration newIpConfiguration(IpAssignment ipAssignment,
|
||||
ProxySettings proxySettings, StaticIpConfiguration staticIpConfig, ProxyInfo info) {
|
||||
final IpConfiguration config = new IpConfiguration();
|
||||
config.setIpAssignment(ipAssignment);
|
||||
config.setProxySettings(proxySettings);
|
||||
config.setStaticIpConfiguration(staticIpConfig);
|
||||
config.setHttpProxy(info);
|
||||
return config;
|
||||
}
|
||||
|
||||
// This is simplified snapshot of code that was used to store values in V2 format (key as int).
|
||||
private static void writeDhcpConfigV2(DataOutputStream out, int configKey,
|
||||
IpConfiguration config) throws IOException {
|
||||
out.writeInt(2); // VERSION 2
|
||||
switch (config.getIpAssignment()) {
|
||||
case DHCP:
|
||||
out.writeUTF("ipAssignment");
|
||||
out.writeUTF(config.getIpAssignment().toString());
|
||||
break;
|
||||
default:
|
||||
fail("Not supported in test environment");
|
||||
}
|
||||
|
||||
out.writeUTF("id");
|
||||
out.writeInt(configKey);
|
||||
out.writeUTF("eos");
|
||||
}
|
||||
|
||||
/** Synchronously writes into given byte steam */
|
||||
private static class MockedDelayedDiskWrite extends DelayedDiskWrite {
|
||||
final ByteArrayOutputStream mByteStream = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void write(String filePath, Writer w) {
|
||||
DataOutputStream outputStream = new DataOutputStream(mByteStream);
|
||||
|
||||
try {
|
||||
w.onWriteCalled(outputStream);
|
||||
} catch (IOException e) {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user